diff --git a/Changelog.md b/Changelog.md index 9772ec2a3..b6643af58 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Compiler Features: * Yul Optimizer: Prune unused parameters in functions. * Yul Optimizer: Inline into functions further down in the call graph first. * Yul Optimizer: Try to simplify function names. + * Yul IR Generator: Report source locations related to unimplemented features. Bugfixes: diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 8fb932d01..151b8c9fd 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -164,7 +164,7 @@ string IRGenerator::generate( string IRGenerator::generate(Block const& _block) { IRGeneratorForStatements generator(m_context, m_utils); - _block.accept(generator); + generator.generate(_block); return generator.code(); } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 34b677c9c..176f876bd 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -38,6 +38,8 @@ #include #include +#include + #include #include #include @@ -152,70 +154,130 @@ string IRGeneratorForStatements::code() const return m_code.str(); } +void IRGeneratorForStatements::generate(Block const& _block) +{ + try + { + _block.accept(*this); + } + catch (langutil::UnimplementedFeatureError const& _error) + { + if (!boost::get_error_info(_error)) + _error << langutil::errinfo_sourceLocation(m_currentLocation); + throw _error; + } +} + void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl) { - solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); - solAssert(!_varDecl.isConstant(), ""); - if (!_varDecl.value()) - return; + try + { + setLocation(_varDecl); - _varDecl.value()->accept(*this); - writeToLValue( - _varDecl.immutable() ? - IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} : - IRLValue{*_varDecl.annotation().type, IRLValue::Storage{ - util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first), - m_context.storageLocationOfStateVariable(_varDecl).second - }}, - *_varDecl.value() - ); + solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); + solAssert(!_varDecl.isConstant(), ""); + if (!_varDecl.value()) + return; + + _varDecl.value()->accept(*this); + writeToLValue( + _varDecl.immutable() ? + IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} : + IRLValue{*_varDecl.annotation().type, IRLValue::Storage{ + util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first), + m_context.storageLocationOfStateVariable(_varDecl).second + }}, + *_varDecl.value() + ); + } + catch (langutil::UnimplementedFeatureError const& _error) + { + if (!boost::get_error_info(_error)) + _error << langutil::errinfo_sourceLocation(m_currentLocation); + throw _error; + } } void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl) { - solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable."); + try + { + setLocation(_varDecl); - auto const* type = _varDecl.type(); - if (auto const* refType = dynamic_cast(type)) - if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer()) - return; + solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable."); - IRVariable zero = zeroValue(*type); - assign(m_context.localVariable(_varDecl), zero); + auto const* type = _varDecl.type(); + if (auto const* refType = dynamic_cast(type)) + if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer()) + return; + + IRVariable zero = zeroValue(*type); + assign(m_context.localVariable(_varDecl), zero); + } + catch (langutil::UnimplementedFeatureError const& _error) + { + if (!boost::get_error_info(_error)) + _error << langutil::errinfo_sourceLocation(m_currentLocation); + throw _error; + } } IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType) { - _expression.accept(*this); - IRVariable variable{m_context.newYulVariable(), _targetType}; - define(variable, _expression); - return variable; + try + { + setLocation(_expression); + + _expression.accept(*this); + IRVariable variable{m_context.newYulVariable(), _targetType}; + define(variable, _expression); + return variable; + } + catch (langutil::UnimplementedFeatureError const& _error) + { + if (!boost::get_error_info(_error)) + _error << langutil::errinfo_sourceLocation(m_currentLocation); + throw _error; + } } string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant) { - string functionName = IRNames::constantValueFunction(_constant); - return m_context.functionCollector().createFunction(functionName, [&] { - Whiskers templ(R"( - function () -> { - - := - } - )"); - templ("functionName", functionName); - IRGeneratorForStatements generator(m_context, m_utils); - solAssert(_constant.value(), ""); - Type const& constantType = *_constant.type(); - templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList()); - templ("code", generator.code()); - templ("ret", IRVariable("ret", constantType).commaSeparatedList()); + try + { + setLocation(_constant); - return templ.render(); - }); + string functionName = IRNames::constantValueFunction(_constant); + return m_context.functionCollector().createFunction(functionName, [&] { + Whiskers templ(R"( + function () -> { + + := + } + )"); + templ("functionName", functionName); + IRGeneratorForStatements generator(m_context, m_utils); + solAssert(_constant.value(), ""); + Type const& constantType = *_constant.type(); + templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList()); + templ("code", generator.code()); + templ("ret", IRVariable("ret", constantType).commaSeparatedList()); + + return templ.render(); + }); + } + catch (langutil::UnimplementedFeatureError const& _error) + { + if (!boost::get_error_info(_error)) + _error << langutil::errinfo_sourceLocation(m_currentLocation); + throw _error; + } } void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement) { + setLocation(_varDeclStatement); + if (Expression const* expression = _varDeclStatement.initialValue()) { if (_varDeclStatement.declarations().size() > 1) @@ -249,14 +311,22 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional) { _conditional.condition().accept(*this); + setLocation(_conditional); + string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean()); declare(_conditional); m_code << "switch " << condition << "\n" "case 0 {\n"; + _conditional.falseExpression().accept(*this); + setLocation(_conditional); + assign(_conditional, _conditional.falseExpression()); m_code << "}\n" "default {\n"; + _conditional.trueExpression().accept(*this); + setLocation(_conditional); + assign(_conditional, _conditional.trueExpression()); m_code << "}\n"; @@ -266,6 +336,7 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional) bool IRGeneratorForStatements::visit(Assignment const& _assignment) { _assignment.rightHandSide().accept(*this); + setLocation(_assignment); Token assignmentOperator = _assignment.assignmentOperator(); Token binaryOperator = @@ -283,6 +354,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType); _assignment.leftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); + setLocation(_assignment); if (assignmentOperator != Token::Assign) { @@ -323,6 +395,8 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) { + setLocation(_tuple); + if (_tuple.isInlineArray()) { auto const& arrayType = dynamic_cast(*_tuple.annotation().type); @@ -339,6 +413,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) { Expression const& component = *_tuple.components()[i]; component.accept(*this); + setLocation(_tuple); IRVariable converted = convert(component, baseType); m_code << m_utils.writeToMemoryFunction(baseType) << @@ -358,6 +433,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) { solAssert(_tuple.components().front(), ""); _tuple.components().front()->accept(*this); + setLocation(_tuple); if (willBeWrittenTo) solAssert(!!m_currentLValue, ""); else @@ -370,6 +446,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) if (auto const& component = _tuple.components()[i]) { component->accept(*this); + setLocation(_tuple); if (willBeWrittenTo) { solAssert(!!m_currentLValue, ""); @@ -395,17 +472,20 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) { _ifStatement.condition().accept(*this); + setLocation(_ifStatement); string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean()); if (_ifStatement.falseStatement()) { m_code << "switch " << condition << "\n" "case 0 {\n"; _ifStatement.falseStatement()->accept(*this); + setLocation(_ifStatement); m_code << "}\n" "default {\n"; } else m_code << "if " << condition << " {\n"; _ifStatement.trueStatement().accept(*this); + setLocation(_ifStatement); m_code << "}\n"; return false; @@ -413,6 +493,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) bool IRGeneratorForStatements::visit(ForStatement const& _forStatement) { + setLocation(_forStatement); generateLoop( _forStatement.body(), _forStatement.condition(), @@ -425,6 +506,7 @@ bool IRGeneratorForStatements::visit(ForStatement const& _forStatement) bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement) { + setLocation(_whileStatement); generateLoop( _whileStatement.body(), &_whileStatement.condition(), @@ -436,20 +518,23 @@ bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement) return false; } -bool IRGeneratorForStatements::visit(Continue const&) +bool IRGeneratorForStatements::visit(Continue const& _continue) { + setLocation(_continue); m_code << "continue\n"; return false; } -bool IRGeneratorForStatements::visit(Break const&) +bool IRGeneratorForStatements::visit(Break const& _break) { + setLocation(_break); m_code << "break\n"; return false; } void IRGeneratorForStatements::endVisit(Return const& _return) { + setLocation(_return); if (Expression const* value = _return.expression()) { solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); @@ -466,6 +551,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return) void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) { + setLocation(_unaryOperation); Type const& resultType = type(_unaryOperation); Token const op = _unaryOperation.getOperator(); @@ -551,6 +637,8 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) { + setLocation(_binOp); + solAssert(!!_binOp.annotation().commonType, ""); TypePointer commonType = _binOp.annotation().commonType; langutil::Token op = _binOp.getOperator(); @@ -570,6 +658,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) _binOp.leftExpression().accept(*this); _binOp.rightExpression().accept(*this); + setLocation(_binOp); if (TokenTraits::isCompareOp(op)) { @@ -629,6 +718,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall) { + setLocation(_functionCall); FunctionTypePointer functionType = dynamic_cast(&type(_functionCall.expression())); if ( functionType && @@ -643,6 +733,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall) void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { + setLocation(_functionCall); auto functionCallKind = *_functionCall.annotation().kind; if (functionCallKind == FunctionCallKind::TypeConversion) @@ -1360,6 +1451,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options) { + setLocation(_options); FunctionType const& previousType = dynamic_cast(*_options.expression().annotation().type); solUnimplementedAssert(!previousType.bound(), ""); @@ -1379,6 +1471,7 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options) void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { + setLocation(_memberAccess); ASTString const& member = _memberAccess.memberName(); auto memberFunctionType = dynamic_cast(_memberAccess.annotation().type); Type::Category objectCategory = _memberAccess.expression().annotation().type->category(); @@ -1764,6 +1857,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) { + setLocation(_inlineAsm); CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences}; yul::Statement modified = bodyCopier(_inlineAsm.operations()); @@ -1778,6 +1872,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) { + setLocation(_indexAccess); Type const& baseType = *_indexAccess.baseExpression().annotation().type; if (baseType.category() == Type::Category::Mapping) @@ -1913,6 +2008,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess) { + setLocation(_indexRangeAccess); Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type; solAssert( baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice, @@ -1959,6 +2055,7 @@ void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAcces void IRGeneratorForStatements::endVisit(Identifier const& _identifier) { + setLocation(_identifier); Declaration const* declaration = _identifier.annotation().referencedDeclaration; if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) { @@ -2017,6 +2114,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) bool IRGeneratorForStatements::visit(Literal const& _literal) { + setLocation(_literal); Type const& literalType = type(_literal); switch (literalType.category()) @@ -2039,6 +2137,7 @@ void IRGeneratorForStatements::handleVariableReference( Expression const& _referencingExpression ) { + setLocation(_referencingExpression); if (_variable.isStateVariable() && _variable.isConstant()) define(_referencingExpression) << constantValueFunction(_variable) << "()\n"; else if (_variable.isStateVariable() && _variable.immutable()) @@ -2480,6 +2579,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b solAssert(op == Token::Or || op == Token::And, ""); _binOp.leftExpression().accept(*this); + setLocation(_binOp); IRVariable value(_binOp); define(value, _binOp.leftExpression()); @@ -2488,6 +2588,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b else m_code << "if " << value.name() << " {\n"; _binOp.rightExpression().accept(*this); + setLocation(_binOp); assign(value, _binOp.rightExpression()); m_code << "}\n"; } @@ -2687,6 +2788,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement) { Expression const& externalCall = _tryStatement.externalCall(); externalCall.accept(*this); + setLocation(_tryStatement); m_code << "switch iszero(" << IRNames::trySuccessConditionVariable(externalCall) << ")\n"; @@ -2707,6 +2809,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement) } successClause.block().accept(*this); + setLocation(_tryStatement); m_code << "}\n"; m_code << "default { // failure case\n"; @@ -2797,3 +2900,8 @@ bool IRGeneratorForStatements::visit(TryCatchClause const& _clause) _clause.block().accept(*this); return false; } + +void IRGeneratorForStatements::setLocation(ASTNode const& _node) +{ + m_currentLocation = _node.location(); +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 09c9c7d27..3d87eae57 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -47,6 +47,9 @@ public: std::string code() const; + /// Generate the code for the statements in the block; + void generate(Block const& _block); + /// Generates code to initialize the given state variable. void initializeStateVar(VariableDeclaration const& _varDecl); /// Generates code to initialize the given local variable. @@ -179,10 +182,13 @@ private: static Type const& type(Expression const& _expression); + void setLocation(ASTNode const& _node); + std::ostringstream m_code; IRGenerationContext& m_context; YulUtilFunctions& m_utils; std::optional m_currentLValue; + langutil::SourceLocation m_currentLocation; }; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 79380a703..32469e566 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -524,6 +524,10 @@ bool CompilerStack::compile() { if (m_generateEvmBytecode) compileContract(*contract, otherCompilers); + if (m_generateIR || m_generateEwasm) + generateIR(*contract); + if (m_generateEwasm) + generateEwasm(*contract); } catch (Error const& _error) { @@ -532,10 +536,28 @@ bool CompilerStack::compile() m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what()); return false; } - if (m_generateIR || m_generateEwasm) - generateIR(*contract); - if (m_generateEwasm) - generateEwasm(*contract); + catch (UnimplementedFeatureError const& _unimplementedError) + { + if ( + SourceLocation const* sourceLocation = + boost::get_error_info(_unimplementedError) + ) + { + string const* comment = _unimplementedError.comment(); + m_errorReporter.error( + 1834_error, + Error::Type::CodeGenerationError, + *sourceLocation, + "Unimplemented feature error" + + ((comment && !comment->empty()) ? ": " + *comment : string{}) + + " in " + + _unimplementedError.lineInfo() + ); + return false; + } + else + throw; + } } m_stackState = CompilationSuccessful; this->link(); diff --git a/scripts/error_codes.py b/scripts/error_codes.py index 69e452b2a..d77fcacaa 100755 --- a/scripts/error_codes.py +++ b/scripts/error_codes.py @@ -188,6 +188,7 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): # Warning (1878): SPDX license identifier not provided in source file. .... # Warning (3420): Source file does not specify required compiler version! test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err")) + test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "yul_unimplemented", "err")) # white list of ids which are not covered by tests white_ids = { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index a22fb7339..abb7ab5ba 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -124,6 +124,7 @@ function test_solc_behaviour() sed -i.bak -e '/^Warning (3805): This is a pre-release compiler version, please do not use it in production./d' "$stderr_path" sed -i.bak -e 's/\(^[ ]*auxdata: \)0x[0-9a-f]*$/\1AUXDATA REMOVED/' "$stdout_path" sed -i.bak -e 's/ Consider adding "pragma .*$//' "$stderr_path" + sed -i.bak -e 's/\(Unimplemented feature error: .* in \).*$/\1FILENAME REMOVED/' "$stderr_path" sed -i.bak -e 's/"version": "[^"]*"/"version": "VERSION REMOVED"/' "$stdout_path" # Remove trailing empty lines. Needs a line break to make OSX sed happy. sed -i.bak -e '1{/^$/d diff --git a/test/cmdlineTests/yul_unimplemented/args b/test/cmdlineTests/yul_unimplemented/args new file mode 100644 index 000000000..e869749f2 --- /dev/null +++ b/test/cmdlineTests/yul_unimplemented/args @@ -0,0 +1 @@ +--ir --error-codes diff --git a/test/cmdlineTests/yul_unimplemented/err b/test/cmdlineTests/yul_unimplemented/err new file mode 100644 index 000000000..d6159a1f1 --- /dev/null +++ b/test/cmdlineTests/yul_unimplemented/err @@ -0,0 +1,5 @@ +Error (1834): Unimplemented feature error: setToZero for type t_struct$_S_$4_storage not yet implemented! in FILENAME REMOVED + --> yul_unimplemented/input.sol:9:9: + | +9 | delete str; + | ^^^^^^^^^^ diff --git a/test/cmdlineTests/yul_unimplemented/exit b/test/cmdlineTests/yul_unimplemented/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_unimplemented/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_unimplemented/input.sol b/test/cmdlineTests/yul_unimplemented/input.sol new file mode 100644 index 000000000..d024eeba0 --- /dev/null +++ b/test/cmdlineTests/yul_unimplemented/input.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +contract test { + struct S { + uint x; + } + S str; + constructor() { + delete str; + } +} \ No newline at end of file diff --git a/test/libsolidity/SolidityExecutionFramework.cpp b/test/libsolidity/SolidityExecutionFramework.cpp index 84333269e..4a0745431 100644 --- a/test/libsolidity/SolidityExecutionFramework.cpp +++ b/test/libsolidity/SolidityExecutionFramework.cpp @@ -54,6 +54,12 @@ bytes SolidityExecutionFramework::multiSourceCompileContract( m_compiler.setRevertStringBehaviour(m_revertStrings); if (!m_compiler.compile()) { + // The testing framework expects an exception for + // "unimplemented" yul IR generation. + if (m_compileViaYul) + for (auto const& error: m_compiler.errors()) + if (error->type() == langutil::Error::Type::CodeGenerationError) + BOOST_THROW_EXCEPTION(*error); langutil::SourceReferenceFormatter formatter(std::cerr); for (auto const& error: m_compiler.errors())