diff --git a/liblangutil/Token.h b/liblangutil/Token.h index ab4eba8bab73..5313484de439 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -188,6 +188,7 @@ namespace solidity::langutil K(Struct, "struct", 0) \ K(Throw, "throw", 0) \ K(Type, "type", 0) \ + K(Unchecked, "unchecked", 0) \ K(Using, "using", 0) \ K(View, "view", 0) \ K(Virtual, "virtual", 0) \ @@ -263,7 +264,6 @@ namespace solidity::langutil K(Try, "try", 0) \ K(Typedef, "typedef", 0) \ K(TypeOf, "typeof", 0) \ - K(Unchecked, "unchecked", 0) \ K(Var, "var", 0) \ \ /* Illegal token - not able to scan. */ \ @@ -312,7 +312,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..fe2da0820064 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1335,18 +1335,23 @@ 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 d04fcbe0bd22..9d844b0cb599 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 e3cb8c6fa6ab..fdf577f6d158 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..1b3643378b50 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -122,6 +122,9 @@ class CompilerContext void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; } ContractDefinition const& mostDerivedContract() const; + void setCheckedArithmetics(bool _value) { m_checkedArithmetics = _value; } + bool checkedArithmetics() const { return m_checkedArithmetics; } + /// @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 +376,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 or not to use checked arithmatics. + bool m_checkedArithmetics = true; /// 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..0f65f9833060 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -1332,6 +1332,10 @@ void ContractCompiler::appendModifierOrFunctionCode() if (codeBlock) { + bool previousChecked = m_context.checkedArithmetics(); + // TODO test that checks are also applied for initializing state variables + m_context.setCheckedArithmetics(true); + m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight()); codeBlock->accept(*this); @@ -1342,6 +1346,8 @@ void ContractCompiler::appendModifierOrFunctionCode() CompilerUtils(m_context).popStackSlots(stackSurplus); for (auto var: addedVariables) m_context.removeVariable(*var); + + m_context.setCheckedArithmetics(previousChecked); } m_modifierDepth--; m_context.setModifierDepth(m_modifierDepth); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 428f59993004..6fbe82bf638c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -449,7 +449,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 = TokenTraits::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType; @@ -2054,37 +2054,71 @@ 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; - } - case Token::Exp: - m_context << Instruction::EXP; - 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; + } + case Token::Exp: + m_context << Instruction::EXP; + break; + default: + solAssert(false, "Unknown arithmetic operator."); + } } } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 16ed2ae375c5..20f586cc1cee 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();