diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 366c354cd73b..c63c367b6ee1 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -190,6 +190,7 @@ namespace solidity::langutil K(Throw, "throw", 0) \ K(Try, "try", 0) \ K(Type, "type", 0) \ + K(Unchecked, "unchecked", 0) \ K(Unicode, "unicode", 0) \ K(Using, "using", 0) \ K(View, "view", 0) \ @@ -265,7 +266,6 @@ namespace solidity::langutil K(Switch, "switch", 0) \ K(Typedef, "typedef", 0) \ K(TypeOf, "typeof", 0) \ - K(Unchecked, "unchecked", 0) \ K(Var, "var", 0) \ \ /* Illegal token - not able to scan. */ \ @@ -314,7 +314,7 @@ namespace TokenTraits constexpr bool isEtherSubdenomination(Token op) { return op >= Token::SubWei && op <= Token::SubEther; } constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; } - constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); } + constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Var); } inline Token AssignmentToBinaryOp(Token op) { diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index de358a9e711e..2b0f2403a83b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1335,18 +1335,24 @@ class Block: public Statement, public Scopable int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, + bool _unchecked, std::vector> _statements ): - Statement(_id, _location, _docString), m_statements(std::move(_statements)) {} + Statement(_id, _location, _docString), + m_statements(std::move(_statements)), + m_unchecked(_unchecked) + {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; std::vector> const& statements() const { return m_statements; } + bool unchecked() const { return m_unchecked; } BlockAnnotation& annotation() const override; private: std::vector> m_statements; + bool m_unchecked; }; /** diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 5403b05c575e..22be5b199311 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -535,7 +535,8 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node) bool ASTJsonConverter::visit(Block const& _node) { setJsonNode(_node, "Block", { - make_pair("statements", toJson(_node.statements())) + make_pair("statements", toJson(_node.statements())), + make_pair("unchecked", _node.unchecked()) }); return false; } diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 2d1fda322b64..c4a24a7c4b45 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -576,6 +576,7 @@ ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) return createASTNode( _node, nullOrASTString(_node, "documentation"), + member(_node, "unchecked") && memberAsBool(_node, "unchecked"), statements ); } diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index f82c705d8847..60c53e0156fa 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -122,6 +122,10 @@ class CompilerContext void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; } ContractDefinition const& mostDerivedContract() const; + void pushCheckedArithmetics(bool _value) { m_checkedArithmetics.push(_value); } + void popCheckedArithmetics() { m_checkedArithmetics.pop(); } + bool checkedArithmetics() const { return m_checkedArithmetics.empty() ? true : m_checkedArithmetics.top(); } + /// @returns the next function in the queue of functions that are still to be compiled /// (i.e. that were referenced during compilation but where we did not yet generate code for). /// Returns nullptr if the queue is empty. Does not remove the function from the queue, @@ -373,6 +377,8 @@ class CompilerContext std::map> m_localVariables; /// The contract currently being compiled. Virtual function lookup starts from this contarct. ContractDefinition const* m_mostDerivedContract = nullptr; + /// Whether to use checked arithmetics. + std::stack m_checkedArithmetics; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 225628a6cf5c..2de81d43316b 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -1253,12 +1253,14 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) bool ContractCompiler::visit(Block const& _block) { + m_context.pushCheckedArithmetics(!_block.unchecked()); storeStackHeight(&_block); return true; } void ContractCompiler::endVisit(Block const& _block) { + m_context.popCheckedArithmetics(); // Frees local variables declared in the scope of this block. popScopedVariables(&_block); } @@ -1332,6 +1334,9 @@ void ContractCompiler::appendModifierOrFunctionCode() if (codeBlock) { + m_context.pushCheckedArithmetics(true); + // TODO test that checks are also applied for initializing state variables + m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight()); codeBlock->accept(*this); @@ -1342,6 +1347,8 @@ void ContractCompiler::appendModifierOrFunctionCode() CompilerUtils(m_context).popStackSlots(stackSurplus); for (auto var: addedVariables) m_context.removeVariable(*var); + + m_context.popCheckedArithmetics(); } m_modifierDepth--; m_context.setModifierDepth(m_modifierDepth); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b43e0cf9feaf..f420681f1e9e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -450,7 +450,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) m_context << commonType->literalValue(nullptr); else { - bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); + bool cleanupNeeded = m_context.checkedArithmetics() || cleanupNeededForOp(commonType->category(), c_op); TypePointer leftTargetType = commonType; TypePointer rightTargetType = @@ -2060,34 +2060,68 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons solUnimplemented("Not yet implemented - FixedPointType."); IntegerType const& type = dynamic_cast(_type); - bool const c_isSigned = type.isSigned(); - - switch (_operator) + if (m_context.checkedArithmetics()) { - case Token::Add: - m_context << Instruction::ADD; - break; - case Token::Sub: - m_context << Instruction::SUB; - break; - case Token::Mul: - m_context << Instruction::MUL; - break; - case Token::Div: - case Token::Mod: + string functionName; + switch (_operator) + { + case Token::Add: + functionName = m_context.utilFunctions().overflowCheckedIntAddFunction(type); + break; + case Token::Sub: + functionName = m_context.utilFunctions().overflowCheckedIntSubFunction(type); + break; + case Token::Mul: + functionName = m_context.utilFunctions().overflowCheckedIntMulFunction(type); + break; + case Token::Div: + functionName = m_context.utilFunctions().overflowCheckedIntDivFunction(type); + break; + case Token::Mod: + functionName = m_context.utilFunctions().checkedIntModFunction(type); + break; + case Token::Exp: + // TODO + m_context << Instruction::EXP; + break; + default: + solAssert(false, "Unknown arithmetic operator."); + } + // TODO Maybe we want to force-inline this? + // TODO revert with special error + m_context.callYulFunction(functionName, 2, 1); + } + else { - // Test for division by zero - m_context << Instruction::DUP2 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + bool const c_isSigned = type.isSigned(); - if (_operator == Token::Div) - m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); - else - m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); - break; - } - default: - solAssert(false, "Unknown arithmetic operator."); + switch (_operator) + { + case Token::Add: + m_context << Instruction::ADD; + break; + case Token::Sub: + m_context << Instruction::SUB; + break; + case Token::Mul: + m_context << Instruction::MUL; + break; + case Token::Div: + case Token::Mod: + { + // Test for division by zero + m_context << Instruction::DUP2 << Instruction::ISZERO; + m_context.appendConditionalInvalid(); + + if (_operator == Token::Div) + m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV); + else + m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD); + break; + } + default: + solAssert(false, "Unknown arithmetic operator."); + } } } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index cfb7de46eea1..c89a13806082 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1084,6 +1084,9 @@ ASTPointer Parser::parseBlock(ASTPointer const& _docString) { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); + bool const unchecked = m_scanner->currentToken() == Token::Unchecked; + if (unchecked) + m_scanner->next(); expectToken(Token::LBrace); vector> statements; try @@ -1106,7 +1109,7 @@ ASTPointer Parser::parseBlock(ASTPointer const& _docString) expectTokenOrConsumeUntil(Token::RBrace, "Block"); else expectToken(Token::RBrace); - return nodeFactory.createNode(_docString, statements); + return nodeFactory.createNode(_docString, unchecked, statements); } ASTPointer Parser::parseStatement() @@ -1128,9 +1131,9 @@ ASTPointer Parser::parseStatement() return parseDoWhileStatement(docString); case Token::For: return parseForStatement(docString); + case Token::Unchecked: case Token::LBrace: return parseBlock(docString); - // starting from here, all statements must be terminated by a semicolon case Token::Continue: statement = ASTNodeFactory(*this).createNode(docString); m_scanner->next();