From 4174c6a3fb2597a12e1ed4824268b185e5c2b902 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 28 Jan 2021 12:56:22 +0100 Subject: [PATCH] Code generation for errors. --- libsolidity/codegen/CompilerUtils.cpp | 16 ++++ libsolidity/codegen/CompilerUtils.h | 6 ++ libsolidity/codegen/ContractCompiler.cpp | 9 ++ libsolidity/codegen/ContractCompiler.h | 1 + libsolidity/codegen/ExpressionCompiler.cpp | 17 +++- .../codegen/ir/IRGeneratorForStatements.cpp | 88 ++++++++++++++++++- .../codegen/ir/IRGeneratorForStatements.h | 7 ++ .../errors/error_in_library_and_interface.sol | 24 +++++ .../semanticTests/errors/named_error_args.sol | 10 +++ .../semanticTests/errors/panic_via_import.sol | 13 +++ .../errors/revert_conversion.sol | 12 +++ .../semanticTests/errors/simple.sol | 10 +++ .../semanticTests/errors/using_structs.sol | 16 ++++ .../errors/via_contract_type.sol | 18 ++++ .../semanticTests/errors/via_import.sol | 25 ++++++ .../semanticTests/errors/weird_name.sol | 10 +++ 16 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 test/libsolidity/semanticTests/errors/error_in_library_and_interface.sol create mode 100644 test/libsolidity/semanticTests/errors/named_error_args.sol create mode 100644 test/libsolidity/semanticTests/errors/panic_via_import.sol create mode 100644 test/libsolidity/semanticTests/errors/revert_conversion.sol create mode 100644 test/libsolidity/semanticTests/errors/simple.sol create mode 100644 test/libsolidity/semanticTests/errors/using_structs.sol create mode 100644 test/libsolidity/semanticTests/errors/via_contract_type.sol create mode 100644 test/libsolidity/semanticTests/errors/via_import.sol create mode 100644 test/libsolidity/semanticTests/errors/weird_name.sol diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index ad461d2da651..db2c4876701c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -103,6 +103,22 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType) m_context << Instruction::REVERT; } +void CompilerUtils::revertWithError( + string const& _signature, + vector const& _parameterTypes, + vector const& _argumentTypes +) +{ + fetchFreeMemoryPointer(); + m_context << util::selectorFromSignature(_signature); + m_context << Instruction::DUP2 << Instruction::MSTORE; + m_context << u256(4) << Instruction::ADD; + // Stack: + abiEncode(_argumentTypes, _parameterTypes); + toSizeAfterFreeMemoryPointer(); + m_context << Instruction::REVERT; +} + void CompilerUtils::returnDataToArray() { if (m_context.evmVersion().supportsReturndata()) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 7bc26d971f99..0930ff1c877e 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -69,6 +69,12 @@ class CompilerUtils /// Stack post: void revertWithStringData(Type const& _argumentType); + void revertWithError( + std::string const& _errorName, + std::vector const& _parameterTypes, + std::vector const& _argumentTypes + ); + /// Allocates a new array and copies the return data to it. /// If the EVM does not support return data, creates an empty array. void returnDataToArray(); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9ce2b743d4c7..dad3fd1db427 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -1281,6 +1281,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit) return false; } +bool ContractCompiler::visit(RevertStatement const& _revert) +{ + CompilerContext::LocationSetter locationSetter(m_context, _revert); + StackHeightChecker checker(m_context); + compileExpression(_revert.errorCall()); + checker.check(); + return false; +} + bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index d3290a4c79e6..3656ae3bf193 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -117,6 +117,7 @@ class ContractCompiler: private ASTConstVisitor bool visit(Return const& _return) override; bool visit(Throw const& _throw) override; bool visit(EmitStatement const& _emit) override; + bool visit(RevertStatement const& _revert) override; bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; bool visit(ExpressionStatement const& _expressionStatement) override; bool visit(PlaceholderStatement const&) override; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index ec3f0a28687e..0940761baee6 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -914,7 +915,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::Error: { - solAssert(false, ""); + _functionCall.expression().accept(*this); + vector argumentTypes; + for (ASTPointer const& arg: _functionCall.sortedArguments()) + { + arg->accept(*this); + argumentTypes.push_back(arg->annotation().type); + } + solAssert(dynamic_cast(&function.declaration()), ""); + utils().revertWithError( + function.externalSignature(), + function.parameterTypes(), + argumentTypes + ); + break; } case FunctionType::Kind::BlockHash: { @@ -1832,6 +1846,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert( dynamic_cast(_memberAccess.annotation().referencedDeclaration) || dynamic_cast(_memberAccess.annotation().referencedDeclaration) || + dynamic_cast(_memberAccess.annotation().referencedDeclaration) || category == Type::Category::TypeType || category == Type::Category::Module, "" diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index ede2f285e837..1ed5bc3f7ab4 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1043,7 +1043,29 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } case FunctionType::Kind::Error: { - solAssert(false, ""); + vector arguments; + vector argumentTypes; + for (ASTPointer const& arg: _functionCall.sortedArguments()) + { + argumentTypes.push_back(arg->annotation().type); + arguments += IRVariable(*arg).stackSlots(); + } + ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); + solAssert(dynamic_cast(&functionType->declaration()), ""); + Whiskers templ(R"({ + let := () + mstore(, ) + let := (add(, 4) ) + revert(, sub(, )) + })"); + templ("pos", m_context.newYulVariable()); + templ("selector", util::selectorFromSignature(functionType->externalSignature()).str()); + templ("end", m_context.newYulVariable()); + templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); + templ("encode", abi.tupleEncoder(argumentTypes, functionType->parameterTypes())); + templ("data", joinHumanReadablePrefixed(arguments)); + m_code << templ.render(); + break; } case FunctionType::Kind::Assert: case FunctionType::Kind::Require: @@ -1966,6 +1988,13 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) ); // the call will do the resolving break; + case FunctionType::Kind::Error: + solAssert( + dynamic_cast(_memberAccess.annotation().referencedDeclaration), + "Error not found" + ); + // The function call will resolve the selector. + break; case FunctionType::Kind::DelegateCall: define(IRVariable(_memberAccess).part("address"), _memberAccess.expression()); define(IRVariable(_memberAccess).part("functionSelector")) << formatNumber(memberFunctionType->externalIdentifier()) << "\n"; @@ -2010,6 +2039,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) solAssert( dynamic_cast(_memberAccess.annotation().referencedDeclaration) || dynamic_cast(_memberAccess.annotation().referencedDeclaration) || + dynamic_cast(_memberAccess.annotation().referencedDeclaration) || category == Type::Category::TypeType || category == Type::Category::Module, "" @@ -3107,6 +3137,62 @@ void IRGeneratorForStatements::rethrow() m_code << "revert(0, 0) // rethrow\n"s; } +void IRGeneratorForStatements::revertWithError(ASTPointer const& _error) +{ + solAssert(_error, ""); + + bool usesString = _error->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()); + if (usesString && m_context.revertStrings() == RevertStrings::Strip) + { + m_code << "revert(0, 0)\n"; + return; + } + + string signature; + vector> errorArguments; + vector parameterTypes; + if (usesString) + { + signature = "Error(string)"; + errorArguments.push_back(_error); + parameterTypes.push_back(TypeProvider::stringMemory()); + } + else + { + FunctionCall const* errorCall = dynamic_cast(_error.get()); + solAssert(errorCall, ""); + solAssert(*errorCall->annotation().kind == FunctionCallKind::FunctionCall, ""); + ErrorDefinition const* error = dynamic_cast(referencedDeclaration(errorCall->expression())); + solAssert(error, ""); + signature = error->functionType(true)->externalSignature(); + parameterTypes = error->functionType(true)->parameterTypes(); + errorArguments = errorCall->sortedArguments(); + } + + Whiskers templ(R"({ + let := () + mstore(, ) + let := (add(, 4) ) + revert(, sub(, )) + })"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("hash", util::selectorFromSignature(signature).str()); + templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); + + vector errorArgumentVars; + vector errorArgumentTypes; + for (ASTPointer const& arg: errorArguments) + { + errorArgumentVars += IRVariable(*arg).stackSlots(); + errorArgumentTypes.push_back(arg->annotation().type); + } + templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars)); + templ("encode", m_context.abiFunctions().tupleEncoder(errorArgumentTypes, parameterTypes)); + + m_code << templ.render(); +} + bool IRGeneratorForStatements::visit(TryCatchClause const& _clause) { _clause.block().accept(*this); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index e212398b8b8f..7cf3217d8545 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -106,6 +106,13 @@ class IRGeneratorForStatements: public ASTConstVisitor /// Generates code to rethrow an exception. void rethrow(); + /// Generates code to revert with an error, which might be a plain string + /// or an error instance. The error arguments and the strings are assumed to + /// be already evaluated and available in local IRVariables, but not yet + /// converted. + /// Honors the revert strings setting. + void revertWithError(ASTPointer const& _error); + void handleVariableReference( VariableDeclaration const& _variable, Expression const& _referencingExpression diff --git a/test/libsolidity/semanticTests/errors/error_in_library_and_interface.sol b/test/libsolidity/semanticTests/errors/error_in_library_and_interface.sol new file mode 100644 index 000000000000..81f6c1548449 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/error_in_library_and_interface.sol @@ -0,0 +1,24 @@ +error E(uint a); +library L { + error E(uint a, uint b); +} +interface I { + error E(uint a, uint b, uint c); +} +contract C { + function f() public pure { + revert E(1); + } + function g() public pure { + revert L.E(1, 2); + } + function h() public pure { + revert I.E(1, 2, 3); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000001" +// g() -> FAILURE, hex"85208890", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000002" +// h() -> FAILURE, hex"7924ea7c", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000002", hex"0000000000000000000000000000000000000000000000000000000000000003" diff --git a/test/libsolidity/semanticTests/errors/named_error_args.sol b/test/libsolidity/semanticTests/errors/named_error_args.sol new file mode 100644 index 000000000000..7c3f35f31eb4 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/named_error_args.sol @@ -0,0 +1,10 @@ +error E(uint a, uint b); +contract C { + function f() public pure { + revert E({b: 7, a: 2}); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"85208890", hex"0000000000000000000000000000000000000000000000000000000000000002", hex"0000000000000000000000000000000000000000000000000000000000000007" diff --git a/test/libsolidity/semanticTests/errors/panic_via_import.sol b/test/libsolidity/semanticTests/errors/panic_via_import.sol new file mode 100644 index 000000000000..7e1b6b40e51a --- /dev/null +++ b/test/libsolidity/semanticTests/errors/panic_via_import.sol @@ -0,0 +1,13 @@ +==== Source: s1.sol ==== +error E(uint); +==== Source: s2.sol ==== +import { E as Panic } from "s1.sol"; +contract C { + function a() public pure { + revert Panic(1); + } +} +// ==== +// compileViaYul: also +// ---- +// a() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000001" \ No newline at end of file diff --git a/test/libsolidity/semanticTests/errors/revert_conversion.sol b/test/libsolidity/semanticTests/errors/revert_conversion.sol new file mode 100644 index 000000000000..fd8fb7dc9357 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/revert_conversion.sol @@ -0,0 +1,12 @@ +error E(string a, uint[] b); +contract C { + uint[] x; + function f() public { + x.push(7); + revert E("abc", x); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"59e4d4df", 0x40, 0x80, 3, "abc", 1, 7 diff --git a/test/libsolidity/semanticTests/errors/simple.sol b/test/libsolidity/semanticTests/errors/simple.sol new file mode 100644 index 000000000000..26b40c8d3780 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/simple.sol @@ -0,0 +1,10 @@ +error E(uint a, uint b); +contract C { + function f() public pure { + revert E(2, 7); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"85208890", 2, 7 diff --git a/test/libsolidity/semanticTests/errors/using_structs.sol b/test/libsolidity/semanticTests/errors/using_structs.sol new file mode 100644 index 000000000000..9ca6f0983100 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/using_structs.sol @@ -0,0 +1,16 @@ +pragma abicoder v2; +struct S { uint a; string b; } +error E(uint a, S, uint b); +contract C { + S s; + function f(bool c) public { + s.a = 9; + s.b = "abc"; + revert E(2, s, 7); + } +} +// ==== +// compileViaYul: also +// ---- +// f(bool): true -> FAILURE, hex"e96e07f0", hex"0000000000000000000000000000000000000000000000000000000000000002", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"0000000000000000000000000000000000000000000000000000000000000007", hex"0000000000000000000000000000000000000000000000000000000000000009", hex"0000000000000000000000000000000000000000000000000000000000000040", hex"0000000000000000000000000000000000000000000000000000000000000003", hex"6162630000000000000000000000000000000000000000000000000000000000" +// f(bool): false -> FAILURE, hex"e96e07f0", hex"0000000000000000000000000000000000000000000000000000000000000002", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"0000000000000000000000000000000000000000000000000000000000000007", hex"0000000000000000000000000000000000000000000000000000000000000009", hex"0000000000000000000000000000000000000000000000000000000000000040", hex"0000000000000000000000000000000000000000000000000000000000000003", hex"6162630000000000000000000000000000000000000000000000000000000000" diff --git a/test/libsolidity/semanticTests/errors/via_contract_type.sol b/test/libsolidity/semanticTests/errors/via_contract_type.sol new file mode 100644 index 000000000000..767628c2a09e --- /dev/null +++ b/test/libsolidity/semanticTests/errors/via_contract_type.sol @@ -0,0 +1,18 @@ +contract A { + error E(uint); +} +contract X { + error E(string); +} +contract B is A { + function f() public pure { revert E(1); } + function g() public pure { revert A.E(1); } + function h() public pure { revert X.E("abc"); } + +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000001" +// g() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000001" +// h() -> FAILURE, hex"3e9992c9", hex"0000000000000000000000000000000000000000000000000000000000000020", hex"0000000000000000000000000000000000000000000000000000000000000003", hex"6162630000000000000000000000000000000000000000000000000000000000" diff --git a/test/libsolidity/semanticTests/errors/via_import.sol b/test/libsolidity/semanticTests/errors/via_import.sol new file mode 100644 index 000000000000..9dd9a2cd34f5 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/via_import.sol @@ -0,0 +1,25 @@ +==== Source: s1.sol ==== +error E(uint); +==== Source: s2.sol ==== +import "s1.sol" as S; +==== Source: s3.sol ==== +import "s1.sol" as S; +import "s2.sol" as T; +import "s1.sol"; +contract C { + function x() public pure { + revert E(1); + } + function y() public pure { + revert S.E(2); + } + function z() public pure { + revert T.S.E(3); + } +} +// ==== +// compileViaYul: also +// ---- +// x() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000001" +// y() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000002" +// z() -> FAILURE, hex"002ff067", hex"0000000000000000000000000000000000000000000000000000000000000003" diff --git a/test/libsolidity/semanticTests/errors/weird_name.sol b/test/libsolidity/semanticTests/errors/weird_name.sol new file mode 100644 index 000000000000..080522346454 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/weird_name.sol @@ -0,0 +1,10 @@ +error error(uint a); +contract C { + function f() public pure { + revert error(2); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> FAILURE, hex"b48fb6cf", hex"0000000000000000000000000000000000000000000000000000000000000002"