diff options
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/CMakeLists.txt | 3 | ||||
-rw-r--r-- | libsolidity/ast/ASTJsonConverter.cpp | 121 | ||||
-rw-r--r-- | libsolidity/ast/ASTJsonConverter.h | 18 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 11 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 2 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 4 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 1 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 9 | ||||
-rw-r--r-- | libsolidity/formal/Why3Translator.cpp | 200 | ||||
-rw-r--r-- | libsolidity/formal/Why3Translator.h | 17 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 117 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 21 | ||||
-rw-r--r-- | libsolidity/parsing/Scanner.cpp | 43 | ||||
-rw-r--r-- | libsolidity/parsing/Scanner.h | 2 | ||||
-rw-r--r-- | libsolidity/parsing/Token.h | 4 |
15 files changed, 454 insertions, 119 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index e86792c1..bcc47e5a 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -15,8 +15,7 @@ file(GLOB HEADERS "*/*.h") include_directories(BEFORE ..) add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) -eth_use(${EXECUTABLE} REQUIRED Dev::devcore Solidity::evmasm) +eth_use(${EXECUTABLE} REQUIRED Dev::soldevcore Solidity::solevmasm) install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) -install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 89d0bf35..fc43976d 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -42,12 +42,17 @@ void ASTJsonConverter::addKeyValue(Json::Value& _obj, string const& _key, string _obj[_key] = _val; } -void ASTJsonConverter::addJsonNode(string const& _nodeName, - initializer_list<pair<string const, string const>> _list, - bool _hasChildren = false) +void ASTJsonConverter::addJsonNode( + ASTNode const& _node, + string const& _nodeName, + initializer_list<pair<string const, string const>> _list, + bool _hasChildren = false +) { Json::Value node; + node["id"] = reinterpret_cast<Json::UInt64>(&_node); + node["src"] = sourceLocationToString(_node.location()); node["name"] = _nodeName; if (_list.size() != 0) { @@ -68,7 +73,21 @@ void ASTJsonConverter::addJsonNode(string const& _nodeName, } } -ASTJsonConverter::ASTJsonConverter(ASTNode const& _ast): m_ast(&_ast) +string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const +{ + int sourceIndex{-1}; + if (_location.sourceName && m_sourceIndices.count(*_location.sourceName)) + sourceIndex = m_sourceIndices.at(*_location.sourceName); + int length = -1; + if (_location.start >= 0 && _location.end >= 0) + length = _location.end - _location.start; + return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex); +} + +ASTJsonConverter::ASTJsonConverter( + ASTNode const& _ast, + map<string, unsigned> _sourceIndices +): m_ast(&_ast), m_sourceIndices(_sourceIndices) { Json::Value children(Json::arrayValue); @@ -91,31 +110,31 @@ Json::Value const& ASTJsonConverter::json() bool ASTJsonConverter::visit(ImportDirective const& _node) { - addJsonNode("Import", { make_pair("file", _node.path())}); + addJsonNode(_node, "Import", { make_pair("file", _node.path())}); return true; } bool ASTJsonConverter::visit(ContractDefinition const& _node) { - addJsonNode("Contract", { make_pair("name", _node.name()) }, true); + addJsonNode(_node, "Contract", { make_pair("name", _node.name()) }, true); return true; } bool ASTJsonConverter::visit(StructDefinition const& _node) { - addJsonNode("Struct", { make_pair("name", _node.name()) }, true); + addJsonNode(_node, "Struct", { make_pair("name", _node.name()) }, true); return true; } -bool ASTJsonConverter::visit(ParameterList const&) +bool ASTJsonConverter::visit(ParameterList const& _node) { - addJsonNode("ParameterList", {}, true); + addJsonNode(_node, "ParameterList", {}, true); return true; } bool ASTJsonConverter::visit(FunctionDefinition const& _node) { - addJsonNode("Function", + addJsonNode(_node, "Function", { make_pair("name", _node.name()), make_pair("public", boost::lexical_cast<std::string>(_node.isPublic())), make_pair("const", boost::lexical_cast<std::string>(_node.isDeclaredConst())) }, @@ -125,7 +144,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) bool ASTJsonConverter::visit(VariableDeclaration const& _node) { - addJsonNode("VariableDeclaration", { + addJsonNode(_node, "VariableDeclaration", { make_pair("name", _node.name()), make_pair("name", _node.name()), }, true); @@ -139,114 +158,114 @@ bool ASTJsonConverter::visit(TypeName const&) bool ASTJsonConverter::visit(ElementaryTypeName const& _node) { - addJsonNode("ElementaryTypeName", { make_pair("name", _node.typeName().toString()) }); + addJsonNode(_node, "ElementaryTypeName", { make_pair("name", _node.typeName().toString()) }); return true; } bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) { - addJsonNode("UserDefinedTypeName", { + addJsonNode(_node, "UserDefinedTypeName", { make_pair("name", boost::algorithm::join(_node.namePath(), ".")) }); return true; } -bool ASTJsonConverter::visit(Mapping const&) +bool ASTJsonConverter::visit(Mapping const& _node) { - addJsonNode("Mapping", {}, true); + addJsonNode(_node, "Mapping", {}, true); return true; } -bool ASTJsonConverter::visit(InlineAssembly const&) +bool ASTJsonConverter::visit(InlineAssembly const& _node) { - addJsonNode("InlineAssembly", {}, true); + addJsonNode(_node, "InlineAssembly", {}, true); return true; } -bool ASTJsonConverter::visit(Block const&) +bool ASTJsonConverter::visit(Block const& _node) { - addJsonNode("Block", {}, true); + addJsonNode(_node, "Block", {}, true); return true; } -bool ASTJsonConverter::visit(IfStatement const&) +bool ASTJsonConverter::visit(IfStatement const& _node) { - addJsonNode("IfStatement", {}, true); + addJsonNode(_node, "IfStatement", {}, true); return true; } -bool ASTJsonConverter::visit(WhileStatement const&) +bool ASTJsonConverter::visit(WhileStatement const& _node) { - addJsonNode("WhileStatement", {}, true); + addJsonNode(_node, "WhileStatement", {}, true); return true; } -bool ASTJsonConverter::visit(ForStatement const&) +bool ASTJsonConverter::visit(ForStatement const& _node) { - addJsonNode("ForStatement", {}, true); + addJsonNode(_node, "ForStatement", {}, true); return true; } -bool ASTJsonConverter::visit(Continue const&) +bool ASTJsonConverter::visit(Continue const& _node) { - addJsonNode("Continue", {}); + addJsonNode(_node, "Continue", {}); return true; } -bool ASTJsonConverter::visit(Break const&) +bool ASTJsonConverter::visit(Break const& _node) { - addJsonNode("Break", {}); + addJsonNode(_node, "Break", {}); return true; } -bool ASTJsonConverter::visit(Return const&) +bool ASTJsonConverter::visit(Return const& _node) { - addJsonNode("Return", {}, true);; + addJsonNode(_node, "Return", {}, true);; return true; } -bool ASTJsonConverter::visit(Throw const&) +bool ASTJsonConverter::visit(Throw const& _node) { - addJsonNode("Throw", {}, true);; + addJsonNode(_node, "Throw", {}, true);; return true; } -bool ASTJsonConverter::visit(VariableDeclarationStatement const&) +bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node) { - addJsonNode("VariableDefinition", {}, true); + addJsonNode(_node, "VariableDefinition", {}, true); return true; } -bool ASTJsonConverter::visit(ExpressionStatement const&) +bool ASTJsonConverter::visit(ExpressionStatement const& _node) { - addJsonNode("ExpressionStatement", {}, true); + addJsonNode(_node, "ExpressionStatement", {}, true); return true; } -bool ASTJsonConverter::visit(Conditional const&) +bool ASTJsonConverter::visit(Conditional const& _node) { - addJsonNode("Conditional", {}, true); + addJsonNode(_node, "Conditional", {}, true); return true; } bool ASTJsonConverter::visit(Assignment const& _node) { - addJsonNode("Assignment", + addJsonNode(_node, "Assignment", { make_pair("operator", Token::toString(_node.assignmentOperator())), make_pair("type", type(_node)) }, true); return true; } -bool ASTJsonConverter::visit(TupleExpression const&) +bool ASTJsonConverter::visit(TupleExpression const& _node) { - addJsonNode("TupleExpression",{}, true); + addJsonNode(_node, "TupleExpression",{}, true); return true; } bool ASTJsonConverter::visit(UnaryOperation const& _node) { - addJsonNode("UnaryOperation", + addJsonNode(_node, "UnaryOperation", { make_pair("prefix", boost::lexical_cast<std::string>(_node.isPrefixOperation())), make_pair("operator", Token::toString(_node.getOperator())), make_pair("type", type(_node)) }, @@ -256,7 +275,7 @@ bool ASTJsonConverter::visit(UnaryOperation const& _node) bool ASTJsonConverter::visit(BinaryOperation const& _node) { - addJsonNode("BinaryOperation", { + addJsonNode(_node, "BinaryOperation", { make_pair("operator", Token::toString(_node.getOperator())), make_pair("type", type(_node)) }, true); @@ -265,7 +284,7 @@ bool ASTJsonConverter::visit(BinaryOperation const& _node) bool ASTJsonConverter::visit(FunctionCall const& _node) { - addJsonNode("FunctionCall", { + addJsonNode(_node, "FunctionCall", { make_pair("type_conversion", boost::lexical_cast<std::string>(_node.annotation().isTypeConversion)), make_pair("type", type(_node)) }, true); @@ -274,13 +293,13 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) bool ASTJsonConverter::visit(NewExpression const& _node) { - addJsonNode("NewExpression", { make_pair("type", type(_node)) }, true); + addJsonNode(_node, "NewExpression", { make_pair("type", type(_node)) }, true); return true; } bool ASTJsonConverter::visit(MemberAccess const& _node) { - addJsonNode("MemberAccess", + addJsonNode(_node, "MemberAccess", { make_pair("member_name", _node.memberName()), make_pair("type", type(_node)) }, true); @@ -289,20 +308,20 @@ bool ASTJsonConverter::visit(MemberAccess const& _node) bool ASTJsonConverter::visit(IndexAccess const& _node) { - addJsonNode("IndexAccess", { make_pair("type", type(_node)) }, true); + addJsonNode(_node, "IndexAccess", { make_pair("type", type(_node)) }, true); return true; } bool ASTJsonConverter::visit(Identifier const& _node) { - addJsonNode("Identifier", + addJsonNode(_node, "Identifier", { make_pair("value", _node.name()), make_pair("type", type(_node)) }); return true; } bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) { - addJsonNode("ElementaryTypenameExpression", + addJsonNode(_node, "ElementaryTypenameExpression", { make_pair("value", _node.typeName().toString()), make_pair("type", type(_node)) }); return true; } @@ -310,7 +329,7 @@ bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) bool ASTJsonConverter::visit(Literal const& _node) { char const* tokenString = Token::toString(_node.token()); - addJsonNode("Literal", + addJsonNode(_node, "Literal", { make_pair("string", (tokenString) ? tokenString : "null"), make_pair("value", _node.value()), make_pair("type", type(_node)) }); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 91ee72e1..ca4d9c2d 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -42,7 +42,11 @@ class ASTJsonConverter: public ASTConstVisitor { public: /// Create a converter to JSON for the given abstract syntax tree. - explicit ASTJsonConverter(ASTNode const& _ast); + /// @a _sourceIndices is used to abbreviate source names in source locations. + explicit ASTJsonConverter( + ASTNode const& _ast, + std::map<std::string, unsigned> _sourceIndices = std::map<std::string, unsigned>() + ); /// Output the json representation of the AST to _stream. void print(std::ostream& _stream); Json::Value const& json(); @@ -118,9 +122,13 @@ public: private: void process(); void addKeyValue(Json::Value& _obj, std::string const& _key, std::string const& _val); - void addJsonNode(std::string const& _nodeName, - std::initializer_list<std::pair<std::string const, std::string const>> _list, - bool _hasChildren); + void addJsonNode( + ASTNode const& _node, + std::string const& _nodeName, + std::initializer_list<std::pair<std::string const, std::string const>> _list, + bool _hasChildren + ); + std::string sourceLocationToString(SourceLocation const& _location) const; std::string type(Expression const& _expression); std::string type(VariableDeclaration const& _varDecl); inline void goUp() @@ -132,8 +140,8 @@ private: bool processed = false; Json::Value m_astJson; std::stack<Json::Value*> m_jsonNodePtrs; - std::string m_source; ASTNode const* m_ast; + std::map<std::string, unsigned> m_sourceIndices; }; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 5630743b..28f7e1b7 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -26,6 +26,7 @@ #include <libdevcore/CommonIO.h> #include <libdevcore/CommonData.h> #include <libdevcore/SHA3.h> +#include <libdevcore/UTF8.h> #include <libsolidity/interface/Utils.h> #include <libsolidity/ast/AST.h> @@ -852,6 +853,16 @@ bool StringLiteralType::operator==(const Type& _other) const return m_value == dynamic_cast<StringLiteralType const&>(_other).m_value; } +std::string StringLiteralType::toString(bool) const +{ + size_t invalidSequence; + + if (!dev::validate(m_value, invalidSequence)) + return "literal_string (contains invalid UTF-8 sequence at position " + dev::toString(invalidSequence) + ")"; + + return "literal_string \"" + m_value + "\""; +} + TypePointer StringLiteralType::mobileType() const { return make_shared<ArrayType>(DataLocation::Memory, true); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 1ee762e5..1282e5d8 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -419,7 +419,7 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override { return 0; } - virtual std::string toString(bool) const override { return "literal_string \"" + m_value + "\""; } + virtual std::string toString(bool) const override; virtual TypePointer mobileType() const override; std::string const& value() const { return m_value; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index efb9b10a..d7d17b8e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -288,8 +288,8 @@ void CompilerUtils::memoryCopy() m_context << u256(0) << u256(identityContractAddress); // compute gas costs m_context << u256(32) << Instruction::DUP5 << u256(31) << Instruction::ADD; - static unsigned c_identityGas = 3; - static unsigned c_identityWordGas = 15; + static unsigned c_identityGas = 15; + static unsigned c_identityWordGas = 3; m_context << Instruction::DIV << u256(c_identityWordGas) << Instruction::MUL; m_context << u256(c_identityGas) << Instruction::ADD; m_context << Instruction::CALL; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 55254013..da74dc90 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -104,6 +104,7 @@ public: ); /// Zero-initialises (the data part of) an already allocated memory array. + /// Length has to be nonzero! /// Stack pre: <length> <memptr> /// Stack post: <updated_memptr> void zeroInitialiseMemoryArray(ArrayType const& _type); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b973a117..80009a90 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -792,15 +792,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().storeFreeMemoryPointer(); // Stack: memptr requested_length + // Check if length is zero + m_context << Instruction::DUP1 << Instruction::ISZERO; + auto skipInit = m_context.appendConditionalJump(); + // We only have to initialise if the base type is a not a value type. if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get())) { m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; utils().zeroInitialiseMemoryArray(arrayType); - m_context << Instruction::POP; } - else - m_context << Instruction::POP; + m_context << skipInit; + m_context << Instruction::POP; break; } default: diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index c794cb24..bd0a020d 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -67,23 +67,11 @@ void Why3Translator::fatalError(ASTNode const& _node, string const& _description BOOST_THROW_EXCEPTION(FatalError()); } -void Why3Translator::appendPreface() -{ - m_lines.push_back(Line{R"( -module UInt256 - use import mach.int.Unsigned - type uint256 - constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - clone export mach.int.Unsigned with - type t = uint256, - constant max = max_uint256 -end -)", 0}); -} - string Why3Translator::toFormalType(Type const& _type) const { - if (auto type = dynamic_cast<IntegerType const*>(&_type)) + if (_type.category() == Type::Category::Bool) + return "bool"; + else if (auto type = dynamic_cast<IntegerType const*>(&_type)) { if (!type->isAddress() && !type->isSigned() && type->numBits() == 256) return "uint256"; @@ -129,6 +117,7 @@ bool Why3Translator::visit(ContractDefinition const& _contract) if (m_seenContract) error(_contract, "More than one contract not supported."); m_seenContract = true; + m_currentContract.contract = &_contract; if (_contract.isLibrary()) error(_contract, "Libraries not supported."); @@ -141,21 +130,41 @@ bool Why3Translator::visit(ContractDefinition const& _contract) addLine("use import int.ComputerDivision"); addLine("use import mach.int.Unsigned"); addLine("use import UInt256"); - addLine("exception Ret"); + addLine("exception Revert"); + addLine("exception Return"); - addLine("type state = {"); - indent(); - m_stateVariables = _contract.stateVariables(); - for (VariableDeclaration const* variable: m_stateVariables) + if (_contract.stateVariables().empty()) + addLine("type state = ()"); + else { - string varType = toFormalType(*variable->annotation().type); - if (varType.empty()) - fatalError(*variable, "Type not supported."); - addLine("mutable _" + variable->name() + ": ref " + varType); + addLine("type state = {"); + indent(); + m_currentContract.stateVariables = _contract.stateVariables(); + for (VariableDeclaration const* variable: m_currentContract.stateVariables) + { + string varType = toFormalType(*variable->annotation().type); + if (varType.empty()) + fatalError(*variable, "Type not supported for state variable."); + addLine("mutable _" + variable->name() + ": " + varType); + } + unindent(); + addLine("}"); } + + addLine("type account = {"); + indent(); + addLine("mutable balance: uint256;"); + addLine("storage: state"); unindent(); addLine("}"); + addLine("val external_call (this: account): bool"); + indent(); + addLine("ensures { result = false -> this = (old this) }"); + addLine("writes { this }"); + addSourceFromDocStrings(m_currentContract.contract->annotation()); + unindent(); + if (!_contract.baseContracts().empty()) error(*_contract.baseContracts().front(), "Inheritance not supported."); if (!_contract.definedStructs().empty()) @@ -172,10 +181,9 @@ bool Why3Translator::visit(ContractDefinition const& _contract) return false; } -void Why3Translator::endVisit(ContractDefinition const& _contract) +void Why3Translator::endVisit(ContractDefinition const&) { - m_stateVariables.clear(); - addSourceFromDocStrings(_contract.annotation()); + m_currentContract.reset(); unindent(); addLine("end"); } @@ -207,7 +215,7 @@ bool Why3Translator::visit(FunctionDefinition const& _function) m_localVariables[var->name()] = var; add("let rec _" + _function.name()); - add(" (state: state)"); + add(" (this: account)"); for (auto const& param: _function.parameters()) { string paramType = toFormalType(*param->annotation().type); @@ -235,9 +243,20 @@ bool Why3Translator::visit(FunctionDefinition const& _function) unindent(); addSourceFromDocStrings(_function.annotation()); + if (!m_currentContract.contract) + error(_function, "Only functions inside contracts allowed."); + addSourceFromDocStrings(m_currentContract.contract->annotation()); + + if (_function.isDeclaredConst()) + addLine("ensures { (old this) = this }"); + else + addLine("writes { this }"); addLine("="); + // store the prestate in the case we need to revert + addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in "); + // initialise local variables for (auto const& variable: _function.parameters()) addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in"); @@ -259,7 +278,7 @@ bool Why3Translator::visit(FunctionDefinition const& _function) _function.body().accept(*this); add(";"); - addLine("raise Ret"); + addLine("raise Return"); string retVals; for (auto const& variable: _function.returnParameters()) @@ -268,8 +287,14 @@ bool Why3Translator::visit(FunctionDefinition const& _function) retVals += ", "; retVals += "!_" + variable->name(); } - addLine("with Ret -> (" + retVals + ")"); - newLine(); + addLine("with Return -> (" + retVals + ") |"); + string reversion = " Revert -> this.balance <- prestate.balance; "; + for (auto const* variable: m_currentContract.stateVariables) + reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; "; + //@TODO in case of reversion the return values are wrong - we need to change the + // return type to include a bool to signify if an exception was thrown. + reversion += "(" + retVals + ")"; + addLine(reversion); unindent(); addLine("end"); addLine(""); @@ -349,10 +374,17 @@ bool Why3Translator::visit(Return const& _node) } add("begin _" + params.front()->name() + " := "); _node.expression()->accept(*this); - add("; raise Ret end"); + add("; raise Return end"); } else - add("raise Ret"); + add("raise Return"); + return false; +} + +bool Why3Translator::visit(Throw const& _node) +{ + addSourceFromDocStrings(_node.annotation()); + add("raise Revert"); return false; } @@ -385,7 +417,8 @@ bool Why3Translator::visit(Assignment const& _node) error(_node, "Compound assignment not supported."); _node.leftHandSide().accept(*this); - add(" := "); + + add(m_currentLValueIsRef ? " := " : " <- "); _node.rightHandSide().accept(*this); return false; @@ -402,7 +435,7 @@ bool Why3Translator::visit(TupleExpression const& _node) bool Why3Translator::visit(UnaryOperation const& _unaryOperation) { if (toFormalType(*_unaryOperation.annotation().type).empty()) - error(_unaryOperation, "Type not supported."); + error(_unaryOperation, "Type not supported in unary operation."); switch (_unaryOperation.getOperator()) { @@ -512,6 +545,42 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")"); return false; } + case FunctionType::Location::Bare: + { + if (!_node.arguments().empty()) + { + error(_node, "Function calls with named arguments not supported."); + return true; + } + + add("("); + indent(); + add("let amount = 0 in "); + _node.expression().accept(*this); + addLine("if amount <= this.balance then"); + indent(); + addLine("let balance_precall = this.balance in"); + addLine("begin"); + indent(); + addLine("this.balance <- this.balance - amount;"); + addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true"); + unindent(); + addLine("end"); + unindent(); + addLine("else false"); + + unindent(); + add(")"); + return false; + } + case FunctionType::Location::SetValue: + { + add("let amount = "); + solAssert(_node.arguments().size() == 1, ""); + _node.arguments()[0]->accept(*this); + add(" in "); + return false; + } default: error(_node, "Only internal function calls supported."); return true; @@ -531,8 +600,17 @@ bool Why3Translator::visit(MemberAccess const& _node) add(".length"); add(")"); } + else if ( + _node.memberName() == "call" && + *_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address) + ) + { + // Do nothing, do not even visit the address because this will be an external call + //@TODO ensure that the expression itself does not have side-effects + return false; + } else - error(_node, "Only read-only length access for arrays supported."); + error(_node, "Member access: Only call and array length supported."); return false; } @@ -568,13 +646,14 @@ bool Why3Translator::visit(Identifier const& _identifier) { bool isStateVar = isStateVariable(variable); bool lvalue = _identifier.annotation().lValueRequested; - if (!lvalue) - add("!("); if (isStateVar) - add("state."); + add("this.storage."); + else if (!lvalue) + add("!("); add("_" + variable->name()); - if (!lvalue) + if (!isStateVar && !lvalue) add(")"); + m_currentLValueIsRef = !isStateVar; } else error(_identifier, "Not supported."); @@ -608,12 +687,12 @@ bool Why3Translator::visit(Literal const& _literal) bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const { - return contains(m_stateVariables, _var); + return contains(m_currentContract.stateVariables, _var); } bool Why3Translator::isStateVariable(string const& _name) const { - for (auto const& var: m_stateVariables) + for (auto const& var: m_currentContract.stateVariables) if (var->name() == _name) return true; return false; @@ -632,6 +711,23 @@ bool Why3Translator::isLocalVariable(string const& _name) const return m_localVariables.count(_name); } +string Why3Translator::copyOfStorage() const +{ + if (m_currentContract.stateVariables.empty()) + return "()"; + string ret = "{"; + bool first = true; + for (auto const* variable: m_currentContract.stateVariables) + { + if (first) + first = false; + else + ret += "; "; + ret += "_" + variable->name() + " = this.storage._" + variable->name(); + } + return ret + "}"; +} + void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement) { bool isBlock = !!dynamic_cast<Block const*>(&_statement); @@ -674,11 +770,10 @@ string Why3Translator::transformVariableReferences(string const& _annotation) }); string varName(hash + 1, hashEnd); if (isLocalVariable(varName)) - ret += "(to_int !_" + varName + ")"; + ret += "(!_" + varName + ")"; else if (isStateVariable(varName)) - ret += "(to_int !(state._" + varName + "))"; - else if (varName == "result") //@todo actually use the name of the return parameters - ret += "(to_int result)"; + ret += "(this.storage._" + varName + ")"; + //@todo return variables else ret.append(hash, hashEnd); @@ -687,3 +782,16 @@ string Why3Translator::transformVariableReferences(string const& _annotation) return ret; } +void Why3Translator::appendPreface() +{ + m_lines.push_back(Line{R"( +module UInt256 + use import mach.int.Unsigned + type uint256 + constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + clone export mach.int.Unsigned with + type t = uint256, + constant max = max_uint256 +end + )", 0}); +} diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h index 588b6d80..e5b16844 100644 --- a/libsolidity/formal/Why3Translator.h +++ b/libsolidity/formal/Why3Translator.h @@ -80,6 +80,7 @@ private: virtual bool visit(IfStatement const& _node) override; virtual bool visit(WhileStatement const& _node) override; virtual bool visit(Return const& _node) override; + virtual bool visit(Throw const& _node) override; virtual bool visit(VariableDeclarationStatement const& _node) override; virtual bool visit(ExpressionStatement const&) override; virtual bool visit(Assignment const& _node) override; @@ -104,6 +105,9 @@ private: bool isLocalVariable(VariableDeclaration const* _var) const; bool isLocalVariable(std::string const& _name) const; + /// @returns a string representing an expression that is a copy of this.storage + std::string copyOfStorage() const; + /// Visits the givin statement and indents it unless it is a block /// (which does its own indentation). void visitIndentedUnlessBlock(Statement const& _statement); @@ -117,7 +121,17 @@ private: bool m_seenContract = false; bool m_errorOccured = false; - std::vector<VariableDeclaration const*> m_stateVariables; + /// Metadata relating to the current contract + struct ContractMetadata + { + ContractDefinition const* contract = nullptr; + std::vector<VariableDeclaration const*> stateVariables; + + void reset() { contract = nullptr; stateVariables.clear(); } + }; + + ContractMetadata m_currentContract; + bool m_currentLValueIsRef = false; std::map<std::string, VariableDeclaration const*> m_localVariables; struct Line @@ -129,6 +143,5 @@ private: ErrorList& m_errors; }; - } } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c28e926b..f7982872 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -253,9 +253,11 @@ void CompilerStack::link(const std::map<string, h160>& _libraries) } } -bool CompilerStack::prepareFormalAnalysis() +bool CompilerStack::prepareFormalAnalysis(ErrorList* _errors) { - Why3Translator translator(m_errors); + if (!_errors) + _errors = &m_errors; + Why3Translator translator(*_errors); for (Source const* source: m_sourceOrder) if (!translator.process(*source->ast)) return false; @@ -277,6 +279,28 @@ eth::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _con return currentContract.compiler ? &contract(_contractName).compiler->runtimeAssemblyItems() : nullptr; } +string const* CompilerStack::sourceMapping(string const& _contractName) const +{ + Contract const& c = contract(_contractName); + if (!c.sourceMapping) + { + if (auto items = assemblyItems(_contractName)) + c.sourceMapping.reset(new string(computeSourceMapping(*items))); + } + return c.sourceMapping.get(); +} + +string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const +{ + Contract const& c = contract(_contractName); + if (!c.runtimeSourceMapping) + { + if (auto items = runtimeAssemblyItems(_contractName)) + c.runtimeSourceMapping.reset(new string(computeSourceMapping(*items))); + } + return c.runtimeSourceMapping.get(); +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { return contract(_contractName).object; @@ -313,6 +337,22 @@ Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _co } } +vector<string> CompilerStack::sourceNames() const +{ + vector<string> names; + for (auto const& s: m_sources) + names.push_back(s.first); + return names; +} + +map<string, unsigned> CompilerStack::sourceIndices() const +{ + map<string, unsigned> indices; + for (auto const& s: m_sources) + indices[s.first] = indices.size(); + return indices; +} + string const& CompilerStack::interface(string const& _contractName) const { return metadata(_contractName, DocumentationType::ABIInterface); @@ -602,3 +642,76 @@ CompilerStack::Source const& CompilerStack::source(string const& _sourceName) co return it->second; } + +string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const +{ + string ret; + map<string, unsigned> sourceIndicesMap = sourceIndices(); + int prevStart = -1; + int prevLength = -1; + int prevSourceIndex = -1; + char prevJump = 0; + for (auto const& item: _items) + { + if (!ret.empty()) + ret += ";"; + + SourceLocation const& location = item.location(); + int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1; + int sourceIndex = + location.sourceName && sourceIndicesMap.count(*location.sourceName) ? + sourceIndicesMap.at(*location.sourceName) : + -1; + char jump = '-'; + if (item.getJumpType() == eth::AssemblyItem::JumpType::IntoFunction) + jump = 'i'; + else if (item.getJumpType() == eth::AssemblyItem::JumpType::OutOfFunction) + jump = 'o'; + + unsigned components = 4; + if (jump == prevJump) + { + components--; + if (sourceIndex == prevSourceIndex) + { + components--; + if (length == prevLength) + { + components--; + if (location.start == prevStart) + components--; + } + } + } + + if (components-- > 0) + { + if (location.start != prevStart) + ret += std::to_string(location.start); + if (components-- > 0) + { + ret += ':'; + if (length != prevLength) + ret += std::to_string(length); + if (components-- > 0) + { + ret += ':'; + if (sourceIndex != prevSourceIndex) + ret += std::to_string(sourceIndex); + if (components-- > 0) + { + ret += ':'; + if (jump != prevJump) + ret += jump; + } + } + } + } + + prevStart = location.start; + prevLength = length; + prevSourceIndex = sourceIndex; + prevJump = jump; + } + return ret; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index e111c982..a4b8447f 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -124,8 +124,9 @@ public: void link(std::map<std::string, h160> const& _libraries); /// Tries to translate all source files into a language suitable for formal analysis. + /// @param _errors list to store errors - defaults to the internal error list. /// @returns false on error. - bool prepareFormalAnalysis(); + bool prepareFormalAnalysis(ErrorList* _errors = nullptr); std::string const& formalTranslation() const { return m_formalTranslation; } /// @returns the assembled object for a contract. @@ -141,6 +142,12 @@ public: eth::AssemblyItems const* assemblyItems(std::string const& _contractName = "") const; /// @returns runtime contract assembly items eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName = "") const; + /// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr + /// if the contract does not (yet) have bytecode. + std::string const* sourceMapping(std::string const& _contractName = "") const; + /// @returns the string that provides a mapping between runtime bytecode and sourcecode. + /// if the contract does not (yet) have bytecode. + std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; /// @returns hash of the runtime bytecode for the contract, i.e. the code that is /// returned by the constructor or the zero-h256 if the contract still needs to be linked or /// does not have runtime code. @@ -152,6 +159,11 @@ public: /// Prerequisite: Successful compilation. Json::Value streamAssembly(std::ostream& _outStream, std::string const& _contractName = "", StringMap _sourceCodes = StringMap(), bool _inJsonFormat = false) const; + /// @returns the list of sources (paths) used + std::vector<std::string> sourceNames() const; + /// @returns a mapping assigning each source name its index inside the vector returned + /// by sourceNames(). + std::map<std::string, unsigned> sourceIndices() const; /// @returns a string representing the contract interface in JSON. /// Prerequisite: Successful call to parse or compile. std::string const& interface(std::string const& _contractName = "") const; @@ -195,9 +207,8 @@ private: { std::shared_ptr<Scanner> scanner; std::shared_ptr<SourceUnit> ast; - std::string interface; bool isLibrary = false; - void reset() { scanner.reset(); ast.reset(); interface.clear(); } + void reset() { scanner.reset(); ast.reset(); } }; struct Contract @@ -211,6 +222,8 @@ private: mutable std::unique_ptr<std::string const> solidityInterface; mutable std::unique_ptr<std::string const> userDocumentation; mutable std::unique_ptr<std::string const> devDocumentation; + mutable std::unique_ptr<std::string const> sourceMapping; + mutable std::unique_ptr<std::string const> runtimeSourceMapping; }; /// Loads the missing sources from @a _ast (named @a _path) using the callback @@ -235,6 +248,8 @@ private: Contract const& contract(std::string const& _contractName = "") const; Source const& source(std::string const& _sourceName = "") const; + std::string computeSourceMapping(eth::AssemblyItems const& _items) const; + struct Remapping { std::string context; diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index d630d0ab..d730210a 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -177,6 +177,41 @@ bool Scanner::scanHexByte(char& o_scannedByte) return true; } +bool Scanner::scanUnicode(unsigned & o_codepoint) +{ + unsigned x = 0; + for (int i = 0; i < 4; i++) + { + int d = hexValue(m_char); + if (d < 0) + { + rollback(i); + return false; + } + x = x * 16 + d; + advance(); + } + o_codepoint = x; + return true; +} + +// This supports codepoints between 0000 and FFFF. +void Scanner::addUnicodeAsUTF8(unsigned codepoint) +{ + if (codepoint <= 0x7f) + addLiteralChar(codepoint); + else if (codepoint <= 0x7ff) + { + addLiteralChar(0xc0 | (codepoint >> 6)); + addLiteralChar(0x80 | (codepoint & 0x3f)); + } + else + { + addLiteralChar(0xe0 | (codepoint >> 12)); + addLiteralChar(0x80 | ((codepoint >> 6) & 0x3f)); + addLiteralChar(0x80 | (codepoint & 0x3f)); + } +} // Ensure that tokens can be stored in a byte. BOOST_STATIC_ASSERT(Token::NUM_TOKENS <= 0x100); @@ -607,6 +642,14 @@ bool Scanner::scanEscape() case 'v': c = '\v'; break; + case 'u': + { + unsigned codepoint; + if (!scanUnicode(codepoint)) + return false; + addUnicodeAsUTF8(codepoint); + return true; + } case 'x': if (!scanHexByte(c)) return false; diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h index cd60aff8..708adf8f 100644 --- a/libsolidity/parsing/Scanner.h +++ b/libsolidity/parsing/Scanner.h @@ -175,6 +175,7 @@ private: inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); } inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); } inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } + void addUnicodeAsUTF8(unsigned codepoint); ///@} bool advance() { m_char = m_source.advanceAndGet(); return !m_source.isPastEndOfInput(); } @@ -185,6 +186,7 @@ private: inline Token::Value selectToken(char _next, Token::Value _then, Token::Value _else); bool scanHexByte(char& o_scannedByte); + bool scanUnicode(unsigned& o_codepoint); /// Scans a single Solidity token. void scanToken(); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 703e88f6..581df3a5 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -129,7 +129,6 @@ namespace solidity T(GreaterThan, ">", 7) \ T(LessThanOrEqual, "<=", 7) \ T(GreaterThanOrEqual, ">=", 7) \ - K(In, "in", 7) \ \ /* Unary operators. */ \ /* IsUnaryOp() relies on this block of enum values */ \ @@ -220,6 +219,7 @@ namespace solidity K(Case, "case", 0) \ K(Catch, "catch", 0) \ K(Final, "final", 0) \ + K(In, "in", 0) \ K(Inline, "inline", 0) \ K(Let, "let", 0) \ K(Match, "match", 0) \ @@ -267,7 +267,7 @@ public: static bool isCommutativeOp(Value op) { return op == BitOr || op == BitXor || op == BitAnd || op == Add || op == Mul || op == Equal || op == NotEqual; } static bool isArithmeticOp(Value op) { return Add <= op && op <= Exp; } - static bool isCompareOp(Value op) { return Equal <= op && op <= In; } + static bool isCompareOp(Value op) { return Equal <= op && op <= GreaterThanOrEqual; } static Value AssignmentToBinaryOp(Value op) { |