Skip to content

Commit 62b6642

Browse files
authored
Merge pull request #2216 from CosmWasm/co/debug-logs-check
Finish `cosmwasm-check --verbose`
2 parents 541d3ce + 9cca6a8 commit 62b6642

File tree

4 files changed

+89
-98
lines changed

4 files changed

+89
-98
lines changed

packages/check/src/main.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use std::collections::HashSet;
22
use std::fs::File;
33
use std::io::Read;
4-
use std::path::Path;
54
use std::process::exit;
65

76
use clap::{Arg, ArgAction, Command};
87
use colored::Colorize;
98

109
use cosmwasm_vm::capabilities_from_csv;
11-
use cosmwasm_vm::internals::{check_wasm_with_logs, compile, make_compiling_engine, Logs};
10+
use cosmwasm_vm::internals::{
11+
check_wasm_with_logs, compile, make_compiling_engine, LogOutput, Logger,
12+
};
1213

1314
const DEFAULT_AVAILABLE_CAPABILITIES: &str =
1415
"iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1";
@@ -28,6 +29,13 @@ pub fn main() {
2829
.num_args(1)
2930
.action(ArgAction::Set),
3031
)
32+
.arg(
33+
Arg::new("VERBOSE")
34+
.long("verbose")
35+
.num_args(0)
36+
.help("Prints additional information on stderr")
37+
.action(ArgAction::SetTrue),
38+
)
3139
.arg(
3240
Arg::new("WASM")
3341
.help("Wasm file to read and compile")
@@ -54,7 +62,7 @@ pub fn main() {
5462

5563
let (passes, failures): (Vec<_>, _) = paths
5664
.map(|p| {
57-
let result = check_contract(p, &available_capabilities);
65+
let result = check_contract(p, &available_capabilities, matches.get_flag("VERBOSE"));
5866
match &result {
5967
Ok(_) => println!("{}: {}", p, "pass".green()),
6068
Err(e) => {
@@ -86,22 +94,27 @@ pub fn main() {
8694
}
8795

8896
fn check_contract(
89-
path: impl AsRef<Path>,
97+
path: &str,
9098
available_capabilities: &HashSet<String>,
99+
verbose: bool,
91100
) -> anyhow::Result<()> {
92101
let mut file = File::open(path)?;
93102

94103
// Read wasm
95104
let mut wasm = Vec::<u8>::new();
96105
file.read_to_end(&mut wasm)?;
97106

98-
let logs = Logs::new();
107+
let prefix = format!("{}: ", path);
108+
let logs = if verbose {
109+
Logger::On {
110+
prefix: &prefix,
111+
output: LogOutput::StdErr,
112+
}
113+
} else {
114+
Logger::Off
115+
};
99116
// Check wasm
100-
let res = check_wasm_with_logs(&wasm, available_capabilities, logs.clone());
101-
for line in logs.iter() {
102-
eprintln!("{}", line);
103-
}
104-
res?;
117+
check_wasm_with_logs(&wasm, available_capabilities, logs)?;
105118

106119
// Compile module
107120
let engine = make_compiling_engine(None);

packages/check/tests/cosmwasm_check_tests.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ fn valid_contract_check() -> Result<(), Box<dyn std::error::Error>> {
1414
Ok(())
1515
}
1616

17+
#[test]
18+
fn contract_check_verbose() -> Result<(), Box<dyn std::error::Error>> {
19+
let mut cmd = Command::cargo_bin("cosmwasm-check")?;
20+
21+
cmd.arg("../vm/testdata/empty.wasm").arg("--verbose");
22+
cmd.assert()
23+
.success()
24+
.stdout(predicate::str::contains("pass"))
25+
.stderr(predicate::str::contains("Max function parameters"));
26+
27+
Ok(())
28+
}
29+
1730
#[test]
1831
fn empty_contract_check() -> Result<(), Box<dyn std::error::Error>> {
1932
let mut cmd = Command::cargo_bin("cosmwasm-check")?;

packages/vm/src/compatibility.rs

Lines changed: 52 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
use std::cell::RefCell;
21
use std::collections::BTreeSet;
32
use std::collections::HashSet;
4-
use std::iter::empty;
5-
use std::rc::Rc;
6-
use std::sync::Mutex;
73

84
use wasmer::wasmparser::Import;
95
use wasmer::wasmparser::TypeRef;
@@ -101,38 +97,42 @@ const MAX_TOTAL_FUNCTION_PARAMS: usize = 10_000;
10197
/// during static validation.
10298
const MAX_FUNCTION_RESULTS: usize = 1;
10399

104-
#[derive(Clone)]
105-
pub enum Logs {
106-
On(RefCell<Vec<String>>),
100+
#[derive(Clone, Copy)]
101+
pub enum LogOutput {
102+
StdOut,
103+
StdErr,
104+
}
105+
#[derive(Clone, Copy, Default)]
106+
pub enum Logger<'a> {
107+
On {
108+
prefix: &'a str,
109+
output: LogOutput,
110+
},
111+
#[default]
107112
Off,
108113
}
109114

110-
impl Logs {
111-
pub fn new() -> Self {
112-
On(RefCell::new(Vec::new()))
115+
impl<'a> Logger<'a> {
116+
pub fn with_config(output: LogOutput, prefix: &'a str) -> Self {
117+
On { output, prefix }
113118
}
114119

115-
// Gets access to logs for writing
116-
pub fn open(&mut self) -> Option<&mut Vec<String>> {
117-
match self {
118-
On(data) => {
119-
let mut data = data.borrow_mut();
120-
Some(data.as_mut())
120+
/// Adds a message to the logs, if they are enabled.
121+
/// This is a convenience method for adding a single message.
122+
///
123+
/// Takes a closure that returns the message to add to avoid unnecessary allocations.
124+
pub fn add(&self, msg_fn: impl FnOnce() -> String) {
125+
if let On { prefix, output } = &self {
126+
let msg = msg_fn();
127+
match output {
128+
LogOutput::StdOut => println!("{prefix}{msg}"),
129+
LogOutput::StdErr => eprintln!("{prefix}{msg}"),
121130
}
122-
Off => None,
123131
}
124132
}
125-
126-
pub fn iter(&self) -> impl Iterator<Item = String> {
127-
let iter = match self {
128-
On(data) => data.borrow().iter(),
129-
Off => Vec::new().into_iter().into(), // How to create am empty Iter<String> ?!?!?!?
130-
};
131-
iter
132-
}
133133
}
134134

135-
use Logs::*;
135+
use Logger::*;
136136

137137
/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
138138
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
@@ -142,21 +142,19 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) ->
142142
pub fn check_wasm_with_logs(
143143
wasm_code: &[u8],
144144
available_capabilities: &HashSet<String>,
145-
logs: Logs,
145+
logs: Logger<'_>,
146146
) -> VmResult<()> {
147-
if let Some(logs) = logs.clone().open() {
148-
logs.push(format!("Size of Wasm blob: {}", wasm_code.len()));
149-
}
147+
logs.add(|| format!("Size of Wasm blob: {}", wasm_code.len()));
150148

151149
let mut module = ParsedWasm::parse(wasm_code)?;
152150

153151
check_wasm_tables(&module)?;
154152
check_wasm_memories(&module)?;
155153
check_interface_version(&module)?;
156-
check_wasm_exports(&module, logs.clone())?;
157-
check_wasm_imports(&module, SUPPORTED_IMPORTS, logs.clone())?;
158-
check_wasm_capabilities(&module, available_capabilities, logs.clone())?;
159-
check_wasm_functions(&module, logs.clone())?;
154+
check_wasm_exports(&module, logs)?;
155+
check_wasm_imports(&module, SUPPORTED_IMPORTS, logs)?;
156+
check_wasm_capabilities(&module, available_capabilities, logs)?;
157+
check_wasm_functions(&module, logs)?;
160158

161159
module.validate_funcs()
162160
}
@@ -237,15 +235,10 @@ fn check_interface_version(module: &ParsedWasm) -> VmResult<()> {
237235
}
238236
}
239237

240-
fn check_wasm_exports(module: &ParsedWasm, mut logs: Logs) -> VmResult<()> {
238+
fn check_wasm_exports(module: &ParsedWasm, logs: Logger) -> VmResult<()> {
241239
let available_exports: HashSet<String> = module.exported_function_names(None);
242240

243-
if let Some(logs) = logs.open() {
244-
logs.push(format!(
245-
"Exports: {}",
246-
available_exports.to_string_limited(20_000)
247-
));
248-
}
241+
logs.add(|| format!("Exports: {}", available_exports.to_string_limited(20_000)));
249242

250243
for required_export in REQUIRED_EXPORTS {
251244
if !available_exports.contains(*required_export) {
@@ -263,10 +256,10 @@ fn check_wasm_exports(module: &ParsedWasm, mut logs: Logs) -> VmResult<()> {
263256
fn check_wasm_imports(
264257
module: &ParsedWasm,
265258
supported_imports: &[&str],
266-
mut logs: Logs,
259+
logs: Logger,
267260
) -> VmResult<()> {
268-
if let Some(logs) = logs.open() {
269-
logs.push(format!(
261+
logs.add(|| {
262+
format!(
270263
"Imports ({}): {}",
271264
module.imports.len(),
272265
module
@@ -275,8 +268,8 @@ fn check_wasm_imports(
275268
.map(|import| full_import_name(import))
276269
.collect::<Vec<_>>()
277270
.join(", ")
278-
));
279-
}
271+
)
272+
});
280273

281274
if module.imports.len() > MAX_IMPORTS {
282275
return Err(VmError::static_validation_err(format!(
@@ -314,15 +307,15 @@ fn full_import_name(ie: &Import) -> String {
314307
fn check_wasm_capabilities(
315308
module: &ParsedWasm,
316309
available_capabilities: &HashSet<String>,
317-
mut logs: Logs,
310+
logs: Logger,
318311
) -> VmResult<()> {
319312
let required_capabilities = required_capabilities_from_module(module);
320-
if let Some(logs) = logs.open() {
321-
logs.push(format!(
313+
logs.add(|| {
314+
format!(
322315
"Required capabilities: {}",
323316
required_capabilities.to_string_limited(20_000)
324-
));
325-
}
317+
)
318+
});
326319
if !required_capabilities.is_subset(available_capabilities) {
327320
// We switch to BTreeSet to get a sorted error message
328321
let unavailable: BTreeSet<_> = required_capabilities
@@ -336,19 +329,16 @@ fn check_wasm_capabilities(
336329
Ok(())
337330
}
338331

339-
fn check_wasm_functions(module: &ParsedWasm, mut logs: Logs) -> VmResult<()> {
340-
if let Some(logs) = logs.open() {
341-
logs.push(format!("Function count: {}", module.function_count));
342-
logs.push(format!(
343-
"Max function parameters: {}",
344-
module.max_func_params
345-
));
346-
logs.push(format!("Max function results: {}", module.max_func_results));
347-
logs.push(format!(
332+
fn check_wasm_functions(module: &ParsedWasm, logs: Logger) -> VmResult<()> {
333+
logs.add(|| format!("Function count: {}", module.function_count));
334+
logs.add(|| format!("Max function parameters: {}", module.max_func_params));
335+
logs.add(|| format!("Max function results: {}", module.max_func_results));
336+
logs.add(|| {
337+
format!(
348338
"Total function parameter count: {}",
349339
module.total_func_params
350-
));
351-
}
340+
)
341+
});
352342

353343
if module.function_count > MAX_FUNCTIONS {
354344
return Err(VmError::static_validation_err(format!(
@@ -392,31 +382,6 @@ mod tests {
392382
capabilities_from_csv("cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,iterator,staking,stargate")
393383
}
394384

395-
#[test]
396-
fn logs_works() {
397-
let mut logs = Logs::new();
398-
399-
if let Some(logs) = logs.open() {
400-
logs.push(format!("a test"));
401-
}
402-
403-
if let Some(logs) = logs.open() {
404-
logs.push(format!("second test"));
405-
logs.push(format!("third test"));
406-
}
407-
408-
let mut logs_b = logs.clone();
409-
if let Some(logs) = logs_b.open() {
410-
logs.push(format!("added in b"));
411-
}
412-
413-
let mut iter = logs.iter();
414-
assert_eq!(iter.next(), Some(String::from("a test")));
415-
assert_eq!(iter.next(), Some(String::from("second test")));
416-
assert_eq!(iter.next(), Some(String::from("third test")));
417-
assert_eq!(iter.next(), None);
418-
}
419-
420385
#[test]
421386
fn check_wasm_passes_for_latest_contract() {
422387
// this is our reference check, must pass

packages/vm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub mod internals {
5555
//! Please don't use any of these types directly, as
5656
//! they might change frequently or be removed in the future.
5757
58-
pub use crate::compatibility::{check_wasm, check_wasm_with_logs, Logs};
58+
pub use crate::compatibility::{check_wasm, check_wasm_with_logs, LogOutput, Logger};
5959
pub use crate::instance::instance_from_module;
6060
pub use crate::wasm_backend::{compile, make_compiling_engine, make_runtime_engine};
6161
}

0 commit comments

Comments
 (0)