Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 45 additions & 18 deletions crates/artifacts/solc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")?;

Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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>(error).unwrap();
let s = error.to_string();
Expand All @@ -414,12 +422,31 @@ 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>(error).unwrap();
let s = error.to_string();
eprintln!("{s}");
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>(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>(error).unwrap();
let s = error.to_string();
eprintln!("{s}");
assert_eq!(s.match_indices("too deep inside the stack.").count(), 1, "\n{s}");
}
}