diff --git a/crates/artifacts/solc/src/error.rs b/crates/artifacts/solc/src/error.rs index 78453cd6f..b9b2a8781 100644 --- a/crates/artifacts/solc/src/error.rs +++ b/crates/artifacts/solc/src/error.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use std::{fmt, ops::Range, str::FromStr}; use yansi::{Color, Style}; +const ARROW: &str = "-->"; + #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct SourceLocation { pub file: String, @@ -146,22 +148,30 @@ impl fmt::Display for Error { let mut lines = fmtd_msg.lines(); - // skip the first line if it contains the same message as the one we just formatted, - // unless it also contains a source location, in which case the entire error message is an - // old style error message, like: - // path/to/file:line:column: ErrorType: message - if lines - .clone() - .next() - .is_some_and(|l| l.contains(short_msg) && l.bytes().filter(|b| *b == b':').count() < 3) - { - let _ = lines.next(); + if let Some(l) = lines.clone().next() { + if l.bytes().filter(|&b| b == b':').count() >= 3 + && (l.contains(['/', '\\']) || l.contains(".sol")) + { + // This is an old style error message, like: + // path/to/file:line:column: ErrorType: message + // We want to display this as-is. + } else { + // Otherwise, assume that the messages are the same until we find a source + // location. + lines.next(); + while let Some(line) = lines.clone().next() { + if line.contains(ARROW) { + break; + } + lines.next(); + } + } } - // format the main source location + // Format the main source location. fmt_source_location(f, &mut lines)?; - // format remaining lines as secondary locations + // Format remaining lines as secondary locations. while let Some(line) = lines.next() { f.write_str("\n")?; @@ -260,11 +270,9 @@ fn fmt_source_location(f: &mut fmt::Formatter<'_>, lines: &mut std::str::Lines<' // --> source if let Some(line) = lines.next() { f.write_str("\n")?; - - let arrow = "-->"; - if let Some((left, loc)) = line.split_once(arrow) { + if let Some((left, loc)) = line.split_once(ARROW) { f.write_str(left)?; - styled(f, Error::frame_style(), |f| f.write_str(arrow))?; + styled(f, Error::frame_style(), |f| f.write_str(ARROW))?; f.write_str(loc)?; } else { f.write_str(line)?; @@ -404,7 +412,7 @@ mod tests { } #[test] - fn solc_not_formatting_the_message1() { + fn no_source_location() { let error = r#"{"component":"general","errorCode":"6553","formattedMessage":"SyntaxError: The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction.\n\n","message":"The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction.","severity":"error","sourceLocation":{"end":173,"file":"","start":114},"type":"SyntaxError"}"#; let error = serde_json::from_str::(error).unwrap(); let s = error.to_string(); @@ -414,7 +422,7 @@ mod tests { } #[test] - fn solc_not_formatting_the_message2() { + fn no_source_location2() { let error = r#"{"component":"general","errorCode":"5667","formattedMessage":"Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.\n\n","message":"Unused function parameter. Remove or comment out the variable name to silence this warning.","severity":"warning","sourceLocation":{"end":104,"file":"","start":95},"type":"Warning"}"#; let error = serde_json::from_str::(error).unwrap(); let s = error.to_string(); @@ -422,4 +430,23 @@ mod tests { assert!(s.contains("Warning (5667)"), "\n{s}"); assert!(s.contains("Unused function parameter. Remove or comment out the variable name to silence this warning."), "\n{s}"); } + + #[test] + fn stack_too_deep_multiline() { + let error = r#"{"sourceLocation":{"file":"test/LibMap.t.sol","start":15084,"end":15113},"type":"YulException","component":"general","severity":"error","errorCode":null,"message":"Yul exception:Cannot swap Variable _23 with Slot RET[fun_assertEq]: too deep in the stack by 1 slots in [ var_136614_mpos RET _23 _21 _23 var_map_136608_slot _34 _34 _29 _33 _33 _39 expr_48 var_bitWidth var_map_136608_slot _26 _29 var_bitWidth TMP[eq, 0] RET[fun_assertEq] ]\nmemoryguard was present.","formattedMessage":"YulException: Cannot swap Variable _23 with Slot RET[fun_assertEq]: too deep in the stack by 1 slots in [ var_136614_mpos RET _23 _21 _23 var_map_136608_slot _34 _34 _29 _33 _33 _39 expr_48 var_bitWidth var_map_136608_slot _26 _29 var_bitWidth TMP[eq, 0] RET[fun_assertEq] ]\nmemoryguard was present.\n --> test/LibMap.t.sol:461:34:\n |\n461 | uint256 end = t.o - (t.o > 0 ? _random() % t.o : 0);\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n"}"#; + let error = serde_json::from_str::(error).unwrap(); + let s = error.to_string(); + eprintln!("{s}"); + assert_eq!(s.match_indices("Cannot swap Variable _23").count(), 1, "\n{s}"); + assert!(s.contains("-->"), "\n{s}"); + } + + #[test] + fn stack_too_deep_no_source_location() { + let error = r#"{"type":"CompilerError","component":"general","severity":"error","errorCode":null,"message":"Compiler error (/solidity/libyul/backends/evm/AsmCodeGen.cpp:63):Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables. When compiling inline assembly: Variable key_ is 2 slot(s) too deep inside the stack. Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables.","formattedMessage":"CompilerError: Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables. When compiling inline assembly: Variable key_ is 2 slot(s) too deep inside the stack. Stack too deep. Try compiling with `--via-ir` (cli) or the equivalent `viaIR: true` (standard JSON) while enabling the optimizer. Otherwise, try removing local variables.\n\n"}"#; + let error = serde_json::from_str::(error).unwrap(); + let s = error.to_string(); + eprintln!("{s}"); + assert_eq!(s.match_indices("too deep inside the stack.").count(), 1, "\n{s}"); + } }