From b85d4789316225145c44515dfe8cf2ed30670e21 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 21 Jun 2018 20:15:02 +0200 Subject: [PATCH 001/277] Add assertion for contract name clash in the resolver in CompilerStack --- libsolidity/interface/CompilerStack.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1474b2826..0c2e303cd 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -310,12 +310,19 @@ bool CompilerStack::analyze() if (!resolver.resolveNamesAndTypes(*node)) return false; if (ContractDefinition* contract = dynamic_cast(node.get())) + { // Note that we now reference contracts by their fully qualified names, and - // thus contracts can only conflict if declared in the same source file. This - // already causes a double-declaration error elsewhere, so we do not report - // an error here and instead silently drop any additional contracts we find. + // thus contracts can only conflict if declared in the same source file. This + // should already cause a double-declaration error elsewhere. if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) m_contracts[contract->fullyQualifiedName()].contract = contract; + else + solAssert( + m_errorReporter.hasErrors(), + "Contract already present (name clash?), but no error was reported." + ); + } + } // Next, we check inheritance, overrides, function collisions and other things at From 49a05b59356622196f63c87fde0e1c2c31a4b11d Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 18 Nov 2019 12:12:30 +0100 Subject: [PATCH 002/277] Permit variable declarations inside for init block --- test/tools/ossfuzz/protoToYul.cpp | 320 ++++++++++++++++++++++++------ test/tools/ossfuzz/protoToYul.h | 14 +- 2 files changed, 274 insertions(+), 60 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index bf152052b..cc7fde3ae 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -95,18 +95,32 @@ string ProtoConverter::visit(Literal const& _x) void ProtoConverter::consolidateVarDeclsInFunctionDef() { m_currentFuncVars.clear(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid operation"); + auto const& scopes = m_funcVars.back(); for (auto const& s: scopes) for (auto const& var: s) m_currentFuncVars.push_back(&var); + yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid operation"); + auto const& forinitscopes = m_funcForLoopInitVars.back(); + for (auto const& s: forinitscopes) + for (auto const& var: s) + m_currentFuncVars.push_back(&var); } void ProtoConverter::consolidateGlobalVarDecls() { m_currentGlobalVars.clear(); + // Place pointers to all global variables that are in scope + // into a single vector for (auto const& scope: m_globalVars) for (auto const& var: scope) m_currentGlobalVars.push_back(&var); + // Place pointers to all variables declared in for-init blocks + // that are still live into the same vector + for (auto const& init: m_globalForLoopInitVars) + for (auto const& var: init) + m_currentGlobalVars.push_back(&var); } bool ProtoConverter::varDeclAvailable() @@ -283,10 +297,54 @@ void ProtoConverter::visit(VarDecl const& _x) m_output << "let " << varName << " := "; visit(_x.expr()); m_output << "\n"; + // If we are inside a for-init block, there are two places + // where the visited vardecl may have been defined: + // - directly inside the for-init block + // - inside a block within the for-init block + // In the latter case, we don't scope extend. if (m_inFunctionDef) - m_funcVars.back().back().push_back(varName); + { + // Variables declared directly in for-init block + // are tracked separately because their scope + // extends beyond the block they are defined in + // to the rest of the for-loop statement. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcForLoopInitVars.back().back().push_back(varName); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcVars.back().back().push_back(varName); + } + + } else - m_globalVars.back().push_back(varName); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalForLoopInitVars.back().push_back(varName); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalVars.back().push_back(varName); + } + } } void ProtoConverter::visit(TypedVarDecl const& _x) @@ -351,10 +409,53 @@ void ProtoConverter::visit(TypedVarDecl const& _x) m_output << " : u256\n"; break; } + // If we are inside a for-init block, there are two places + // where the visited vardecl may have been defined: + // - directly inside the for-init block + // - inside a block within the for-init block + // In the latter case, we don't scope extend. if (m_inFunctionDef) - m_funcVars.back().back().push_back(varName); + { + // Variables declared directly in for-init block + // are tracked separately because their scope + // extends beyond the block they are defined in + // to the rest of the for-loop statement. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcForLoopInitVars.back().back().push_back(varName); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid operation" + ); + m_funcVars.back().back().push_back(varName); + } + } else - m_globalVars.back().push_back(varName); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalForLoopInitVars.back().push_back(varName); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid operation" + ); + m_globalVars.back().push_back(varName); + } + } } void ProtoConverter::visit(UnaryOp const& _x) @@ -713,30 +814,27 @@ void ProtoConverter::visit(FunctionCall const& _x) convertFunctionCall(_x, funcName, numInParams, /*newLine=*/false); break; case FunctionCall::MULTIDECL: - // Hack: Disallow (multi) variable declarations until scope extension - // is implemented for "for-init" - if (!m_inForInitScope) - { - // Ensure that the chosen function returns at most 4 values - yulAssert( - numOutParams <= 4, - "Proto fuzzer: Function call with too many output params encountered." - ); + { + // Ensure that the chosen function returns at most 4 values + yulAssert( + numOutParams <= 4, + "Proto fuzzer: Function call with too many output params encountered." + ); - // Obtain variable name suffix - unsigned startIdx = counter(); - vector varsVec = createVarDecls( - startIdx, - startIdx + numOutParams, - /*isAssignment=*/true - ); + // Obtain variable name suffix + unsigned startIdx = counter(); + vector varsVec = createVarDecls( + startIdx, + startIdx + numOutParams, + /*isAssignment=*/true + ); - // Create RHS of multi var decl - convertFunctionCall(_x, funcName, numInParams); - // Add newly minted vars in the multidecl statement to current scope - addVarsToScope(varsVec); - } + // Create RHS of multi var decl + convertFunctionCall(_x, funcName, numInParams); + // Add newly minted vars in the multidecl statement to current scope + addVarsToScope(varsVec); break; + } case FunctionCall::MULTIASSIGN: // Ensure that the chosen function returns at most 4 values yulAssert( @@ -749,22 +847,22 @@ void ProtoConverter::visit(FunctionCall const& _x) // This helps reduce the size of this switch statement. switch (numOutParams) { - case 4: - visit(_x.out_param4()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 3: - visit(_x.out_param3()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 2: - visit(_x.out_param2()); - m_output << ", "; - visit(_x.out_param1()); - break; - default: - yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); - break; + case 4: + visit(_x.out_param4()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 3: + visit(_x.out_param3()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 2: + visit(_x.out_param2()); + m_output << ", "; + visit(_x.out_param1()); + break; + default: + yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); + break; } m_output << " := "; @@ -868,17 +966,33 @@ void ProtoConverter::visit(ForStmt const& _x) { bool wasInForBody = m_inForBodyScope; bool wasInForInit = m_inForInitScope; + bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled; m_inForBodyScope = false; m_inForInitScope = true; + m_forInitScopeExtEnabled = true; m_output << "for "; visit(_x.for_init()); m_inForInitScope = false; + m_forInitScopeExtEnabled = wasForInitScopeExtEnabled; visit(_x.for_cond()); visit(_x.for_post()); m_inForBodyScope = true; visit(_x.for_body()); m_inForBodyScope = wasInForBody; m_inForInitScope = wasInForInit; + if (m_inFunctionDef) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid data structure"); + // Remove variables in for-init + m_funcForLoopInitVars.back().pop_back(); + } + else + { + yulAssert(!m_globalForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure"); + m_globalForLoopInitVars.pop_back(); + } } void ProtoConverter::visit(BoundedForStmt const& _x) @@ -1065,9 +1179,7 @@ void ProtoConverter::visit(Statement const& _x) switch (_x.stmt_oneof_case()) { case Statement::kDecl: - // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" - if (!m_inForInitScope) - visit(_x.decl()); + visit(_x.decl()); break; case Statement::kAssignment: // Create an assignment statement only if there is at least one variable @@ -1138,16 +1250,36 @@ void ProtoConverter::visit(Statement const& _x) void ProtoConverter::openBlockScope() { m_scopeFuncs.push_back({}); + // Create new block scope inside current function scope if (m_inFunctionDef) + { + yulAssert( + !m_funcVars.empty(), + "Proto fuzzer: Invalid data structure" + ); m_funcVars.back().push_back(vector{}); + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcForLoopInitVars.back().push_back(vector{}); + } + } else - m_globalVars.push_back(vector{}); + { + m_globalVars.push_back({}); + if (m_inForInitScope && m_forInitScopeExtEnabled) + m_globalForLoopInitVars.push_back(vector{}); + } } void ProtoConverter::openFunctionScope(vector const& _funcParams) { m_funcVars.push_back(vector>({_funcParams})); + m_funcForLoopInitVars.push_back(vector>({})); } void ProtoConverter::updateFunctionMaps(string const& _var) @@ -1166,6 +1298,8 @@ void ProtoConverter::updateFunctionMaps(string const& _var) void ProtoConverter::closeBlockScope() { + // Remove functions declared in the block that is going + // out of scope from the global function map. for (auto const& f: m_scopeFuncs.back()) { unsigned numFuncsRemoved = m_functions.size(); @@ -1177,35 +1311,95 @@ void ProtoConverter::closeBlockScope() ); updateFunctionMaps(f); } + // Pop back the vector of scoped functions. if (!m_scopeFuncs.empty()) m_scopeFuncs.pop_back(); - if (!m_inFunctionDef) + + // If block belongs to function body, then remove + // local variables in function body that are going out of scope. + if (m_inFunctionDef) { - if (!m_globalVars.empty()) - m_globalVars.pop_back(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure"); + if (!m_funcVars.back().empty()) + m_funcVars.back().pop_back(); } + // Remove variables declared in vanilla block from current + // global scope. else { - // Variables that have been declared in a - // function block, go out of scope - if (!m_funcVars.empty()) - if (!m_funcVars.back().empty()) - m_funcVars.back().pop_back(); + yulAssert(!m_globalVars.empty(), "Proto fuzzer: Invalid data structure"); + m_globalVars.pop_back(); } } void ProtoConverter::closeFunctionScope() { - if (!m_funcVars.empty()) - m_funcVars.pop_back(); + yulAssert(!m_funcVars.empty(), "Proto fuzzer: Invalid data structure"); + m_funcVars.pop_back(); + yulAssert(!m_funcForLoopInitVars.empty(), "Proto fuzzer: Invalid data structure"); + m_funcForLoopInitVars.pop_back(); } void ProtoConverter::addVarsToScope(vector const& _vars) { + // If we are in function definition, add the new vars to current function scope if (m_inFunctionDef) - m_funcVars.back().back().insert(m_funcVars.back().back().end(), _vars.begin(), _vars.end()); + { + // If we are directly in for-init block, add the newly created vars to the + // stack of for-init variables. + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcForLoopInitVars.back().back().insert( + m_funcForLoopInitVars.back().back().end(), + _vars.begin(), + _vars.end() + ); + } + else + { + yulAssert( + !m_funcVars.empty() && !m_funcVars.back().empty(), + "Proto fuzzer: Invalid data structure" + ); + m_funcVars.back().back().insert( + m_funcVars.back().back().end(), + _vars.begin(), + _vars.end() + ); + } + } + // If we are in a vanilla block, add the new vars to current global scope else - m_globalVars.back().insert(m_globalVars.back().end(), _vars.begin(), _vars.end()); + { + if (m_inForInitScope && m_forInitScopeExtEnabled) + { + yulAssert( + !m_globalForLoopInitVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_globalForLoopInitVars.back().insert( + m_globalForLoopInitVars.back().end(), + _vars.begin(), + _vars.end() + ); + } + else + { + yulAssert( + !m_globalVars.empty(), + "Proto fuzzer: Invalid data structure" + ); + m_globalVars.back().insert( + m_globalVars.back().end(), + _vars.begin(), + _vars.end() + ); + } + } } void ProtoConverter::visit(Block const& _x) @@ -1222,8 +1416,20 @@ void ProtoConverter::visit(Block const& _x) if (_x.statements_size() > 0) { m_output << "{\n"; + bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled; for (auto const& st: _x.statements()) + { + // If statement is block or introduces one and we are in for-init block + // then temporarily disable scope extension if it is not already disabled. + if ( + (st.has_blockstmt() || st.has_switchstmt() || st.has_ifstmt()) && + m_inForInitScope && + m_forInitScopeExtEnabled + ) + m_forInitScopeExtEnabled = false; visit(st); + m_forInitScopeExtEnabled = wasForInitScopeExtEnabled; + } m_output << "}\n"; } else diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 43058f45c..4150e7c64 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -48,6 +48,7 @@ public: m_inFunctionDef = false; m_objectId = 0; m_isObject = false; + m_forInitScopeExtEnabled = true; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; @@ -314,9 +315,13 @@ private: std::vector m_currentGlobalVars; /// Functions in current scope std::vector> m_scopeFuncs; - /// Variables + /// Global variables std::vector> m_globalVars; - /// Functions + /// Variables declared in for loop init block that is in global scope + std::vector> m_globalForLoopInitVars; + /// Variables declared in for loop init block that is in function scope + std::vector>> m_funcForLoopInitVars; + /// Vector of function names std::vector m_functions; /// Maps FunctionDef object to its name std::map m_functionDefMap; @@ -331,7 +336,7 @@ private: static unsigned constexpr s_modOutputParams = 5; /// Hard-coded identifier for a Yul object's data block static auto constexpr s_dataIdentifier = "datablock"; - /// Predicate to keep track of for body scope. If true, break/continue + /// Predicate to keep track of for body scope. If false, break/continue /// statements can not be created. bool m_inForBodyScope; // Index used for naming loop variable of bounded for loops @@ -350,5 +355,8 @@ private: /// Flag to track whether program is an object (true) or a statement block /// (false: default value) bool m_isObject; + /// Flag to track whether scope extension of variables defined in for-init + /// block is enabled. + bool m_forInitScopeExtEnabled; }; } From adc4774d4ad0a3423c1b25b807ef099fdc743dd9 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 14 Jan 2020 12:46:47 +0100 Subject: [PATCH 003/277] ASTJSON: Export evm version --- libsolidity/ast/ASTJsonConverter.cpp | 9 +++- test/libsolidity/ASTJSON/assembly/call.json | 1 + .../ASTJSON/assembly/call_legacy.json | 1 + .../ASTJSON/assembly/function.json | 1 + .../ASTJSON/assembly/function_legacy.json | 1 + test/libsolidity/ASTJSON/assembly/leave.json | 1 + .../ASTJSON/assembly/leave_legacy.json | 1 + test/libsolidity/ASTJSON/assembly/loop.json | 1 + .../ASTJSON/assembly/loop_legacy.json | 1 + .../ASTJSON/assembly/slot_offset.json | 1 + .../ASTJSON/assembly/slot_offset_legacy.json | 1 + .../ASTJSON/assembly/stringlit.json | 1 + .../ASTJSON/assembly/stringlit_legacy.json | 1 + test/libsolidity/ASTJSON/assembly/switch.json | 1 + .../ASTJSON/assembly/switch_legacy.json | 1 + .../ASTJSON/assembly/var_access.json | 1 + .../ASTJSON/assembly/var_access_legacy.json | 1 + test/libsolidity/ASTJSONTest.cpp | 42 ++++++++++++++++++- 18 files changed, 64 insertions(+), 3 deletions(-) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 8d4c08272..22eaaa246 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -26,6 +26,8 @@ #include #include #include +#include + #include #include @@ -492,13 +494,16 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node) { vector> externalReferences; + for (auto const& it: _node.annotation().externalReferences) if (it.first) externalReferences.emplace_back(make_pair( it.first->name.str(), inlineAssemblyIdentifierToJson(it) )); + Json::Value externalReferencesJson = Json::arrayValue; + for (auto&& it: boost::range::sort(externalReferences)) externalReferencesJson.append(std::move(it.second)); @@ -506,8 +511,10 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node) m_legacy ? make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) : make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))), - make_pair("externalReferences", std::move(externalReferencesJson)) + make_pair("externalReferences", std::move(externalReferencesJson)), + make_pair("evmVersion", dynamic_cast(_node.dialect()).evmVersion().name()) }); + return false; } diff --git a/test/libsolidity/ASTJSON/assembly/call.json b/test/libsolidity/ASTJSON/assembly/call.json index 25558da84..1c2a4bf7e 100644 --- a/test/libsolidity/ASTJSON/assembly/call.json +++ b/test/libsolidity/ASTJSON/assembly/call.json @@ -124,6 +124,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/call_legacy.json b/test/libsolidity/ASTJSON/assembly/call_legacy.json index 478365834..619d132bc 100644 --- a/test/libsolidity/ASTJSON/assembly/call_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/call_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/function.json b/test/libsolidity/ASTJSON/assembly/function.json index 93a5be76a..7e9895261 100644 --- a/test/libsolidity/ASTJSON/assembly/function.json +++ b/test/libsolidity/ASTJSON/assembly/function.json @@ -111,6 +111,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/function_legacy.json b/test/libsolidity/ASTJSON/assembly/function_legacy.json index 9af45691c..163d8d05a 100644 --- a/test/libsolidity/ASTJSON/assembly/function_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/function_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/leave.json b/test/libsolidity/ASTJSON/assembly/leave.json index 0ccbab2cf..c8ef5eaee 100644 --- a/test/libsolidity/ASTJSON/assembly/leave.json +++ b/test/libsolidity/ASTJSON/assembly/leave.json @@ -61,6 +61,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/leave_legacy.json b/test/libsolidity/ASTJSON/assembly/leave_legacy.json index a1a4f7591..35ce02f55 100644 --- a/test/libsolidity/ASTJSON/assembly/leave_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/leave_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/loop.json b/test/libsolidity/ASTJSON/assembly/loop.json index 9ad3f4f88..b815da447 100644 --- a/test/libsolidity/ASTJSON/assembly/loop.json +++ b/test/libsolidity/ASTJSON/assembly/loop.json @@ -124,6 +124,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/loop_legacy.json b/test/libsolidity/ASTJSON/assembly/loop_legacy.json index 109962d01..cf4095dd8 100644 --- a/test/libsolidity/ASTJSON/assembly/loop_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/loop_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset.json b/test/libsolidity/ASTJSON/assembly/slot_offset.json index e7f4c6089..68a7603ea 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset.json @@ -176,6 +176,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [ { diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json index 51783a58b..800e5b627 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json @@ -166,6 +166,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ { diff --git a/test/libsolidity/ASTJSON/assembly/stringlit.json b/test/libsolidity/ASTJSON/assembly/stringlit.json index 1f328d970..a7a596e0c 100644 --- a/test/libsolidity/ASTJSON/assembly/stringlit.json +++ b/test/libsolidity/ASTJSON/assembly/stringlit.json @@ -65,6 +65,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/stringlit_legacy.json b/test/libsolidity/ASTJSON/assembly/stringlit_legacy.json index 549ed5b40..bb7a5c5c3 100644 --- a/test/libsolidity/ASTJSON/assembly/stringlit_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/stringlit_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/switch.json b/test/libsolidity/ASTJSON/assembly/switch.json index b159fb8e4..5e16f8cfb 100644 --- a/test/libsolidity/ASTJSON/assembly/switch.json +++ b/test/libsolidity/ASTJSON/assembly/switch.json @@ -157,6 +157,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [], "id": 3, "nodeType": "InlineAssembly", diff --git a/test/libsolidity/ASTJSON/assembly/switch_legacy.json b/test/libsolidity/ASTJSON/assembly/switch_legacy.json index ea3bfd3d1..9d055e52e 100644 --- a/test/libsolidity/ASTJSON/assembly/switch_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/switch_legacy.json @@ -89,6 +89,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ null diff --git a/test/libsolidity/ASTJSON/assembly/var_access.json b/test/libsolidity/ASTJSON/assembly/var_access.json index d6f02aa7d..1e66ef6b3 100644 --- a/test/libsolidity/ASTJSON/assembly/var_access.json +++ b/test/libsolidity/ASTJSON/assembly/var_access.json @@ -107,6 +107,7 @@ } ] }, + "evmVersion": %EVMVERSION%, "externalReferences": [ { diff --git a/test/libsolidity/ASTJSON/assembly/var_access_legacy.json b/test/libsolidity/ASTJSON/assembly/var_access_legacy.json index 595da142b..0442ace76 100644 --- a/test/libsolidity/ASTJSON/assembly/var_access_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/var_access_legacy.json @@ -135,6 +135,7 @@ { "attributes": { + "evmVersion": %EVMVERSION%, "externalReferences": [ { diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index c42daf14c..926399352 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -15,6 +15,7 @@ along with solidity. If not, see . */ +#include #include #include #include @@ -38,6 +39,30 @@ using namespace std; namespace fs = boost::filesystem; using namespace boost::unit_test; +namespace +{ + +void replaceVersionWithTag(string& _input) +{ + boost::algorithm::replace_all( + _input, + "\"" + solidity::test::Options::get().evmVersion().name() + "\"", + "%EVMVERSION%" + ); +} + +void replaceTagWithVersion(string& _input) +{ + boost::algorithm::replace_all( + _input, + "%EVMVERSION%", + "\"" + solidity::test::Options::get().evmVersion().name() + "\"" + ); +} + +} + + ASTJSONTest::ASTJSONTest(string const& _filename) { if (!boost::algorithm::ends_with(_filename, ".sol")) @@ -126,6 +151,8 @@ TestCase::TestResult ASTJSONTest::run(ostream& _stream, string const& _linePrefi bool resultsMatch = true; + replaceTagWithVersion(m_expectation); + if (m_expectation != m_result) { string nextIndentLevel = _linePrefix + " "; @@ -158,6 +185,8 @@ TestCase::TestResult ASTJSONTest::run(ostream& _stream, string const& _linePrefi m_resultLegacy += "\n"; } + replaceTagWithVersion(m_expectationLegacy); + if (m_expectationLegacy != m_resultLegacy) { string nextIndentLevel = _linePrefix + " "; @@ -202,12 +231,21 @@ void ASTJSONTest::printUpdatedExpectations(std::ostream&, std::string const&) co ofstream file(m_astFilename.c_str()); if (!file) BOOST_THROW_EXCEPTION(runtime_error("Cannot write AST expectation to \"" + m_astFilename + "\".")); file.exceptions(ios::badbit); - file << m_result; + + string replacedResult = m_result; + replaceVersionWithTag(replacedResult); + + file << replacedResult; file.flush(); file.close(); + file.open(m_legacyAstFilename.c_str()); if (!file) BOOST_THROW_EXCEPTION(runtime_error("Cannot write legacy AST expectation to \"" + m_legacyAstFilename + "\".")); - file << m_resultLegacy; + + string replacedResultLegacy = m_resultLegacy; + replaceVersionWithTag(replacedResultLegacy); + + file << replacedResultLegacy; file.flush(); file.close(); } From e8556fa1b2d41652f599fb70d7c61d46b998ddbb Mon Sep 17 00:00:00 2001 From: djudjuu Date: Wed, 11 Sep 2019 21:16:35 +0200 Subject: [PATCH 004/277] Ast-Import from standard-json --- Changelog.md | 1 + liblangutil/CMakeLists.txt | 1 + liblangutil/Exceptions.h | 4 + liblangutil/SourceLocation.cpp | 51 + liblangutil/SourceLocation.h | 54 +- libsolidity/CMakeLists.txt | 4 + libsolidity/analysis/GlobalContext.cpp | 40 +- libsolidity/ast/AST.cpp | 23 +- libsolidity/ast/AST.h | 185 ++-- libsolidity/ast/ASTJsonImporter.cpp | 982 ++++++++++++++++++ libsolidity/ast/ASTJsonImporter.h | 161 +++ libsolidity/ast/AsmJsonImporter.cpp | 303 ++++++ libsolidity/ast/AsmJsonImporter.h | 74 ++ libsolidity/interface/CompilerStack.cpp | 28 +- libsolidity/interface/CompilerStack.h | 7 + libsolidity/parsing/Parser.cpp | 11 +- libsolidity/parsing/Parser.h | 5 + scripts/ASTImportTest.sh | 113 ++ scripts/splitSources.py | 62 ++ solc/CommandLineInterface.cpp | 65 +- solc/CommandLineInterface.h | 6 + test/cmdlineTests.sh | 13 + test/libsolidity/ASTJSON/address_payable.json | 2 +- .../ASTJSON/address_payable_legacy.json | 2 +- test/libsolidity/SolidityTypes.cpp | 17 +- 25 files changed, 2072 insertions(+), 142 deletions(-) create mode 100644 liblangutil/SourceLocation.cpp create mode 100644 libsolidity/ast/ASTJsonImporter.cpp create mode 100644 libsolidity/ast/ASTJsonImporter.h create mode 100644 libsolidity/ast/AsmJsonImporter.cpp create mode 100644 libsolidity/ast/AsmJsonImporter.h create mode 100755 scripts/ASTImportTest.sh create mode 100755 scripts/splitSources.py diff --git a/Changelog.md b/Changelog.md index 21e0e91f5..cd71d617f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: * General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon). + * General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing. * Yul Optimizer: Apply penalty when trying to rematerialize into loops. Bugfixes: diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index 63c9de39d..6a7663d46 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -16,6 +16,7 @@ set(sources SemVerHandler.cpp SemVerHandler.h SourceLocation.h + SourceLocation.cpp SourceReferenceExtractor.cpp SourceReferenceExtractor.h SourceReferenceFormatter.cpp diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index de72eba37..575847ed9 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -40,6 +40,7 @@ struct CompilerError: virtual util::Exception {}; struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {}; +struct InvalidAstError: virtual util::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ @@ -51,6 +52,9 @@ struct UnimplementedFeatureError: virtual util::Exception {}; #define solUnimplemented(DESCRIPTION) \ solUnimplementedAssert(false, DESCRIPTION) +#define astAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION) + class Error: virtual public util::Exception { public: diff --git a/liblangutil/SourceLocation.cpp b/liblangutil/SourceLocation.cpp new file mode 100644 index 000000000..8c531c625 --- /dev/null +++ b/liblangutil/SourceLocation.cpp @@ -0,0 +1,51 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +using namespace solidity; +namespace solidity::langutil +{ + +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex) +{ + // Expected input: "start:length:sourceindex" + enum SrcElem : size_t { Start, Length, Index }; + + std::vector pos; + + boost::algorithm::split(pos, _input, boost::is_any_of(":")); + + astAssert( + pos.size() == 3 && + _maxIndex >= static_cast(stoi(pos[Index])), + "'src'-field ill-formatted or src-index too high" + ); + + int start = stoi(pos[Start]); + int end = start + stoi(pos[Length]); + + // ASSUMPTION: only the name of source is used from here on, the m_source of the CharStream-Object can be empty + std::shared_ptr source = std::make_shared("", _sourceName); + + return SourceLocation{start, end, source}; +} + +} diff --git a/liblangutil/SourceLocation.h b/liblangutil/SourceLocation.h index d86dd206a..46808f8e0 100644 --- a/liblangutil/SourceLocation.h +++ b/liblangutil/SourceLocation.h @@ -23,13 +23,12 @@ #pragma once #include -#include // defines noexcept macro for MSVC #include + #include + #include #include -#include -#include namespace solidity::langutil { @@ -46,9 +45,28 @@ struct SourceLocation return source.get() == _other.source.get() && start == _other.start && end == _other.end; } bool operator!=(SourceLocation const& _other) const { return !operator==(_other); } - inline bool operator<(SourceLocation const& _other) const; - inline bool contains(SourceLocation const& _other) const; - inline bool intersects(SourceLocation const& _other) const; + + inline bool operator<(SourceLocation const& _other) const + { + if (!source|| !_other.source) + return std::make_tuple(int(!!source), start, end) < std::make_tuple(int(!!_other.source), _other.start, _other.end); + else + return std::make_tuple(source->name(), start, end) < std::make_tuple(_other.source->name(), _other.start, _other.end); + } + + inline bool contains(SourceLocation const& _other) const + { + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return start <= _other.start && _other.end <= end; + } + + inline bool intersects(SourceLocation const& _other) const + { + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return _other.start < end && start < _other.end; + } bool isEmpty() const { return start == -1 && end == -1; } @@ -86,6 +104,8 @@ struct SourceLocation std::shared_ptr source; }; +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex = -1); + /// Stream output for Location (used e.g. in boost exceptions). inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) { @@ -100,26 +120,4 @@ inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _locat return _out; } -bool SourceLocation::operator<(SourceLocation const& _other) const -{ - if (!source|| !_other.source) - return std::make_tuple(int(!!source), start, end) < std::make_tuple(int(!!_other.source), _other.start, _other.end); - else - return std::make_tuple(source->name(), start, end) < std::make_tuple(_other.source->name(), _other.start, _other.end); -} - -bool SourceLocation::contains(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) - return false; - return start <= _other.start && _other.end <= end; -} - -bool SourceLocation::intersects(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) - return false; - return _other.start < end && start < _other.end; -} - } diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 87eb0e9b7..242f26c35 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -39,10 +39,14 @@ set(sources ast/ASTAnnotations.h ast/ASTEnums.h ast/ASTForward.h + ast/AsmJsonImporter.cpp + ast/AsmJsonImporter.h ast/ASTJsonConverter.cpp ast/ASTJsonConverter.h ast/ASTUtils.cpp ast/ASTUtils.h + ast/ASTJsonImporter.cpp + ast/ASTJsonImporter.h ast/ASTVisitor.h ast/ExperimentalFeatures.h ast/Types.cpp diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 2a4ce374b..a0764edba 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -33,10 +33,44 @@ using namespace std; namespace solidity::frontend { +/// Magic variables get negative ids for easy differentiation +int magicVariableToID(std::string const& _name) +{ + if (_name == "abi") return -1; + else if (_name == "addmod") return -2; + else if (_name == "assert") return -3; + else if (_name == "block") return -4; + else if (_name == "blockhash") return -5; + else if (_name == "ecrecover") return -6; + else if (_name == "gasleft") return -7; + else if (_name == "keccak256") return -8; + else if (_name == "log0") return -10; + else if (_name == "log1") return -11; + else if (_name == "log2") return -12; + else if (_name == "log3") return -13; + else if (_name == "log4") return -14; + else if (_name == "msg") return -15; + else if (_name == "mulmod") return -16; + else if (_name == "now") return -17; + else if (_name == "require") return -18; + else if (_name == "revert") return -19; + else if (_name == "ripemd160") return -20; + else if (_name == "selfdestruct") return -21; + else if (_name == "sha256") return -22; + else if (_name == "sha3") return -23; + else if (_name == "suicide") return -24; + else if (_name == "super") return -25; + else if (_name == "tx") return -26; + else if (_name == "type") return -27; + else if (_name == "this") return -28; + else + solAssert(false, "Unknown magic variable: \"" + _name + "\"."); +} + inline vector> constructMagicVariables() { static auto const magicVarDecl = [](string const& _name, Type const* _type) { - return make_shared(_name, _type); + return make_shared(magicVariableToID(_name), _name, _type); }; return { @@ -97,7 +131,7 @@ vector GlobalContext::declarations() const MagicVariableDeclaration const* GlobalContext::currentThis() const { if (!m_thisPointer[m_currentContract]) - m_thisPointer[m_currentContract] = make_shared("this", TypeProvider::contract(*m_currentContract)); + m_thisPointer[m_currentContract] = make_shared(magicVariableToID("this"), "this", TypeProvider::contract(*m_currentContract)); return m_thisPointer[m_currentContract].get(); } @@ -105,7 +139,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const MagicVariableDeclaration const* GlobalContext::currentSuper() const { if (!m_superPointer[m_currentContract]) - m_superPointer[m_currentContract] = make_shared("super", TypeProvider::contract(*m_currentContract, true)); + m_superPointer[m_currentContract] = make_shared(magicVariableToID("super"), "super", TypeProvider::contract(*m_currentContract, true)); return m_superPointer[m_currentContract].get(); } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 9f8e1471d..74b042852 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -35,31 +35,12 @@ using namespace std; using namespace solidity; using namespace solidity::frontend; -class IDDispenser -{ -public: - static size_t next() { return ++instance(); } - static void reset() { instance() = 0; } -private: - static size_t& instance() - { - static IDDispenser dispenser; - return dispenser.id; - } - size_t id = 0; -}; - -ASTNode::ASTNode(SourceLocation const& _location): - m_id(IDDispenser::next()), +ASTNode::ASTNode(int64_t _id, SourceLocation const& _location): + m_id(_id), m_location(_location) { } -void ASTNode::resetID() -{ - IDDispenser::reset(); -} - ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index fc0b3a5be..a603a93fb 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -64,13 +64,11 @@ class ASTNode: private boost::noncopyable public: using SourceLocation = langutil::SourceLocation; - explicit ASTNode(SourceLocation const& _location); + explicit ASTNode(int64_t _id, SourceLocation const& _location); virtual ~ASTNode() {} /// @returns an identifier of this AST node that is unique for a single compilation run. - size_t id() const { return m_id; } - /// Resets the global ID counter. This invalidates all previous IDs. - static void resetID(); + int64_t id() const { return m_id; } virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; @@ -139,8 +137,8 @@ std::vector<_T const*> ASTNode::filteredNodes(std::vector> c class SourceUnit: public ASTNode { public: - SourceUnit(SourceLocation const& _location, std::vector> const& _nodes): - ASTNode(_location), m_nodes(_nodes) {} + SourceUnit(int64_t _id, SourceLocation const& _location, std::vector> const& _nodes): + ASTNode(_id, _location), m_nodes(_nodes) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -206,11 +204,12 @@ public: } Declaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility = Visibility::Default ): - ASTNode(_location), m_name(_name), m_visibility(_visibility) {} + ASTNode(_id, _location), m_name(_name), m_visibility(_visibility) {} /// @returns the declared name. ASTString const& name() const { return *m_name; } @@ -253,10 +252,11 @@ class PragmaDirective: public ASTNode { public: PragmaDirective( + int64_t _id, SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals - ): ASTNode(_location), m_tokens(_tokens), m_literals(_literals) + ): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals) {} void accept(ASTVisitor& _visitor) override; @@ -295,12 +295,13 @@ public: using SymbolAliasList = std::vector; ImportDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _path, ASTPointer const& _unitAlias, SymbolAliasList _symbolAliases ): - Declaration(_location, _unitAlias), + Declaration(_id, _location, _unitAlias), m_path(_path), m_symbolAliases(move(_symbolAliases)) { } @@ -385,6 +386,7 @@ class ContractDefinition: public Declaration, public Documented { public: ContractDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, @@ -393,7 +395,7 @@ public: ContractKind _contractKind = ContractKind::Contract, bool _abstract = false ): - Declaration(_location, _name), + Declaration(_id, _location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), @@ -465,11 +467,12 @@ class InheritanceSpecifier: public ASTNode { public: InheritanceSpecifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseName, std::unique_ptr>> _arguments ): - ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -494,11 +497,12 @@ class UsingForDirective: public ASTNode { public: UsingForDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _libraryName, ASTPointer const& _typeName ): - ASTNode(_location), m_libraryName(_libraryName), m_typeName(_typeName) {} + ASTNode(_id, _location), m_libraryName(_libraryName), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -516,11 +520,12 @@ class StructDefinition: public Declaration { public: StructDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -539,11 +544,12 @@ class EnumDefinition: public Declaration { public: EnumDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -563,8 +569,8 @@ private: class EnumValue: public Declaration { public: - EnumValue(SourceLocation const& _location, ASTPointer const& _name): - Declaration(_location, _name) {} + EnumValue(int64_t _id, SourceLocation const& _location, ASTPointer const& _name): + Declaration(_id, _location, _name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -581,10 +587,11 @@ class ParameterList: public ASTNode { public: ParameterList( + int64_t _id, SourceLocation const& _location, std::vector> const& _parameters ): - ASTNode(_location), m_parameters(_parameters) {} + ASTNode(_id, _location), m_parameters(_parameters) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -602,6 +609,7 @@ class CallableDeclaration: public Declaration, public VariableScope { public: CallableDeclaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -610,7 +618,7 @@ public: ASTPointer const& _overrides = nullptr, ASTPointer const& _returnParameters = ASTPointer() ): - Declaration(_location, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_parameters(_parameters), m_overrides(_overrides), m_returnParameters(_returnParameters), @@ -643,10 +651,11 @@ class OverrideSpecifier: public ASTNode { public: OverrideSpecifier( + int64_t _id, SourceLocation const& _location, std::vector> const& _overrides ): - ASTNode(_location), + ASTNode(_id, _location), m_overrides(_overrides) { } @@ -665,6 +674,7 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public { public: FunctionDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -678,7 +688,7 @@ public: ASTPointer const& _returnParameters, ASTPointer const& _body ): - CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), + CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), Documented(_documentation), ImplementationOptional(_body != nullptr), m_stateMutability(_stateMutability), @@ -748,7 +758,8 @@ public: enum Location { Unspecified, Storage, Memory, CallData }; VariableDeclaration( - SourceLocation const& _sourceLocation, + int64_t _id, + SourceLocation const& _location, ASTPointer const& _type, ASTPointer const& _name, ASTPointer _value, @@ -759,7 +770,7 @@ public: ASTPointer const& _overrides = nullptr, Location _referenceLocation = Location::Unspecified ): - Declaration(_sourceLocation, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_typeName(_type), m_value(_value), m_isStateVariable(_isStateVar), @@ -847,6 +858,7 @@ class ModifierDefinition: public CallableDeclaration, public Documented { public: ModifierDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, @@ -855,7 +867,7 @@ public: ASTPointer const& _overrides, ASTPointer const& _body ): - CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), + CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), Documented(_documentation), m_body(_body) { @@ -881,11 +893,12 @@ class ModifierInvocation: public ASTNode { public: ModifierInvocation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::unique_ptr>> _arguments ): - ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -908,13 +921,14 @@ class EventDefinition: public CallableDeclaration, public Documented { public: EventDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, ASTPointer const& _parameters, bool _anonymous = false ): - CallableDeclaration(_location, _name, Visibility::Default, _parameters), + CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters), Documented(_documentation), m_anonymous(_anonymous) { @@ -936,13 +950,13 @@ private: /** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global - * functions when such an identifier is encountered. Will never have a valid location in the source code. + * functions when such an identifier is encountered. Will never have a valid location in the source code */ class MagicVariableDeclaration: public Declaration { public: - MagicVariableDeclaration(ASTString const& _name, Type const* _type): - Declaration(SourceLocation(), std::make_shared(_name)), m_type(_type) {} + MagicVariableDeclaration(int _id, ASTString const& _name, Type const* _type): + Declaration(_id, SourceLocation(), std::make_shared(_name)), m_type(_type) { } void accept(ASTVisitor&) override { @@ -973,7 +987,7 @@ private: class TypeName: public ASTNode { protected: - explicit TypeName(SourceLocation const& _location): ASTNode(_location) {} + explicit TypeName(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} public: TypeNameAnnotation& annotation() const override; @@ -987,10 +1001,11 @@ class ElementaryTypeName: public TypeName { public: ElementaryTypeName( + int64_t _id, SourceLocation const& _location, ElementaryTypeNameToken const& _elem, std::optional _stateMutability = {} - ): TypeName(_location), m_type(_elem), m_stateMutability(_stateMutability) + ): TypeName(_id, _location), m_type(_elem), m_stateMutability(_stateMutability) { solAssert(!_stateMutability.has_value() || _elem.token() == Token::Address, ""); } @@ -1013,8 +1028,8 @@ private: class UserDefinedTypeName: public TypeName { public: - UserDefinedTypeName(SourceLocation const& _location, std::vector const& _namePath): - TypeName(_location), m_namePath(_namePath) {} + UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector const& _namePath): + TypeName(_id, _location), m_namePath(_namePath) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1033,13 +1048,14 @@ class FunctionTypeName: public TypeName { public: FunctionTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _parameterTypes, ASTPointer const& _returnTypes, Visibility _visibility, StateMutability _stateMutability ): - TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + TypeName(_id, _location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), m_visibility(_visibility), m_stateMutability(_stateMutability) {} void accept(ASTVisitor& _visitor) override; @@ -1071,11 +1087,12 @@ class Mapping: public TypeName { public: Mapping( + int64_t _id, SourceLocation const& _location, ASTPointer const& _keyType, ASTPointer const& _valueType ): - TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} + TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1094,11 +1111,12 @@ class ArrayTypeName: public TypeName { public: ArrayTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseType, ASTPointer const& _length ): - TypeName(_location), m_baseType(_baseType), m_length(_length) {} + TypeName(_id, _location), m_baseType(_baseType), m_length(_length) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1123,9 +1141,10 @@ class Statement: public ASTNode, public Documented { public: explicit Statement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): ASTNode(_location), Documented(_docString) {} + ): ASTNode(_id, _location), Documented(_docString) {} StatementAnnotation& annotation() const override; }; @@ -1137,12 +1156,13 @@ class InlineAssembly: public Statement { public: InlineAssembly( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, yul::Dialect const& _dialect, std::shared_ptr const& _operations ): - Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {} + Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1163,11 +1183,12 @@ class Block: public Statement, public Scopable { public: Block( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _statements ): - Statement(_location, _docString), m_statements(_statements) {} + Statement(_id, _location, _docString), m_statements(_statements) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1187,9 +1208,10 @@ class PlaceholderStatement: public Statement { public: explicit PlaceholderStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1203,13 +1225,14 @@ class IfStatement: public Statement { public: IfStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _trueBody, ASTPointer const& _falseBody ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_condition(_condition), m_trueBody(_trueBody), m_falseBody(_falseBody) @@ -1237,12 +1260,13 @@ class TryCatchClause: public ASTNode, public Scopable { public: TryCatchClause( + int64_t _id, SourceLocation const& _location, ASTPointer const& _errorName, ASTPointer const& _parameters, ASTPointer const& _block ): - ASTNode(_location), + ASTNode(_id, _location), m_errorName(_errorName), m_parameters(_parameters), m_block(_block) @@ -1280,12 +1304,13 @@ class TryStatement: public Statement { public: TryStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _externalCall, std::vector> const& _clauses ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_externalCall(_externalCall), m_clauses(_clauses) {} @@ -1307,22 +1332,24 @@ class BreakableStatement: public Statement { public: explicit BreakableStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} }; class WhileStatement: public BreakableStatement { public: WhileStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _body, bool _isDoWhile ): - BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body), + BreakableStatement(_id, _location, _docString), m_condition(_condition), m_body(_body), m_isDoWhile(_isDoWhile) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1344,6 +1371,7 @@ class ForStatement: public BreakableStatement, public Scopable { public: ForStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _initExpression, @@ -1351,7 +1379,7 @@ public: ASTPointer const& _loopExpression, ASTPointer const& _body ): - BreakableStatement(_location, _docString), + BreakableStatement(_id, _location, _docString), m_initExpression(_initExpression), m_condExpression(_conditionExpression), m_loopExpression(_loopExpression), @@ -1381,8 +1409,8 @@ private: class Continue: public Statement { public: - explicit Continue(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Continue(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1390,8 +1418,8 @@ public: class Break: public Statement { public: - explicit Break(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Break(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1400,10 +1428,11 @@ class Return: public Statement { public: Return( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression - ): Statement(_location, _docString), m_expression(_expression) {} + ): Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1421,8 +1450,8 @@ private: class Throw: public Statement { public: - explicit Throw(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Throw(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1434,11 +1463,12 @@ class EmitStatement: public Statement { public: explicit EmitStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _functionCall ): - Statement(_location, _docString), m_eventCall(_functionCall) {} + Statement(_id, _location, _docString), m_eventCall(_functionCall) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1460,12 +1490,13 @@ class VariableDeclarationStatement: public Statement { public: VariableDeclarationStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _variables, ASTPointer const& _initialValue ): - Statement(_location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} + Statement(_id, _location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1489,11 +1520,12 @@ class ExpressionStatement: public Statement { public: ExpressionStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression ): - Statement(_location, _docString), m_expression(_expression) {} + Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1516,7 +1548,7 @@ private: class Expression: public ASTNode { public: - explicit Expression(SourceLocation const& _location): ASTNode(_location) {} + explicit Expression(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} ExpressionAnnotation& annotation() const override; }; @@ -1525,12 +1557,13 @@ class Conditional: public Expression { public: Conditional( + int64_t _id, SourceLocation const& _location, ASTPointer const& _condition, ASTPointer const& _trueExpression, ASTPointer const& _falseExpression ): - Expression(_location), + Expression(_id, _location), m_condition(_condition), m_trueExpression(_trueExpression), m_falseExpression(_falseExpression) @@ -1554,12 +1587,13 @@ class Assignment: public Expression { public: Assignment( + int64_t _id, SourceLocation const& _location, ASTPointer const& _leftHandSide, Token _assignmentOperator, ASTPointer const& _rightHandSide ): - Expression(_location), + Expression(_id, _location), m_leftHandSide(_leftHandSide), m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) @@ -1591,11 +1625,12 @@ class TupleExpression: public Expression { public: TupleExpression( + int64_t _id, SourceLocation const& _location, std::vector> const& _components, bool _isArray ): - Expression(_location), + Expression(_id, _location), m_components(_components), m_isArray(_isArray) {} void accept(ASTVisitor& _visitor) override; @@ -1617,12 +1652,13 @@ class UnaryOperation: public Expression { public: UnaryOperation( + int64_t _id, SourceLocation const& _location, Token _operator, ASTPointer const& _subExpression, bool _isPrefix ): - Expression(_location), + Expression(_id, _location), m_operator(_operator), m_subExpression(_subExpression), m_isPrefix(_isPrefix) @@ -1650,12 +1686,13 @@ class BinaryOperation: public Expression { public: BinaryOperation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _left, Token _operator, ASTPointer const& _right ): - Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) + Expression(_id, _location), m_left(_left), m_operator(_operator), m_right(_right) { solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); } @@ -1681,12 +1718,13 @@ class FunctionCall: public Expression { public: FunctionCall( + int64_t _id, SourceLocation const& _location, ASTPointer const& _expression, std::vector> const& _arguments, std::vector> const& _names ): - Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} + Expression(_id, _location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1710,10 +1748,11 @@ class NewExpression: public Expression { public: NewExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _typeName ): - Expression(_location), m_typeName(_typeName) {} + Expression(_id, _location), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1730,11 +1769,12 @@ class MemberAccess: public Expression { public: MemberAccess( + int64_t _id, SourceLocation const& _location, ASTPointer _expression, ASTPointer const& _memberName ): - Expression(_location), m_expression(_expression), m_memberName(_memberName) {} + Expression(_id, _location), m_expression(_expression), m_memberName(_memberName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } @@ -1754,11 +1794,12 @@ class IndexAccess: public Expression { public: IndexAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _index ): - Expression(_location), m_base(_base), m_index(_index) {} + Expression(_id, _location), m_base(_base), m_index(_index) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1777,12 +1818,13 @@ class IndexRangeAccess: public Expression { public: IndexRangeAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _start, ASTPointer const& _end ): - Expression(_location), m_base(_base), m_start(_start), m_end(_end) {} + Expression(_id, _location), m_base(_base), m_start(_start), m_end(_end) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1803,7 +1845,7 @@ private: class PrimaryExpression: public Expression { public: - PrimaryExpression(SourceLocation const& _location): Expression(_location) {} + PrimaryExpression(int64_t _id, SourceLocation const& _location): Expression(_id, _location) {} }; /** @@ -1813,10 +1855,11 @@ class Identifier: public PrimaryExpression { public: Identifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name ): - PrimaryExpression(_location), m_name(_name) {} + PrimaryExpression(_id, _location), m_name(_name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1837,10 +1880,11 @@ class ElementaryTypeNameExpression: public PrimaryExpression { public: ElementaryTypeNameExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _type ): - PrimaryExpression(_location), + PrimaryExpression(_id, _location), m_type(_type) { } @@ -1874,12 +1918,13 @@ public: Year = static_cast(Token::SubYear) }; Literal( + int64_t _id, SourceLocation const& _location, Token _token, ASTPointer const& _value, SubDenomination _sub = SubDenomination::None ): - PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} + PrimaryExpression(_id, _location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp new file mode 100644 index 000000000..6b1005777 --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -0,0 +1,982 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + *Component that imports an AST from json format to the internal format + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +template +ASTPointer ASTJsonImporter::nullOrCast(Json::Value const& _json) +{ + if (_json.isNull()) + return nullptr; + else + return dynamic_pointer_cast(convertJsonToASTNode(_json)); +} + + +// ============ public =========================== + +map> ASTJsonImporter::jsonToSourceUnit(map const& _sourceList) +{ + m_sourceList = _sourceList; + for (auto const& src: _sourceList) + m_sourceLocations.emplace_back(make_shared(src.first)); + for (auto const& srcPair: m_sourceList) + { + astAssert(!srcPair.second.isNull(), ""); + astAssert(member(srcPair.second,"nodeType") == "SourceUnit", "The 'nodeType' of the highest node must be 'SourceUnit'."); + m_currentSourceName = srcPair.first; + m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first); + } + return m_sourceUnits; +} + +// ============ private =========================== + +// =========== general creation functions ============== +template +ASTPointer ASTJsonImporter::createASTNode(Json::Value const& _node, Args&&... _args) +{ + astAssert(member(_node, "id").isInt64(), "'id'-field must be 64bit integer."); + + int64_t id = _node["id"].asInt64(); + + astAssert(m_usedIDs.insert(id).second, "Found duplicate node ID!"); + + auto n = make_shared( + id, + createSourceLocation(_node), + forward(_args)... + ); + return n; +} + +SourceLocation const ASTJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_currentSourceName, int(m_sourceLocations.size())); +} + +template +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _node) +{ + ASTPointer ret = dynamic_pointer_cast(convertJsonToASTNode(_node)); + astAssert(ret, "cast of converted json-node must not be nullptr"); + return ret; +} + + +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _json) +{ + astAssert(_json["nodeType"].isString() && _json.isMember("id"), "JSON-Node needs to have 'nodeType' and 'id' fields."); + string nodeType = _json["nodeType"].asString(); + if (nodeType == "PragmaDirective") + return createPragmaDirective(_json); + if (nodeType == "ImportDirective") + return createImportDirective(_json); + if (nodeType == "ContractDefinition") + return createContractDefinition(_json); + if (nodeType == "InheritanceSpecifier") + return createInheritanceSpecifier(_json); + if (nodeType == "UsingForDirective") + return createUsingForDirective(_json); + if (nodeType == "StructDefinition") + return createStructDefinition(_json); + if (nodeType == "EnumDefinition") + return createEnumDefinition(_json); + if (nodeType == "EnumValue") + return createEnumValue(_json); + if (nodeType == "ParameterList") + return createParameterList(_json); + if (nodeType == "OverrideSpecifier") + return createOverrideSpecifier(_json); + if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_json); + if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_json); + if (nodeType == "ModifierDefinition") + return createModifierDefinition(_json); + if (nodeType == "ModifierInvocation") + return createModifierInvocation(_json); + if (nodeType == "EventDefinition") + return createEventDefinition(_json); + if (nodeType == "ElementaryTypeName") + return createElementaryTypeName(_json); + if (nodeType == "UserDefinedTypeName") + return createUserDefinedTypeName(_json); + if (nodeType == "FunctionTypeName") + return createFunctionTypeName(_json); + if (nodeType == "Mapping") + return createMapping(_json); + if (nodeType == "ArrayTypeName") + return createArrayTypeName(_json); + if (nodeType == "InlineAssembly") + return createInlineAssembly(_json); + if (nodeType == "Block") + return createBlock(_json); + if (nodeType == "PlaceholderStatement") + return createPlaceholderStatement(_json); + if (nodeType == "IfStatement") + return createIfStatement(_json); + if (nodeType == "TryCatchClause") + return createTryCatchClause(_json); + if (nodeType == "TryStatement") + return createTryStatement(_json); + if (nodeType == "WhileStatement") + return createWhileStatement(_json, false); + if (nodeType == "DoWhileStatement") + return createWhileStatement(_json, true); + if (nodeType == "ForStatement") + return createForStatement(_json); + if (nodeType == "Continue") + return createContinue(_json); + if (nodeType == "Break") + return createBreak(_json); + if (nodeType == "Return") + return createReturn(_json); + if (nodeType == "EmitStatement") + return createEmitStatement(_json); + if (nodeType == "Throw") + return createThrow(_json); + if (nodeType == "VariableDeclarationStatement") + return createVariableDeclarationStatement(_json); + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_json); + if (nodeType == "Conditional") + return createConditional(_json); + if (nodeType == "Assignment") + return createAssignment(_json); + if (nodeType == "TupleExpression") + return createTupleExpression(_json); + if (nodeType == "UnaryOperation") + return createUnaryOperation(_json); + if (nodeType == "BinaryOperation") + return createBinaryOperation(_json); + if (nodeType == "FunctionCall") + return createFunctionCall(_json); + if (nodeType == "NewExpression") + return createNewExpression(_json); + if (nodeType == "MemberAccess") + return createMemberAccess(_json); + if (nodeType == "IndexAccess") + return createIndexAccess(_json); + if (nodeType == "IndexRangeAccess") + return createIndexRangeAccess(_json); + if (nodeType == "Identifier") + return createIdentifier(_json); + if (nodeType == "ElementaryTypeNameExpression") + return createElementaryTypeNameExpression(_json); + if (nodeType == "Literal") + return createLiteral(_json); + else + astAssert(false, "Unknown type of ASTNode: " + nodeType); +} + +// ============ functions to instantiate the AST-Nodes from Json-Nodes ============== + +ASTPointer ASTJsonImporter::createSourceUnit(Json::Value const& _node, string const& _srcName) +{ + vector> nodes; + for (auto& child: member(_node, "nodes")) + nodes.emplace_back(convertJsonToASTNode(child)); + ASTPointer tmp = createASTNode(_node, nodes); + tmp->annotation().path = _srcName; + return tmp; +} + +ASTPointer ASTJsonImporter::createPragmaDirective(Json::Value const& _node) +{ + vector tokens; + vector literals; + for (auto const& lit: member(_node, "literals")) + { + string l = lit.asString(); + literals.push_back(l); + tokens.push_back(scanSingleToken(l)); + } + return createASTNode(_node, tokens, literals); +} + +ASTPointer ASTJsonImporter::createImportDirective(Json::Value const& _node) +{ + ASTPointer unitAlias = memberAsASTString(_node, "unitAlias"); + ASTPointer path = memberAsASTString(_node, "file"); + ImportDirective::SymbolAliasList symbolAliases; + + for (auto& tuple: member(_node, "symbolAliases")) + { + astAssert(tuple["local"].isNull() || tuple["local"].isString(), "expected 'local' to be a string or null!"); + + symbolAliases.push_back({ + createIdentifier(tuple["foreign"]), + tuple["local"].isNull() ? nullptr : make_shared(tuple["local"].asString()), + createSourceLocation(tuple["foreign"])} + ); + } + ASTPointer tmp = createASTNode( + _node, + path, + unitAlias, + move(symbolAliases) + ); + + astAssert(_node["absolutePath"].isString(), "Expected 'absolutePath' to be a string!"); + + tmp->annotation().absolutePath = _node["absolutePath"].asString(); + return tmp; +} + +ASTPointer ASTJsonImporter::createContractDefinition(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + std::vector> baseContracts; + + for (auto& base: _node["baseContracts"]) + baseContracts.push_back(createInheritanceSpecifier(base)); + + std::vector> subNodes; + + for (auto& subnode: _node["nodes"]) + subNodes.push_back(convertJsonToASTNode(subnode)); + + return createASTNode( + _node, + make_shared(_node["name"].asString()), + nullOrASTString(_node, "documentation"), + baseContracts, + subNodes, + contractKind(_node), + memberAsBool(_node, "abstract") + ); +} + +ASTPointer ASTJsonImporter::createInheritanceSpecifier(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "baseName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createUsingForDirective(Json::Value const& _node) +{ + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "libraryName")), + _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]) + ); +} + +ASTPointer ASTJsonImporter::createStructDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createVariableDeclaration(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createEnumValue(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumValue(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name") + ); +} + +ASTPointer ASTJsonImporter::createParameterList(Json::Value const& _node) +{ + std::vector> parameters; + for (auto& param: _node["parameters"]) + parameters.push_back(createVariableDeclaration(param)); + return createASTNode( + _node, + parameters + ); +} + +ASTPointer ASTJsonImporter::createOverrideSpecifier(Json::Value const& _node) +{ + std::vector> overrides; + + for (auto& param: _node["overrides"]) + overrides.push_back(createUserDefinedTypeName(param)); + + return createASTNode( + _node, + overrides + ); +} + +ASTPointer ASTJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + astAssert(_node["kind"].isString(), "Expected 'kind' to be a string!"); + + Token kind; + string kindStr = member(_node, "kind").asString(); + + if (kindStr == "constructor") + kind = Token::Constructor; + else if (kindStr == "function") + kind = Token::Function; + else if (kindStr == "fallback") + kind = Token::Fallback; + else if (kindStr == "receive") + kind = Token::Receive; + else + astAssert(false, "Expected 'kind' to be one of [constructor, function, fallback, receive]"); + + std::vector> modifiers; + for (auto& mod: member(_node, "modifiers")) + modifiers.push_back(createModifierInvocation(mod)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + visibility(_node), + stateMutability(_node), + kind, + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + nullOrASTString(_node, "documentation"), + createParameterList(member(_node, "parameters")), + modifiers, + createParameterList(member(_node, "returnParameters")), + memberAsBool(_node, "implemented") ? createBlock(member(_node, "body")) : nullptr + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + return createASTNode( + _node, + nullOrCast(member(_node, "typeName")), + make_shared(member(_node, "name").asString()), + nullOrCast(member(_node, "value")), + visibility(_node), + memberAsBool(_node, "stateVariable"), + _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, + memberAsBool(_node, "constant"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + location(_node) + ); +} + +ASTPointer ASTJsonImporter::createModifierDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + nullOrASTString(_node,"documentation"), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + createBlock(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createModifierInvocation(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createIdentifier(member(_node, "modifierName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createEventDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + nullOrASTString(_node, "documentation"), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "anonymous") + ); +} + +ASTPointer ASTJsonImporter::createElementaryTypeName(Json::Value const& _node) +{ + unsigned short firstNum; + unsigned short secondNum; + + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + string name = member(_node, "name").asString(); + Token token; + tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(name); + ElementaryTypeNameToken elem(token, firstNum, secondNum); + + std::optional mutability = {}; + if (_node.isMember("stateMutability")) + mutability = stateMutability(_node); + + return createASTNode(_node, elem, mutability); +} + +ASTPointer ASTJsonImporter::createUserDefinedTypeName(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + vector namePath; + vector strs; + string nameString = member(_node, "name").asString(); + boost::algorithm::split(strs, nameString, boost::is_any_of(".")); + for (string s: strs) + namePath.push_back(ASTString(s)); + return createASTNode( + _node, + namePath + ); +} + +ASTPointer ASTJsonImporter::createFunctionTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + createParameterList(member(_node, "parameterTypes")), + createParameterList(member(_node, "returnParameterTypes")), + visibility(_node), + stateMutability(_node) + ); +} + +ASTPointer ASTJsonImporter::createMapping(Json::Value const& _node) +{ + return createASTNode( + _node, + createElementaryTypeName(member(_node, "keyType")), + convertJsonToASTNode(member(_node, "valueType")) + ); +} + +ASTPointer ASTJsonImporter::createArrayTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseType")), + nullOrCast(member(_node, "length")) + ); +} + +ASTPointer ASTJsonImporter::createInlineAssembly(Json::Value const& _node) +{ + astAssert(_node["evmVersion"].isString(), "Expected evmVersion to be a string!"); + auto evmVersion = langutil::EVMVersion::fromString(_node["evmVersion"].asString()); + astAssert(evmVersion.has_value(), "Invalid EVM version!"); + astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!"); + + yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value()); + shared_ptr operations = make_shared(AsmJsonImporter(m_currentSourceName).createBlock(member(_node, "AST"))); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + dialect, + operations + ); +} + +ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) +{ + std::vector> statements; + for (auto& stat: member(_node, "statements")) + statements.push_back(convertJsonToASTNode(stat)); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + statements + ); +} + +ASTPointer ASTJsonImporter::createPlaceholderStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createIfStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueBody")), + nullOrCast(member(_node, "falseBody")) + ); +} + +ASTPointer ASTJsonImporter::createTryCatchClause(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "errorName"), + nullOrCast(member(_node, "parameters")), + convertJsonToASTNode(member(_node, "block")) + ); +} + +ASTPointer ASTJsonImporter::createTryStatement(Json::Value const& _node) +{ + vector> clauses; + + for (auto& param: _node["clauses"]) + clauses.emplace_back(createTryCatchClause(param)); + + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "externalCall")), + clauses + ); +} + +ASTPointer ASTJsonImporter::createWhileStatement(Json::Value const& _node, bool _isDoWhile=false) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "body")), + _isDoWhile + ); +} + +ASTPointer ASTJsonImporter::createForStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "initializationExpression")), + nullOrCast(member(_node, "condition")), + nullOrCast(member(_node, "loopExpression")), + convertJsonToASTNode(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createContinue(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createBreak(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createReturn(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createThrow(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createEmitStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + createFunctionCall(member(_node, "eventCall")) + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclarationStatement(Json::Value const& _node) +{ + std::vector> variables; + for (auto& var: member(_node, "declarations")) + variables.push_back(var.isNull() ? nullptr : createVariableDeclaration(var)); //unnamed components are empty pointers + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + variables, + nullOrCast(member(_node, "initialValue")) + ); +} + +ASTPointer ASTJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createConditional(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueExpression")), + convertJsonToASTNode(member(_node, "falseExpression")) + ); +} + +ASTPointer ASTJsonImporter::createAssignment(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftHandSide")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightHandSide")) + ); +} + +ASTPointer ASTJsonImporter::createTupleExpression(Json::Value const& _node) +{ + std::vector> components; + for (auto& comp: member(_node, "components")) + components.push_back(nullOrCast(comp)); + return createASTNode( + _node, + components, + memberAsBool(_node, "isInlineArray") + ); +} + +ASTPointer ASTJsonImporter::createUnaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "subExpression")), + memberAsBool(_node, "prefix") + ); +} + +ASTPointer ASTJsonImporter::createBinaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftExpression")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightExpression")) + ); +} + +ASTPointer ASTJsonImporter::createFunctionCall(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + std::vector> names; + for (auto& name: member(_node, "names")) + { + astAssert(name.isString(), "Expected 'names' members to be strings!"); + names.push_back(make_shared(name.asString())); + } + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + arguments, + names + ); +} + +ASTPointer ASTJsonImporter::createNewExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createMemberAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + memberAsASTString(_node, "memberName") + ); +} + +ASTPointer ASTJsonImporter::createIndexAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "indexExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIndexRangeAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "startExpression")), + nullOrCast(member(_node, "endExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIdentifier(Json::Value const& _node) +{ + return createASTNode(_node, memberAsASTString(_node, "name")); +} + +ASTPointer ASTJsonImporter::createElementaryTypeNameExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + createElementaryTypeName(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createLiteral(Json::Value const& _node) +{ + static string const valStr = "value"; + static string const hexValStr = "hexValue"; + + astAssert(member(_node, valStr).isString() || member(_node, hexValStr).isString(), "Literal-value is unset."); + + ASTPointer value = _node.isMember(hexValStr) ? + make_shared(util::asString(util::fromHex(_node[hexValStr].asString()))) : + make_shared(_node[valStr].asString()); + + return createASTNode( + _node, + literalTokenKind(_node), + value, + member(_node, "subdenomination").isNull() ? Literal::SubDenomination::None : subdenomination(_node) + ); +} + +// ===== helper functions ========== + +Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node '" + _node["nodeType"].asString() + "' (id " + _node["id"].asString() + ") is missing field '" + _name + "'."); + return _node[_name]; +} + +Token ASTJsonImporter::scanSingleToken(Json::Value const& _node) +{ + langutil::Scanner scanner{langutil::CharStream(_node.asString(), "")}; + astAssert(scanner.peekNextToken() == Token::EOS, "Token string is too long."); + return scanner.currentToken(); +} + +ASTPointer ASTJsonImporter::nullOrASTString(Json::Value const& _json, string const& _name) +{ + return _json[_name].isString() ? memberAsASTString(_json, _name) : nullptr; +} + +ASTPointer ASTJsonImporter::memberAsASTString(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isString(), "field " + _name + " must be of type string."); + return make_shared(_node[_name].asString()); +} + +bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isBool(), "field " + _name + " must be of type boolean."); + return _node[_name].asBool(); +} + + +// =========== JSON to definition helpers ======================= + +ContractKind ASTJsonImporter::contractKind(Json::Value const& _node) +{ + ContractKind kind; + astAssert(!member(_node, "contractKind").isNull(), "'Contract-kind' can not be null."); + if (_node["contractKind"].asString() == "interface") + kind = ContractKind::Interface; + else if (_node["contractKind"].asString() == "contract") + kind = ContractKind::Contract; + else if (_node["contractKind"].asString() == "library") + kind = ContractKind::Library; + else + astAssert(false, "Unknown ContractKind"); + return kind; +} + +Token ASTJsonImporter::literalTokenKind(Json::Value const& _node) +{ + astAssert(member(_node, "kind").isString(), "Token-'kind' expected to be a string."); + Token tok; + if (_node["kind"].asString() == "number") + tok = Token::Number; + else if (_node["kind"].asString() == "string") + tok = Token::StringLiteral; + else if (_node["kind"].asString() == "bool") + tok = (member(_node, "value").asString() == "true") ? Token::TrueLiteral : Token::FalseLiteral; + else + astAssert(false, "Unknown kind of literalString"); + return tok; +} + +Visibility ASTJsonImporter::visibility(Json::Value const& _node) +{ + Json::Value visibility = member(_node, "visibility"); + astAssert(visibility.isString(), "'visibility' expected to be a string."); + + string const visibilityStr = visibility.asString(); + + if (visibilityStr == "default") + return Visibility::Default; + else if (visibilityStr == "private") + return Visibility::Private; + else if ( visibilityStr == "internal") + return Visibility::Internal; + else if (visibilityStr == "public") + return Visibility::Public; + else if (visibilityStr == "external") + return Visibility::External; + else + astAssert(false, "Unknown visibility declaration"); +} + +VariableDeclaration::Location ASTJsonImporter::location(Json::Value const& _node) +{ + Json::Value storageLoc = member(_node, "storageLocation"); + astAssert(storageLoc.isString(), "'storageLocation' expected to be a string."); + + string const storageLocStr = storageLoc.asString(); + + if (storageLocStr == "default") + return VariableDeclaration::Location::Unspecified; + else if (storageLocStr == "storage") + return VariableDeclaration::Location::Storage; + else if (storageLocStr == "memory") + return VariableDeclaration::Location::Memory; + else if (storageLocStr == "calldata") + return VariableDeclaration::Location::CallData; + else + astAssert(false, "Unknown location declaration"); +} + +Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _node) +{ + Json::Value subDen = member(_node, "subdenomination"); + + if (subDen.isNull()) + return Literal::SubDenomination::None; + + astAssert(subDen.isString(), "'subDenomination' expected to be string."); + + string const subDenStr = subDen.asString(); + + if (subDenStr == "wei") + return Literal::SubDenomination::Wei; + else if (subDenStr == "szabo") + return Literal::SubDenomination::Szabo; + else if (subDenStr == "finney") + return Literal::SubDenomination::Finney; + else if (subDenStr == "ether") + return Literal::SubDenomination::Ether; + else if (subDenStr == "seconds") + return Literal::SubDenomination::Second; + else if (subDenStr == "minutes") + return Literal::SubDenomination::Minute; + else if (subDenStr == "hours") + return Literal::SubDenomination::Hour; + else if (subDenStr == "days") + return Literal::SubDenomination::Day; + else if (subDenStr == "weeks") + return Literal::SubDenomination::Week; + else if (subDenStr == "years") + return Literal::SubDenomination::Year; + else + astAssert(false, "Unknown subdenomination"); +} + +StateMutability ASTJsonImporter::stateMutability(Json::Value const& _node) +{ + astAssert(member(_node, "stateMutability").isString(), "StateMutability' expected to be string."); + string const mutabilityStr = member(_node, "stateMutability").asString(); + + if (mutabilityStr == "pure") + return StateMutability::Pure; + else if (mutabilityStr == "view") + return StateMutability::View; + else if (mutabilityStr == "nonpayable") + return StateMutability::NonPayable; + else if (mutabilityStr == "payable") + return StateMutability::Payable; + else + astAssert(false, "Unknown stateMutability"); +} + +} diff --git a/libsolidity/ast/ASTJsonImporter.h b/libsolidity/ast/ASTJsonImporter.h new file mode 100644 index 000000000..28786961a --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.h @@ -0,0 +1,161 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts the AST from JSON format to ASTNode + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class ASTJsonImporter +{ +public: + ASTJsonImporter(langutil::EVMVersion _evmVersion) + :m_evmVersion(_evmVersion) + {} + + /// Converts the AST from JSON-format to ASTPointer + /// @a _sourceList used to provide source names for the ASTs + /// @returns map of sourcenames to their respective ASTs + std::map> jsonToSourceUnit(std::map const& _sourceList); + +private: + + // =========== general creation functions ============== + + /// Sets the source location and nodeID + /// @returns the ASTNode Object class of the respective JSON node, + template + ASTPointer createASTNode(Json::Value const& _node, Args&&... _args); + /// @returns the sourceLocation-object created from the string in the JSON node + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + /// Creates an ASTNode for a given JSON-ast of unknown type + /// @returns Pointer to a new created ASTNode + ASTPointer convertJsonToASTNode(Json::Value const& _ast); + /// @returns a pointer to the more specific subclass of ASTNode + /// as indicated by the nodeType field of the json + template + ASTPointer convertJsonToASTNode(Json::Value const& _node); + + + /// \defgroup nodeCreators JSON to AST-Nodes + ///@{ + ASTPointer createSourceUnit(Json::Value const& _node, std::string const& _srcName); + ASTPointer createPragmaDirective(Json::Value const& _node); + ASTPointer createImportDirective(Json::Value const& _node); + ASTPointer createContractDefinition(Json::Value const& _node); + ASTPointer createInheritanceSpecifier(Json::Value const& _node); + ASTPointer createUsingForDirective(Json::Value const& _node); + ASTPointer createStructDefinition(Json::Value const& _node); + ASTPointer createEnumDefinition(Json::Value const& _node); + ASTPointer createEnumValue(Json::Value const& _node); + ASTPointer createParameterList(Json::Value const& _node); + ASTPointer createOverrideSpecifier(Json::Value const& _node); + ASTPointer createFunctionDefinition(Json::Value const& _node); + ASTPointer createVariableDeclaration(Json::Value const& _node); + ASTPointer createModifierDefinition(Json::Value const& _node); + ASTPointer createModifierInvocation(Json::Value const& _node); + ASTPointer createEventDefinition(Json::Value const& _node); + ASTPointer createElementaryTypeName(Json::Value const& _node); + ASTPointer createUserDefinedTypeName(Json::Value const& _node); + ASTPointer createFunctionTypeName(Json::Value const& _node); + ASTPointer createMapping(Json::Value const& _node); + ASTPointer createArrayTypeName(Json::Value const& _node); + ASTPointer createInlineAssembly(Json::Value const& _node); + ASTPointer createBlock(Json::Value const& _node); + ASTPointer createPlaceholderStatement(Json::Value const& _node); + ASTPointer createIfStatement(Json::Value const& _node); + ASTPointer createTryCatchClause(Json::Value const& _node); + ASTPointer createTryStatement(Json::Value const& _node); + ASTPointer createWhileStatement(Json::Value const& _node, bool _isDoWhile); + ASTPointer createForStatement(Json::Value const& _node); + ASTPointer createContinue(Json::Value const& _node); + ASTPointer createBreak(Json::Value const& _node); + ASTPointer createReturn(Json::Value const& _node); + ASTPointer createThrow(Json::Value const& _node); + ASTPointer createEmitStatement(Json::Value const& _node); + ASTPointer createVariableDeclarationStatement(Json::Value const& _node); + ASTPointer createExpressionStatement(Json::Value const& _node); + ASTPointer createConditional(Json::Value const& _node); + ASTPointer createAssignment(Json::Value const& _node); + ASTPointer createTupleExpression(Json::Value const& _node); + ASTPointer createUnaryOperation(Json::Value const& _node); + ASTPointer createBinaryOperation(Json::Value const& _node); + ASTPointer createFunctionCall(Json::Value const& _node); + ASTPointer createNewExpression(Json::Value const& _node); + ASTPointer createMemberAccess(Json::Value const& _node); + ASTPointer createIndexAccess(Json::Value const& _node); + ASTPointer createIndexRangeAccess(Json::Value const& _node); + ASTPointer createIdentifier(Json::Value const& _node); + ASTPointer createElementaryTypeNameExpression(Json::Value const& _node); + ASTPointer createLiteral(Json::Value const& _node); + ///@} + + // =============== general helper functions =================== + /// @returns the member of a given JSON object, throws if member does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + /// @returns the appropriate TokenObject used in parsed Strings (pragma directive or operator) + Token scanSingleToken(Json::Value const& _node); + template + ///@returns nullptr or an ASTPointer cast to a specific Class + ASTPointer nullOrCast(Json::Value const& _json); + /// @returns nullptr or ASTString, given an JSON string or an empty field + ASTPointer nullOrASTString(Json::Value const& _json, std::string const& _name); + + // ============== JSON to definition helpers =============== + /// \defgroup typeHelpers Json to ast-datatype helpers + /// {@ + ASTPointer memberAsASTString(Json::Value const& _node, std::string const& _name); + bool memberAsBool(Json::Value const& _node, std::string const& _name); + Visibility visibility(Json::Value const& _node); + StateMutability stateMutability(Json::Value const& _node); + VariableDeclaration::Location location(Json::Value const& _node); + ContractKind contractKind(Json::Value const& _node); + Token literalTokenKind(Json::Value const& _node); + Literal::SubDenomination subdenomination(Json::Value const& _node); + ///@} + + // =========== member variables =============== + /// Stores filepath as sourcenames to AST in JSON format + std::map m_sourceList; + /// list of filepaths (used as sourcenames) + std::vector> m_sourceLocations; + /// filepath to AST + std::map> m_sourceUnits; + std::string m_currentSourceName; + /// IDs already used by the nodes + std::set m_usedIDs; + /// Configured EVM version + langutil::EVMVersion m_evmVersion; +}; + +} diff --git a/libsolidity/ast/AsmJsonImporter.cpp b/libsolidity/ast/AsmJsonImporter.cpp new file mode 100644 index 000000000..8f4559a55 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.cpp @@ -0,0 +1,303 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +using namespace std; +using namespace solidity::yul; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +SourceLocation const AsmJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_sourceName); +} + +template +T AsmJsonImporter::createAsmNode(Json::Value const& _node) +{ + T r; + r.location = createSourceLocation(_node); + astAssert(!r.location.isEmpty() || !r.location.source, "Invalid source location in Asm AST"); + return r; + +} + +Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node is missing field '" + _name + "'."); + return _node[_name]; +} + +yul::TypedName AsmJsonImporter::createTypedName(Json::Value const& _node) +{ + auto typedName = createAsmNode(_node); + typedName.type = YulString{member(_node, "type").asString()}; + typedName.name = YulString{member(_node, "name").asString()}; + return typedName; +} + +yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_node); + else if (nodeType == "Assignment") + return createAssignment(_node); + else if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_node); + else if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_node); + else if (nodeType == "If") + return createIf(_node); + else if (nodeType == "Switch") + return createSwitch(_node); + else if (nodeType == "ForLoop") + return createForLoop(_node); + else if (nodeType == "Break") + return createBreak(_node); + else if (nodeType == "Continue") + return createContinue(_node); + else if (nodeType == "Leave") + return createLeave(_node); + else + astAssert(false, "Invalid nodeType as statement"); +} + +yul::Expression AsmJsonImporter::createExpression(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "FunctionCall") + return createFunctionCall(_node); + else if (nodeType == "Identifier") + return createIdentifier(_node); + else if (nodeType == "Literal") + return createLiteral(_node); + else + astAssert(false, "Invalid nodeType as expression"); +} + +vector AsmJsonImporter::createExpressionVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createExpression(var)); + return ret; +} + +vector AsmJsonImporter::createStatementVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createStatement(var)); + return ret; +} + +yul::Block AsmJsonImporter::createBlock(Json::Value const& _node) +{ + auto block = createAsmNode(_node); + block.statements = createStatementVector(_node["statements"]); + return block; +} + +yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node) +{ + auto lit = createAsmNode(_node); + string kind = member(_node, "kind").asString(); + + lit.value = YulString{member(_node, "value").asString()}; + lit.type= YulString{member(_node, "type").asString()}; + + langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")}; + + if (kind == "number") + { + lit.kind = yul::LiteralKind::Number; + astAssert( + scanner.currentToken() == Token::Number, + "Expected number but got " + langutil::TokenTraits::friendlyName(scanner.currentToken()) + string(" while scanning ") + lit.value.str() + ); + } + else if (kind == "bool") + { + lit.kind = yul::LiteralKind::Boolean; + astAssert( + scanner.currentToken() == Token::TrueLiteral || + scanner.currentToken() == Token::FalseLiteral, + "Expected true/false literal!" + ); + } + else if (kind == "string") + { + lit.kind = yul::LiteralKind::String; + astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!"); + } + else + solAssert(false, "unknown type of literal"); + + return lit; +} + +yul::Leave AsmJsonImporter::createLeave(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Identifier AsmJsonImporter::createIdentifier(Json::Value const& _node) +{ + auto identifier = createAsmNode(_node); + identifier.name = YulString(member(_node, "name").asString()); + return identifier; +} + +yul::Assignment AsmJsonImporter::createAssignment(Json::Value const& _node) +{ + auto assignment = createAsmNode(_node); + + if (_node.isMember("variableNames")) + for (auto const& var: member(_node, "variableNames")) + assignment.variableNames.emplace_back(createIdentifier(var)); + + assignment.value = make_unique(createExpression(member(_node, "value"))); + return assignment; +} + +yul::FunctionCall AsmJsonImporter::createFunctionCall(Json::Value const& _node) +{ + auto functionCall = createAsmNode(_node); + + for (auto const& var: member(_node, "arguments")) + functionCall.arguments.emplace_back(createExpression(var)); + + functionCall.functionName = createIdentifier(member(_node, "functionName")); + + return functionCall; +} + +yul::ExpressionStatement AsmJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + auto statement = createAsmNode(_node); + statement.expression = createExpression(member(_node, "expression")); + return statement; +} + +yul::VariableDeclaration AsmJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + auto varDec = createAsmNode(_node); + for (auto const& var: member(_node, "variables")) + varDec.variables.emplace_back(createTypedName(var)); + varDec.value = make_unique(createExpression(member(_node, "value"))); + return varDec; +} + +yul::FunctionDefinition AsmJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + auto funcDef = createAsmNode(_node); + funcDef.name = YulString{member(_node, "name").asString()}; + + if (_node.isMember("parameters")) + for (auto const& var: member(_node, "parameters")) + funcDef.parameters.emplace_back(createTypedName(var)); + + if (_node.isMember("returnVariables")) + for (auto const& var: member(_node, "returnVariables")) + funcDef.returnVariables.emplace_back(createTypedName(var)); + + funcDef.body = createBlock(member(_node, "body")); + return funcDef; +} + +yul::If AsmJsonImporter::createIf(Json::Value const& _node) +{ + auto ifStatement = createAsmNode(_node); + ifStatement.condition = make_unique(createExpression(member(_node, "condition"))); + ifStatement.body = createBlock(member(_node, "body")); + return ifStatement; +} + +yul::Case AsmJsonImporter::createCase(Json::Value const& _node) +{ + auto caseStatement = createAsmNode(_node); + caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique(createLiteral(member(_node, "value"))); + caseStatement.body = createBlock(member(_node, "body")); + return caseStatement; +} + +yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node) +{ + auto switchStatement = createAsmNode(_node); + switchStatement.expression = make_unique(createExpression(member(_node, "value"))); + for (auto const& var: member(_node, "cases")) + switchStatement.cases.emplace_back(createCase(var)); + return switchStatement; +} + +yul::ForLoop AsmJsonImporter::createForLoop(Json::Value const& _node) +{ + auto forLoop = createAsmNode(_node); + forLoop.pre = createBlock(member(_node, "pre")); + forLoop.condition = make_unique(createExpression(member(_node, "condition"))); + forLoop.post = createBlock(member(_node, "post")); + forLoop.body = createBlock(member(_node, "body")); + return forLoop; +} + +yul::Break AsmJsonImporter::createBreak(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Continue AsmJsonImporter::createContinue(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +} diff --git a/libsolidity/ast/AsmJsonImporter.h b/libsolidity/ast/AsmJsonImporter.h new file mode 100644 index 000000000..e7f4821c2 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.h @@ -0,0 +1,74 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + */ + +#pragma once + +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class AsmJsonImporter +{ +public: + explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(_sourceName) {} + yul::Block createBlock(Json::Value const& _node); + +private: + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + template + T createAsmNode(Json::Value const& _node); + /// helper function to access member functions of the JSON + /// and throw an error if it does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + + yul::Statement createStatement(Json::Value const& _node); + yul::Expression createExpression(Json::Value const& _node); + std::vector createStatementVector(Json::Value const& _array); + std::vector createExpressionVector(Json::Value const& _array); + + yul::TypedName createTypedName(Json::Value const& _node); + yul::Literal createLiteral(Json::Value const& _node); + yul::Leave createLeave(Json::Value const& _node); + yul::Identifier createIdentifier(Json::Value const& _node); + yul::Assignment createAssignment(Json::Value const& _node); + yul::FunctionCall createFunctionCall(Json::Value const& _node); + yul::ExpressionStatement createExpressionStatement(Json::Value const& _node); + yul::VariableDeclaration createVariableDeclaration(Json::Value const& _node); + yul::FunctionDefinition createFunctionDefinition(Json::Value const& _node); + yul::If createIf(Json::Value const& _node); + yul::Case createCase(Json::Value const& _node); + yul::Switch createSwitch(Json::Value const& _node); + yul::ForLoop createForLoop(Json::Value const& _node); + yul::Break createBreak(Json::Value const& _node); + yul::Continue createContinue(Json::Value const& _node); + + std::string m_sourceName; + +}; + +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1474b2826..bbc94e031 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -233,11 +234,12 @@ bool CompilerStack::parse() if (m_stackState != SourcesSet) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call parse only after the SourcesSet state.")); m_errorReporter.clear(); - ASTNode::resetID(); if (SemVerVersion{string(VersionString)}.isPrerelease()) m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production."); + Parser parser{m_errorReporter, m_evmVersion, m_parserErrorRecovery}; + vector sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); @@ -246,7 +248,7 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner); + source.ast = parser.parse(source.scanner); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); else @@ -268,6 +270,26 @@ bool CompilerStack::parse() return !m_hasError; } +void CompilerStack::importASTs(map const& _sources) +{ + if (m_stackState != Empty) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call importASTs only before the SourcesSet state.")); + m_sourceJsons = _sources; + map> reconstructedSources = ASTJsonImporter(m_evmVersion).jsonToSourceUnit(m_sourceJsons); + for (auto& src: reconstructedSources) + { + string const& path = src.first; + Source source; + source.ast = src.second; + string srcString = util::jsonCompactPrint(m_sourceJsons[src.first]); + ASTPointer scanner = make_shared(langutil::CharStream(srcString, src.first)); + source.scanner = scanner; + m_sources[path] = source; + } + m_stackState = ParsingPerformed; + m_importedSources = true; +} + bool CompilerStack::analyze() { if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed) @@ -1158,7 +1180,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const { Json::Value meta; meta["version"] = 1; - meta["language"] = "Solidity"; + meta["language"] = m_importedSources ? "SolidityAST" : "Solidity"; meta["compiler"]["version"] = VersionStringStrict; /// All the source files (including self), which should be included in the metadata. diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 0e9ffaff1..42279a499 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -199,6 +199,10 @@ public: /// @returns false on error. bool parse(); + /// Imports given SourceUnits so they can be analyzed. Leads to the same internal state as parse(). + /// Will throw errors if the import fails + void importASTs(std::map const& _sources); + /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// typechecking, staticAnalysis) on previously parsed sources. /// @returns false on error. @@ -440,6 +444,8 @@ private: /// "context:prefix=target" std::vector m_remappings; std::map m_sources; + // if imported, store AST-JSONS for each filename + std::map m_sourceJsons; std::vector m_unhandledSMTLib2Queries; std::map m_smtlib2Responses; std::shared_ptr m_globalContext; @@ -453,6 +459,7 @@ private: MetadataHash m_metadataHash = MetadataHash::IPFS; bool m_parserErrorRecovery = false; State m_stackState = Empty; + bool m_importedSources = false; /// Whether or not there has been an error during processing. /// If this is true, the stack will refuse to generate code. bool m_hasError = false; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 7991e1aaa..406226da4 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -44,9 +44,9 @@ namespace solidity::frontend class Parser::ASTNodeFactory { public: - explicit ASTNodeFactory(Parser const& _parser): + explicit ASTNodeFactory(Parser& _parser): m_parser(_parser), m_location{_parser.position(), -1, _parser.source()} {} - ASTNodeFactory(Parser const& _parser, ASTPointer const& _childNode): + ASTNodeFactory(Parser& _parser, ASTPointer const& _childNode): m_parser(_parser), m_location{_childNode->location()} {} void markEndPosition() { m_location.end = m_parser.endPosition(); } @@ -61,18 +61,19 @@ public: solAssert(m_location.source, ""); if (m_location.end < 0) markEndPosition(); - return make_shared(m_location, std::forward(_args)...); + return make_shared(m_parser.nextID(), m_location, std::forward(_args)...); } SourceLocation const& location() const noexcept { return m_location; } private: - Parser const& m_parser; + Parser& m_parser; SourceLocation m_location; }; ASTPointer Parser::parse(shared_ptr const& _scanner) { + solAssert(!m_insideModifier, ""); try { m_recursionDepth = 0; @@ -1193,7 +1194,7 @@ ASTPointer Parser::parseInlineAssembly(ASTPointer con BOOST_THROW_EXCEPTION(FatalError()); location.end = block->location.end; - return make_shared(location, _docString, dialect, block); + return make_shared(nextID(), location, _docString, dialect, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 2314e59ce..d066b7f36 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -173,6 +173,9 @@ private: bool empty() const; }; + /// Returns the next AST node ID + int64_t nextID() { return ++m_currentNodeID; } + std::pair tryParseIndexAccessedPath(); /// Performs limited look-ahead to distinguish between variable declaration and expression statement. /// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to @@ -198,6 +201,8 @@ private: /// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier. bool m_insideModifier = false; langutil::EVMVersion m_evmVersion; + /// Counter for the next AST node ID + int64_t m_currentNodeID = 0; }; } diff --git a/scripts/ASTImportTest.sh b/scripts/ASTImportTest.sh new file mode 100755 index 000000000..7b34b14b0 --- /dev/null +++ b/scripts/ASTImportTest.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +# Bash script to test the ast-import option of the compiler by +# first exporting a .sol file to JSON, then loading it into the compiler +# and exporting it again. The second JSON should be identical to the first + +REPO_ROOT=$(realpath "$(dirname "$0")"/..) +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} +SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc +SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py + +SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" +NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)" + +# DEV_DIR="${REPO_ROOT}/../tmp/contracts/" +# NSOURCES="$(find $DEV_DIR -type f | wc -l)" #TODO use find command + +FAILED=0 +UNCOMPILABLE=0 +TESTED=0 + +if [ $(ls | wc -l) -ne 0 ]; then + echo "Test directory not empty. Skipping!" + exit -1 +fi + +# function tests whether exporting and importing again leaves the JSON ast unchanged +# Results are recorded by adding to FAILED or UNCOMPILABLE. +# Also, in case of a mismatch a diff and the respective ASTs are printed +# Expected parameters: +# $1 name of the file to be exported and imported +# $2 any files needed to do so that might be in parent directories +function testImportExportEquivalence { + if $SOLC $1 $2 > /dev/null 2>&1 + then + # save exported json as expected result (silently) + $SOLC --combined-json ast,compact-format --pretty-json $1 $2> expected.json 2> /dev/null + # import it, and export it again as obtained result (silently) + $SOLC --import-ast --combined-json ast,compact-format --pretty-json expected.json > obtained.json 2> /dev/null + if [ $? -ne 0 ] + then + # For investigating, use exit 1 here so the script stops at the + # first failing test + # exit 1 + FAILED=$((FAILED + 1)) + return 1 + fi + DIFF="$(diff expected.json obtained.json)" + if [ "$DIFF" != "" ] + then + if [ "$DIFFVIEW" == "" ] + then + echo -e "ERROR: JSONS differ for $1: \n $DIFF \n" + echo "Expected:" + echo "$(cat ./expected.json)" + echo "Obtained:" + echo "$(cat ./obtained.json)" + else + # Use user supplied diff view binary + $DIFFVIEW expected.json obtained.json + fi + FAILED=$((FAILED + 1)) + return 2 + fi + TESTED=$((TESTED + 1)) + rm expected.json obtained.json + else + # echo "contract $solfile could not be compiled " + UNCOMPILABLE=$((UNCOMPILABLE + 1)) + fi + # return 0 +} +echo "Looking at $NSOURCES .sol files..." + +WORKINGDIR=$PWD + +# for solfile in $(find $DEV_DIR -name *.sol) +for solfile in $(find $SYNTAXTESTS_DIR -name *.sol) +do + echo -n "." + # create a temporary sub-directory + FILETMP=$(mktemp -d -p $WORKINGDIR) + cd $FILETMP + + OUTPUT=$($SPLITSOURCES $solfile) + if [ $? != 1 ] + then + # echo $OUTPUT + NSOURCES=$((NSOURCES - 1)) + for i in $OUTPUT; + do + testImportExportEquivalence $i $OUTPUT + NSOURCES=$((NSOURCES + 1)) + done + + else + testImportExportEquivalence $solfile + fi + + cd $WORKINGDIR + # Delete temporary files + rm -rf $FILETMP +done + +echo "" + +if [ "$FAILED" = 0 ] +then + echo "SUCCESS: $TESTED syntaxTests passed, $FAILED failed, $UNCOMPILABLE could not be compiled ($NSOURCES sources total)." +else + echo "FAILURE: Out of $NSOURCES sources, $FAILED failed, ($UNCOMPILABLE could not be compiled)." + exit 1 +fi diff --git a/scripts/splitSources.py b/scripts/splitSources.py new file mode 100755 index 000000000..0f6f1f86d --- /dev/null +++ b/scripts/splitSources.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python2 +# +# This script reads a syntaxTest file and writes all +# sources into their own files. If one source-name specifies subdirectories +# those will be created too. + +# Usage: scripts/splitSources.py pathToTestfile +# as a result prints +# - string of created files separated by whitespaces +# - 'false' if the file only had one source + +import sys +import os + +hasMultipleSources = False +createdSources = [] + +def extractSourceName(line): + if line.find("/") > -1: + filePath = line[13: line.rindex("/")] + # fileName = line[line.rindex("/")+1: line.find(" ====")] + srcName = line[line.find(":")+2: line.find(" ====")] + return filePath, srcName + return False, line[line.find(":")+2 : line.find(" ====")] + +# expects the first line of lines to be "==== Source: sourceName ====" +# writes the following source into a file named sourceName +def writeSourceToFile(lines): + filePath, srcName = extractSourceName(lines[0]) + # print "sourceName is", srcName + # print "filePath is", filePath + if filePath != False: + os.system("mkdir -p " + filePath) + f = open(srcName, 'a+') + createdSources.append(srcName) + i = 0 + for idx, line in enumerate(lines[1:]): + + # write to file + if line[:12] != "==== Source:": + f.write(line) + + # recursive call if there is another source + else: + writeSourceToFile(lines[1+idx:]) + break + +if __name__ == '__main__': + filePath = sys.argv[1] + # decide if file has multiple sources + lines = open(filePath, 'rb').read().splitlines() + if lines[0][:12] == "==== Source:": + hasMultipleSources = True + writeSourceToFile(lines) + + if hasMultipleSources: + srcString = "" + for src in createdSources: + srcString += src + ' ' + print srcString + else: + sys.exit(1) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index af5d45179..de7814785 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,7 @@ static string const g_strEVMVersion = "evm-version"; static string const g_strEwasm = "ewasm"; static string const g_strGas = "gas"; static string const g_strHelp = "help"; +static string const g_strImportAst = "import-ast"; static string const g_strInputFile = "input-file"; static string const g_strInterface = "interface"; static string const g_strYul = "yul"; @@ -184,6 +186,7 @@ static string const g_argCompactJSON = g_strCompactJSON; static string const g_argErrorRecovery = g_strErrorRecovery; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; +static string const g_argImportAst = g_strImportAst; static string const g_argInputFile = g_strInputFile; static string const g_argYul = g_strYul; static string const g_argIR = g_strIR; @@ -623,6 +626,33 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) return true; } +map CommandLineInterface::parseAstFromInput() +{ + map sourceJsons; + map tmpSources; + + for (auto const& srcPair: m_sourceCodes) + { + Json::Value ast; + astAssert(jsonParseStrict(srcPair.second, ast), "Input file could not be parsed to JSON"); + astAssert(ast.isMember("sources"), "Invalid Format for import-JSON: Must have 'sources'-object"); + + for (auto& src: ast["sources"].getMemberNames()) + { + std::string astKey = ast["sources"][src].isMember("ast") ? "ast" : "AST"; + + astAssert(ast["sources"][src].isMember(astKey), "astkey is not member"); + astAssert(ast["sources"][src][astKey]["nodeType"].asString() == "SourceUnit", "Top-level node should be a 'SourceUnit'"); + astAssert(sourceJsons.count(src) == 0, "All sources must have unique names"); + sourceJsons.emplace(src, move(ast["sources"][src][astKey])); + tmpSources[src] = util::jsonCompactPrint(ast); + } + } + + m_sourceCodes = std::move(tmpSources); + return sourceJsons; +} + void CommandLineInterface::createFile(string const& _fileName, string const& _data) { namespace fs = boost::filesystem; @@ -722,6 +752,12 @@ Allowed options)", "Switch to Standard JSON input / output mode, ignoring all options. " "It reads from standard input and provides the result on the standard output." ) + ( + g_argImportAst.c_str(), + "Import ASTs to be compiled, assumes input holds the AST in compact JSON format." + " Supported Inputs is the output of the standard-json or the one produced by --combined-json ast,compact-format" + ) + ( g_argAssemble.c_str(), "Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly." @@ -1065,10 +1101,9 @@ bool CommandLineInterface::processInput() m_compiler->setMetadataHash(m_metadataHash); if (m_args.count(g_argInputFile)) m_compiler->setRemappings(m_remappings); - m_compiler->setSources(m_sourceCodes); + if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); - m_compiler->setParserErrorRecovery(m_args.count(g_argErrorRecovery)); m_compiler->setEVMVersion(m_evmVersion); m_compiler->setRevertStringBehaviour(m_revertStrings); // TODO: Perhaps we should not compile unless requested @@ -1082,6 +1117,32 @@ bool CommandLineInterface::processInput() settings.optimizeStackAllocation = settings.runYulOptimiser; m_compiler->setOptimiserSettings(settings); + if (m_args.count(g_argImportAst)) + { + try + { + m_compiler->importASTs(parseAstFromInput()); + + if (!m_compiler->analyze()) + { + for (auto const& error: m_compiler->errors()) + formatter->printErrorInformation(*error); + astAssert(false, "Analysis of the AST failed"); + } + } + catch (Exception const& _exc) + { + serr() << string("Failed to import AST: ") << _exc.what() << endl; + return false; + } + } + else + { + m_compiler->setSources(m_sourceCodes); + if (m_args.count(g_argErrorRecovery)) + m_compiler->setParserErrorRecovery(true); + } + bool successful = m_compiler->compile(); for (auto const& error: m_compiler->errors()) diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index fd19fd8a7..553f9e69b 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -80,6 +80,12 @@ private: /// It then tries to parse the contents and appends to m_libraries. bool parseLibraryOption(std::string const& _input); + /// Tries to read @ m_sourceCodes as a JSONs holding ASTs + /// such that they can be imported into the compiler (importASTs()) + /// (produced by --combined-json ast,compact-format + /// or standard-json output + std::map parseAstFromInput(); + /// Create a file in the given directory /// @arg _fileName the name of the file /// @arg _data to be written diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 068f02d4c..95e7bb315 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -437,6 +437,19 @@ SOLTMPDIR=$(mktemp -d) fi ) +printTask "Testing AST import..." +SOLTMPDIR=$(mktemp -d) +( + cd "$SOLTMPDIR" + $REPO_ROOT/scripts/ASTImportTest.sh + if [ $? -ne 0 ] + then + rm -rf "$SOLTMPDIR" + exit 1 + fi +) +rm -rf "$SOLTMPDIR" + printTask "Testing soljson via the fuzzer..." SOLTMPDIR=$(mktemp -d) ( diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index fbab520af..fbf2f3e59 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -282,7 +282,7 @@ "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 68, + "referencedDeclaration": -28, "src": "217:4:1", "typeDescriptions": { diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index b5f4cf2ff..c45e75c4a 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -446,7 +446,7 @@ [ null ], - "referencedDeclaration": 68, + "referencedDeclaration": -28, "type": "contract C", "value": "this" }, diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 584c18ffe..c6cd25c56 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -145,7 +145,8 @@ BOOST_AUTO_TEST_CASE(type_identifier_escaping) BOOST_AUTO_TEST_CASE(type_identifiers) { - ASTNode::resetID(); + int64_t id = 0; + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("int128")->identifier(), "t_int128"); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("address")->identifier(), "t_address"); @@ -157,7 +158,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77"); BOOST_CHECK_EQUAL(RationalNumberType(rational(-2 * 200, 2 * 77)).identifier(), "t_rational_minus_200_by_77"); BOOST_CHECK_EQUAL( - StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), + StringLiteralType(Literal(++id, SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4" ); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("byte")->identifier(), "t_bytes1"); @@ -178,14 +179,14 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TypePointer multiArray = TypeProvider::array(DataLocation::Storage, stringArray); BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); - ContractDefinition c(SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, ContractKind::Contract); + ContractDefinition c(++id, SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, ContractKind::Contract); BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$"); BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); - StructDefinition s({}, make_shared("Struct"), {}); + StructDefinition s(++id, {}, make_shared("Struct"), {}); BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$"); - EnumDefinition e({}, make_shared("Enum"), {}); + EnumDefinition e(++id, {}, make_shared("Enum"), {}); BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type$_t_enum$_Enum_$4_$"); TupleType t({e.type(), s.type(), stringArray, nullptr}); @@ -203,11 +204,11 @@ BOOST_AUTO_TEST_CASE(type_identifiers) // TypeType is tested with contract - auto emptyParams = make_shared(SourceLocation(), std::vector>()); - ModifierDefinition mod(SourceLocation{}, make_shared("modif"), {}, emptyParams, {}, {}, {}); + auto emptyParams = make_shared(++id, SourceLocation(), std::vector>()); + ModifierDefinition mod(++id, SourceLocation{}, make_shared("modif"), {}, emptyParams, {}, {}, {}); BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$"); - SourceUnit su({}, {}); + SourceUnit su(++id, {}, {}); BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7"); BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block"); BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message"); From 123ea0a2509c06fe2e5fa13581522324f258ffb5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 19 Dec 2019 17:58:20 +0100 Subject: [PATCH 005/277] Remove asm flavour. --- .../codegen/ir/IRGeneratorForStatements.cpp | 2 +- libyul/AsmAnalysis.cpp | 27 +++------- libyul/AsmAnalysis.h | 2 +- libyul/AsmParser.cpp | 19 ++----- libyul/AsmPrinter.cpp | 2 +- libyul/AsmPrinter.h | 5 +- libyul/CMakeLists.txt | 1 + libyul/CompilabilityChecker.cpp | 5 -- libyul/Dialect.cpp | 53 +++++++++++++++++++ libyul/Dialect.h | 20 +++---- libyul/Object.cpp | 2 +- libyul/backends/evm/EVMDialect.cpp | 9 ++-- libyul/backends/evm/EVMDialect.h | 2 +- libyul/backends/evm/NoOutputAssembly.cpp | 2 +- libyul/backends/wasm/WasmDialect.cpp | 7 ++- libyul/backends/wasm/WordSizeTransform.cpp | 23 ++++---- libyul/backends/wasm/WordSizeTransform.h | 10 +++- test/cmdlineTests/evm_to_wasm/output | 10 ++-- .../standard_eWasm_requested/output.json | 12 ++--- test/libsolidity/InlineAssembly.cpp | 8 +-- .../invalid/empty_fun_arg_beginning.sol | 2 +- .../invalid/empty_fun_arg_end.sol | 2 +- .../invalid/empty_fun_arg_middle.sol | 2 +- .../inlineAssembly/invalid/invalid_number.sol | 2 +- .../invalid/missing_variable_in_assign.sol | 2 +- .../invalid/whitespace_in_assignment.sol | 2 +- .../whitespace_in_multiple_assignment.sol | 2 +- test/libyul/Common.cpp | 23 +++++++- test/libyul/Common.h | 6 +++ test/libyul/Parser.cpp | 18 +++---- test/libyul/YulOptimizerTest.cpp | 17 +++--- 31 files changed, 173 insertions(+), 126 deletions(-) create mode 100644 libyul/Dialect.cpp diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 87782a4a3..214640e5f 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -78,7 +78,7 @@ struct CopyTranslate: public yul::ASTCopier _identifier.location, yul::LiteralKind::Number, yul::YulString{value}, - yul::YulString{"uint256"} + {} }; } } diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 3f82b169f..d47ce115c 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -43,13 +43,6 @@ using namespace solidity::yul; using namespace solidity::util; using namespace solidity::langutil; -namespace -{ - -set const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"}; - -} - bool AsmAnalyzer::analyze(Block const& _block) { bool success = false; @@ -88,7 +81,7 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, bool AsmAnalyzer::operator()(Literal const& _literal) { - expectValidType(_literal.type.str(), _literal.location); + expectValidType(_literal.type, _literal.location); ++m_stackHeight; if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32) { @@ -107,10 +100,7 @@ bool AsmAnalyzer::operator()(Literal const& _literal) return false; } else if (_literal.kind == LiteralKind::Boolean) - { - yulAssert(m_dialect.flavour == AsmFlavour::Yul, ""); yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); - } m_info.stackHeightInfo[&_literal] = m_stackHeight; return true; } @@ -250,7 +240,7 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) for (auto const& variable: _varDecl.variables) { - expectValidType(variable.type.str(), variable.location); + expectValidType(variable.type, variable.location); m_activeVariables.insert(&std::get(m_currentScope->identifiers.at(variable.name))); } m_info.stackHeightInfo[&_varDecl] = m_stackHeight; @@ -265,7 +255,7 @@ bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef) Scope& varScope = scope(virtualBlock); for (auto const& var: _funDef.parameters + _funDef.returnVariables) { - expectValidType(var.type.str(), var.location); + expectValidType(var.type, var.location); m_activeVariables.insert(&std::get(varScope.identifiers.at(var.name))); } @@ -388,7 +378,6 @@ bool AsmAnalyzer::operator()(Switch const& _switch) if (!expectExpression(*_switch.expression)) success = false; - if (m_dialect.flavour == AsmFlavour::Yul) { YulString caseType; bool mismatchingTypes = false; @@ -630,15 +619,12 @@ Scope& AsmAnalyzer::scope(Block const* _block) yulAssert(scopePtr, "Scope requested but not present."); return *scopePtr; } -void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) +void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location) { - if (m_dialect.flavour != AsmFlavour::Yul) - return; - - if (!builtinTypes.count(type)) + if (!_type.empty() && !contains(m_dialect.types, _type)) m_errorReporter.typeError( _location, - "\"" + type + "\" is not a valid type (user defined types are not yet supported)." + "\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)." ); } @@ -658,7 +644,6 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation yulAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); // Similarly we assume bitwise shifting and create2 go together. yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); - yulAssert(m_dialect.flavour != AsmFlavour::Yul, ""); auto errorForVM = [=](string const& vmKindMessage) { m_errorReporter.typeError( diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 9bd9fc1b1..0bc1884df 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -102,7 +102,7 @@ private: bool checkAssignment(Identifier const& _assignment, size_t _valueSize = size_t(-1)); Scope& scope(Block const* _block); - void expectValidType(std::string const& type, langutil::SourceLocation const& _location); + void expectValidType(YulString _type, langutil::SourceLocation const& _location); bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location); bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location); diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 0dd2a633c..3b955b96f 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -369,23 +369,18 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() {} }; advance(); - if (m_dialect.flavour == AsmFlavour::Yul) + if (currentToken() == Token::Colon) { expectToken(Token::Colon); literal.location.end = endPosition(); literal.type = expectAsmIdentifier(); } - else if (kind == LiteralKind::Boolean) - fatalParserError("True and false are not valid literals."); + ret = std::move(literal); break; } default: - fatalParserError( - m_dialect.flavour == AsmFlavour::Yul ? - "Literal or identifier expected." : - "Literal, identifier or instruction expected." - ); + fatalParserError("Literal or identifier expected."); } return ret; } @@ -474,11 +469,7 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) else if (holds_alternative(_initialOp)) ret = std::move(std::get(_initialOp)); else - fatalParserError( - m_dialect.flavour == AsmFlavour::Yul ? - "Function name expected." : - "Assembly instruction or function name required in front of \"(\")" - ); + fatalParserError("Function name expected."); expectToken(Token::LParen); if (currentToken() != Token::RParen) @@ -500,7 +491,7 @@ TypedName Parser::parseTypedName() RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation(); typedName.name = expectAsmIdentifier(); - if (m_dialect.flavour == AsmFlavour::Yul) + if (currentToken() == Token::Colon) { expectToken(Token::Colon); typedName.location.end = endPosition(); diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index eb894f0a1..a62db6123 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -238,7 +238,7 @@ string AsmPrinter::formatTypedName(TypedName _variable) const string AsmPrinter::appendTypeName(YulString _type) const { - if (m_yul && !_type.empty()) + if (!_type.empty()) return ":" + _type.str(); return ""; } diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index 331b502fa..30569922d 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -28,11 +28,12 @@ namespace solidity::yul { +struct Dialect; class AsmPrinter { public: - explicit AsmPrinter(bool _yul = false): m_yul(_yul) {} + explicit AsmPrinter() {} std::string operator()(Literal const& _literal) const; std::string operator()(Identifier const& _identifier) const; @@ -52,8 +53,6 @@ public: private: std::string formatTypedName(TypedName _variable) const; std::string appendTypeName(YulString _type) const; - - bool m_yul = false; }; } diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index ac766d918..416a02455 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(yul AssemblyStack.cpp CompilabilityChecker.cpp CompilabilityChecker.h + Dialect.cpp Dialect.h Exceptions.h Object.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index bb61c7d97..344266a12 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -39,11 +39,6 @@ map CompilabilityChecker::run( bool _optimizeStackAllocation ) { - if (_dialect.flavour == AsmFlavour::Yul) - return {}; - - yulAssert(_dialect.flavour == AsmFlavour::Strict, ""); - if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) { NoOutputEVMDialect noOutputDialect(*evmDialect); diff --git a/libyul/Dialect.cpp b/libyul/Dialect.cpp new file mode 100644 index 000000000..fd9f54d0a --- /dev/null +++ b/libyul/Dialect.cpp @@ -0,0 +1,53 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Yul dialect. + */ + +#include + +using namespace solidity::yul; +using namespace std; + +Dialect const& Dialect::yul() +{ + static unique_ptr dialect; + static YulStringRepository::ResetCallback callback{[&] { dialect.reset(); }}; + + if (!dialect) + { + // TODO will probably change, especially the list of types. + dialect = make_unique(); + dialect->defaultType = "u256"_yulstring; + dialect->boolType = "bool"_yulstring; + dialect->types = { + "bool"_yulstring, + "u8"_yulstring, + "s8"_yulstring, + "u32"_yulstring, + "s32"_yulstring, + "u64"_yulstring, + "s64"_yulstring, + "u128"_yulstring, + "s128"_yulstring, + "u256"_yulstring, + "s256"_yulstring + }; + }; + + return *dialect; +} diff --git a/libyul/Dialect.h b/libyul/Dialect.h index c0a7d7581..b96e1851c 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -34,12 +34,6 @@ namespace solidity::yul class YulString; using Type = YulString; -enum class AsmFlavour -{ - Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations - Yul // same as Strict mode with types -}; - struct BuiltinFunction { YulString name; @@ -54,7 +48,11 @@ struct BuiltinFunction struct Dialect: boost::noncopyable { - AsmFlavour const flavour = AsmFlavour::Strict; + YulString defaultType; + /// Type used for the literals "true" and "false". + YulString boolType; + std::vector types; + /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; } @@ -64,14 +62,10 @@ struct Dialect: boost::noncopyable virtual std::set fixedFunctionNames() const { return {}; } - Dialect(AsmFlavour _flavour): flavour(_flavour) {} + Dialect() = default; virtual ~Dialect() = default; - static Dialect const& yul() - { - static Dialect yulDialect(AsmFlavour::Yul); - return yulDialect; - } + static Dialect const& yul(); }; } diff --git a/libyul/Object.cpp b/libyul/Object.cpp index 4006f6d81..2d4a5e33a 100644 --- a/libyul/Object.cpp +++ b/libyul/Object.cpp @@ -53,7 +53,7 @@ string Data::toString(bool) const string Object::toString(bool _yul) const { yulAssert(code, "No code"); - string inner = "code " + AsmPrinter{_yul}(*code); + string inner = "code " + AsmPrinter{}(*code); for (auto const& obj: subObjects) inner += "\n" + obj->toString(_yul); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 8a7a17b9a..6e2407ced 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -169,8 +169,7 @@ map createBuiltins(langutil::EVMVersion _evmVe } -EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVersion _evmVersion): - Dialect{_flavour}, +EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess): m_objectAccess(_objectAccess), m_evmVersion(_evmVersion), m_functions(createBuiltins(_evmVersion, _objectAccess)) @@ -191,7 +190,7 @@ EVMDialect const& EVMDialect::strictAssemblyForEVM(langutil::EVMVersion _version static map> dialects; static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }}; if (!dialects[_version]) - dialects[_version] = make_unique(AsmFlavour::Strict, false, _version); + dialects[_version] = make_unique(_version, false); return *dialects[_version]; } @@ -200,7 +199,7 @@ EVMDialect const& EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion _ static map> dialects; static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }}; if (!dialects[_version]) - dialects[_version] = make_unique(AsmFlavour::Strict, true, _version); + dialects[_version] = make_unique(_version, true); return *dialects[_version]; } @@ -209,7 +208,7 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version) static map> dialects; static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }}; if (!dialects[_version]) - dialects[_version] = make_unique(AsmFlavour::Yul, false, _version); + dialects[_version] = make_unique(_version, false); return *dialects[_version]; } diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 58b08b486..0d7d8b6d9 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -63,7 +63,7 @@ struct BuiltinFunctionForEVM: BuiltinFunction struct EVMDialect: public Dialect { /// Constructor, should only be used internally. Use the factory functions below. - EVMDialect(AsmFlavour _flavour, bool _objectAccess, langutil::EVMVersion _evmVersion); + EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess); /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. BuiltinFunctionForEVM const* builtin(YulString _name) const override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index b3e4feba4..8d01c2c5e 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -143,7 +143,7 @@ AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&) } NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): - EVMDialect(_copyFrom.flavour, _copyFrom.providesObjectAccess(), _copyFrom.evmVersion()) + EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess()) { for (auto& fun: m_functions) { diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 78f9b045c..896059870 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -23,9 +23,12 @@ using namespace std; using namespace solidity::yul; -WasmDialect::WasmDialect(): - Dialect{AsmFlavour::Strict} +WasmDialect::WasmDialect() { + defaultType = "i64"_yulstring; + boolType = "i64"_yulstring; + types = {"i64"_yulstring, "i32"_yulstring}; + for (auto const& name: { "i64.add", "i64.sub", diff --git a/libyul/backends/wasm/WordSizeTransform.cpp b/libyul/backends/wasm/WordSizeTransform.cpp index 79df8be9a..47d91b7d1 100644 --- a/libyul/backends/wasm/WordSizeTransform.cpp +++ b/libyul/backends/wasm/WordSizeTransform.cpp @@ -97,12 +97,12 @@ void WordSizeTransform::operator()(Block& _block) for (int i = 0; i < 3; i++) ret.push_back(VariableDeclaration{ varDecl.location, - {TypedName{varDecl.location, newLhs[i], "u64"_yulstring}}, - make_unique(Literal{locationOf(*varDecl.value), LiteralKind::Number, "0"_yulstring, "u64"_yulstring}) + {TypedName{varDecl.location, newLhs[i], m_defaultType}}, + make_unique(Literal{locationOf(*varDecl.value), LiteralKind::Number, "0"_yulstring, m_defaultType}) }); ret.push_back(VariableDeclaration{ varDecl.location, - {TypedName{varDecl.location, newLhs[3], "u64"_yulstring}}, + {TypedName{varDecl.location, newLhs[3], m_defaultType}}, std::move(varDecl.value) }); return {std::move(ret)}; @@ -130,7 +130,7 @@ void WordSizeTransform::operator()(Block& _block) ret.push_back( VariableDeclaration{ varDecl.location, - {TypedName{varDecl.location, newLhs[i], "u64"_yulstring}}, + {TypedName{varDecl.location, newLhs[i], m_defaultType}}, std::move(newRhs[i]) } ); @@ -157,7 +157,7 @@ void WordSizeTransform::operator()(Block& _block) ret.push_back(Assignment{ assignment.location, {Identifier{assignment.location, newLhs[i]}}, - make_unique(Literal{locationOf(*assignment.value), LiteralKind::Number, "0"_yulstring, "u64"_yulstring}) + make_unique(Literal{locationOf(*assignment.value), LiteralKind::Number, "0"_yulstring, m_defaultType}) }); ret.push_back(Assignment{ assignment.location, @@ -208,7 +208,8 @@ void WordSizeTransform::run(Dialect const& _inputDialect, Block& _ast, NameDispe { // Free the name `or_bool`. NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast); - WordSizeTransform{_inputDialect, _nameDispenser}(_ast); + YulString defaultType; // should be i64 at some point. + WordSizeTransform{_inputDialect, _nameDispenser, defaultType}(_ast); } void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList) @@ -219,7 +220,7 @@ void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList) { TypedNameList ret; for (auto newName: generateU64IdentifierNames(_n.name)) - ret.emplace_back(TypedName{_n.location, newName, "u64"_yulstring}); + ret.emplace_back(TypedName{_n.location, newName, m_defaultType}); return ret; } ); @@ -283,7 +284,7 @@ vector WordSizeTransform::handleSwitchInternal( for (auto& c: cases) { - Literal label{_location, LiteralKind::Number, YulString(c.first.str()), "u64"_yulstring}; + Literal label{_location, LiteralKind::Number, YulString(c.first.str()), m_defaultType}; ret.cases.emplace_back(Case{ c.second.front().location, make_unique(std::move(label)), @@ -304,7 +305,7 @@ vector WordSizeTransform::handleSwitchInternal( Assignment{ _location, {{_location, _runDefaultFlag}}, - make_unique(Literal{_location, LiteralKind::Number, "1"_yulstring, "u64"_yulstring}) + make_unique(Literal{_location, LiteralKind::Number, "1"_yulstring, m_defaultType}) } )} }); @@ -329,7 +330,7 @@ std::vector WordSizeTransform::handleSwitch(Switch& _switch) _switch.cases.pop_back(); ret.emplace_back(VariableDeclaration{ _switch.location, - {TypedName{_switch.location, runDefaultFlag, "u64"_yulstring}}, + {TypedName{_switch.location, runDefaultFlag, m_defaultType}}, {} }); } @@ -384,7 +385,7 @@ array, 4> WordSizeTransform::expandValue(Expression const lit.location, LiteralKind::Number, YulString(currentVal.str()), - "u64"_yulstring + m_defaultType } ); } diff --git a/libyul/backends/wasm/WordSizeTransform.h b/libyul/backends/wasm/WordSizeTransform.h index 199b01b49..75afa279e 100644 --- a/libyul/backends/wasm/WordSizeTransform.h +++ b/libyul/backends/wasm/WordSizeTransform.h @@ -70,9 +70,14 @@ public: static void run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser); private: - explicit WordSizeTransform(Dialect const& _inputDialect, NameDispenser& _nameDispenser): + explicit WordSizeTransform( + Dialect const& _inputDialect, + NameDispenser& _nameDispenser, + YulString _defaultType + ): m_inputDialect(_inputDialect), - m_nameDispenser(_nameDispenser) + m_nameDispenser(_nameDispenser), + m_defaultType(_defaultType) { } void rewriteVarDeclList(std::vector&); @@ -94,6 +99,7 @@ private: Dialect const& m_inputDialect; NameDispenser& m_nameDispenser; + YulString m_defaultType; /// maps original u256 variable's name to corresponding u64 variables' names std::map> m_variableMapping; }; diff --git a/test/cmdlineTests/evm_to_wasm/output b/test/cmdlineTests/evm_to_wasm/output index a85239b23..1c31f9488 100644 --- a/test/cmdlineTests/evm_to_wasm/output +++ b/test/cmdlineTests/evm_to_wasm/output @@ -15,9 +15,9 @@ object "object" { function main() { let _1 := 0 - mstore_internal(0, _1, _1, _1, _1) + mstore_internal(_1, _1, _1, _1, _1) mstore_internal(32, _1, _1, _1, 1) - eth.storageStore(0, 32) + eth.storageStore(_1, 32) } function endian_swap_16(x) -> y { @@ -45,7 +45,7 @@ object "object" { Binary representation: -0061736d0100000001160460000060017e017e60057e7e7e7e7e0060027f7f0002190108657468657265756d0c73746f7261676553746f7265000303060500010101020503010001060100071102066d656d6f72790200046d61696e00010ab501052801017e420021004200200020002000200010054220200020002000420110054200a74220a710000b1c01017e20004208864280fe0383200042088842ff018384210120010b1b01027e20001002421086210220022000421088100284210120010b1b01027e20001003422086210220022000422088100384210120010b3501007e2000a720011004370300200042087ca720021004370300200042107ca720031004370300200042187ca7200410043703000b +0061736d0100000001160460000060017e017e60057e7e7e7e7e0060027f7f0002190108657468657265756d0c73746f7261676553746f7265000303060500010101020503010001060100071102066d656d6f72790200046d61696e00010ab501052801017e420021002000200020002000200010054220200020002000420110052000a74220a710000b1c01017e20004208864280fe0383200042088842ff018384210120010b1b01027e20001002421086210220022000421088100284210120010b1b01027e20001003422086210220022000422088100384210120010b3501007e2000a720011004370300200042087ca720021004370300200042107ca720031004370300200042187ca7200410043703000b Text representation: (module @@ -56,9 +56,9 @@ Text representation: (func $main (local $_1 i64) (local.set $_1 (i64.const 0)) - (call $mstore_internal (i64.const 0) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) + (call $mstore_internal (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (call $mstore_internal (i64.const 32) (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 1)) - (call $eth.storageStore (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 32))) + (call $eth.storageStore (i32.wrap_i64 (local.get $_1)) (i32.wrap_i64 (i64.const 32))) ) (func $endian_swap_16 diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index a0c6849f1..11e7bb252 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -10,23 +10,21 @@ (local $p i64) (local $r i64) (local $hi i64) - (local $hi_1 i64) (local $y i64) - (local $hi_2 i64) + (local $hi_1 i64) (local $_2 i64) (local.set $_1 (i64.const 0)) (local.set $p (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64))) (local.set $r (i64.add (local.get $p) (i64.const 64))) (if (i64.ne (i64.extend_i32_u (i64.lt_u (local.get $r) (local.get $p))) (i64.const 0)) (then (unreachable))) - (local.set $hi (i64.shl (call $endian_swap_16 (local.get $_1)) (i64.const 16))) - (local.set $hi_1 (i64.shl (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) - (local.set $y (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) + (local.set $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (local.get $_1) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (local.get $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) + (local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) (i64.store (i32.wrap_i64 (local.get $r)) (local.get $y)) (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 8))) (local.get $y)) (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 16))) (local.get $y)) - (local.set $hi_2 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) - (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 24))) (i64.or (local.get $hi_2) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) + (local.set $hi_1 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) + (i64.store (i32.wrap_i64 (i64.add (local.get $r) (i64.const 24))) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) (local.set $_2 (datasize \"C_2_deployed\")) (call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\"))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2)))) (call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2)))) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index c4fa1ab8c..8d55f9a08 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -224,8 +224,8 @@ BOOST_AUTO_TEST_CASE(vardecl_multi_conflict) BOOST_AUTO_TEST_CASE(vardecl_bool) { - CHECK_PARSE_ERROR("{ let x := true }", ParserError, "True and false are not valid literals."); - CHECK_PARSE_ERROR("{ let x := false }", ParserError, "True and false are not valid literals."); + successParse("{ let x := true }"); + successParse("{ let x := false }"); } BOOST_AUTO_TEST_CASE(vardecl_empty) @@ -313,7 +313,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_AUTO_TEST_CASE(switch_invalid_expression) { - CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); + CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal or identifier expected."); CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected '(' but got reserved keyword 'default'"); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", TypeError, "Expected expression to return one item to the stack, but did return 0 items"); } @@ -346,7 +346,7 @@ BOOST_AUTO_TEST_CASE(for_statement) BOOST_AUTO_TEST_CASE(for_invalid_expression) { - CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected."); + CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal or identifier expected."); CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected '{' but got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected '{' but got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected '{' but got 'Number'"); diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol index 9d99c8b4d..114c76afa 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol @@ -8,4 +8,4 @@ contract C { } } // ---- -// ParserError: (101-102): Literal, identifier or instruction expected. +// ParserError: (101-102): Literal or identifier expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol index 0d041492d..a501405b4 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol @@ -8,4 +8,4 @@ contract C { } } // ---- -// ParserError: (103-104): Literal, identifier or instruction expected. +// ParserError: (103-104): Literal or identifier expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol index 09e0ba038..ef9202ac4 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// ParserError: (96-97): Literal, identifier or instruction expected. +// ParserError: (96-97): Literal or identifier expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/invalid_number.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/invalid_number.sol index 739c45bfe..a446097e1 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/invalid_number.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/invalid_number.sol @@ -6,4 +6,4 @@ contract C { } } // ---- -// ParserError: (72-73): Literal, identifier or instruction expected. +// ParserError: (72-73): Literal or identifier expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/missing_variable_in_assign.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/missing_variable_in_assign.sol index ec1cc2baf..d8361268d 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/missing_variable_in_assign.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/missing_variable_in_assign.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// ParserError: (87-89): Literal, identifier or instruction expected. +// ParserError: (87-89): Literal or identifier expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_assignment.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_assignment.sol index 940863276..582733bef 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_assignment.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_assignment.sol @@ -6,4 +6,4 @@ contract C { } } // ---- -// ParserError: (69-70): Literal, identifier or instruction expected. +// ParserError: (71-72): Expected identifier but got '=' diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_multiple_assignment.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_multiple_assignment.sol index 3601c3117..478e14e8f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_multiple_assignment.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/whitespace_in_multiple_assignment.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// ParserError: (107-108): Literal, identifier or instruction expected. +// ParserError: (109-110): Expected identifier but got '=' diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index e50239807..c80b09fa7 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -75,6 +75,27 @@ pair, shared_ptr> yul::test::parse(strin return make_pair(stack.parserResult()->code, stack.parserResult()->analysisInfo); } +pair, shared_ptr> yul::test::parse( + string const& _source, + Dialect const& _dialect, + ErrorList& _errors +) +{ + ErrorReporter errorReporter(_errors); + shared_ptr scanner = make_shared(CharStream(_source, "")); + shared_ptr parserResult = yul::ObjectParser(errorReporter, _dialect).parse(scanner, false); + if (!parserResult) + return {}; + if (!parserResult->code || !errorReporter.errors().empty()) + return {}; + shared_ptr analysisInfo = make_shared(); + AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->dataNames()); + // TODO this should be done recursively. + if (!analyzer.analyze(*parserResult->code) || !errorReporter.errors().empty()) + return {}; + return {std::move(parserResult->code), std::move(analysisInfo)}; +} + yul::Block yul::test::disambiguate(string const& _source, bool _yul) { auto result = parse(_source, _yul); @@ -83,5 +104,5 @@ yul::Block yul::test::disambiguate(string const& _source, bool _yul) string yul::test::format(string const& _source, bool _yul) { - return yul::AsmPrinter(_yul)(*parse(_source, _yul).first); + return yul::AsmPrinter()(*parse(_source, _yul).first); } diff --git a/test/libyul/Common.h b/test/libyul/Common.h index 96de22aa9..0b154e69c 100644 --- a/test/libyul/Common.h +++ b/test/libyul/Common.h @@ -36,14 +36,20 @@ using ErrorList = std::vector>; namespace solidity::yul { struct AsmAnalysisInfo; +struct Dialect; } namespace solidity::yul::test { void printErrors(langutil::ErrorList const& _errors); + std::pair, std::shared_ptr> parse(std::string const& _source, bool _yul = true); + +std::pair, std::shared_ptr> +parse(std::string const& _source, Dialect const& _dialect, langutil::ErrorList& _errors); + Block disambiguate(std::string const& _source, bool _yul = true); std::string format(std::string const& _source, bool _yul = true); diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index 6f25304d8..749ec5ea5 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -161,9 +161,9 @@ BOOST_AUTO_TEST_CASE(period_not_as_identifier_start) BOOST_AUTO_TEST_CASE(period_in_identifier_spaced) { - CHECK_ERROR("{ let x. y:u256 }", ParserError, "Expected ':' but got identifier"); - CHECK_ERROR("{ let x .y:u256 }", ParserError, "Expected ':' but got '.'"); - CHECK_ERROR("{ let x . y:u256 }", ParserError, "Expected ':' but got '.'"); + CHECK_ERROR("{ let x. y:u256 }", ParserError, "Call or assignment expected"); + CHECK_ERROR("{ let x .y:u256 }", ParserError, "Literal or identifier expected"); + CHECK_ERROR("{ let x . y:u256 }", ParserError, "Literal or identifier expected"); } BOOST_AUTO_TEST_CASE(period_in_identifier_start) @@ -234,12 +234,12 @@ BOOST_AUTO_TEST_CASE(tokens_as_identifers) BOOST_CHECK(successParse("{ let bool:u256 := 1:u256 }")); } -BOOST_AUTO_TEST_CASE(lacking_types) +BOOST_AUTO_TEST_CASE(optional_types) { - CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected ':' but got ':='"); - CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected ':' but got '}'"); - CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected ':' but got ')'"); - CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected ':' but got '{'"); + BOOST_CHECK(successParse("{ let x := 1:u256 }")); + BOOST_CHECK(successParse("{ let x:u256 := 1 }")); + BOOST_CHECK(successParse("{ function f(a) {} }")); + BOOST_CHECK(successParse("{ function f(a:u256) -> b {} }")); } BOOST_AUTO_TEST_CASE(invalid_types) @@ -531,7 +531,6 @@ BOOST_AUTO_TEST_CASE(builtins_parser) { struct SimpleDialect: public Dialect { - SimpleDialect(): Dialect(AsmFlavour::Strict) {} BuiltinFunction const* builtin(YulString _name) const override { return _name == "builtin"_yulstring ? &f : nullptr; @@ -551,7 +550,6 @@ BOOST_AUTO_TEST_CASE(builtins_analysis) { struct SimpleDialect: public Dialect { - SimpleDialect(): Dialect(AsmFlavour::Strict) {} BuiltinFunction const* builtin(YulString _name) const override { return _name == "builtin"_yulstring ? &f : nullptr; diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 053b7772e..791551bc6 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -364,7 +365,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line return TestResult::FatalError; } - m_obtainedResult = AsmPrinter{m_dialect->flavour == AsmFlavour::Yul}(*m_ast) + "\n"; + m_obtainedResult = AsmPrinter{}(*m_ast) + "\n"; if (m_optimizerStep != m_validatedSettings["step"]) { @@ -418,19 +419,15 @@ void YulOptimizerTest::printIndented(ostream& _stream, string const& _output, st bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { - AssemblyStack stack( - solidity::test::Options::get().evmVersion(), - m_dialect->flavour == AsmFlavour::Yul ? AssemblyStack::Language::Yul : AssemblyStack::Language::StrictAssembly, - solidity::frontend::OptimiserSettings::none() - ); - if (!stack.parseAndAnalyze("", m_source) || !stack.errors().empty()) + ErrorList errors; + soltestAssert(m_dialect, ""); + std::tie(m_ast, m_analysisInfo) = yul::test::parse(m_source, *m_dialect, errors); + if (!m_ast || !m_analysisInfo || !errors.empty()) { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; - printErrors(_stream, stack.errors()); + printErrors(_stream, errors); return false; } - m_ast = stack.parserResult()->code; - m_analysisInfo = stack.parserResult()->analysisInfo; return true; } From 0f014144c8fd7114768e7bc853664a0173f57ee4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Dec 2019 09:24:20 +0100 Subject: [PATCH 006/277] Test for true/false in inline assembly. --- .../semanticTests/inlineAssembly/truefalse.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/libsolidity/semanticTests/inlineAssembly/truefalse.sol diff --git a/test/libsolidity/semanticTests/inlineAssembly/truefalse.sol b/test/libsolidity/semanticTests/inlineAssembly/truefalse.sol new file mode 100644 index 000000000..c10cdd65a --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/truefalse.sol @@ -0,0 +1,12 @@ +contract C { + function f() public returns (uint x, uint y) { + assembly { + x := true + y := false + } + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 1, 0 From 180344ab17243276e03a45bbb58e0cfbaa159442 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 20 Dec 2019 12:06:40 +0100 Subject: [PATCH 007/277] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 08c790155..c59cf6417 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Language Features: * Allow accessing external functions via contract and interface names to obtain their selector. + * Inline Assembly: Support literals ``true`` and ``false``. Compiler Features: From fe35df4b7823c5e94a80a05b684f8e790ee189ec Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Jan 2020 17:17:20 +0100 Subject: [PATCH 008/277] Style. --- libyul/AsmAnalysis.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index d47ce115c..5a88671ce 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -378,26 +378,25 @@ bool AsmAnalyzer::operator()(Switch const& _switch) if (!expectExpression(*_switch.expression)) success = false; - { - YulString caseType; - bool mismatchingTypes = false; - for (auto const& _case: _switch.cases) - if (_case.value) + YulString caseType; + bool mismatchingTypes = false; + for (auto const& _case: _switch.cases) + if (_case.value) + { + if (caseType.empty()) + caseType = _case.value->type; + else if (caseType != _case.value->type) { - if (caseType.empty()) - caseType = _case.value->type; - else if (caseType != _case.value->type) - { - mismatchingTypes = true; - break; - } + mismatchingTypes = true; + break; } - if (mismatchingTypes) - m_errorReporter.typeError( - _switch.location, - "Switch cases have non-matching types." - ); - } + } + + if (mismatchingTypes) + m_errorReporter.typeError( + _switch.location, + "Switch cases have non-matching types." + ); set cases; for (auto const& _case: _switch.cases) From 21611f6b6fa133882d266eed9db48dece8b3a28d Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 14 Jan 2020 18:42:43 +0100 Subject: [PATCH 009/277] Add test for incorrect evm version when importing an ast tree --- .../ast_json_import_wrong_evmVersion/args | 1 + .../ast_json_import_wrong_evmVersion/err | 1 + .../ast_json_import_wrong_evmVersion/exit | 1 + .../input.sol | 117 ++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 test/cmdlineTests/ast_json_import_wrong_evmVersion/args create mode 100644 test/cmdlineTests/ast_json_import_wrong_evmVersion/err create mode 100644 test/cmdlineTests/ast_json_import_wrong_evmVersion/exit create mode 100644 test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol diff --git a/test/cmdlineTests/ast_json_import_wrong_evmVersion/args b/test/cmdlineTests/ast_json_import_wrong_evmVersion/args new file mode 100644 index 000000000..87444708c --- /dev/null +++ b/test/cmdlineTests/ast_json_import_wrong_evmVersion/args @@ -0,0 +1 @@ +--evm-version=homestead --import-ast --combined-json ast,compact-format --pretty-json diff --git a/test/cmdlineTests/ast_json_import_wrong_evmVersion/err b/test/cmdlineTests/ast_json_import_wrong_evmVersion/err new file mode 100644 index 000000000..1b86e4997 --- /dev/null +++ b/test/cmdlineTests/ast_json_import_wrong_evmVersion/err @@ -0,0 +1 @@ +Failed to import AST: Imported tree evm version differs from configured evm version! diff --git a/test/cmdlineTests/ast_json_import_wrong_evmVersion/exit b/test/cmdlineTests/ast_json_import_wrong_evmVersion/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/ast_json_import_wrong_evmVersion/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol b/test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol new file mode 100644 index 000000000..a8e59cfa4 --- /dev/null +++ b/test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol @@ -0,0 +1,117 @@ +{ + "contracts": + { + "test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol:C": {} + }, + "sourceList": + [ + "test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol" + ], + "sources": + { + "test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol": + { + "AST": + { + "absolutePath": "test/cmdlineTests/ast_json_import_wrong_evmVersion/input.sol", + "exportedSymbols": + { + "C": + [ + 7 + ] + }, + "id": 8, + "nodeType": "SourceUnit", + "nodes": + [ + { + "id": 1, + "literals": + [ + "solidity", + ">=", + "0.0" + ], + "nodeType": "PragmaDirective", + "src": "0:22:0" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 7, + "linearizedBaseContracts": + [ + 7 + ], + "name": "C", + "nodeType": "ContractDefinition", + "nodes": + [ + { + "body": + { + "id": 5, + "nodeType": "Block", + "src": "65:21:0", + "statements": + [ + { + "AST": + { + "nodeType": "YulBlock", + "src": "78:2:0", + "statements": [] + }, + "evmVersion": "istanbul", + "externalReferences": [], + "id": 4, + "nodeType": "InlineAssembly", + "src": "69:11:0" + } + ] + }, + "documentation": null, + "functionSelector": "26121ff0", + "id": 6, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "f", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": + { + "id": 2, + "nodeType": "ParameterList", + "parameters": [], + "src": "50:2:0" + }, + "returnParameters": + { + "id": 3, + "nodeType": "ParameterList", + "parameters": [], + "src": "65:0:0" + }, + "scope": 7, + "src": "40:46:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + } + ], + "scope": 8, + "src": "23:65:0" + } + ], + "src": "0:89:0" + } + } + }, + "version": "0.6.2-develop.2020.1.14+commit.e8556fa1.mod.Linux.g++" +} From 9649c92e12aae22fe371fba34b35599654797f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 14 Jan 2020 18:52:01 -0300 Subject: [PATCH 010/277] Fix error message suggestion for receive --- libsolidity/parsing/Parser.cpp | 2 +- test/libsolidity/syntaxTests/fallback/old_syntax.sol | 2 +- test/libsolidity/syntaxTests/receiveEther/old_syntax.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 406226da4..be1ff115e 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -680,7 +680,7 @@ ASTPointer Parser::parseVariableDeclaration( fatalParserError( "Expected a state variable declaration. If you intended this as a fallback function " "or a function to handle plain ether transactions, use the \"fallback\" keyword " - "or the \"ether\" keyword instead." + "or the \"receive\" keyword instead." ); bool isIndexed = false; diff --git a/test/libsolidity/syntaxTests/fallback/old_syntax.sol b/test/libsolidity/syntaxTests/fallback/old_syntax.sol index 26297063b..88a45756c 100644 --- a/test/libsolidity/syntaxTests/fallback/old_syntax.sol +++ b/test/libsolidity/syntaxTests/fallback/old_syntax.sol @@ -2,4 +2,4 @@ contract C { function() external {} } // ---- -// ParserError: (37-38): Expected a state variable declaration. If you intended this as a fallback function or a function to handle plain ether transactions, use the "fallback" keyword or the "ether" keyword instead. +// ParserError: (37-38): Expected a state variable declaration. If you intended this as a fallback function or a function to handle plain ether transactions, use the "fallback" keyword or the "receive" keyword instead. diff --git a/test/libsolidity/syntaxTests/receiveEther/old_syntax.sol b/test/libsolidity/syntaxTests/receiveEther/old_syntax.sol index 52a2f5f17..ef50608d4 100644 --- a/test/libsolidity/syntaxTests/receiveEther/old_syntax.sol +++ b/test/libsolidity/syntaxTests/receiveEther/old_syntax.sol @@ -2,4 +2,4 @@ contract C { function() external payable {} } // ---- -// ParserError: (45-46): Expected a state variable declaration. If you intended this as a fallback function or a function to handle plain ether transactions, use the "fallback" keyword or the "ether" keyword instead. +// ParserError: (45-46): Expected a state variable declaration. If you intended this as a fallback function or a function to handle plain ether transactions, use the "fallback" keyword or the "receive" keyword instead. From 3760e90582ef46861f7b937380675f83030b4346 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Wed, 15 Jan 2020 14:06:36 +0100 Subject: [PATCH 011/277] [WASM] mstore8 function implementation added. --- libyul/backends/wasm/EVMToEwasmTranslator.cpp | 4 ++-- test/libyul/ewasmTranslationTests/mstore8.yul | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index 168f5fbf4..7f01253c9 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -577,8 +577,8 @@ function mstore_address(pos, a1, a2, a3, a4) { mstore_internal(pos, 0, a1, a2, a3) } function mstore8(x1, x2, x3, x4, y1, y2, y3, y4) { - // TODO implement - unreachable() + let v := u256_to_byte(y1, y2, y3, y4) + i64.store8(to_internal_i32ptr(x1, x2, x3, x4), v) } // Needed? function msize() -> z1, z2, z3, z4 { diff --git a/test/libyul/ewasmTranslationTests/mstore8.yul b/test/libyul/ewasmTranslationTests/mstore8.yul index 12b6a0c0c..78d101800 100644 --- a/test/libyul/ewasmTranslationTests/mstore8.yul +++ b/test/libyul/ewasmTranslationTests/mstore8.yul @@ -1,15 +1,15 @@ { mstore(0x30, not(0)) mstore8(0x20, 0xaa) - mstore8(0x25, 0xbbbb) mstore8(0x26, 0xcc) mstore8(0x3b, 0x11) sstore(0, mload(0x20)) } // ---- // Trace: -// INVALID() // Memory dump: -// 60: 00000000000000000000000000000000ffffffffffffffffffffffffffffffff +// 20: aa0000000000cc000000000000000000ffffffffffffffffffffff11ffffffff +// 60: aa0000000000cc000000000000000000ffffffffffffffffffffff11ffffffff // 80: ffffffffffffffffffffffffffffffff00000000000000000000000000000000 // Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: aa0000000000cc000000000000000000ffffffffffffffffffffff11ffffffff From 1ee4b9dc3b753cb40e981072ac9643df4fc5e11a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 10 Dec 2019 23:50:01 +0000 Subject: [PATCH 012/277] Remove the LLL compiler --- CMakeLists.txt | 7 - cmake/EthOptions.cmake | 1 - liblll/CMakeLists.txt | 14 - liblll/CodeFragment.cpp | 758 -------------------- liblll/CodeFragment.h | 68 -- liblll/Compiler.cpp | 126 ---- liblll/Compiler.h | 40 -- liblll/CompilerState.cpp | 86 --- liblll/CompilerState.h | 54 -- liblll/Exceptions.h | 42 -- liblll/Parser.cpp | 156 ----- liblll/Parser.h | 39 -- lllc/CMakeLists.txt | 19 - lllc/main.cpp | 157 ----- test/CMakeLists.txt | 19 - test/boostTest.cpp | 5 - test/liblll/Compiler.cpp | 670 ------------------ test/liblll/EndToEndTest.cpp | 1026 ---------------------------- test/liblll/ExecutionFramework.cpp | 33 - test/liblll/ExecutionFramework.h | 69 -- test/liblll/LLL_ENS.cpp | 503 -------------- test/liblll/LLL_ERC20.cpp | 652 ------------------ test/liblll/Parser.cpp | 181 ----- 23 files changed, 4725 deletions(-) delete mode 100644 liblll/CMakeLists.txt delete mode 100644 liblll/CodeFragment.cpp delete mode 100644 liblll/CodeFragment.h delete mode 100644 liblll/Compiler.cpp delete mode 100644 liblll/Compiler.h delete mode 100644 liblll/CompilerState.cpp delete mode 100644 liblll/CompilerState.h delete mode 100644 liblll/Exceptions.h delete mode 100644 liblll/Parser.cpp delete mode 100644 liblll/Parser.h delete mode 100644 lllc/CMakeLists.txt delete mode 100644 lllc/main.cpp delete mode 100644 test/liblll/Compiler.cpp delete mode 100644 test/liblll/EndToEndTest.cpp delete mode 100644 test/liblll/ExecutionFramework.cpp delete mode 100644 test/liblll/ExecutionFramework.h delete mode 100644 test/liblll/LLL_ENS.cpp delete mode 100644 test/liblll/LLL_ERC20.cpp delete mode 100644 test/liblll/Parser.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a112deaa..f64c7bd94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,7 @@ if (IS_BIG_ENDIAN) message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.") endif() -option(LLL "Build LLL" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) -option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) -option(INSTALL_LLLC "Include lllc executable in installation" ${LLL}) # Setup cccache. include(EthCcache) @@ -61,10 +58,6 @@ add_subdirectory(libsolc) if (NOT EMSCRIPTEN) add_subdirectory(solc) - if (LLL) - add_subdirectory(liblll) - add_subdirectory(lllc) - endif() endif() if (TESTS AND NOT EMSCRIPTEN) diff --git a/cmake/EthOptions.cmake b/cmake/EthOptions.cmake index 6dca5e1be..cd6fe8133 100644 --- a/cmake/EthOptions.cmake +++ b/cmake/EthOptions.cmake @@ -41,7 +41,6 @@ if (SUPPORT_TOOLS) endif() message("------------------------------------------------------------------ flags") message("-- OSSFUZZ ${OSSFUZZ}") - message("-- LLL ${LLL}") message("------------------------------------------------------------------------") message("") endmacro() diff --git a/liblll/CMakeLists.txt b/liblll/CMakeLists.txt deleted file mode 100644 index a6c8f3a9a..000000000 --- a/liblll/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -set(sources - CodeFragment.cpp - CodeFragment.h - Compiler.cpp - Compiler.h - CompilerState.cpp - CompilerState.h - Exceptions.h - Parser.cpp - Parser.h -) - -add_library(lll ${sources}) -target_link_libraries(lll PUBLIC evmasm solutil) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp deleted file mode 100644 index 556f3dcf6..000000000 --- a/liblll/CodeFragment.cpp +++ /dev/null @@ -1,758 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file CodeFragment.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include -#include -#include -#include - -#include - -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif // defined(__GNUC__) - -#include - -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif // defined(__GNUC__) - - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; -using namespace solidity::lll; - -void CodeFragment::finalise(CompilerState const& _cs) -{ - // NOTE: add this as a safeguard in case the user didn't issue an - // explicit stop at the end of the sequence - m_asm.append(Instruction::STOP); - - if (_cs.usedAlloc && _cs.vars.size() && !m_finalised) - { - m_finalised = true; - m_asm.injectStart(Instruction::MSTORE8); - m_asm.injectStart((u256)((_cs.vars.size() + 2) * 32) - 1); - m_asm.injectStart((u256)1); - } -} - -namespace -{ -/// Returns true iff the instruction is valid in "inline assembly". -bool validAssemblyInstruction(string us) -{ - auto it = c_instructions.find(us); - return !( - it == c_instructions.end() || - isPushInstruction(it->second) - ); -} - -/// Returns true iff the instruction is valid as a function. -bool validFunctionalInstruction(string us) -{ - auto it = c_instructions.find(us); - return !( - it == c_instructions.end() || - isPushInstruction(it->second) || - isDupInstruction(it->second) || - isSwapInstruction(it->second) || - it->second == Instruction::JUMPDEST - ); -} -} - -CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM): - m_readFile(_readFile) -{ -/* - std::cout << "CodeFragment. Locals:"; - for (auto const& i: _s.defs) - std::cout << i.first << ":" << i.second.m_asm.out(); - std::cout << "Args:"; - for (auto const& i: _s.args) - std::cout << i.first << ":" << i.second.m_asm.out(); - std::cout << "Outers:"; - for (auto const& i: _s.outers) - std::cout << i.first << ":" << i.second.m_asm.out(); - debugOutAST(std::cout, _t); - std::cout << endl << flush; -*/ - switch (_t.which()) - { - case sp::utree_type::list_type: - constructOperation(_t, _s); - break; - case sp::utree_type::string_type: - { - auto sr = _t.get, sp::utree_type::string_type>>(); - string s(sr.begin(), sr.end()); - m_asm.append(s); - break; - } - case sp::utree_type::symbol_type: - { - auto sr = _t.get, sp::utree_type::symbol_type>>(); - string s(sr.begin(), sr.end()); - string us = boost::algorithm::to_upper_copy(s); - if (_allowASM && c_instructions.count(us) && validAssemblyInstruction(us)) - m_asm.append(c_instructions.at(us)); - else if (_s.defs.count(s)) - m_asm.append(_s.defs.at(s).m_asm); - else if (_s.args.count(s)) - m_asm.append(_s.args.at(s).m_asm); - else if (_s.outers.count(s)) - m_asm.append(_s.outers.at(s).m_asm); - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) - { - auto it = _s.vars.find(s); - if (it == _s.vars.end()) - error(std::string("Symbol not found: ") + s); - m_asm.append((u256)it->second.first); - } - else - error(s); - - break; - } - case sp::utree_type::any_type: - { - bigint i = *_t.get(); - if (i < 0 || i > bigint(u256(0) - 1)) - error(toString(i)); - m_asm.append((u256)i); - break; - } - default: - error("Unexpected fragment type"); - break; - } -} - -void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) -{ - if (_t.tag() == 0 && _t.empty()) - error(); - else if (_t.tag() == 0 && _t.front().which() != sp::utree_type::symbol_type) - error(); - else - { - string s; - string us; - switch (_t.tag()) - { - case 0: - { - auto sr = _t.front().get, sp::utree_type::symbol_type>>(); - s = string(sr.begin(), sr.end()); - us = boost::algorithm::to_upper_copy(s); - break; - } - case 1: - us = "MLOAD"; - break; - case 2: - us = "SLOAD"; - break; - case 3: - us = "MSTORE"; - break; - case 4: - us = "SSTORE"; - break; - case 5: - us = "SEQ"; - break; - case 6: - us = "CALLDATALOAD"; - break; - default:; - } - - auto firstAsString = [&]() - { - auto i = *++_t.begin(); - if (i.tag()) - error(toString(i)); - if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - return string(sr.begin(), sr.end()); - } - else if (i.which() == sp::utree_type::symbol_type) - { - auto sr = i.get, sp::utree_type::symbol_type>>(); - return _s.getDef(string(sr.begin(), sr.end())).m_asm.backString(); - } - return string(); - }; - - auto varAddress = [&](string const& n, bool createMissing = false) - { - if (n.empty()) - error("Empty variable name not allowed"); - auto it = _s.vars.find(n); - if (it == _s.vars.end()) - { - if (createMissing) - { - // Create new variable - bool ok; - tie(it, ok) = _s.vars.insert(make_pair(n, make_pair(_s.stackSize, 32))); - _s.stackSize += 32; - } - else - error(std::string("Symbol not found: ") + n); - } - return it->second.first; - }; - - // Operations who args are not standard stack-pushers. - bool nonStandard = true; - if (us == "ASM") - { - int c = 0; - for (auto const& i: _t) - if (c++) - { - auto fragment = CodeFragment(i, _s, m_readFile, true).m_asm; - if ((m_asm.deposit() + fragment.deposit()) < 0) - error("The assembly instruction resulted in stack underflow"); - m_asm.append(fragment); - } - } - else if (us == "INCLUDE") - { - if (_t.size() != 2) - error(us); - string fileName = firstAsString(); - if (fileName.empty()) - error("Empty file name provided"); - if (!m_readFile) - error("Import callback not present"); - string contents = m_readFile(fileName); - if (contents.empty()) - error(std::string("File not found (or empty): ") + fileName); - m_asm.append(CodeFragment::compile(std::move(contents), _s, m_readFile).m_asm); - } - else if (us == "SET") - { - // TODO: move this to be a stack variable (and not a memory variable) - if (_t.size() != 3) - error(us); - int c = 0; - for (auto const& i: _t) - if (c++ == 2) - m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); - m_asm.append((u256)varAddress(firstAsString(), true)); - m_asm.append(Instruction::MSTORE); - } - else if (us == "UNSET") - { - // TODO: this doesn't actually free up anything, since it is a memory variable (see "SET") - if (_t.size() != 2) - error(); - auto it = _s.vars.find(firstAsString()); - if (it != _s.vars.end()) - _s.vars.erase(it); - } - else if (us == "GET") - { - if (_t.size() != 2) - error(us); - m_asm.append((u256)varAddress(firstAsString())); - m_asm.append(Instruction::MLOAD); - } - else if (us == "WITH") - { - if (_t.size() != 4) - error(); - string key = firstAsString(); - if (_s.vars.find(key) != _s.vars.end()) - error(string("Symbol already used: ") + key); - - // Create variable - // TODO: move this to be a stack variable (and not a memory variable) - size_t c = 0; - for (auto const& i: _t) - if (c++ == 2) - m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); - m_asm.append((u256)varAddress(key, true)); - m_asm.append(Instruction::MSTORE); - - // Insert sub with variable access, but new state - CompilerState ns = _s; - c = 0; - for (auto const& i: _t) - if (c++ == 3) - m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); - - // Remove variable - auto it = _s.vars.find(key); - if (it != _s.vars.end()) - _s.vars.erase(it); - } - else if (us == "REF") - m_asm.append((u256)varAddress(firstAsString())); - else if (us == "DEF") - { - string n; - unsigned ii = 0; - if (_t.size() != 3 && _t.size() != 4) - error(us); - vector args; - for (auto const& i: _t) - { - if (ii == 1) - { - if (i.tag()) - error(toString(i)); - if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - n = string(sr.begin(), sr.end()); - } - else if (i.which() == sp::utree_type::symbol_type) - { - auto sr = i.get, sp::utree_type::symbol_type>>(); - n = _s.getDef(string(sr.begin(), sr.end())).m_asm.backString(); - } - } - else if (ii == 2) - if (_t.size() == 3) - { - /// NOTE: some compilers could do the assignment first if this is done in a single line - CodeFragment code = CodeFragment(i, _s, m_readFile); - _s.defs[n] = code; - } - else - for (auto const& j: i) - { - if (j.tag() || j.which() != sp::utree_type::symbol_type) - error(); - auto sr = j.get, sp::utree_type::symbol_type>>(); - args.emplace_back(sr.begin(), sr.end()); - } - else if (ii == 3) - { - auto k = make_pair(n, args.size()); - _s.macros[k].code = i; - _s.macros[k].env = _s.outers; - _s.macros[k].args = args; - for (auto const& i: _s.args) - _s.macros[k].env[i.first] = i.second; - for (auto const& i: _s.defs) - _s.macros[k].env[i.first] = i.second; - } - ++ii; - } - } - else if (us == "LIT") - { - if (_t.size() < 3) - error(us); - unsigned ii = 0; - CodeFragment pos; - bytes data; - for (auto const& i: _t) - { - if (ii == 0) - { - ii++; - continue; - } - else if (ii == 1) - { - pos = CodeFragment(i, _s, m_readFile); - if (pos.m_asm.deposit() != 1) - error(toString(i)); - } - else if (i.tag() != 0) - { - error(toString(i)); - } - else if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - data.insert(data.end(), (uint8_t const *)sr.begin(), (uint8_t const*)sr.end()); - } - else if (i.which() == sp::utree_type::any_type) - { - bigint bi = *i.get(); - if (bi < 0) - error(toString(i)); - else - { - bytes tmp = toCompactBigEndian(bi); - data.insert(data.end(), tmp.begin(), tmp.end()); - } - } - else - { - error(toString(i)); - } - - ii++; - } - m_asm.append((u256)data.size()); - m_asm.append(Instruction::DUP1); - m_asm.append(data); - m_asm.append(pos.m_asm, 1); - m_asm.append(Instruction::CODECOPY); - } - else - nonStandard = false; - - if (nonStandard) - return; - - std::map const c_arith = { - { "+", Instruction::ADD }, - { "-", Instruction::SUB }, - { "*", Instruction::MUL }, - { "/", Instruction::DIV }, - { "%", Instruction::MOD }, - { "&", Instruction::AND }, - { "|", Instruction::OR }, - { "^", Instruction::XOR } - }; - std::map> const c_binary = { - { "<", { Instruction::LT, false } }, - { "<=", { Instruction::GT, true } }, - { ">", { Instruction::GT, false } }, - { ">=", { Instruction::LT, true } }, - { "S<", { Instruction::SLT, false } }, - { "S<=", { Instruction::SGT, true } }, - { "S>", { Instruction::SGT, false } }, - { "S>=", { Instruction::SLT, true } }, - { "=", { Instruction::EQ, false } }, - { "!=", { Instruction::EQ, true } } - }; - std::map const c_unary = { - { "!", Instruction::ISZERO }, - { "~", Instruction::NOT } - }; - - vector code; - CompilerState ns = _s; - ns.vars.clear(); - ns.usedAlloc = false; - int c = _t.tag() ? 1 : 0; - for (auto const& i: _t) - if (c++) - { - if (us == "LLL" && c == 1) - code.emplace_back(i, ns, m_readFile); - else - code.emplace_back(i, _s, m_readFile); - } - auto requireSize = [&](unsigned s) { if (code.size() != s) error(us); }; - auto requireMinSize = [&](unsigned s) { if (code.size() < s) error(us); }; - auto requireMaxSize = [&](unsigned s) { if (code.size() > s) error(us); }; - auto requireDeposit = [&](unsigned i, int s) { if (code[i].m_asm.deposit() != s) error(us); }; - - if (_s.macros.count(make_pair(s, code.size()))) - { - Macro const& m = _s.macros.at(make_pair(s, code.size())); - CompilerState cs = _s; - for (auto const& i: m.env) - cs.outers[i.first] = i.second; - for (auto const& i: cs.defs) - cs.outers[i.first] = i.second; - cs.defs.clear(); - for (unsigned i = 0; i < m.args.size(); ++i) - { - //requireDeposit(i, 1); - cs.args[m.args[i]] = code[i]; - } - m_asm.append(CodeFragment(m.code, cs, m_readFile).m_asm); - for (auto const& i: cs.defs) - _s.defs[i.first] = i.second; - for (auto const& i: cs.macros) - _s.macros.insert(i); - } - else if (c_instructions.count(us) && validFunctionalInstruction(us)) - { - auto it = c_instructions.find(us); - requireSize(instructionInfo(it->second).args); - - for (unsigned i = code.size(); i; --i) - m_asm.append(code[i - 1].m_asm, 1); - m_asm.append(it->second); - } - else if (c_arith.count(us)) - { - auto it = c_arith.find(us); - requireMinSize(1); - for (unsigned i = code.size(); i; --i) - { - requireDeposit(i - 1, 1); - m_asm.append(code[i - 1].m_asm, 1); - } - for (unsigned i = 1; i < code.size(); ++i) - m_asm.append(it->second); - } - else if (c_binary.count(us)) - { - auto it = c_binary.find(us); - requireSize(2); - requireDeposit(0, 1); - requireDeposit(1, 1); - m_asm.append(code[1].m_asm, 1); - m_asm.append(code[0].m_asm, 1); - m_asm.append(it->second.first); - if (it->second.second) - m_asm.append(Instruction::ISZERO); - } - else if (c_unary.count(us)) - { - auto it = c_unary.find(us); - requireSize(1); - requireDeposit(0, 1); - m_asm.append(code[0].m_asm, 1); - m_asm.append(it->second); - } - else if (us == "IF") - { - requireSize(3); - requireDeposit(0, 1); - int minDep = min(code[1].m_asm.deposit(), code[2].m_asm.deposit()); - - m_asm.append(code[0].m_asm); - auto mainBranch = m_asm.appendJumpI(); - - /// The else branch. - int startDeposit = m_asm.deposit(); - m_asm.append(code[2].m_asm, minDep); - auto end = m_asm.appendJump(); - int deposit = m_asm.deposit(); - m_asm.setDeposit(startDeposit); - - /// The main branch. - m_asm << mainBranch.tag(); - m_asm.append(code[1].m_asm, minDep); - m_asm << end.tag(); - if (m_asm.deposit() != deposit) - error(us); - } - else if (us == "WHEN" || us == "UNLESS") - { - requireSize(2); - requireDeposit(0, 1); - - m_asm.append(code[0].m_asm); - if (us == "WHEN") - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[1].m_asm, 0); - m_asm << end.tag(); - } - else if (us == "WHILE" || us == "UNTIL") - { - requireSize(2); - requireDeposit(0, 1); - - auto begin = m_asm.append(m_asm.newTag()); - m_asm.append(code[0].m_asm); - if (us == "WHILE") - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[1].m_asm, 0); - m_asm.appendJump(begin); - m_asm << end.tag(); - } - else if (us == "FOR") - { - requireSize(4); - requireDeposit(1, 1); - - m_asm.append(code[0].m_asm, 0); - auto begin = m_asm.append(m_asm.newTag()); - m_asm.append(code[1].m_asm); - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[3].m_asm, 0); - m_asm.append(code[2].m_asm, 0); - m_asm.appendJump(begin); - m_asm << end.tag(); - } - else if (us == "SWITCH") - { - requireMinSize(1); - - bool hasDefault = (code.size() % 2 == 1); - int startDeposit = m_asm.deposit(); - int targetDeposit = hasDefault ? code[code.size() - 1].m_asm.deposit() : 0; - - // The conditions - evmasm::AssemblyItems jumpTags; - for (unsigned i = 0; i < code.size() - 1; i += 2) - { - requireDeposit(i, 1); - m_asm.append(code[i].m_asm); - jumpTags.push_back(m_asm.appendJumpI()); - } - - // The default, if present - if (hasDefault) - m_asm.append(code[code.size() - 1].m_asm); - - // The targets - appending in reverse makes the top case the most efficient. - if (code.size() > 1) - { - auto end = m_asm.appendJump(); - for (int i = 2 * (code.size() / 2 - 1); i >= 0; i -= 2) - { - m_asm << jumpTags[i / 2].tag(); - requireDeposit(i + 1, targetDeposit); - m_asm.append(code[i + 1].m_asm); - if (i != 0) - m_asm.appendJump(end); - } - m_asm << end.tag(); - } - - m_asm.setDeposit(startDeposit + targetDeposit); - } - else if (us == "ALLOC") - { - requireSize(1); - requireDeposit(0, 1); - - // (alloc N): - // - Evaluates to (msize) before the allocation - the start of the allocated memory - // - Does not allocate memory when N is zero - // - Size of memory allocated is N bytes rounded up to a multiple of 32 - // - Uses MLOAD to expand MSIZE to avoid modifying memory. - - auto end = m_asm.newTag(); - m_asm.append(Instruction::MSIZE); // Result will be original top of memory - m_asm.append(code[0].m_asm, 1); // The alloc argument N - m_asm.append(Instruction::DUP1); - m_asm.append(Instruction::ISZERO);// (alloc 0) does not change MSIZE - m_asm.appendJumpI(end); - m_asm.append(u256(1)); - m_asm.append(Instruction::DUP2); // Copy N - m_asm.append(Instruction::SUB); // N-1 - m_asm.append(u256(0x1f)); // Bit mask - m_asm.append(Instruction::NOT); // Invert - m_asm.append(Instruction::AND); // Align N-1 on 32 byte boundary - m_asm.append(Instruction::MSIZE); // MSIZE is cheap - m_asm.append(Instruction::ADD); - m_asm.append(Instruction::MLOAD); // Updates MSIZE - m_asm.append(Instruction::POP); // Discard the result of the MLOAD - m_asm.append(end); - m_asm.append(Instruction::POP); // Discard duplicate N - - _s.usedAlloc = true; - } - else if (us == "LLL") - { - requireMinSize(2); - requireMaxSize(3); - requireDeposit(1, 1); - - auto subPush = m_asm.appendSubroutine(make_shared(code[0].assembly(ns))); - m_asm.append(Instruction::DUP1); - if (code.size() == 3) - { - requireDeposit(2, 1); - m_asm.append(code[2].m_asm, 1); - m_asm.append(Instruction::LT); - m_asm.append(Instruction::ISZERO); - m_asm.append(Instruction::MUL); - m_asm.append(Instruction::DUP1); - } - m_asm.append(subPush); - m_asm.append(code[1].m_asm, 1); - m_asm.append(Instruction::CODECOPY); - } - else if (us == "&&" || us == "||") - { - requireMinSize(1); - for (unsigned i = 0; i < code.size(); ++i) - requireDeposit(i, 1); - - auto end = m_asm.newTag(); - if (code.size() > 1) - { - m_asm.append((u256)(us == "||" ? 1 : 0)); - for (unsigned i = 1; i < code.size(); ++i) - { - // Check if true - predicate - m_asm.append(code[i - 1].m_asm, 1); - if (us == "&&") - m_asm.append(Instruction::ISZERO); - m_asm.appendJumpI(end); - } - m_asm.append(Instruction::POP); - } - - // Check if true - predicate - m_asm.append(code.back().m_asm, 1); - - // At end now. - m_asm.append(end); - } - else if (us == "SEQ") - { - unsigned ii = 0; - for (auto const& i: code) - if (++ii < code.size()) - m_asm.append(i.m_asm, 0); - else - m_asm.append(i.m_asm); - } - else if (us == "RAW") - { - for (auto const& i: code) - m_asm.append(i.m_asm); - // Leave only the last item on stack. - while (m_asm.deposit() > 1) - m_asm.append(Instruction::POP); - } - else if (us == "BYTECODESIZE") - { - m_asm.appendProgramSize(); - } - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) - m_asm.append((u256)varAddress(s)); - else - error("Unsupported keyword: '" + us + "'"); - } -} - -CodeFragment CodeFragment::compile(string _src, CompilerState& _s, ReadCallback const& _readFile) -{ - CodeFragment ret; - sp::utree o; - parseTreeLLL(std::move(_src), o); - if (!o.empty()) - ret = CodeFragment(o, _s, _readFile); - _s.treesToKill.push_back(o); - return ret; -} diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h deleted file mode 100644 index 894c91821..000000000 --- a/liblll/CodeFragment.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file CodeFragment.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace boost { namespace spirit { class utree; } } -namespace sp = boost::spirit; - -namespace solidity::lll -{ - -struct CompilerState; - -class CodeFragment -{ -public: - using ReadCallback = std::function; - - CodeFragment() = default; - CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM = false); - - static CodeFragment compile(std::string _src, CompilerState& _s, ReadCallback const& _readFile); - - /// Consolidates data and compiles code. - evmasm::Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; } - -private: - void finalise(CompilerState const& _cs); - - template static void error() { BOOST_THROW_EXCEPTION(T() ); } - template static void error(std::string const& reason) { - auto err = T(); - err << util::errinfo_comment(reason); - BOOST_THROW_EXCEPTION(err); - } - void constructOperation(sp::utree const& _t, CompilerState& _s); - - bool m_finalised = false; - evmasm::Assembly m_asm; - ReadCallback m_readFile; -}; - -static CodeFragment const NullCodeFragment; - -} diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp deleted file mode 100644 index a1ccf47d6..000000000 --- a/liblll/Compiler.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file Compiler.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::lll; - -bytes solidity::lll::compileLLL(string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) -{ - try - { - CompilerState cs; - cs.populateStandard(); - auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); - if (_opt) - assembly = assembly.optimise(true, _evmVersion, true, 200); - bytes ret = assembly.assemble().bytecode; - for (auto i: cs.treesToKill) - killBigints(i); - return ret; - } - catch (Exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse error."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (std::exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse exception."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (...) - { - if (_errors) - _errors->emplace_back("Internal compiler exception."); - } - return bytes(); -} - -std::string solidity::lll::compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) -{ - try - { - CompilerState cs; - cs.populateStandard(); - auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); - if (_opt) - assembly = assembly.optimise(true, _evmVersion, true, 200); - string ret = assembly.assemblyString(); - for (auto i: cs.treesToKill) - killBigints(i); - return ret; - } - catch (Exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse error."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (std::exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse exception."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (...) - { - if (_errors) - _errors->emplace_back("Internal compiler exception."); - } - return string(); -} - -string solidity::lll::parseLLL(string _src) -{ - sp::utree o; - - try - { - parseTreeLLL(std::move(_src), o); - } - catch (...) - { - killBigints(o); - return string(); - } - - ostringstream ret; - debugOutAST(ret, o); - killBigints(o); - return ret.str(); -} diff --git a/liblll/Compiler.h b/liblll/Compiler.h deleted file mode 100644 index 8d2989ce5..000000000 --- a/liblll/Compiler.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file Compiler.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include - -#include - -#include -#include - -namespace solidity::lll -{ - -using ReadCallback = std::function; - -std::string parseLLL(std::string _src); -std::string compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); -bytes compileLLL(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); - -} diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp deleted file mode 100644 index b35763ca5..000000000 --- a/liblll/CompilerState.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file CompilerState.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::lll; - -CompilerState::CompilerState() -{ -} - -CodeFragment const& CompilerState::getDef(std::string const& _s) const -{ - if (defs.count(_s)) - return defs.at(_s); - else if (args.count(_s)) - return args.at(_s); - else if (outers.count(_s)) - return outers.at(_s); - else - return NullCodeFragment; -} - -void CompilerState::populateStandard() -{ - static string const s = "{" - "(def 'panic () (asm INVALID))" - // Alternative macro version of alloc, which is currently implemented in the parser - // "(def 'alloc (n) (raw (msize) (when n (pop (mload (+ (msize) (& (- n 1) (~ 0x1f))))))))" - "(def 'allgas (- (gas) 21))" - "(def 'send (to value) (call allgas to value 0 0 0 0))" - "(def 'send (gaslimit to value) (call gaslimit to value 0 0 0 0))" - // NOTE: in this macro, memory location 0 is set in order to force msize to be at least 32 bytes. - "(def 'msg (gaslimit to value data datasize outsize) { [0]:0 [0]:(msize) (call gaslimit to value data datasize @0 outsize) @0 })" - "(def 'msg (gaslimit to value data datasize) { (call gaslimit to value data datasize 0 32) @0 })" - "(def 'msg (gaslimit to value data) { [0]:data (msg gaslimit to value 0 32) })" - "(def 'msg (to value data) { [0]:data (msg allgas to value 0 32) })" - "(def 'msg (to data) { [0]:data (msg allgas to 0 0 32) })" - // NOTE: in the create macros, memory location 0 is set in order to force msize to be at least 32 bytes. - "(def 'create (value code) { [0]:0 [0]:(msize) (create value @0 (lll code @0)) })" - "(def 'create (code) { [0]:0 [0]:(msize) (create 0 @0 (lll code @0)) })" - "(def 'sha3 (loc len) (keccak256 loc len))" - "(def 'sha3 (val) { [0]:val (sha3 0 32) })" - "(def 'sha3pair (a b) { [0]:a [32]:b (sha3 0 64) })" - "(def 'sha3trip (a b c) { [0]:a [32]:b [64]:c (sha3 0 96) })" - "(def 'return (val) { [0]:val (return 0 32) })" - "(def 'returnlll (code) (return 0 (lll code 0)) )" - "(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )" - "(def 'permcount 0)" - "(def 'perm (name) { (makeperm name permcount) (def 'permcount (+ permcount 1)) } )" - "(def 'ecrecover (hash v r s) { [0] hash [32] v [64] r [96] s (msg allgas 1 0 0 128) })" - "(def 'sha256 (data datasize) (msg allgas 2 0 data datasize))" - "(def 'ripemd160 (data datasize) (msg allgas 3 0 data datasize))" - "(def 'sha256 (val) { [0]:val (sha256 0 32) })" - "(def 'ripemd160 (val) { [0]:val (ripemd160 0 32) })" - "(def 'wei 1)" - "(def 'szabo 1000000000000)" - "(def 'finney 1000000000000000)" - "(def 'ether 1000000000000000000)" - // these could be replaced by native instructions once supported by EVM - "(def 'shl (val shift) (mul val (exp 2 shift)))" - "(def 'shr (val shift) (div val (exp 2 shift)))" - "}"; - CodeFragment::compile(s, *this, CodeFragment::ReadCallback()); -} diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h deleted file mode 100644 index 2313272b1..000000000 --- a/liblll/CompilerState.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file CompilerState.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include - -namespace solidity::lll -{ - -struct Macro -{ - std::vector args; - boost::spirit::utree code; - std::map env; -}; - -struct CompilerState -{ - CompilerState(); - - CodeFragment const& getDef(std::string const& _s) const; - void populateStandard(); - - unsigned stackSize = 128; - std::map> vars; ///< maps name to stack offset & size. - std::map defs; - std::map args; - std::map outers; - std::map, Macro> macros; - std::vector treesToKill; - bool usedAlloc = false; -}; - -} diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h deleted file mode 100644 index 57fbf6ad3..000000000 --- a/liblll/Exceptions.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file Exceptions.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include - -namespace solidity::lll -{ - -/// Compile a Low-level Lisp-like Language program into EVM-code. -class CompilerException: public util::Exception {}; -class InvalidOperation: public CompilerException {}; -class IntegerOutOfRange: public CompilerException {}; -class EmptyList: public CompilerException {}; -class DataNotExecutable: public CompilerException {}; -class IncorrectParameterCount: public CompilerException {}; -class InvalidName: public CompilerException {}; -class InvalidMacroArgs: public CompilerException {}; -class InvalidLiteral: public CompilerException {}; -class BareSymbol: public CompilerException {}; -class ParserException: public CompilerException {}; - -} diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp deleted file mode 100644 index 0f8ff1053..000000000 --- a/liblll/Parser.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file Parser.cpp - * @author Gav Wood - * @date 2014 - */ - -#include - -#if _MSC_VER -#pragma warning(disable:4348) -#endif - -#define BOOST_RESULT_OF_USE_DECLTYPE -#define BOOST_SPIRIT_USE_PHOENIX_V3 -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::lll; -namespace qi = boost::spirit::qi; -namespace px = boost::phoenix; -namespace sp = boost::spirit; - -void solidity::lll::killBigints(sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: for (auto const& i: _this) killBigints(i); break; - case sp::utree_type::any_type: delete _this.get(); break; - default:; - } -} - -void solidity::lll::debugOutAST(ostream& _out, sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: - switch (_this.tag()) - { - case 0: _out << "( "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << ")"; break; - case 1: _out << "@ "; debugOutAST(_out, _this.front()); break; - case 2: _out << "@@ "; debugOutAST(_out, _this.front()); break; - case 3: _out << "[ "; debugOutAST(_out, _this.front()); _out << " ] "; debugOutAST(_out, _this.back()); break; - case 4: _out << "[[ "; debugOutAST(_out, _this.front()); _out << " ]] "; debugOutAST(_out, _this.back()); break; - case 5: _out << "{ "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << "}"; break; - case 6: _out << "$ "; debugOutAST(_out, _this.front()); break; - default:; - } - - break; - case sp::utree_type::int_type: _out << _this.get(); break; - case sp::utree_type::string_type: _out << "\"" << _this.get, sp::utree_type::string_type>>() << "\""; break; - case sp::utree_type::symbol_type: _out << _this.get, sp::utree_type::symbol_type>>(); break; - case sp::utree_type::any_type: _out << *_this.get(); break; - default: _out << "nil"; - } -} - -namespace solidity { -namespace lll { -namespace parseTreeLLL_ { - -template -struct tagNode -{ - void operator()(sp::utree& n, qi::rule::context_type& c) const - { - (boost::fusion::at_c<0>(c.attributes) = n).tag(N); - } -}; - -}}} - -void solidity::lll::parseTreeLLL(string const& _s, sp::utree& o_out) -{ - using qi::standard::space; - using qi::standard::space_type; - using solidity::lll::parseTreeLLL_::tagNode; - using symbol_type = sp::basic_string; - using it = string::const_iterator; - - qi::rule element; - qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; - qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; - qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; - qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> +qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr[qi::_val = px::construct(px::new_(qi::_1))]; - qi::rule atom = integer[qi::_val = qi::_1] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule seq = '{' > *element > '}'; - qi::rule mload = '@' > element; - qi::rule sload = qi::lit("@@") > element; - qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; - qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; - qi::rule calldataload = qi::lit("$") > element; - qi::rule list = '(' > *element > ')'; - - qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()]; - element = atom | list | extra; - - string s; - s.reserve(_s.size()); - bool incomment = false; - bool instring = false; - bool insstring = false; - for (auto i: _s) - { - if (i == ';' && !instring && !insstring) - incomment = true; - else if (i == '\n') - incomment = instring = insstring = false; - else if (i == '"' && !insstring) - instring = !instring; - else if (i == '\'') - insstring = true; - else if (i == ' ') - insstring = false; - if (!incomment) - s.push_back(i); - } - auto ret = s.cbegin(); - try - { - qi::phrase_parse(ret, s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); - } - catch (qi::expectation_failure const& e) - { - std::string fragment(e.first, e.last); - std::string loc = to_string(std::distance(s.cbegin(), e.first) - 1); - std::string reason("Lexer failure at " + loc + ": '" + fragment + "'"); - BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment(reason)); - } - for (auto i = ret; i != s.cend(); ++i) - if (!isspace(*i)) - { - BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment("Non-whitespace left in parser")); - } -} diff --git a/liblll/Parser.h b/liblll/Parser.h deleted file mode 100644 index 424356e0c..000000000 --- a/liblll/Parser.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file Parser.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace boost { namespace spirit { class utree; } } -namespace sp = boost::spirit; - -namespace solidity::lll -{ - -void killBigints(sp::utree const& _this); -void parseTreeLLL(std::string const& _s, sp::utree& o_out); -void debugOutAST(std::ostream& _out, sp::utree const& _this); - -} diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt deleted file mode 100644 index 4e78207e2..000000000 --- a/lllc/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -add_executable(lllc main.cpp) -target_link_libraries(lllc PRIVATE lll Boost::boost Boost::system) - -if (INSTALL_LLLC) - include(GNUInstallDirs) - install(TARGETS lllc DESTINATION ${CMAKE_INSTALL_BINDIR}) - - if(LLLC_LINK_STATIC AND UNIX AND NOT APPLE) - # Produce lllc as statically linked binary (includes C/C++ standard libraries) - # This is not supported on macOS, see - # https://developer.apple.com/library/content/qa/qa1118/_index.html. - set_target_properties( - lllc PROPERTIES - LINK_FLAGS -static - LINK_SEARCH_START_STATIC ON - LINK_SEARCH_END_STATIC ON - ) - endif() -endif() diff --git a/lllc/main.cpp b/lllc/main.cpp deleted file mode 100644 index 75d193e23..000000000 --- a/lllc/main.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** @file main.cpp - * @author Gav Wood - * @date 2014 - * Ethereum client. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::lll; - -static string const VersionString = - string(ETH_PROJECT_VERSION) + - (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + - (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); - -static void help() -{ - cout - << "Usage lllc [OPTIONS] " << endl - << "Options:" << endl - << " -b,--binary Parse, compile and assemble; output byte code in binary." << endl - << " -x,--hex Parse, compile and assemble; output byte code in hex." << endl - << " -a,--assembly Only parse and compile; show assembly." << endl - << " -t,--parse-tree Only parse; show parse tree." << endl - << " -o,--optimise Turn on/off the optimiser; off by default." << endl - << " -d,--disassemble Disassemble input into an opcode stream." << endl - << " -h,--help Show this help message and exit." << endl - << " -V,--version Show the version and exit." << endl; - exit(0); -} - -static void version() -{ - cout << "LLLC, the Lovely Little Language Compiler" << endl; - cout << "Version: " << VersionString << endl; - exit(0); -} - -/* -The equivalent of setlocale(LC_ALL, "C") is called before any user code is run. -If the user has an invalid environment setting then it is possible for the call -to set locale to fail, so there are only two possible actions, the first is to -throw a runtime exception and cause the program to quit (default behaviour), -or the second is to modify the environment to something sensible (least -surprising behaviour). - -The follow code produces the least surprising behaviour. It will use the user -specified default locale if it is valid, and if not then it will modify the -environment the process is running in to use a sensible default. This also means -that users do not need to install language packs for their OS. -*/ -static void setDefaultOrCLocale() -{ -#if __unix__ - if (!std::setlocale(LC_ALL, "")) - { - setenv("LC_ALL", "C", 1); - } -#endif -} - -enum Mode { Binary, Hex, Assembly, ParseTree, Disassemble }; - -int main(int argc, char** argv) -{ - setDefaultOrCLocale(); - unsigned optimise = 0; - string infile; - Mode mode = Hex; - - for (int i = 1; i < argc; ++i) - { - string arg = argv[i]; - if (arg == "-h" || arg == "--help") - help(); - else if (arg == "-b" || arg == "--binary") - mode = Binary; - else if (arg == "-x" || arg == "--hex") - mode = Hex; - else if (arg == "-a" || arg == "--assembly") - mode = Assembly; - else if (arg == "-t" || arg == "--parse-tree") - mode = ParseTree; - else if (arg == "-o" || arg == "--optimise") - optimise = 1; - else if (arg == "-d" || arg == "--disassemble") - mode = Disassemble; - else if (arg == "-V" || arg == "--version") - version(); - else - infile = argv[i]; - } - - string src; - if (infile.empty()) - src = readStandardInput(); - else - src = readFileAsString(infile); - - vector errors; - if (src.empty()) - { - errors.push_back("Empty file."); - } - else if (mode == Disassemble) - { - cout << evmasm::disassemble(fromHex(src)) << endl; - } - else if (mode == Binary || mode == Hex) - { - auto bs = compileLLL(std::move(src), langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString); - if (mode == Hex) - cout << toHex(bs) << endl; - else if (mode == Binary) - cout.write((char const*)bs.data(), bs.size()); - } - else if (mode == ParseTree) - { - cout << parseLLL(std::move(src)) << endl; - } - else if (mode == Assembly) - { - cout << compileLLLToAsm(std::move(src), langutil::EVMVersion{}, optimise ? true : false, &errors, readFileAsString) << endl; - } - - for (auto const& i: errors) - cerr << i << endl; - if (errors.size()) - return 1; - return 0; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4ce4439e8..a6b753087 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,19 +51,6 @@ set(liblangutil_sources ) detect_stray_source_files("${liblangutil_sources}" "liblangutil/") -if(LLL) - set (liblll_sources - liblll/Compiler.cpp - liblll/EndToEndTest.cpp - liblll/ExecutionFramework.cpp - liblll/ExecutionFramework.h - liblll/LLL_ENS.cpp - liblll/LLL_ERC20.cpp - liblll/Parser.cpp - ) - detect_stray_source_files("${liblll_sources}" "liblll/") -endif(LLL) - set(libsolidity_sources libsolidity/ABIDecoderTests.cpp libsolidity/ABIEncoderTests.cpp @@ -155,7 +142,6 @@ add_executable(soltest ${sources} ${liblangutil_sources} ${libevmasm_sources} ${libyul_sources} - ${liblll_sources} ${libsolidity_sources} ${libsolidity_util_sources} ) @@ -170,11 +156,6 @@ if (MSVC) target_compile_options(soltest PUBLIC "/bigobj") endif() -if (LLL) - target_link_libraries(soltest PRIVATE lll) - target_compile_definitions(soltest PRIVATE HAVE_LLL=1) -endif() - if (NOT Boost_USE_STATIC_LIBS) target_compile_definitions(soltest PUBLIC -DBOOST_TEST_DYN_LINK) endif() diff --git a/test/boostTest.cpp b/test/boostTest.cpp index edecdd0de..47b700381 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -164,11 +164,6 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) "SolidityAuctionRegistrar", "SolidityFixedFeeRegistrar", "SolidityWallet", -#if HAVE_LLL - "LLLERC20", - "LLLENS", - "LLLEndToEndTest", -#endif "GasMeterTests", "GasCostTests", "SolidityEndToEndTest", diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp deleted file mode 100644 index 3c60e7ee9..000000000 --- a/test/liblll/Compiler.cpp +++ /dev/null @@ -1,670 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * @date 2017 - * Unit tests for the LLL compiler. - */ - -#include - -#include - -#include - -#include - -#include -#include - -using namespace std; -using namespace solidity::util; - -namespace solidity::lll::test -{ - -namespace -{ - -bool successCompile(string const& _sourceCode) -{ - vector errors; - bytes bytecode = lll::compileLLL(_sourceCode, solidity::test::Options::get().evmVersion(), false, &errors); - if (!errors.empty()) - return false; - if (bytecode.empty()) - return false; - return true; -} - -} - -BOOST_AUTO_TEST_SUITE(LLLCompiler) - -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* sourceCode = "1"; - BOOST_CHECK(successCompile(sourceCode)); -} - -BOOST_AUTO_TEST_CASE(switch_valid) -{ - char const* sourceCode = R"( - (switch (origin)) - )"; - BOOST_CHECK(successCompile(sourceCode)); - sourceCode = R"( - (switch - 1 (panic) - 2 (panic)) - )"; - BOOST_CHECK(successCompile(sourceCode)); - sourceCode = R"( - (switch - 1 (panic) - 2 (panic) - (panic)) - )"; - BOOST_CHECK(successCompile(sourceCode)); - sourceCode = R"( - (switch - 1 (origin) - 2 (origin) - (origin)) - )"; - BOOST_CHECK(successCompile(sourceCode)); -} - -BOOST_AUTO_TEST_CASE(switch_invalid_arg_count) -{ - char const* sourceCode = R"( - (switch) - )"; - BOOST_CHECK(!successCompile(sourceCode)); -} - -BOOST_AUTO_TEST_CASE(switch_inconsistent_return_count) -{ - // cannot return stack items if the default case is not present - char const* sourceCode = R"( - (switch - 1 (origin) - 2 (origin) - )"; - BOOST_CHECK(!successCompile(sourceCode)); - // return count mismatch - sourceCode = R"( - (switch - 1 (origin) - 2 (origin) - (panic)) - )"; - BOOST_CHECK(!successCompile(sourceCode)); - // return count mismatch - sourceCode = R"( - (switch - 1 (panic) - 2 (panic) - (origin)) - )"; - BOOST_CHECK(!successCompile(sourceCode)); -} - -BOOST_AUTO_TEST_CASE(disallowed_asm_instructions) -{ - for (unsigned i = 1; i <= 32; i++) - BOOST_CHECK(!successCompile("(asm PUSH" + to_string(i) + ")")); -} - -BOOST_AUTO_TEST_CASE(disallowed_functional_asm_instructions) -{ - for (unsigned i = 1; i <= 32; i++) - BOOST_CHECK(!successCompile("(PUSH" + to_string(i) + ")")); - for (unsigned i = 1; i <= 16; i++) - BOOST_CHECK(!successCompile("(DUP" + to_string(i) + ")")); - for (unsigned i = 1; i <= 16; i++) - BOOST_CHECK(!successCompile("(SWAP" + to_string(i) + ")")); - BOOST_CHECK(!successCompile("(JUMPDEST)")); -} - -BOOST_AUTO_TEST_CASE(valid_opcodes_functional) -{ - vector opcodes_bytecode { - "0000", - "600060000100", - "600060000200", - "600060000300", - "600060000400", - "600060000500", - "600060000600", - "600060000700", - "6000600060000800", - "6000600060000900", - "600060000a00", - "600060000b00", - "600060001000", - "600060001100", - "600060001200", - "600060001300", - "600060001400", - "60001500", - "600060001600", - "600060001700", - "600060001800", - "60001900", - "600060001a00", - "600060002000", - "3000", - "60003100", - "3200", - "3300", - "3400", - "60003500", - "3600", - "6000600060003700", - "3800", - "6000600060003900", - "3a00", - "60003b00", - "60006000600060003c00", - "3d00", - "6000600060003e00", - "60003f00", - "60004000", - "4100", - "4200", - "4300", - "4400", - "4500", - "4600", - "4700", - "60005000", - "60005100", - "600060005200", - "600060005300", - "60005400", - "600060005500", - "60005600", - "600060005700", - "5800", - "5900", - "5a00", - "60ff00", - "61ffff00", - "62ffffff00", - "63ffffffff00", - "64ffffffffff00", - "65ffffffffffff00", - "66ffffffffffffff00", - "67ffffffffffffffff00", - "68ffffffffffffffffff00", - "69ffffffffffffffffffff00", - "6affffffffffffffffffffff00", - "6bffffffffffffffffffffffff00", - "6cffffffffffffffffffffffffff00", - "6dffffffffffffffffffffffffffff00", - "6effffffffffffffffffffffffffffff00", - "6fffffffffffffffffffffffffffffffff00", - "70ffffffffffffffffffffffffffffffffff00", - "71ffffffffffffffffffffffffffffffffffff00", - "72ffffffffffffffffffffffffffffffffffffff00", - "73ffffffffffffffffffffffffffffffffffffffff00", - "74ffffffffffffffffffffffffffffffffffffffffff00", - "75ffffffffffffffffffffffffffffffffffffffffffff00", - "76ffffffffffffffffffffffffffffffffffffffffffffff00", - "77ffffffffffffffffffffffffffffffffffffffffffffffff00", - "78ffffffffffffffffffffffffffffffffffffffffffffffffff00", - "79ffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7affffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "60006000a000", - "600060006000a100", - "6000600060006000a200", - "60006000600060006000a300", - "600060006000600060006000a400", - "600060006000f000", - "6000600060006000600060006000f100", - "6000600060006000600060006000f200", - "60006000f300", - "600060006000600060006000f400", - "600060006000600060006000fa00", - "60006000fd00", - "fe00", - "6000ff00" - }; - - vector opcodes_lll { - "(STOP)", - "(ADD 0 0)", - "(MUL 0 0)", - "(SUB 0 0)", - "(DIV 0 0)", - "(SDIV 0 0)", - "(MOD 0 0)", - "(SMOD 0 0)", - "(ADDMOD 0 0 0)", - "(MULMOD 0 0 0)", - "(EXP 0 0)", - "(SIGNEXTEND 0 0)", - "(LT 0 0)", - "(GT 0 0)", - "(SLT 0 0)", - "(SGT 0 0)", - "(EQ 0 0)", - "(ISZERO 0)", - "(AND 0 0)", - "(OR 0 0)", - "(XOR 0 0)", - "(NOT 0)", - "(BYTE 0 0)", - "(KECCAK256 0 0)", - "(ADDRESS)", - "(BALANCE 0)", - "(ORIGIN)", - "(CALLER)", - "(CALLVALUE)", - "(CALLDATALOAD 0)", - "(CALLDATASIZE)", - "(CALLDATACOPY 0 0 0)", - "(CODESIZE)", - "(CODECOPY 0 0 0)", - "(GASPRICE)", - "(EXTCODESIZE 0)", - "(EXTCODECOPY 0 0 0 0)", - "(RETURNDATASIZE)", - "(RETURNDATACOPY 0 0 0)", - "(EXTCODEHASH 0)", - "(BLOCKHASH 0)", - "(COINBASE)", - "(TIMESTAMP)", - "(NUMBER)", - "(DIFFICULTY)", - "(GASLIMIT)", - "(CHAINID)", - "(SELFBALANCE)", - "(POP 0)", - "(MLOAD 0)", - "(MSTORE 0 0)", - "(MSTORE8 0 0)", - "(SLOAD 0)", - "(SSTORE 0 0)", - "(JUMP 0)", - "(JUMPI 0 0)", - "(PC)", - "(MSIZE)", - "(GAS)", - "0xff", - "0xffff", - "0xffffff", - "0xffffffff", - "0xffffffffff", - "0xffffffffffff", - "0xffffffffffffff", - "0xffffffffffffffff", - "0xffffffffffffffffff", - "0xffffffffffffffffffff", - "0xffffffffffffffffffffff", - "0xffffffffffffffffffffffff", - "0xffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "(LOG0 0 0)", - "(LOG1 0 0 0)", - "(LOG2 0 0 0 0)", - "(LOG3 0 0 0 0 0)", - "(LOG4 0 0 0 0 0 0)", - "(CREATE 0 0 0)", - "(CALL 0 0 0 0 0 0 0)", - "(CALLCODE 0 0 0 0 0 0 0)", - "(RETURN 0 0)", - "(DELEGATECALL 0 0 0 0 0 0)", - "(STATICCALL 0 0 0 0 0 0)", - "(REVERT 0 0)", - "(INVALID)", - "(SELFDESTRUCT 0)" - }; - - for (size_t i = 0; i < opcodes_bytecode.size(); i++) - { - vector errors; - bytes code = lll::compileLLL(opcodes_lll[i], solidity::test::Options::get().evmVersion(), false, &errors); - - BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); - - BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); - } -} - -BOOST_AUTO_TEST_CASE(valid_opcodes_asm) -{ - vector opcodes_bytecode { - "0000", - "600060000100", - "600060000200", - "600060000300", - "600060000400", - "600060000500", - "600060000600", - "600060000700", - "6000600060000800", - "6000600060000900", - "600060000a00", - "600060000b00", - "600060001000", - "600060001100", - "600060001200", - "600060001300", - "600060001400", - "60001500", - "600060001600", - "600060001700", - "600060001800", - "60001900", - "600060001a00", - "600060002000", - "3000", - "60003100", - "3200", - "3300", - "3400", - "60003500", - "3600", - "6000600060003700", - "3800", - "6000600060003900", - "3a00", - "60003b00", - "60006000600060003c00", - "3d00", - "6000600060003e00", - "60003f00", - "4000", - "4100", - "4200", - "4300", - "4400", - "4500", - "4600", - "4700", - "60005000", - "60005100", - "600060005200", - "600060005300", - "60005400", - "600060005500", - "60005600", - "600060005700", - "5800", - "5900", - "5a00", - "5b00", - "60ff00", - "61ffff00", - "62ffffff00", - "63ffffffff00", - "64ffffffffff00", - "65ffffffffffff00", - "66ffffffffffffff00", - "67ffffffffffffffff00", - "68ffffffffffffffffff00", - "69ffffffffffffffffffff00", - "6affffffffffffffffffffff00", - "6bffffffffffffffffffffffff00", - "6cffffffffffffffffffffffffff00", - "6dffffffffffffffffffffffffffff00", - "6effffffffffffffffffffffffffffff00", - "6fffffffffffffffffffffffffffffffff00", - "70ffffffffffffffffffffffffffffffffff00", - "71ffffffffffffffffffffffffffffffffffff00", - "72ffffffffffffffffffffffffffffffffffffff00", - "73ffffffffffffffffffffffffffffffffffffffff00", - "74ffffffffffffffffffffffffffffffffffffffffff00", - "75ffffffffffffffffffffffffffffffffffffffffffff00", - "76ffffffffffffffffffffffffffffffffffffffffffffff00", - "77ffffffffffffffffffffffffffffffffffffffffffffffff00", - "78ffffffffffffffffffffffffffffffffffffffffffffffffff00", - "79ffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7affffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00", - "60006000600060006000600060006000600060006000600060006000600060008000", - "60006000600060006000600060006000600060006000600060006000600060008100", - "60006000600060006000600060006000600060006000600060006000600060008200", - "60006000600060006000600060006000600060006000600060006000600060008300", - "60006000600060006000600060006000600060006000600060006000600060008400", - "60006000600060006000600060006000600060006000600060006000600060008500", - "60006000600060006000600060006000600060006000600060006000600060008600", - "60006000600060006000600060006000600060006000600060006000600060008700", - "60006000600060006000600060006000600060006000600060006000600060008800", - "60006000600060006000600060006000600060006000600060006000600060008900", - "60006000600060006000600060006000600060006000600060006000600060008a00", - "60006000600060006000600060006000600060006000600060006000600060008b00", - "60006000600060006000600060006000600060006000600060006000600060008c00", - "60006000600060006000600060006000600060006000600060006000600060008d00", - "60006000600060006000600060006000600060006000600060006000600060008e00", - "60006000600060006000600060006000600060006000600060006000600060008f00", - "60006000600060006000600060006000600060006000600060006000600060009000", - "60006000600060006000600060006000600060006000600060006000600060009100", - "60006000600060006000600060006000600060006000600060006000600060009200", - "60006000600060006000600060006000600060006000600060006000600060009300", - "60006000600060006000600060006000600060006000600060006000600060009400", - "60006000600060006000600060006000600060006000600060006000600060009500", - "60006000600060006000600060006000600060006000600060006000600060009600", - "60006000600060006000600060006000600060006000600060006000600060009700", - "60006000600060006000600060006000600060006000600060006000600060009800", - "60006000600060006000600060006000600060006000600060006000600060009900", - "60006000600060006000600060006000600060006000600060006000600060009a00", - "60006000600060006000600060006000600060006000600060006000600060009b00", - "60006000600060006000600060006000600060006000600060006000600060009c00", - "60006000600060006000600060006000600060006000600060006000600060009d00", - "60006000600060006000600060006000600060006000600060006000600060009e00", - "60006000600060006000600060006000600060006000600060006000600060009f00", - "60006000a000", - "600060006000a100", - "6000600060006000a200", - "60006000600060006000a300", - "600060006000600060006000a400", - "600060006000f000", - "600060006000600060006000f100", - "600060006000600060006000f200", - "60006000f300", - "60006000600060006000f400", - "60006000600060006000fa00", - "60006000fd00", - "fe00", - "6000ff00" - }; - - vector opcodes_lll { - "(asm STOP)", - "(asm 0 0 ADD)", - "(asm 0 0 MUL)", - "(asm 0 0 SUB)", - "(asm 0 0 DIV)", - "(asm 0 0 SDIV)", - "(asm 0 0 MOD)", - "(asm 0 0 SMOD)", - "(asm 0 0 0 ADDMOD)", - "(asm 0 0 0 MULMOD)", - "(asm 0 0 EXP)", - "(asm 0 0 SIGNEXTEND)", - "(asm 0 0 LT)", - "(asm 0 0 GT)", - "(asm 0 0 SLT)", - "(asm 0 0 SGT)", - "(asm 0 0 EQ)", - "(asm 0 ISZERO)", - "(asm 0 0 AND)", - "(asm 0 0 OR)", - "(asm 0 0 XOR)", - "(asm 0 NOT)", - "(asm 0 0 BYTE)", - "(asm 0 0 KECCAK256)", - "(asm ADDRESS)", - "(asm 0 BALANCE)", - "(asm ORIGIN)", - "(asm CALLER)", - "(asm CALLVALUE)", - "(asm 0 CALLDATALOAD)", - "(asm CALLDATASIZE)", - "(asm 0 0 0 CALLDATACOPY)", - "(asm CODESIZE)", - "(asm 0 0 0 CODECOPY)", - "(asm GASPRICE)", - "(asm 0 EXTCODESIZE)", - "(asm 0 0 0 0 EXTCODECOPY)", - "(asm RETURNDATASIZE)", - "(asm 0 0 0 RETURNDATACOPY)", - "(asm 0 EXTCODEHASH)", - "(asm BLOCKHASH)", - "(asm COINBASE)", - "(asm TIMESTAMP)", - "(asm NUMBER)", - "(asm DIFFICULTY)", - "(asm GASLIMIT)", - "(asm CHAINID)", - "(asm SELFBALANCE)", - "(asm 0 POP)", - "(asm 0 MLOAD)", - "(asm 0 0 MSTORE)", - "(asm 0 0 MSTORE8)", - "(asm 0 SLOAD)", - "(asm 0 0 SSTORE)", - "(asm 0 JUMP)", - "(asm 0 0 JUMPI)", - "(asm PC)", - "(asm MSIZE)", - "(asm GAS)", - "(asm JUMPDEST)", - "(asm 0xff)", - "(asm 0xffff)", - "(asm 0xffffff)", - "(asm 0xffffffff)", - "(asm 0xffffffffff)", - "(asm 0xffffffffffff)", - "(asm 0xffffffffffffff)", - "(asm 0xffffffffffffffff)", - "(asm 0xffffffffffffffffff)", - "(asm 0xffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP1)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP2)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP3)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP4)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP5)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP6)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP7)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP8)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP9)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP10)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP11)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP12)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP13)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP14)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP15)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DUP16)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP1)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP2)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP3)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP4)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP5)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP6)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP7)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP8)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP9)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP10)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP11)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP12)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP13)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP14)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP15)", - "(asm 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SWAP16)", - "(asm 0 0 LOG0)", - "(asm 0 0 0 LOG1)", - "(asm 0 0 0 0 LOG2)", - "(asm 0 0 0 0 0 LOG3)", - "(asm 0 0 0 0 0 0 LOG4)", - "(asm 0 0 0 CREATE)", - "(asm 0 0 0 0 0 0 CALL)", - "(asm 0 0 0 0 0 0 CALLCODE)", - "(asm 0 0 RETURN)", - "(asm 0 0 0 0 0 DELEGATECALL)", - "(asm 0 0 0 0 0 STATICCALL)", - "(asm 0 0 REVERT)", - "(asm INVALID)", - "(asm 0 SELFDESTRUCT)" - }; - - for (size_t i = 0; i < opcodes_bytecode.size(); i++) - { - vector errors; - bytes code = lll::compileLLL(opcodes_lll[i], solidity::test::Options::get().evmVersion(), false, &errors); - - BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); - - BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); - } -} - -BOOST_AUTO_TEST_SUITE_END() - -} // end namespaces diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp deleted file mode 100644 index cadfae757..000000000 --- a/test/liblll/EndToEndTest.cpp +++ /dev/null @@ -1,1026 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * @date 2016 - * End to end tests for LLL. - */ - -#include -#include - -#include - -#include -#include - -using namespace std; -using namespace solidity::util; -using namespace solidity::test; - -namespace solidity::lll::test -{ - -BOOST_FIXTURE_TEST_SUITE(LLLEndToEndTest, LLLExecutionFramework) - -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* sourceCode = "(returnlll { (return \"test\") })"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(string("test", 4))); -} - -BOOST_AUTO_TEST_CASE(bare_panic) -{ - char const* sourceCode = "(panic)"; - compileAndRunWithoutCheck(sourceCode); - BOOST_REQUIRE(!m_transactionSuccessful); - BOOST_REQUIRE(m_output.empty()); -} - -BOOST_AUTO_TEST_CASE(panic) -{ - char const* sourceCode = "{ (panic) }"; - compileAndRunWithoutCheck(sourceCode); - BOOST_REQUIRE(!m_transactionSuccessful); - BOOST_REQUIRE(m_output.empty()); -} - -BOOST_AUTO_TEST_CASE(macro_zeroarg) -{ - char const* sourceCode = R"( - (returnlll - (seq - (def 'zeroarg () (seq (mstore 0 0x1234) (return 0 32))) - (zeroarg))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(m_transactionSuccessful); - BOOST_CHECK(callFallback() == encodeArgs(u256(0x1234))); -} - -BOOST_AUTO_TEST_CASE(macros) -{ - char const* sourceCode = R"( - (returnlll - (seq - (def 'x 1) - (def 'y () { (def 'x (+ x 2)) }) - (y) - (return x))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(3))); -} - -BOOST_AUTO_TEST_CASE(variables) -{ - char const* sourceCode = R"( - (returnlll - (seq - (set 'x 1) - (set 'y 2) - ;; this should equal to 3 - (set 'z (add (get 'x) (get 'y))) - ;; overwriting it here - (set 'y 4) - ;; each variable has a 32 byte slot, starting from memory location 0x80 - ;; variable addresses can also be retrieved by x or (ref 'x) - (set 'k (add (add (ref 'x) (ref 'y)) z)) - (return (add (add (get 'x) (add (get 'y) (get 'z))) (get 'k))))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(488))); -} - -BOOST_AUTO_TEST_CASE(with) -{ - char const* sourceCode = R"( - (returnlll - (seq - (set 'x 11) - (with 'y 22 { [0]:(+ (get 'x) (get 'y)) }) - (return 0 32))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == toBigEndian(u256(33))); -} - -BOOST_AUTO_TEST_CASE(when) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= (calldatasize) 0) (return 1)) - (when (!= (calldatasize) 0) (return 2)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2))); - BOOST_CHECK(callFallback() == toBigEndian(u256(1))); -} - -BOOST_AUTO_TEST_CASE(unless) -{ - char const* sourceCode = R"( - (returnlll - (seq - (unless (!= (calldatasize) 0) (return 1)) - (unless (= (calldatasize) 0) (return 2)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2))); - BOOST_CHECK(callFallback() == toBigEndian(u256(1))); -} - -BOOST_AUTO_TEST_CASE(conditional_literal) -{ - char const* sourceCode = R"( - (returnlll - (seq - (return (if (= (calldatasize) 0) 1 2)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2))); - BOOST_CHECK(callFallback() == toBigEndian(u256(1))); -} - -BOOST_AUTO_TEST_CASE(conditional) -{ - char const* sourceCode = R"( - (returnlll - (seq - (if (= (calldatasize) 0) (return 1) (return 2)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2))); - BOOST_CHECK(callFallback() == toBigEndian(u256(1))); -} - -BOOST_AUTO_TEST_CASE(conditional_seq) -{ - char const* sourceCode = R"( - (returnlll - (seq - (return (if (= (calldatasize) 0) { 0 2 1 } { 0 1 2 })))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2))); - BOOST_CHECK(callFallback() == toBigEndian(u256(1))); -} - -BOOST_AUTO_TEST_CASE(conditional_nested_else) -{ - char const* sourceCode = R"( - (returnlll - (seq - (def 'input (calldataload 0x04)) - ;; Calculates width in bytes of utf-8 characters. - (return - (if (< input 0x80) 1 - (if (< input 0xE0) 2 - (if (< input 0xF0) 3 - (if (< input 0xF8) 4 - (if (< input 0xFC) 5 - 6)))))))) - - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0x00) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("test()", 0x80) == encodeArgs(u256(2))); - BOOST_CHECK(callContractFunction("test()", 0xe0) == encodeArgs(u256(3))); - BOOST_CHECK(callContractFunction("test()", 0xf0) == encodeArgs(u256(4))); - BOOST_CHECK(callContractFunction("test()", 0xf8) == encodeArgs(u256(5))); - BOOST_CHECK(callContractFunction("test()", 0xfc) == encodeArgs(u256(6))); -} - -BOOST_AUTO_TEST_CASE(conditional_nested_then) -{ - char const* sourceCode = R"( - (returnlll - (seq - (def 'input (calldataload 0x04)) - ;; Calculates width in bytes of utf-8 characters. - (return - (if (>= input 0x80) - (if (>= input 0xE0) - (if (>= input 0xF0) - (if (>= input 0xF8) - (if (>= input 0xFC) - 6 5) 4) 3) 2) 1)))) - - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0x00) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("test()", 0x80) == encodeArgs(u256(2))); - BOOST_CHECK(callContractFunction("test()", 0xe0) == encodeArgs(u256(3))); - BOOST_CHECK(callContractFunction("test()", 0xf0) == encodeArgs(u256(4))); - BOOST_CHECK(callContractFunction("test()", 0xf8) == encodeArgs(u256(5))); - BOOST_CHECK(callContractFunction("test()", 0xfc) == encodeArgs(u256(6))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch) -{ - char const* sourceCode = R"( - (returnlll - (seq - (def 'input (calldataload 0x04)) - ;; Calculates width in bytes of utf-8 characters. - (return - (switch - (< input 0x80) 1 - (< input 0xE0) 2 - (< input 0xF0) 3 - (< input 0xF8) 4 - (< input 0xFC) 5 - 6)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0x00) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("test()", 0x80) == encodeArgs(u256(2))); - BOOST_CHECK(callContractFunction("test()", 0xe0) == encodeArgs(u256(3))); - BOOST_CHECK(callContractFunction("test()", 0xf0) == encodeArgs(u256(4))); - BOOST_CHECK(callContractFunction("test()", 0xf8) == encodeArgs(u256(5))); - BOOST_CHECK(callContractFunction("test()", 0xfc) == encodeArgs(u256(6))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch_one_arg_with_deposit) -{ - char const* sourceCode = R"( - (returnlll - (return - (switch 42))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch_one_arg_no_deposit) -{ - char const* sourceCode = R"( - (returnlll - (seq - (switch [0]:42) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch_two_args) -{ - char const* sourceCode = R"( - (returnlll - (seq - (switch (= (calldataload 0x04) 1) [0]:42) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch_three_args_with_deposit) -{ - char const* sourceCode = R"( - (returnlll - (return - (switch (= (calldataload 0x04) 1) 41 42))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(42))); - BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(41))); -} - -BOOST_AUTO_TEST_CASE(conditional_switch_three_args_no_deposit) -{ - char const* sourceCode = R"( - (returnlll - (switch - (= (calldataload 0x04) 1) (return 41) - (return 42))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(42))); - BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(41))); -} - -BOOST_AUTO_TEST_CASE(exp_operator_const) -{ - char const* sourceCode = R"( - (returnlll - (return (exp 2 3))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == toBigEndian(u256(8))); -} - -BOOST_AUTO_TEST_CASE(exp_operator_const_signed) -{ - char const* sourceCode = R"( - (returnlll - (return (exp (- 0 2) 3))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == toBigEndian(u256(-8))); -} - -BOOST_AUTO_TEST_CASE(exp_operator_on_range) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= (div (calldataload 0x00) (exp 2 224)) 0xb3de648b) - (return (exp 2 (calldataload 0x04)))) - (jump 0x02))) - )"; - compileAndRun(sourceCode); - testContractAgainstCppOnRange("f(uint256)", [](u256 const& a) -> u256 { return u256(1 << a.convert_to()); }, 0, 16); -} - -BOOST_AUTO_TEST_CASE(constructor_argument_internal_numeric) -{ - char const* sourceCode = R"( - (seq - (sstore 0x00 65535) - (returnlll - (return @@0x00))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(65535))); -} - -BOOST_AUTO_TEST_CASE(constructor_argument_internal_string) -{ - char const* sourceCode = R"( - (seq - (sstore 0x00 "test") - (returnlll - (return @@0x00))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs("test")); -} - -BOOST_AUTO_TEST_CASE(constructor_arguments_external) -{ - char const* sourceCode = R"( - (seq - (codecopy 0x00 (bytecodesize) 64) - (sstore 0x00 @0x00) - (sstore 0x01 @0x20) - (returnlll - (seq - (when (= (div (calldataload 0x00) (exp 2 224)) 0xf2c9ecd8) - (return @@0x00)) - (when (= (div (calldataload 0x00) (exp 2 224)) 0x89ea642f) - (return @@0x01))))) - )"; - compileAndRun(sourceCode, 0, "", encodeArgs(u256(65535), "test")); - BOOST_CHECK(callContractFunction("getNumber()") == encodeArgs(u256(65535))); - BOOST_CHECK(callContractFunction("getString()") == encodeArgs("test")); -} - -BOOST_AUTO_TEST_CASE(fallback_and_invalid_function) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= (div (calldataload 0x00) (exp 2 224)) 0xab5ed150) - (return "one")) - (when (= (div (calldataload 0x00) (exp 2 224)) 0xee784123) - (return "two")) - (return "three"))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("getOne()") == encodeArgs("one")); - BOOST_CHECK(callContractFunction("getTwo()") == encodeArgs("two")); - BOOST_CHECK(callContractFunction("invalidFunction()") == encodeArgs("three")); - BOOST_CHECK(callFallback() == encodeArgs("three")); -} - -BOOST_AUTO_TEST_CASE(lit_string) -{ - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x00 "abcdef") - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(string("abcdef"))); -} - -BOOST_AUTO_TEST_CASE(arithmetic) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore8 0x00 (+ 160 22)) - (mstore8 0x01 (- 223 41)) - (mstore8 0x02 (* 33 2)) - (mstore8 0x03 (/ 10 2)) - (mstore8 0x04 (% 67 2)) - (mstore8 0x05 (& 15 8)) - (mstore8 0x06 (| 18 8)) - (mstore8 0x07 (^ 26 6)) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("b6b6420501081a1c000000000000000000000000000000000000000000000000"))); -} - -BOOST_AUTO_TEST_CASE(binary) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore8 0x00 (< 53 87)) - (mstore8 0x01 (< 73 42)) - (mstore8 0x02 (<= 37 94)) - (mstore8 0x03 (<= 37 37)) - (mstore8 0x04 (<= 183 34)) - (mstore8 0x05 (S< (- 0 53) 87)) - (mstore8 0x06 (S< 73 (- 0 42))) - (mstore8 0x07 (S<= (- 0 37) 94)) - (mstore8 0x08 (S<= (- 0 37) (- 0 37))) - (mstore8 0x09 (S<= 183 (- 0 34))) - (mstore8 0x0a (> 73 42)) - (mstore8 0x0b (> 53 87)) - (mstore8 0x0c (>= 94 37)) - (mstore8 0x0d (>= 94 94)) - (mstore8 0x0e (>= 34 183)) - (mstore8 0x0f (S> 73 (- 0 42))) - (mstore8 0x10 (S> (- 0 53) 87)) - (mstore8 0x11 (S>= 94 (- 0 37))) - (mstore8 0x12 (S>= (- 0 94) (- 0 94))) - (mstore8 0x13 (S>= (- 0 34) 183)) - (mstore8 0x14 (= 53 53)) - (mstore8 0x15 (= 73 42)) - (mstore8 0x16 (!= 37 94)) - (mstore8 0x17 (!= 37 37)) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0100010100010001010001000101000100010100010001000000000000000000"))); -} - -BOOST_AUTO_TEST_CASE(unary) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore8 0x00 (! (< 53 87))) - (mstore8 0x01 (! (>= 42 73))) - (mstore8 0x02 (~ 0x7f)) - (mstore8 0x03 (~ 0xaa)) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0001805500000000000000000000000000000000000000000000000000000000"))); -} - -BOOST_AUTO_TEST_CASE(assembly_mload_mstore) -{ - char const* sourceCode = R"( - (returnlll - (asm - 0x07 0x00 mstore - "abcdef" 0x20 mstore - 0x00 mload 0x40 mstore - 0x20 mload 0x60 mstore - 0x40 0x40 return)) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(7), string("abcdef"))); -} - -BOOST_AUTO_TEST_CASE(assembly_sload_sstore) -{ - char const* sourceCode = R"( - (returnlll - (asm - 0x07 0x00 sstore - "abcdef" 0x01 sstore - 0x00 sload 0x00 mstore - 0x01 sload 0x20 mstore - 0x40 0x00 return)) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(7), string("abcdef"))); -} - -BOOST_AUTO_TEST_CASE(assembly_codecopy) -{ - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x00 "abcdef") - (asm - 0x06 6 codesize sub 0x20 codecopy - 0x20 0x20 return))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(string("abcdef"))); -} - -BOOST_AUTO_TEST_CASE(for_loop) -{ - char const* sourceCode = R"( - (returnlll - (seq - (for - { (set 'i 1) (set 'j 1) } ; INIT - (<= @i 10) ; PRED - [i]:(+ @i 1) ; POST - [j]:(* @j @i)) ; BODY - (return j 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(3628800))); // 10! -} - -BOOST_AUTO_TEST_CASE(while_loop) -{ - char const* sourceCode = R"( - (returnlll - (seq - ;; Euclid's GCD algorithm - (set 'a 1071) - (set 'b 462) - (while @b - [a]:(raw @b [b]:(mod @a @b))) - (return a 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(21))); // GCD(1071,462) -} - -BOOST_AUTO_TEST_CASE(keccak256_32bytes) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore 0x00 0x01) - (return (keccak256 0x00 0x20)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"))); -} - -// The following tests are for the built-in macros. -// Note that panic, returnlll and return_one_arg are well covered above. - -BOOST_AUTO_TEST_CASE(allgas) -{ - char const* sourceCode = R"( - (returnlll - (return (- (gas) allgas))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(16))); // == 21 - SUB - GAS -} - -BOOST_AUTO_TEST_CASE(send_two_args) -{ - // "send" does not retain enough gas to be able to pay for account creation. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (send 0xdead 42)) - )"; - compileAndRun(sourceCode); - callFallbackWithValue(42); - BOOST_CHECK(balanceAt(Address(0xdead)) == 42); - } -} - -BOOST_AUTO_TEST_CASE(send_three_args) -{ - // "send" does not retain enough gas to be able to pay for account creation. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (send allgas 0xdead 42)) - )"; - compileAndRun(sourceCode); - callFallbackWithValue(42); - BOOST_CHECK(balanceAt(Address(0xdead)) == 42); - } -} - -// Regression test for edge case that previously failed -BOOST_AUTO_TEST_CASE(alloc_zero) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore 0x00 (~ 0)) - (alloc 0) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(-1))); -} - -BOOST_AUTO_TEST_CASE(alloc_size) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore 0x00 0) ; reserve space for the result of the alloc - (mstore 0x00 (alloc (calldataload 0x04))) - (return (- (msize) (mload 0x00))))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(32))); - BOOST_CHECK(callContractFunction("test()", 32) == encodeArgs(u256(32))); - BOOST_CHECK(callContractFunction("test()", 33) == encodeArgs(u256(64))); -} - -BOOST_AUTO_TEST_CASE(alloc_start) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore 0x40 0) ; Set initial MSIZE to 0x60 - (return (alloc 1)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(96)); -} - -BOOST_AUTO_TEST_CASE(alloc_with_variable) -{ - char const* sourceCode = R"( - (returnlll - (seq - (set 'x (alloc 1)) - (mstore8 @x 42) ; ASCII '*' - (return @x 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs("*")); -} - -BOOST_AUTO_TEST_CASE(msg_six_args) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (seq - (mstore 0x40 1) - (def 'outsize 0x20) - (return (msg 1000 (address) 42 0x40 0x20 outsize) outsize))) - (when (= 1 (calldataload 0x00)) - (return (callvalue))))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(msg_five_args) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (seq - (mstore 0x20 1) - (mstore 0x40 2) - (return (msg 1000 (address) 42 0x20 0x40)))) - (when (= 3 (+ (calldataload 0x00) (calldataload 0x20))) - (return (callvalue))))) - - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(msg_four_args) -{ - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (return (msg 1000 (address) 42 0xff))) - (return (callvalue)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); -} - -BOOST_AUTO_TEST_CASE(msg_three_args) -{ - // "msg" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (return (msg (address) 42 0xff))) - (return (callvalue)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); - } -} - -BOOST_AUTO_TEST_CASE(msg_two_args) -{ - // "msg" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (return (msg (address) 0xff))) - (return 42))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); - } -} - -BOOST_AUTO_TEST_CASE(create_one_arg) -{ - // "call" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (call allgas - (create (returnlll (return 42))) - 0 0 0 0x00 0x20) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); - } -} - -BOOST_AUTO_TEST_CASE(create_two_args) -{ - // "call" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (call allgas - (create 42 (returnlll (return (balance (address))))) - 0 0 0 0x00 0x20) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); - } -} - -BOOST_AUTO_TEST_CASE(sha3_two_args) -{ - char const* sourceCode = R"( - (returnlll - (seq - (mstore 0x00 0x01) - (return (sha3 0x00 0x20)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"))); -} - -BOOST_AUTO_TEST_CASE(sha3_one_arg) -{ - char const* sourceCode = R"( - (returnlll - (return (sha3 0x01))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"))); -} - -BOOST_AUTO_TEST_CASE(sha3pair) -{ - char const* sourceCode = R"( - (returnlll - (return (sha3pair 0x01 0x02))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0"))); -} - -BOOST_AUTO_TEST_CASE(sha3trip) -{ - char const* sourceCode = R"( - (returnlll - (return (sha3trip 0x01 0x02 0x03))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0x6e0c627900b24bd432fe7b1f713f1b0744091a646a9fe4a65a18dfed21f2949c"))); -} - -BOOST_AUTO_TEST_CASE(makeperm) // Covers makeperm (implicit), permcount and perm -{ - char const* sourceCode = R"( - (returnlll - (seq - (perm 'x) (x (+ 1 x)) - (perm 'y) (y (+ 10 y)) - (when (= 2 permcount) - (return (+ x y))))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(11))); -} - -BOOST_AUTO_TEST_CASE(ecrecover) -{ - // "ecrecover" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (return - (ecrecover - ; Hash of 'hello world' - 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad - ; v = 1 + 27 - 0x1c - ; r - 0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb104215 - ; s - 0x61a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(fromHex("0x8743523d96a1b2cbe0c6909653a56da18ed484af"))); - } -} - -BOOST_AUTO_TEST_CASE(sha256_two_args) -{ - // "sha256" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") - (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") - (sha256 0x20 0x40) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xcf25a9fe3d86ae228c226c81d2d8c64c687cd6dc4586d10d8e7e4e5b6706d429"))); - } -} - -BOOST_AUTO_TEST_CASE(ripemd160_two_args) -{ - // "ripemd160" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") - (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") - (ripemd160 0x20 0x40) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0x36c6b90a49e17d4c1e1b0e634ec74124d9b207da"))); - } -} - -BOOST_AUTO_TEST_CASE(sha256_one_arg) -{ - // "sha256" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (sha256 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xcfd2f1fad75a1978da0a444883db7251414b139f31f5a04704c291fdb0e175e6"))); - } -} - -BOOST_AUTO_TEST_CASE(ripemd160_one_arg) -{ - // "ripemd160" does not retain enough gas. - // Disabling for non-tangerineWhistle VMs. - if (solidity::test::Options::get().evmVersion().canOverchargeGasForCall()) - { - char const* sourceCode = R"( - (returnlll - (seq - (ripemd160 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xac5ab22e07b0fb80c69b6207902f725e2507e546"))); - } -} - -BOOST_AUTO_TEST_CASE(wei_szabo_finney_ether) -{ - char const* sourceCode = R"( - (returnlll - (return (+ wei (+ szabo (+ finney ether))))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(1001001000000000001))); -} - -BOOST_AUTO_TEST_CASE(shift_left) -{ - char const* sourceCode = R"( - (returnlll - (return (shl 1 8))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(256))); -} - -BOOST_AUTO_TEST_CASE(shift_right) -{ - char const* sourceCode = R"( - (returnlll - (return (shr 65536 8))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(256))); -} - -BOOST_AUTO_TEST_CASE(sub_assemblies) -{ - char const* sourceCode = R"( - (returnlll - (return (create 0 (returnlll (sstore 1 1))))) - )"; - compileAndRun(sourceCode); - bytes ret = callFallback(); - BOOST_REQUIRE(ret.size() == 32); - u256 rVal = u256(toHex(ret, HexPrefix::Add)); - BOOST_CHECK(rVal != 0); - BOOST_CHECK(rVal < u256("0x10000000000000000000000000000000000000000")); -} - -BOOST_AUTO_TEST_CASE(string_literal) -{ - char const* sourceCode = R"( - (returnlll - (return "hello")) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256("0x68656c6c6f000000000000000000000000000000000000000000000000000000"))); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // end namespaces diff --git a/test/liblll/ExecutionFramework.cpp b/test/liblll/ExecutionFramework.cpp deleted file mode 100644 index 8f2cec672..000000000 --- a/test/liblll/ExecutionFramework.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * @date 2016 - * Framework for executing LLL contracts and testing them via RPC. - */ - -#include -#include -#include - -using namespace solidity::test; -using namespace solidity::lll::test; - -LLLExecutionFramework::LLLExecutionFramework() : - ExecutionFramework() -{ -} diff --git a/test/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h deleted file mode 100644 index 1fc14f528..000000000 --- a/test/liblll/ExecutionFramework.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * @date 2016 - * Framework for executing LLL contracts and testing them via RPC. - */ - -#pragma once - - -#include - -#include - -#include - -namespace solidity::lll::test -{ - -class LLLExecutionFramework: public solidity::test::ExecutionFramework -{ -public: - LLLExecutionFramework(); - - virtual bytes const& compileAndRunWithoutCheck( - std::string const& _sourceCode, - u256 const& _value = 0, - std::string const& _contractName = "", - bytes const& _arguments = bytes(), - std::map const& _libraryAddresses = {} - ) override - { - BOOST_REQUIRE(_contractName.empty()); - BOOST_REQUIRE(_libraryAddresses.empty()); - - std::vector errors; - bytes bytecode = lll::compileLLL( - _sourceCode, - solidity::test::Options::get().evmVersion(), - m_optimiserSettings == solidity::frontend::OptimiserSettings::standard(), - &errors - ); - if (!errors.empty()) - { - for (auto const& error: errors) - std::cerr << error << std::endl; - BOOST_ERROR("Compiling contract failed"); - } - sendMessage(bytecode + _arguments, true, _value); - return m_output; - } -}; - -} // end namespaces diff --git a/test/liblll/LLL_ENS.cpp b/test/liblll/LLL_ENS.cpp deleted file mode 100644 index 51d303b82..000000000 --- a/test/liblll/LLL_ENS.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Ben Edgington - * @date 2017 - * Tests for the deployed ENS Registry implementation written in LLL - */ - -#include -#include -#include -#include - -#define ACCOUNT(n) h256(account(n), h256::AlignRight) - -using namespace std; -using namespace solidity::lll; -using namespace solidity::util; -using namespace solidity::test; - -namespace solidity::lll::test -{ - -namespace -{ - -static char const* ensCode = R"DELIMITER( -;;; --------------------------------------------------------------------------- -;;; @title The Ethereum Name Service registry. -;;; @author Daniel Ellison - -(seq - - ;; -------------------------------------------------------------------------- - ;; Constant definitions. - - ;; Memory layout. - (def 'node-bytes 0x00) - (def 'label-bytes 0x20) - (def 'call-result 0x40) - - ;; Struct: Record - (def 'resolver 0x00) ; address - (def 'owner 0x20) ; address - (def 'ttl 0x40) ; uint64 - - ;; Precomputed function IDs. - (def 'get-node-owner 0x02571be3) ; owner(bytes32) - (def 'get-node-resolver 0x0178b8bf) ; resolver(bytes32) - (def 'get-node-ttl 0x16a25cbd) ; ttl(bytes32) - (def 'set-node-owner 0x5b0fc9c3) ; setOwner(bytes32,address) - (def 'set-subnode-owner 0x06ab5923) ; setSubnodeOwner(bytes32,bytes32,address) - (def 'set-node-resolver 0x1896f70a) ; setResolver(bytes32,address) - (def 'set-node-ttl 0x14ab9038) ; setTTL(bytes32,uint64) - - ;; Jumping here causes an EVM error. - (def 'invalid-location 0x02) - - ;; -------------------------------------------------------------------------- - ;; @notice Shifts the leftmost 4 bytes of a 32-byte number right by 28 bytes. - ;; @param input A 32-byte number. - - (def 'shift-right (input) - (div input (exp 2 224))) - - ;; -------------------------------------------------------------------------- - ;; @notice Determines whether the supplied function ID matches a known - ;; function hash and executes if so. - ;; @dev The function ID is in the leftmost four bytes of the call data. - ;; @param function-hash The four-byte hash of a known function signature. - ;; @param code-body The code to run in the case of a match. - - (def 'function (function-hash code-body) - (when (= (shift-right (calldataload 0x00)) function-hash) - code-body)) - - ;; -------------------------------------------------------------------------- - ;; @notice Calculates record location for the node and label passed in. - ;; @param node The parent node. - ;; @param label The hash of the subnode label. - - (def 'get-record (node label) - (seq - (mstore node-bytes node) - (mstore label-bytes label) - (sha3 node-bytes 64))) - - ;; -------------------------------------------------------------------------- - ;; @notice Retrieves owner from node record. - ;; @param node Get owner of this node. - - (def 'get-owner (node) - (sload (+ node owner))) - - ;; -------------------------------------------------------------------------- - ;; @notice Stores new owner in node record. - ;; @param node Set owner of this node. - ;; @param new-owner New owner of this node. - - (def 'set-owner (node new-owner) - (sstore (+ node owner) new-owner)) - - ;; -------------------------------------------------------------------------- - ;; @notice Stores new subnode owner in node record. - ;; @param node Set owner of this node. - ;; @param label The hash of the label specifying the subnode. - ;; @param new-owner New owner of the subnode. - - (def 'set-subowner (node label new-owner) - (sstore (+ (get-record node label) owner) new-owner)) - - ;; -------------------------------------------------------------------------- - ;; @notice Retrieves resolver from node record. - ;; @param node Get resolver of this node. - - (def 'get-resolver (node) - (sload node)) - - ;; -------------------------------------------------------------------------- - ;; @notice Stores new resolver in node record. - ;; @param node Set resolver of this node. - ;; @param new-resolver New resolver for this node. - - (def 'set-resolver (node new-resolver) - (sstore node new-resolver)) - - ;; -------------------------------------------------------------------------- - ;; @notice Retrieves TTL From node record. - ;; @param node Get TTL of this node. - - (def 'get-ttl (node) - (sload (+ node ttl))) - - ;; -------------------------------------------------------------------------- - ;; @notice Stores new TTL in node record. - ;; @param node Set TTL of this node. - ;; @param new-resolver New TTL for this node. - - (def 'set-ttl (node new-ttl) - (sstore (+ node ttl) new-ttl)) - - ;; -------------------------------------------------------------------------- - ;; @notice Checks that the caller is the node owner. - ;; @param node Check owner of this node. - - (def 'only-node-owner (node) - (when (!= (caller) (get-owner node)) - (jump invalid-location))) - - ;; -------------------------------------------------------------------------- - ;; INIT - - ;; Set the owner of the root node (0x00) to the deploying account. - (set-owner 0x00 (caller)) - - ;; -------------------------------------------------------------------------- - ;; CODE - - (returnlll - (seq - - ;; ---------------------------------------------------------------------- - ;; @notice Returns the address of the resolver for the specified node. - ;; @dev Signature: resolver(bytes32) - ;; @param node Return this node's resolver. - ;; @return The associated resolver. - - (def 'node (calldataload 0x04)) - - (function get-node-resolver - (seq - - ;; Get the node's resolver and save it. - (mstore call-result (get-resolver node)) - - ;; Return result. - (return call-result 32))) - - ;; ---------------------------------------------------------------------- - ;; @notice Returns the address that owns the specified node. - ;; @dev Signature: owner(bytes32) - ;; @param node Return this node's owner. - ;; @return The associated address. - - (def 'node (calldataload 0x04)) - - (function get-node-owner - (seq - - ;; Get the node's owner and save it. - (mstore call-result (get-owner node)) - - ;; Return result. - (return call-result 32))) - - ;; ---------------------------------------------------------------------- - ;; @notice Returns the TTL of a node and any records associated with it. - ;; @dev Signature: ttl(bytes32) - ;; @param node Return this node's TTL. - ;; @return The node's TTL. - - (def 'node (calldataload 0x04)) - - (function get-node-ttl - (seq - - ;; Get the node's TTL and save it. - (mstore call-result (get-ttl node)) - - ;; Return result. - (return call-result 32))) - - ;; ---------------------------------------------------------------------- - ;; @notice Transfers ownership of a node to a new address. May only be - ;; called by the current owner of the node. - ;; @dev Signature: setOwner(bytes32,address) - ;; @param node The node to transfer ownership of. - ;; @param new-owner The address of the new owner. - - (def 'node (calldataload 0x04)) - (def 'new-owner (calldataload 0x24)) - - (function set-node-owner - (seq (only-node-owner node) - - ;; Transfer ownership by storing passed-in address. - (set-owner node new-owner) - - ;; Emit an event about the transfer. - ;; Transfer(bytes32 indexed node, address owner); - (mstore call-result new-owner) - (log2 call-result 32 - (sha3 0x00 (lit 0x00 "Transfer(bytes32,address)")) node) - - ;; Nothing to return. - (stop))) - - ;; ---------------------------------------------------------------------- - ;; @notice Transfers ownership of a subnode to a new address. May only be - ;; called by the owner of the parent node. - ;; @dev Signature: setSubnodeOwner(bytes32,bytes32,address) - ;; @param node The parent node. - ;; @param label The hash of the label specifying the subnode. - ;; @param new-owner The address of the new owner. - - (def 'node (calldataload 0x04)) - (def 'label (calldataload 0x24)) - (def 'new-owner (calldataload 0x44)) - - (function set-subnode-owner - (seq (only-node-owner node) - - ;; Transfer ownership by storing passed-in address. - (set-subowner node label new-owner) - - ;; Emit an event about the transfer. - ;; NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); - (mstore call-result new-owner) - (log3 call-result 32 - (sha3 0x00 (lit 0x00 "NewOwner(bytes32,bytes32,address)")) - node label) - - ;; Nothing to return. - (stop))) - - ;; ---------------------------------------------------------------------- - ;; @notice Sets the resolver address for the specified node. - ;; @dev Signature: setResolver(bytes32,address) - ;; @param node The node to update. - ;; @param new-resolver The address of the resolver. - - (def 'node (calldataload 0x04)) - (def 'new-resolver (calldataload 0x24)) - - (function set-node-resolver - (seq (only-node-owner node) - - ;; Transfer ownership by storing passed-in address. - (set-resolver node new-resolver) - - ;; Emit an event about the change of resolver. - ;; NewResolver(bytes32 indexed node, address resolver); - (mstore call-result new-resolver) - (log2 call-result 32 - (sha3 0x00 (lit 0x00 "NewResolver(bytes32,address)")) node) - - ;; Nothing to return. - (stop))) - - ;; ---------------------------------------------------------------------- - ;; @notice Sets the TTL for the specified node. - ;; @dev Signature: setTTL(bytes32,uint64) - ;; @param node The node to update. - ;; @param ttl The TTL in seconds. - - (def 'node (calldataload 0x04)) - (def 'new-ttl (calldataload 0x24)) - - (function set-node-ttl - (seq (only-node-owner node) - - ;; Set new TTL by storing passed-in time. - (set-ttl node new-ttl) - - ;; Emit an event about the change of TTL. - ;; NewTTL(bytes32 indexed node, uint64 ttl); - (mstore call-result new-ttl) - (log2 call-result 32 - (sha3 0x00 (lit 0x00 "NewTTL(bytes32,uint64)")) node) - - ;; Nothing to return. - (stop))) - - ;; ---------------------------------------------------------------------- - ;; @notice Fallback: No functions matched the function ID provided. - - (jump invalid-location))) - -) -)DELIMITER"; - -static unique_ptr s_compiledEns; - -class LLLENSTestFramework: public LLLExecutionFramework -{ -protected: - void deployEns() - { - if (!s_compiledEns) - { - vector errors; - s_compiledEns.reset(new bytes(compileLLL(ensCode, solidity::test::Options::get().evmVersion(), solidity::test::Options::get().optimize, &errors))); - BOOST_REQUIRE(errors.empty()); - } - sendMessage(*s_compiledEns, true); - BOOST_REQUIRE(m_transactionSuccessful); - BOOST_REQUIRE(!m_output.empty()); - } - -}; - -} - -// Test suite for the deployed ENS Registry implementation written in LLL -BOOST_FIXTURE_TEST_SUITE(LLLENS, LLLENSTestFramework) - -BOOST_AUTO_TEST_CASE(creation) -{ - deployEns(); - - // Root node 0x00 should initially be owned by the deploying account, account(0). - BOOST_CHECK(callContractFunction("owner(bytes32)", 0x00) == encodeArgs(ACCOUNT(0))); -} - -BOOST_AUTO_TEST_CASE(transfer_ownership) -{ - deployEns(); - - // Transfer ownership of root node from account(0) to account(1). - BOOST_REQUIRE(callContractFunction("setOwner(bytes32,address)", 0x00, ACCOUNT(1)) == encodeArgs()); - - // Check that an event was raised and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(ACCOUNT(1))); - BOOST_REQUIRE(numLogTopics(0) == 2); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("Transfer(bytes32,address)"))); - BOOST_CHECK(logTopic(0, 1) == u256(0x00)); - - // Verify that owner of 0x00 is now account(1). - BOOST_CHECK(callContractFunction("owner(bytes32)", 0x00) == encodeArgs(ACCOUNT(1))); -} - -BOOST_AUTO_TEST_CASE(transfer_ownership_fail) -{ - deployEns(); - - // Try to steal ownership of node 0x01 - BOOST_REQUIRE(callContractFunction("setOwner(bytes32,address)", 0x01, ACCOUNT(0)) == encodeArgs()); - - // Verify that owner of 0x01 remains the default zero address - BOOST_CHECK(callContractFunction("owner(bytes32)", 0x01) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(set_resolver) -{ - deployEns(); - - // Set resolver of root node to account(1). - BOOST_REQUIRE(callContractFunction("setResolver(bytes32,address)", 0x00, ACCOUNT(1)) == encodeArgs()); - - // Check that an event was raised and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(ACCOUNT(1))); - BOOST_REQUIRE(numLogTopics(0) == 2); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("NewResolver(bytes32,address)"))); - BOOST_CHECK(logTopic(0, 1) == u256(0x00)); - - // Verify that the resolver is changed to account(1). - BOOST_CHECK(callContractFunction("resolver(bytes32)", 0x00) == encodeArgs(ACCOUNT(1))); -} - -BOOST_AUTO_TEST_CASE(set_resolver_fail) -{ - deployEns(); - - // Try to set resolver of node 0x01, which is not owned by account(0). - BOOST_REQUIRE(callContractFunction("setResolver(bytes32,address)", 0x01, ACCOUNT(0)) == encodeArgs()); - - // Verify that the resolver of 0x01 remains default zero address. - BOOST_CHECK(callContractFunction("resolver(bytes32)", 0x01) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(set_ttl) -{ - deployEns(); - - // Set ttl of root node to 3600. - BOOST_REQUIRE(callContractFunction("setTTL(bytes32,uint64)", 0x00, 3600) == encodeArgs()); - - // Check that an event was raised and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(3600)); - BOOST_REQUIRE(numLogTopics(0) == 2); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("NewTTL(bytes32,uint64)"))); - BOOST_CHECK(logTopic(0, 1) == u256(0x00)); - - // Verify that the TTL has been set. - BOOST_CHECK(callContractFunction("ttl(bytes32)", 0x00) == encodeArgs(3600)); -} - -BOOST_AUTO_TEST_CASE(set_ttl_fail) -{ - deployEns(); - - // Try to set TTL of node 0x01, which is not owned by account(0). - BOOST_REQUIRE(callContractFunction("setTTL(bytes32,uint64)", 0x01, 3600) == encodeArgs()); - - // Verify that the TTL of node 0x01 has not changed from the default. - BOOST_CHECK(callContractFunction("ttl(bytes32)", 0x01) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(create_subnode) -{ - deployEns(); - - // Set ownership of "eth" sub-node to account(1) - BOOST_REQUIRE(callContractFunction("setSubnodeOwner(bytes32,bytes32,address)", 0x00, keccak256(string("eth")), ACCOUNT(1)) == encodeArgs()); - - // Check that an event was raised and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(ACCOUNT(1))); - BOOST_REQUIRE(numLogTopics(0) == 3); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("NewOwner(bytes32,bytes32,address)"))); - BOOST_CHECK(logTopic(0, 1) == u256(0x00)); - BOOST_CHECK(logTopic(0, 2) == keccak256(string("eth"))); - - // Verify that the sub-node owner is now account(1). - u256 namehash = keccak256(h256(0x00).asBytes() + keccak256("eth").asBytes()); - BOOST_CHECK(callContractFunction("owner(bytes32)", namehash) == encodeArgs(ACCOUNT(1))); -} - -BOOST_AUTO_TEST_CASE(create_subnode_fail) -{ - deployEns(); - - // Send account(1) some ether for gas. - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // account(1) tries to set ownership of the "eth" sub-node. - m_sender = account(1); - BOOST_REQUIRE(callContractFunction("setSubnodeOwner(bytes32,bytes32,address)", 0x00, keccak256(string("eth")), ACCOUNT(1)) == encodeArgs()); - - // Verify that the sub-node owner remains at default zero address. - u256 namehash = keccak256(h256(0x00).asBytes() + keccak256("eth").asBytes()); - BOOST_CHECK(callContractFunction("owner(bytes32)", namehash) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(fallback) -{ - deployEns(); - - // Call fallback - should just abort via jump to invalid location. - BOOST_CHECK(callFallback() == encodeArgs()); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // end namespaces diff --git a/test/liblll/LLL_ERC20.cpp b/test/liblll/LLL_ERC20.cpp deleted file mode 100644 index 4047ede8a..000000000 --- a/test/liblll/LLL_ERC20.cpp +++ /dev/null @@ -1,652 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Ben Edgington - * @date 2017 - * Tests for an ERC20 token implementation written in LLL - */ - -#include -#include -#include -#include - -#define TOKENSUPPLY 100000 -#define TOKENDECIMALS 2 -#define TOKENSYMBOL "BEN" -#define TOKENNAME "Ben Token" -#define ACCOUNT(n) h256(account(n), h256::AlignRight) -#define SUCCESS encodeArgs(1) - -using namespace std; -using namespace solidity::lll; -using namespace solidity::util; -using namespace solidity::test; - -namespace solidity::lll::test -{ - -namespace -{ - -static char const* erc20Code = R"DELIMITER( -(seq - - ;; -------------------------------------------------------------------------- - ;; CONSTANTS - - ;; Token parameters. - ;; 0x40 is a "magic number" - the text of the string is placed here - ;; when returning the string to the caller. See return-string below. - (def 'token-name-string (lit 0x40 "Ben Token")) - (def 'token-symbol-string (lit 0x40 "BEN")) - (def 'token-decimals 2) - (def 'token-supply 100000) ; 1000.00 total tokens - - ;; Booleans - (def 'false 0) - (def 'true 1) - - ;; Memory layout. - (def 'mem-ret 0x00) ; Fixed due to compiler macro for return. - (def 'mem-func 0x00) ; No conflict with mem-ret, so re-use. - (def 'mem-keccak 0x00) ; No conflict with mem-func or mem-ret, so re-use. - (def 'scratch0 0x20) - (def 'scratch1 0x40) - - ;; Precomputed function IDs. - (def 'get-name 0x06fdde03) ; name() - (def 'get-symbol 0x95d89b41) ; symbol() - (def 'get-decimals 0x313ce567) ; decimals() - (def 'get-total-supply 0x18160ddd) ; totalSupply() - (def 'get-balance-of 0x70a08231) ; balanceOf(address) - (def 'transfer 0xa9059cbb) ; transfer(address,uint256) - (def 'transfer-from 0x23b872dd) ; transferFrom(address,address,uint256) - (def 'approve 0x095ea7b3) ; approve(address,uint256) - (def 'get-allowance 0xdd62ed3e) ; allowance(address,address) - - ;; Event IDs - (def 'transfer-event-id ; Transfer(address,address,uint256) - 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) - - (def 'approval-event-id ; Approval(address,address,uint256) - 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925) - - ;; -------------------------------------------------------------------------- - ;; UTILITIES - - ;; -------------------------------------------------------------------------- - ;; The following define the key data-structures: - ;; - balance(addr) => value - ;; - allowance(addr,addr) => value - - ;; Balances are stored at s[owner_addr]. - (def 'balance (address) address) - - ;; Allowances are stored at s[owner_addr + keccak256(spender_addr)] - ;; We use a crypto function here to avoid any situation where - ;; approve(me, spender) can be abused to do approve(target, me). - (def 'allowance (owner spender) - (seq - (mstore mem-keccak spender) - (keccak256 mem-keccak 0x20))) - - ;; -------------------------------------------------------------------------- - ;; For convenience we have macros to refer to function arguments - - (def 'arg1 (calldataload 0x04)) - (def 'arg2 (calldataload 0x24)) - (def 'arg3 (calldataload 0x44)) - - ;; -------------------------------------------------------------------------- - ;; Revert is a soft return that does not consume the remaining gas. - ;; We use it when rejecting invalid user input. - ;; - ;; Note: The REVERT opcode will be implemented in Metropolis (EIP 140). - ;; Meanwhile it just causes an invalid instruction exception (similar - ;; to a "throw" in Solidity). When fully implemented, Revert could be - ;; use to return error codes, or even messages. - - (def 'revert () (revert 0 0)) - - ;; -------------------------------------------------------------------------- - ;; Macro for returning string names. - ;; Compliant with the ABI format for strings. - - (def 'return-string (string-literal) - (seq - (mstore 0x00 0x20) ; Points to our string's memory location - (mstore 0x20 string-literal) ; Length. String itself is copied to 0x40. - (return 0x00 (& (+ (mload 0x20) 0x5f) (~ 0x1f))))) - ; Round return up to 32 byte boundary - - ;; -------------------------------------------------------------------------- - ;; Convenience macro for raising Events - - (def 'event3 (id addr1 addr2 value) - (seq - (mstore scratch0 value) - (log3 scratch0 0x20 id addr1 addr2))) - - ;; -------------------------------------------------------------------------- - ;; Determines whether the stored function ID matches a known - ;; function hash and executes if so. - ;; @param function-hash The four-byte hash of a known function signature. - ;; @param code-body The code to run in the case of a match. - - (def 'function (function-hash code-body) - (when (= (mload mem-func) function-hash) - code-body)) - - ;; -------------------------------------------------------------------------- - ;; Gets the function ID and stores it in memory for reference. - ;; The function ID is in the leftmost four bytes of the call data. - - (def 'uses-functions - (mstore - mem-func - (shr (calldataload 0x00) 224))) - - ;; -------------------------------------------------------------------------- - ;; GUARDS - - ;; -------------------------------------------------------------------------- - ;; Checks that ensure that each function is called with the right - ;; number of arguments. For one thing this addresses the "ERC20 - ;; short address attack". For another, it stops me making - ;; mistakes while testing. We use these only on the non-constant functions. - - (def 'has-one-arg (unless (= 0x24 (calldatasize)) (revert))) - (def 'has-two-args (unless (= 0x44 (calldatasize)) (revert))) - (def 'has-three-args (unless (= 0x64 (calldatasize)) (revert))) - - ;; -------------------------------------------------------------------------- - ;; Check that addresses have only 160 bits and revert if not. - ;; We use these input type-checks on the non-constant functions. - - (def 'is-address (addr) - (when - (shr addr 160) - (revert))) - - ;; -------------------------------------------------------------------------- - ;; Check that transfer values are smaller than total supply and - ;; revert if not. This should effectively exclude negative values. - - (def 'is-value (value) - (when (> value token-supply) (revert))) - - ;; -------------------------------------------------------------------------- - ;; Will revert if sent any Ether. We use the macro immediately so as - ;; to abort if sent any Ether during contract deployment. - - (def 'not-payable - (when (callvalue) (revert))) - - not-payable - - ;; -------------------------------------------------------------------------- - ;; INITIALISATION - ;; - ;; Assign all tokens initially to the owner of the contract. - - (sstore (balance (caller)) token-supply) - - ;; -------------------------------------------------------------------------- - ;; CONTRACT CODE - - (returnlll - (seq not-payable uses-functions - - ;; ---------------------------------------------------------------------- - ;; Getter for the name of the token. - ;; @abi name() constant returns (string) - ;; @return The token name as a string. - - (function get-name - (return-string token-name-string)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the symbol of the token. - ;; @abi symbol() constant returns (string) - ;; @return The token symbol as a string. - - (function get-symbol - (return-string token-symbol-string)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the number of decimals assigned to the token. - ;; @abi decimals() constant returns (uint256) - ;; @return The token decimals. - - (function get-decimals - (return token-decimals)) - - ;; ---------------------------------------------------------------------- - ;; Getter for the total token supply. - ;; @abi totalSupply() constant returns (uint256) - ;; @return The token supply. - - (function get-total-supply - (return token-supply)) - - ;; ---------------------------------------------------------------------- - ;; Returns the account balance of another account. - ;; @abi balanceOf(address) constant returns (uint256) - ;; @param owner The address of the account's owner. - ;; @return The account balance. - - (function get-balance-of - (seq - - (def 'owner arg1) - - (return (sload (balance owner))))) - - ;; ---------------------------------------------------------------------- - ;; Transfers _value amount of tokens to address _to. The command - ;; should throw if the _from account balance has not enough - ;; tokens to spend. - ;; @abi transfer(address, uint256) returns (bool) - ;; @param to The account to receive the tokens. - ;; @param value The quantity of tokens to transfer. - ;; @return Success (true). Other outcomes result in a Revert. - - (function transfer - (seq has-two-args (is-address arg1) (is-value arg2) - - (def 'to arg1) - (def 'value arg2) - - (when value ; value == 0 is a no-op - (seq - - ;; The caller's balance. Save in memory for efficiency. - (mstore scratch0 (sload (balance (caller)))) - - ;; Revert if the caller's balance is not sufficient. - (when (> value (mload scratch0)) - (revert)) - - ;; Make the transfer - ;; It would be good to check invariants (sum of balances). - (sstore (balance (caller)) (- (mload scratch0) value)) - (sstore (balance to) (+ (sload (balance to)) value)) - - ;; Event - Transfer(address,address,uint256) - (event3 transfer-event-id (caller) to value))) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Send _value amount of tokens from address _from to address _to - ;; @abi transferFrom(address,address,uint256) returns (bool) - ;; @param from The account to send the tokens from. - ;; @param to The account to receive the tokens. - ;; @param value The quantity of tokens to transfer. - ;; @return Success (true). Other outcomes result in a Revert. - - (function transfer-from - (seq has-three-args (is-address arg1) (is-address arg2) (is-value arg3) - - (def 'from arg1) - (def 'to arg2) - (def 'value arg3) - - (when value ; value == 0 is a no-op - - (seq - - ;; Save data to memory for efficiency. - (mstore scratch0 (sload (balance from))) - (mstore scratch1 (sload (allowance from (caller)))) - - ;; Revert if not enough funds, or not enough approved. - (when - (|| - (> value (mload scratch0)) - (> value (mload scratch1))) - (revert)) - - ;; Make the transfer and update allowance. - (sstore (balance from) (- (mload scratch0) value)) - (sstore (balance to) (+ (sload (balance to)) value)) - (sstore (allowance from (caller)) (- (mload scratch1) value)) - - ;; Event - Transfer(address,address,uint256) - (event3 transfer-event-id from to value))) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Allows _spender to withdraw from your account multiple times, - ;; up to the _value amount. If this function is called again it - ;; overwrites the current allowance with _value. - ;; @abi approve(address,uint256) returns (bool) - ;; @param spender The withdrawing account having its limit set. - ;; @param value The maximum allowed amount. - ;; @return Success (true). Other outcomes result in a Revert. - - (function approve - (seq has-two-args (is-address arg1) (is-value arg2) - - (def 'spender arg1) - (def 'value arg2) - - ;; Force users set the allowance to 0 before setting it to - ;; another value for the same spender. Prevents this attack: - ;; https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM - (when - (&& value (sload (allowance (caller) spender))) - (revert)) - - (sstore (allowance (caller) spender) value) - - ;; Event - Approval(address,address,uint256) - (event3 approval-event-id (caller) spender value) - - (return true))) - - ;; ---------------------------------------------------------------------- - ;; Returns the amount which _spender is still allowed to withdraw - ;; from _owner. - ;; @abi allowance(address,address) constant returns (uint256) - ;; @param owner The owning account. - ;; @param spender The withdrawing account. - ;; @return The allowed amount remaining. - - (function get-allowance - (seq - - (def 'owner arg1) - (def 'spender arg2) - - (return (sload (allowance owner spender))))) - - ;; ---------------------------------------------------------------------- - ;; Fallback: No functions matched the function ID provided. - - (revert))) - ) -)DELIMITER"; - -static unique_ptr s_compiledErc20; - -class LLLERC20TestFramework: public LLLExecutionFramework -{ -protected: - void deployErc20() - { - if (!s_compiledErc20) - { - vector errors; - s_compiledErc20.reset(new bytes(compileLLL(erc20Code, solidity::test::Options::get().evmVersion(), solidity::test::Options::get().optimize, &errors))); - BOOST_REQUIRE(errors.empty()); - } - sendMessage(*s_compiledErc20, true); - BOOST_REQUIRE(m_transactionSuccessful); - BOOST_REQUIRE(!m_output.empty()); - } - -}; - -} - -// Test suite for an ERC20 contract written in LLL. -BOOST_FIXTURE_TEST_SUITE(LLLERC20, LLLERC20TestFramework) - -BOOST_AUTO_TEST_CASE(creation) -{ - deployErc20(); - - // All tokens are initially assigned to the contract creator. - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); -} - -BOOST_AUTO_TEST_CASE(constants) -{ - deployErc20(); - - BOOST_CHECK(callContractFunction("totalSupply()") == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("decimals()") == encodeArgs(TOKENDECIMALS)); - BOOST_CHECK(callContractFunction("symbol()") == encodeDyn(string(TOKENSYMBOL))); - BOOST_CHECK(callContractFunction("name()") == encodeDyn(string(TOKENNAME))); -} - -BOOST_AUTO_TEST_CASE(send_value) -{ - deployErc20(); - - // Send value to the contract. Should always fail. - m_sender = account(0); - auto contractBalance = balanceAt(m_contractAddress); - - // Fallback: check value is not transferred. - BOOST_CHECK(callFallbackWithValue(42) != SUCCESS); - BOOST_CHECK(balanceAt(m_contractAddress) == contractBalance); - - // Transfer: check nothing happened. - BOOST_CHECK(callContractFunctionWithValue("transfer(address,uint256)", ACCOUNT(1), 100, 42) != SUCCESS); - BOOST_CHECK(balanceAt(m_contractAddress) == contractBalance); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(0)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); -} - -BOOST_AUTO_TEST_CASE(transfer) -{ - deployErc20(); - - // Transfer 100 tokens from account(0) to account(1). - int transfer = 100; - m_sender = account(0); - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(transfer_from) -{ - deployErc20(); - - // Approve account(1) to transfer up to 1000 tokens from account(0). - int allow = 1000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow)); - - // Send account(1) some ether for gas. - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Transfer 300 tokens from account(0) to account(2); check that the allowance decreases. - int transfer = 300; - m_sender = account(1); - BOOST_REQUIRE(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) == SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(2)) == encodeArgs(transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow - transfer)); -} - -BOOST_AUTO_TEST_CASE(transfer_event) -{ - deployErc20(); - - // Transfer 1000 tokens from account(0) to account(1). - int transfer = 1000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - - // Check that a Transfer event was recorded and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(transfer)); - BOOST_REQUIRE(numLogTopics(0) == 3); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("Transfer(address,address,uint256)"))); - BOOST_CHECK(logTopic(0, 1) == ACCOUNT(0)); - BOOST_CHECK(logTopic(0, 2) == ACCOUNT(1)); -} - -BOOST_AUTO_TEST_CASE(transfer_zero_no_event) -{ - deployErc20(); - - // Transfer 0 tokens from account(0) to account(1). This is a no-op. - int transfer = 0; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - - // Check that no Event was recorded. - BOOST_CHECK(numLogs() == 0); - - // Check that balances have not changed. - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(approval_and_transfer_events) -{ - deployErc20(); - - // Approve account(1) to transfer up to 10000 tokens from account(0). - int allow = 10000; - m_sender = account(0); - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow)) == SUCCESS); - - // Check that an Approval event was recorded and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(allow)); - BOOST_REQUIRE(numLogTopics(0) == 3); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("Approval(address,address,uint256)"))); - BOOST_CHECK(logTopic(0, 1) == ACCOUNT(0)); - BOOST_CHECK(logTopic(0, 2) == ACCOUNT(1)); - - // Send account(1) some ether for gas. - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Transfer 3000 tokens from account(0) to account(2); check that the allowance decreases. - int transfer = 3000; - m_sender = account(1); - BOOST_REQUIRE(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) == SUCCESS); - - // Check that a Transfer event was recorded and contents are correct. - BOOST_REQUIRE(numLogs() == 1); - BOOST_CHECK(logData(0) == encodeArgs(transfer)); - BOOST_REQUIRE(numLogTopics(0) == 3); - BOOST_CHECK(logTopic(0, 0) == keccak256(string("Transfer(address,address,uint256)"))); - BOOST_CHECK(logTopic(0, 1) == ACCOUNT(0)); - BOOST_CHECK(logTopic(0, 2) == ACCOUNT(2)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_1) -{ - deployErc20(); - - // Transfer more than the total supply; ensure nothing changes. - int transfer = TOKENSUPPLY + 1; - m_sender = account(0); - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_2) -{ - deployErc20(); - - // Separate transfers that together exceed initial balance. - int transfer = 1 + TOKENSUPPLY / 2; - m_sender = account(0); - - // First transfer should succeed. - BOOST_REQUIRE(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_REQUIRE(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); - - // Second transfer should fail. - BOOST_CHECK(callContractFunction("transfer(address,uint256)", ACCOUNT(1), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY - transfer)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(1)) == encodeArgs(transfer)); -} - -BOOST_AUTO_TEST_CASE(invalid_transfer_from) -{ - deployErc20(); - - // TransferFrom without approval. - int transfer = 300; - - // Send account(1) some ether for gas. - m_sender = account(0); - sendEther(account(1), 1000 * ether); - BOOST_REQUIRE(balanceAt(account(1)) >= 1000 * ether); - - // Try the transfer; ensure nothing changes. - m_sender = account(1); - BOOST_CHECK(callContractFunction("transferFrom(address,address,uint256)", ACCOUNT(0), ACCOUNT(2), u256(transfer)) != SUCCESS); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(2)) == encodeArgs(0)); - BOOST_CHECK(callContractFunction("balanceOf(address)", ACCOUNT(0)) == encodeArgs(TOKENSUPPLY)); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(0)); -} - -BOOST_AUTO_TEST_CASE(invalid_reapprove) -{ - deployErc20(); - - m_sender = account(0); - - // Approve account(1) to transfer up to 1000 tokens from account(0). - int allow1 = 1000; - BOOST_REQUIRE(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow1)) == SUCCESS); - BOOST_REQUIRE(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow1)); - - // Now approve account(1) to transfer up to 500 tokens from account(0). - // Should fail (we need to reset allowance to 0 first). - int allow2 = 500; - BOOST_CHECK(callContractFunction("approve(address,uint256)", ACCOUNT(1), u256(allow2)) != SUCCESS); - BOOST_CHECK(callContractFunction("allowance(address,address)", ACCOUNT(0), ACCOUNT(1)) == encodeArgs(allow1)); -} - -BOOST_AUTO_TEST_CASE(bad_data) -{ - deployErc20(); - - m_sender = account(0); - - // Correct data: transfer(address _to, 1). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a123456789a") + encodeArgs(1), false, 0); - BOOST_CHECK(m_transactionSuccessful); - BOOST_CHECK(m_output == SUCCESS); - - // Too little data (address is truncated by one byte). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a12345678") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); - - // Too much data (address is extended with a zero byte). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000000123456789a123456789a123456789a123456789a00") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); - - // Invalid address (a bit above the 160th is set). - sendMessage((bytes)fromHex("a9059cbb") + (bytes)fromHex("000000000000000000000100123456789a123456789a123456789a123456789a") + encodeArgs(1), false, 0); - BOOST_CHECK(!m_transactionSuccessful); - BOOST_CHECK(m_output != SUCCESS); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // end namespaces diff --git a/test/liblll/Parser.cpp b/test/liblll/Parser.cpp deleted file mode 100644 index bfee38171..000000000 --- a/test/liblll/Parser.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * @date 2016 - * Unit tests for the LLL parser. - */ - -#include -#include -#include -#include - -using namespace std; - -namespace solidity::lll::test -{ - -namespace -{ - -bool successParse(std::string const& _source) -{ - std::string ret = lll::parseLLL(_source); - return ret.size() != 0; -} - -std::string parse(std::string const& _source) -{ - return lll::parseLLL(_source); -} - -} - -BOOST_AUTO_TEST_SUITE(LLLParser) - -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* text = "1"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_CASE(string) -{ - char const* text = "\"string\""; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"("string")"); -} - -BOOST_AUTO_TEST_CASE(symbol) -{ - char const* text = "symbol"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(symbol)"); - - BOOST_CHECK(successParse("'symbol")); - BOOST_CHECK_EQUAL(parse(text), R"(symbol)"); -} - -BOOST_AUTO_TEST_CASE(decimals) -{ - char const* text = "1234"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(1234)"); -} - -BOOST_AUTO_TEST_CASE(hexadecimals) -{ - char const* text = "0x1234"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(4660)"); - - BOOST_CHECK(!successParse("0x")); -} - -BOOST_AUTO_TEST_CASE(sequence) -{ - char const* text = "{ 1234 }"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"({ 1234 })"); -} - -BOOST_AUTO_TEST_CASE(empty_sequence) -{ - char const* text = "{}"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"({ })"); -} - -BOOST_AUTO_TEST_CASE(mload) -{ - char const* text = "@0"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(@ 0)"); - - BOOST_CHECK(successParse("@0x0")); - BOOST_CHECK(successParse("@symbol")); - BOOST_CHECK(!successParse("@")); -} - -BOOST_AUTO_TEST_CASE(sload) -{ - char const* text = "@@0"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(@@ 0)"); - - BOOST_CHECK(successParse("@@0x0")); - BOOST_CHECK(successParse("@@symbol")); - BOOST_CHECK(!successParse("@@")); -} - -BOOST_AUTO_TEST_CASE(mstore) -{ - char const* text = "[0]:0"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"([ 0 ] 0)"); - - BOOST_CHECK(successParse("[0] 0")); - BOOST_CHECK(successParse("[0x0]:0x0")); - BOOST_CHECK(successParse("[symbol]:symbol")); - BOOST_CHECK(!successParse("[]")); - BOOST_CHECK(!successParse("[0]")); -} - -BOOST_AUTO_TEST_CASE(sstore) -{ - char const* text = "[[0]]:0"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"([[ 0 ]] 0)"); - - BOOST_CHECK(successParse("[[0]] 0")); - BOOST_CHECK(successParse("[[0x0]]:0x0")); - BOOST_CHECK(successParse("[[symbol]]:symbol")); - BOOST_CHECK(!successParse("[[]]")); - BOOST_CHECK(!successParse("[[0x0]]")); -} - -BOOST_AUTO_TEST_CASE(calldataload) -{ - char const* text = "$0"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"($ 0)"); - - BOOST_CHECK(successParse("$0x0")); - BOOST_CHECK(successParse("$symbol")); - BOOST_CHECK(!successParse("$")); -} - -BOOST_AUTO_TEST_CASE(list) -{ - char const* text = "( 1234 )"; - BOOST_CHECK(successParse(text)); - BOOST_CHECK_EQUAL(parse(text), R"(( 1234 ))"); - - BOOST_CHECK(successParse("( 1234 5467 )")); - BOOST_CHECK(successParse("()")); -} - -BOOST_AUTO_TEST_CASE(macro_with_zero_args) -{ - char const* text = "(def 'zeroargs () (asm INVALID))"; - BOOST_CHECK(successParse(text)); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // end namespaces From 8d7fd90593bf99519f9a0b7b23867a90b1146577 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 10 Dec 2019 23:51:19 +0000 Subject: [PATCH 013/277] Remove LLL from docs --- docs/index.rst | 1 - docs/lll.rst | 21 --------------------- 2 files changed, 22 deletions(-) delete mode 100644 docs/lll.rst diff --git a/docs/index.rst b/docs/index.rst index 199243018..6610b5217 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -104,4 +104,3 @@ Contents common-patterns.rst bugs.rst contributing.rst - lll.rst diff --git a/docs/lll.rst b/docs/lll.rst deleted file mode 100644 index 16be829e6..000000000 --- a/docs/lll.rst +++ /dev/null @@ -1,21 +0,0 @@ -### -LLL -### - -.. _lll: - -LLL is a low-level language for the EVM with an s-expressions syntax. - -The Solidity repository contains an LLL compiler, which shares the assembler subsystem with Solidity. -However, apart from maintaining that it still compiles, no other improvements are made to it. - -It is not built unless specifically requested: - -.. code-block:: bash - - $ cmake -DLLL=ON .. - $ cmake --build . - -.. warning:: - - The LLL codebase is deprecated and will be removed from the Solidity repository in the future. From 09a909905717b96b9b520c169f004b852b2c5aa0 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Jan 2020 15:42:42 +0000 Subject: [PATCH 014/277] Mention in changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 116e6f7a3..abc62fc80 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: + * LLL: The LLL compiler has been removed. * General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon). * General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing. * Yul Optimizer: Apply penalty when trying to rematerialize into loops. From f35e2bde31015f19113239384907942ecb911819 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 13 Jan 2020 15:46:52 +0000 Subject: [PATCH 015/277] Remove LLL from Circleci --- .circleci/config.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c2e64a764..868b292b2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -295,7 +295,6 @@ jobs: environment: CC: clang CXX: clang++ - CMAKE_OPTIONS: -DLLL=ON steps: - checkout - run: *run_build @@ -305,8 +304,6 @@ jobs: b_ubu: &build_ubuntu1904 docker: - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> - environment: - CMAKE_OPTIONS: -DLLL=ON steps: - checkout - run: *run_build @@ -317,13 +314,12 @@ jobs: <<: *build_ubuntu1904 environment: FORCE_RELEASE: ON - CMAKE_OPTIONS: -DLLL=ON b_ubu18: &build_ubuntu1804 docker: - image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.docker-image-rev >> environment: - CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 -DLLL=ON + CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 CMAKE_BUILD_TYPE: RelWithDebugInfo steps: - checkout @@ -368,7 +364,7 @@ jobs: <<: *build_ubuntu1904 environment: CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF -DLLL=ON + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF steps: - checkout - run: *run_build @@ -408,7 +404,6 @@ jobs: - image: archlinux/base environment: TERM: xterm - CMAKE_OPTIONS: -DLLL=ON steps: - run: name: Install build dependencies @@ -425,7 +420,6 @@ jobs: environment: TERM: xterm CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DLLL=ON steps: - checkout - restore_cache: From 106ac296b90fc9b0f694a6131bb1d1cea5ecac46 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 15 Jan 2020 12:27:07 +0000 Subject: [PATCH 016/277] Remove LLL from release scripts --- scripts/release.sh | 1 - scripts/release_ppa.sh | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/release.sh b/scripts/release.sh index ebc7759f5..a2f2fa875 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -39,7 +39,6 @@ mkdir -p $ZIP_TEMP_DIR # Copy all the solidity executables into a temporary directory prior to ZIP creation -cp $REPO_ROOT/build/lllc/lllc $ZIP_TEMP_DIR cp $REPO_ROOT/build/solc/solc $ZIP_TEMP_DIR # For macOS, we run a fix-up script which alters all of the symbolic links within diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 90ef3c943..5b5ca728f 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -156,7 +156,6 @@ Package: solc Architecture: any-i386 any-amd64 Multi-Arch: same Depends: \${shlibs:Depends}, \${misc:Depends} -Replaces: lllc (<< 1:0.3.6) Conflicts: libethereum (<= 1.2.9) Description: Solidity compiler. The commandline interface to the Solidity smart contract compiler. @@ -194,7 +193,7 @@ override_dh_shlibdeps: dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info override_dh_auto_configure: - dh_auto_configure -- -DINSTALL_LLLC=Off -DTESTS=OFF ${CMAKE_OPTIONS} + dh_auto_configure -- -DTESTS=OFF ${CMAKE_OPTIONS} EOF cat < debian/copyright Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ From 44e892634bb60f295509b775064e6a958b764f1e Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 13 Jan 2020 16:14:18 +0100 Subject: [PATCH 017/277] Use Python 3 instead of Python 2 (EOL'd) --- scripts/bytecodecompare/prepare_report.py | 2 +- scripts/deps-ppa/static_z3.sh | 2 +- scripts/extract_test_cases.py | 2 +- scripts/fix_homebrew_paths_in_standalone_zip.py | 3 +-- scripts/isolate_tests.py | 9 +++++---- scripts/release.sh | 2 +- scripts/run_proofs.sh | 2 +- scripts/splitSources.py | 4 ++-- scripts/update_bugs_by_version.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index 41878bb86..847e3ee0e 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import glob diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh index b38af9e9c..5a860bf7f 100755 --- a/scripts/deps-ppa/static_z3.sh +++ b/scripts/deps-ppa/static_z3.sh @@ -76,7 +76,7 @@ Build-Depends: debhelper (>= 9.0.0), git, libgmp-dev, dh-python, - python + python3 Standards-Version: 3.9.6 Homepage: https://github.com/Z3Prover/z3 Vcs-Git: git://github.com/Z3Prover/z3.git diff --git a/scripts/extract_test_cases.py b/scripts/extract_test_cases.py index 47c53f3c8..e6764b466 100755 --- a/scripts/extract_test_cases.py +++ b/scripts/extract_test_cases.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # # This script reads C++ or RST source files and writes all # multi-line strings into individual files. diff --git a/scripts/fix_homebrew_paths_in_standalone_zip.py b/scripts/fix_homebrew_paths_in_standalone_zip.py index 38aa9685d..980ba1a88 100755 --- a/scripts/fix_homebrew_paths_in_standalone_zip.py +++ b/scripts/fix_homebrew_paths_in_standalone_zip.py @@ -43,7 +43,6 @@ import os import subprocess import sys - def readDependencies(fname): with open(fname) as f: o = subprocess.Popen(['otool', '-L', fname], stdout=subprocess.PIPE) @@ -55,7 +54,7 @@ def readDependencies(fname): command = "install_name_tool -change " + \ library + " @executable_path/./" + \ os.path.basename(library) + " " + fname - print command + print(command) os.system("chmod +w " + fname) os.system(command) diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index 9492ee137..e1a9da46c 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # # This script reads C++ or RST source files and writes all # multi-line strings into individual files. @@ -13,7 +13,7 @@ import hashlib from os.path import join, isfile def extract_test_cases(path): - lines = open(path, 'rb').read().splitlines() + lines = open(path, 'r').read().splitlines() inside = False delimiter = '' @@ -43,7 +43,7 @@ def extract_docs_cases(path): tests = [] # Collect all snippets of indented blocks - for l in open(path, 'rb').read().splitlines(): + for l in open(path, 'r').read().splitlines(): if l != '': if not inside and l.startswith(' '): # start new test @@ -71,7 +71,8 @@ def write_cases(f, tests): # When code examples are extracted they indented by 8 spaces, which violates the style guide, # so before checking remove 4 spaces from each line. remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE) - open('test_%s_%s.sol' % (hashlib.sha256(test).hexdigest(), cleaned_filename), 'wb').write(remainder) + sol_filename = 'test_%s_%s.sol' % (hashlib.sha256(test.encode("utf-8")).hexdigest(), cleaned_filename) + open(sol_filename, 'w').write(remainder) def extract_and_write(f, path): if docs: diff --git a/scripts/release.sh b/scripts/release.sh index ebc7759f5..7d6eee85a 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -49,7 +49,7 @@ cp $REPO_ROOT/build/solc/solc $ZIP_TEMP_DIR # being for kernel-level dylibs. if [[ "$OSTYPE" == "darwin"* ]]; then - python $REPO_ROOT/scripts/fix_homebrew_paths_in_standalone_zip.py $ZIP_TEMP_DIR + python3 $REPO_ROOT/scripts/fix_homebrew_paths_in_standalone_zip.py $ZIP_TEMP_DIR fi # And ZIP it all up, with a filename suffix passed in on the command-line. diff --git a/scripts/run_proofs.sh b/scripts/run_proofs.sh index 3a89d6b70..bc6cd8e8a 100755 --- a/scripts/run_proofs.sh +++ b/scripts/run_proofs.sh @@ -12,7 +12,7 @@ for new_proof in $(git diff origin/develop --name-only test/formal/) do set +e echo "Proving $new_proof..." - output=$(python "$new_proof") + output=$(python3 "$new_proof") result=$? set -e diff --git a/scripts/splitSources.py b/scripts/splitSources.py index 0f6f1f86d..dd6d62eb3 100755 --- a/scripts/splitSources.py +++ b/scripts/splitSources.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # # This script reads a syntaxTest file and writes all # sources into their own files. If one source-name specifies subdirectories @@ -57,6 +57,6 @@ if __name__ == '__main__': srcString = "" for src in createdSources: srcString += src + ' ' - print srcString + print(srcString) else: sys.exit(1) diff --git a/scripts/update_bugs_by_version.py b/scripts/update_bugs_by_version.py index 655ffe233..23b59c01b 100755 --- a/scripts/update_bugs_by_version.py +++ b/scripts/update_bugs_by_version.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # # This script is used to generate the list of bugs per compiler version # from the list of bugs. From 54b81b0fda8c88fbcb5f85a48e19a897f30a21de Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 15 Jan 2020 14:21:33 +0100 Subject: [PATCH 018/277] python scripts: Some pylint tweaks. --- scripts/bytecodecompare/prepare_report.py | 20 +++++----- scripts/extract_test_cases.py | 46 ++++++++++------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index 847e3ee0e..9a3d86c8d 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -5,37 +5,37 @@ import glob import subprocess import json -solc = sys.argv[1] -report = open("report.txt", "wb") +SOLC_BIN = sys.argv[1] +REPORT_FILE = open("report.txt", "wb") for optimize in [False, True]: for f in sorted(glob.glob("*.sol")): sources = {} sources[f] = {'content': open(f, 'r').read()} - input = { + input_json = { 'language': 'Solidity', 'sources': sources, 'settings': { 'optimizer': { 'enabled': optimize }, - 'outputSelection': { '*': { '*': ['evm.bytecode.object', 'metadata'] } } + 'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}} } } - args = [solc, '--standard-json'] + args = [SOLC_BIN, '--standard-json'] if optimize: args += ['--optimize'] proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = proc.communicate(json.dumps(input)) + (out, err) = proc.communicate(json.dumps(input_json)) try: result = json.loads(out.strip()) for filename in sorted(result['contracts'].keys()): for contractName in sorted(result['contracts'][filename].keys()): contractData = result['contracts'][filename][contractName] if 'evm' in contractData and 'bytecode' in contractData['evm']: - report.write(filename + ':' + contractName + ' ' + contractData['evm']['bytecode']['object'] + '\n') + REPORT_FILE.write(filename + ':' + contractName + ' ' + contractData['evm']['bytecode']['object'] + '\n') else: - report.write(filename + ':' + contractName + ' NO BYTECODE\n') - report.write(filename + ':' + contractName + ' ' + contractData['metadata'] + '\n') + REPORT_FILE.write(filename + ':' + contractName + ' NO BYTECODE\n') + REPORT_FILE.write(filename + ':' + contractName + ' ' + contractData['metadata'] + '\n') except KeyError: - report.write(f + ": ERROR\n") + REPORT_FILE.write(f + ": ERROR\n") diff --git a/scripts/extract_test_cases.py b/scripts/extract_test_cases.py index e6764b466..63314760c 100755 --- a/scripts/extract_test_cases.py +++ b/scripts/extract_test_cases.py @@ -8,12 +8,9 @@ import sys import re -import os -import hashlib -from os.path import join -def extract_test_cases(path): - lines = open(path, 'rb').read().splitlines() +def extract_test_cases(_path): + lines = open(_path, 'rb').read().splitlines() inside = False delimiter = '' @@ -23,27 +20,24 @@ def extract_test_cases(path): test_name = '' for l in lines: - if inside: - if l.strip().endswith(')' + delimiter + '";'): - open('%03d_%s.sol' % (ctr, test_name), 'wb').write(test) - ctr += 1 - inside = False - test = '' + if inside: + if l.strip().endswith(')' + delimiter + '";'): + open('%03d_%s.sol' % (ctr, test_name), 'wb').write(test) + ctr += 1 + inside = False + test = '' + else: + l = re.sub('^\t\t', '', l) + l = l.replace('\t', ' ') + test += l + '\n' else: - l = re.sub('^\t\t', '', l) - l = l.replace('\t', ' ') - test += l + '\n' - else: - m = re.search(r'BOOST_AUTO_TEST_CASE\(([^(]*)\)', l.strip()) - if m: - test_name = m.group(1) - m = re.search(r'R"([^(]*)\($', l.strip()) - if m: - inside = True - delimiter = m.group(1) - + m = re.search(r'BOOST_AUTO_TEST_CASE\(([^(]*)\)', l.strip()) + if m: + test_name = m.group(1) + m = re.search(r'R"([^(]*)\($', l.strip()) + if m: + inside = True + delimiter = m.group(1) if __name__ == '__main__': - path = sys.argv[1] - extract_test_cases(path) - + extract_test_cases(sys.argv[1]) From 9c19b93c3735c45ef995a96f0a7bdd022cc36282 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 15 Jan 2020 12:55:30 +0000 Subject: [PATCH 019/277] Remove LLL specific features from libevmasm --- libevmasm/Assembly.cpp | 49 +----------------------------------- libevmasm/Assembly.h | 13 ---------- test/libevmasm/Assembler.cpp | 9 ++----- 3 files changed, 3 insertions(+), 68 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ad4263869..adc7f5b14 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -38,48 +38,6 @@ using namespace solidity::evmasm; using namespace solidity::langutil; using namespace solidity::util; -void Assembly::append(Assembly const& _a) -{ - auto newDeposit = m_deposit + _a.deposit(); - for (AssemblyItem i: _a.m_items) - { - switch (i.type()) - { - case Tag: - case PushTag: - i.setData(i.data() + m_usedTags); - break; - case PushSub: - case PushSubSize: - i.setData(i.data() + m_subs.size()); - break; - default: - break; - } - append(i); - } - m_deposit = newDeposit; - m_usedTags += _a.m_usedTags; - // This does not transfer the names of named tags on purpose. The tags themselves are - // transferred, but their names are only available inside the assembly. - for (auto const& i: _a.m_data) - m_data.insert(i); - for (auto const& i: _a.m_strings) - m_strings.insert(i); - m_subs += _a.m_subs; - for (auto const& lib: _a.m_libraries) - m_libraries.insert(lib); -} - -void Assembly::append(Assembly const& _a, int _deposit) -{ - assertThrow(_deposit <= _a.m_deposit, InvalidDeposit, ""); - - append(_a); - while (_deposit++ < _a.m_deposit) - append(Instruction::POP); -} - AssemblyItem const& Assembly::append(AssemblyItem const& _i) { assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); @@ -88,12 +46,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i) if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) m_items.back().setLocation(m_currentSourceLocation); m_items.back().m_modifierDepth = m_currentModifierDepth; - return back(); -} - -void Assembly::injectStart(AssemblyItem const& _i) -{ - m_items.insert(m_items.begin(), _i); + return m_items.back(); } unsigned Assembly::bytesRequired(unsigned subTagSize) const diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 9e146be01..e859951bd 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -56,7 +56,6 @@ public: AssemblyItem newPushLibraryAddress(std::string const& _identifier); AssemblyItem const& append(AssemblyItem const& _i); - AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } template Assembly& operator<<(T const& _d) { append(_d); return *this; } @@ -137,18 +136,6 @@ public: StringMap const& _sourceCodes = StringMap() ) const; -public: - // These features are only used by LLL - AssemblyItem newPushString(std::string const& _data) { util::h256 h(util::keccak256(_data)); m_strings[h] = _data; return AssemblyItem(PushString, h); } - - void append(Assembly const& _a); - void append(Assembly const& _a, int _deposit); - - void injectStart(AssemblyItem const& _i); - - AssemblyItem const& back() const { return m_items.back(); } - std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((util::h256)m_items.back().data()) : std::string(); } - protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. Also takes an argument containing the tags of this assembly diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index f03c9fbb9..31b30dd45 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -74,8 +74,6 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) _assembly.appendLibraryAddress("someLibrary"); // PushTag + Operation _assembly.appendJump(tag); - // PushString - _assembly.append("Unused feature for pushing string"); // PushData _assembly.append(bytes{0x1, 0x2, 0x3, 0x4}); // PushSubSize @@ -93,9 +91,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) BOOST_CHECK_EQUAL( _assembly.assemble().toHex(), - "5b6001600220606773__$bf005014d9d0f534b8fcb268bd84c491a2$__" - "6000567f556e75736564206665617475726520666f722070757368696e" - "6720737472696e605f6001605e73000000000000000000000000000000000000000000fe" + "5b6001600220604673__$bf005014d9d0f534b8fcb268bd84c491a2$__" + "600056603e6001603d73000000000000000000000000000000000000000000fe" "fe010203044266eeaa" ); BOOST_CHECK_EQUAL( @@ -106,7 +103,6 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) " bytecodeSize\n" " linkerSymbol(\"bf005014d9d0f534b8fcb268bd84c491a2380f4acd260d1ccfe9cd8201f7e994\")\n" " jump(tag_1)\n" - " data_027497964124140851e8a9992ba16b5c1aaf9730b78d6036c8d65e3bb5ea4c8f\n" " data_a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b\n" " dataSize(sub_0)\n" " dataOffset(sub_0)\n" @@ -133,7 +129,6 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) "{\"begin\":1,\"end\":3,\"name\":\"PUSHLIB\",\"value\":\"someLibrary\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH [tag]\",\"value\":\"1\"}," "{\"begin\":1,\"end\":3,\"name\":\"JUMP\"}," - "{\"begin\":1,\"end\":3,\"name\":\"PUSH tag\",\"value\":\"Unused feature for pushing string\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH data\",\"value\":\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," From eafcb42be78ed768a67847307a9cdda34f639b5e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 12 Dec 2019 23:25:12 +0000 Subject: [PATCH 020/277] Enable more compiler warnings in the build system "-pedantic -Wno-unknown-pragmas -Wimplicit-fallthrough" --- cmake/EthCompilerSettings.cmake | 3 +++ libevmasm/SimplificationRule.h | 4 ++-- libsolidity/ast/Types.cpp | 2 +- test/libsolutil/CommonData.cpp | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 18b4e9fe5..4d73a52cf 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -34,6 +34,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-Wall) add_compile_options(-Wextra) add_compile_options(-Werror) + add_compile_options(-pedantic) + add_compile_options(-Wno-unknown-pragmas) + add_compile_options(-Wimplicit-fallthrough) # Configuration-specific compiler settings. set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG") diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index 3e40e25ed..1fa1d8ea8 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -65,7 +65,7 @@ struct EVMBuiltins template constexpr Pattern operator()(Args&&... _args) const { return {inst, {std::forward(_args)...}}; - }; + } }; struct PatternGeneratorInstance @@ -74,7 +74,7 @@ struct EVMBuiltins template constexpr Pattern operator()(Args&&... _args) const { return {instruction, {std::forward(_args)...}}; - }; + } }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f3f575a18..42ce6375d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2354,7 +2354,7 @@ string EnumType::canonicalName() const size_t EnumType::numberOfMembers() const { return m_enum.members().size(); -}; +} BoolResult EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { diff --git a/test/libsolutil/CommonData.cpp b/test/libsolutil/CommonData.cpp index e2687b53b..63bd6bbd7 100644 --- a/test/libsolutil/CommonData.cpp +++ b/test/libsolutil/CommonData.cpp @@ -29,7 +29,7 @@ using namespace std; using namespace solidity::frontend; // TODO: Fix Boost... -BOOST_TEST_DONT_PRINT_LOG_VALUE(solidity::bytes); +BOOST_TEST_DONT_PRINT_LOG_VALUE(solidity::bytes) namespace solidity::util::test { From 1d7f25e809ec00dc622028cca34811f70e288e2d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 15 Jan 2020 14:22:47 +0000 Subject: [PATCH 021/277] Remove unnecessary statement --- libsolidity/codegen/LValue.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index a7eb0aaf3..5fd17bf5c 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -340,7 +340,6 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc { solAssert(sourceType.location() == DataLocation::Memory, ""); // stack layout: source_ref target_ref - TypePointer sourceMemberType = sourceType.memberType(member.name); m_context << sourceType.memoryOffsetOfMember(member.name); m_context << Instruction::DUP3 << Instruction::ADD; MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true); From c450b186738d9e145ff5a5224ae233c406f2f670 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 15 Jan 2020 17:25:08 +0100 Subject: [PATCH 022/277] Remove remaining instances of new. --- libsolidity/analysis/ControlFlowGraph.cpp | 2 +- libsolidity/ast/Types.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp index 244321abc..4cb096ae0 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -47,6 +47,6 @@ FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const CFGNode* CFG::NodeContainer::newNode() { - m_nodes.emplace_back(new CFGNode()); + m_nodes.emplace_back(std::make_unique()); return m_nodes.back().get(); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f3f575a18..b31009688 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -210,7 +210,7 @@ pair const* MemberList::memberStorageOffset(string const& _name) memberTypes.reserve(m_memberTypes.size()); for (auto const& member: m_memberTypes) memberTypes.push_back(member.type); - m_storageOffsets.reset(new StorageOffsets()); + m_storageOffsets = std::make_unique(); m_storageOffsets->computeOffsets(memberTypes); } for (size_t index = 0; index < m_memberTypes.size(); ++index) From ae67cbde7de57d552949a3e118641e7ea74c5ffb Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 15 Jan 2020 18:18:16 +0100 Subject: [PATCH 023/277] Fixes missing alignment output in isoltest. --- test/libsolidity/util/BytesUtils.cpp | 12 +++++++++--- test/libsolidity/util/BytesUtils.h | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index 751318f9c..6377fbe2e 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -158,12 +158,15 @@ string BytesUtils::formatBoolean(bytes const& _bytes) return os.str(); } -string BytesUtils::formatHex(bytes const& _bytes) +string BytesUtils::formatHex(bytes const& _bytes, bool _shorten) { soltestAssert(!_bytes.empty() && _bytes.size() <= 32, ""); u256 value = fromBigEndian(_bytes); + string output = toCompactHexWithPrefix(value); - return toCompactHexWithPrefix(value); + if (_shorten) + return output.substr(0, output.size() - countRightPaddedZeros(_bytes) * 2); + return output; } string BytesUtils::formatHexString(bytes const& _bytes) @@ -258,7 +261,7 @@ string BytesUtils::formatBytes( os << formatBoolean(_bytes); break; case ABIType::Hex: - os << formatHex(_bytes); + os << formatHex(_bytes, _abiType.alignDeclared); break; case ABIType::HexString: os << formatHexString(_bytes); @@ -271,6 +274,9 @@ string BytesUtils::formatBytes( case ABIType::None: break; } + + if (_abiType.alignDeclared) + return (_abiType.align == ABIType::AlignLeft ? "left(" : "right(") + os.str() + ")"; return os.str(); } diff --git a/test/libsolidity/util/BytesUtils.h b/test/libsolidity/util/BytesUtils.h index e97aa90c9..5ec9f3271 100644 --- a/test/libsolidity/util/BytesUtils.h +++ b/test/libsolidity/util/BytesUtils.h @@ -80,7 +80,8 @@ public: /// Converts \param _bytes to a soltest-compliant and human-readable /// string representation of a byte array which is assumed to hold /// a hex value. - static std::string formatHex(bytes const& _bytes); + /// The _shorten flag is used to trim leading and trailing zeros. + static std::string formatHex(bytes const& _bytes, bool _shorten = false); /// Converts \param _bytes to a soltest-compliant and human-readable /// string representation of a byte array which is assumed to hold From b69664e7fd7825d9f61cc2abc394d4021cca5db2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 15 Jan 2020 18:34:38 +0100 Subject: [PATCH 024/277] Only activate Yul optimizer if ``--optimize`` is given. --- Changelog.md | 2 +- solc/CommandLineInterface.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 10b553587..45fe4e639 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,7 +11,7 @@ Compiler Features: * Yul Optimizer: Apply penalty when trying to rematerialize into loops. Bugfixes: - + * Commandline interface: Only activate yul optimizer if ``--optimize`` is given. Build System: * Switch to building soljson.js with an embedded base64-encoded wasm binary. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index de7814785..1598bbb68 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -1113,7 +1113,8 @@ bool CommandLineInterface::processInput() OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal(); settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); - settings.runYulOptimiser = !m_args.count(g_strNoOptimizeYul); + if (m_args.count(g_strNoOptimizeYul)) + settings.runYulOptimiser = false; settings.optimizeStackAllocation = settings.runYulOptimiser; m_compiler->setOptimiserSettings(settings); From 3c88d295b3a5086bc5fbca042785713ddd811963 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 14 Dec 2019 13:38:44 +0000 Subject: [PATCH 025/277] Remove FixedHash(unsigned) constructor --- libsolutil/FixedHash.h | 3 --- test/contracts/FixedFeeRegistrar.cpp | 2 +- test/tools/yulInterpreter/Interpreter.cpp | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/libsolutil/FixedHash.h b/libsolutil/FixedHash.h index 90c0ab4dc..2be702180 100644 --- a/libsolutil/FixedHash.h +++ b/libsolutil/FixedHash.h @@ -63,9 +63,6 @@ public: /// Convert from the corresponding arithmetic type. FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); } - /// Convert from unsigned - explicit FixedHash(unsigned _u) { toBigEndian(_u, m_data); } - /// Explicitly construct, copying from a byte array. explicit FixedHash(bytes const& _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } } diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index a0b1d2a8f..9535c1907 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(reserve) BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee + 1, encodeDyn(name[1])) == encodeArgs()); BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[1])) == encodeArgs(h256(account(0), h256::AlignRight))); BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee - 1, encodeDyn(name[2])) == encodeArgs()); - BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[2])) == encodeArgs(h256(0))); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[2])) == encodeArgs(h256{})); } BOOST_AUTO_TEST_CASE(double_reserve) diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 4d28633f6..cbfdbbe52 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -60,7 +60,7 @@ void InterpreterState::dumpTraceAndState(ostream& _out) const _out << " " << std::uppercase << std::hex << std::setw(4) << offset << ": " << h256(value).hex() << endl; _out << "Storage dump:" << endl; for (auto const& slot: storage) - if (slot.second != h256(0)) + if (slot.second != h256{}) _out << " " << slot.first.hex() << ": " << slot.second.hex() << endl; } From 4d8758fcc338ad01c6f8324419ec57013ebee5ae Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 14 Dec 2019 13:42:04 +0000 Subject: [PATCH 026/277] Remove operator~ from FixedHash --- libsolutil/FixedHash.h | 2 -- test/contracts/Wallet.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libsolutil/FixedHash.h b/libsolutil/FixedHash.h index 2be702180..d36f61e11 100644 --- a/libsolutil/FixedHash.h +++ b/libsolutil/FixedHash.h @@ -84,8 +84,6 @@ public: /// Required to sort objects of this type or use them as map keys. bool operator<(FixedHash const& _c) const { for (unsigned i = 0; i < N; ++i) if (m_data[i] < _c.m_data[i]) return true; else if (m_data[i] > _c.m_data[i]) return false; return false; } - FixedHash operator~() const { FixedHash ret; for (unsigned i = 0; i < N; ++i) ret[i] = ~m_data[i]; return ret; } - /// @returns a particular byte from the hash. uint8_t& operator[](unsigned _i) { return m_data[_i]; } /// @returns a particular byte from the hash. diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 2051c3057..1ec71665a 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -465,7 +465,7 @@ BOOST_AUTO_TEST_CASE(creation) deployWallet(200); BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); bool v2 = solidity::test::Options::get().useABIEncoderV2; - BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == (v2 ? encodeArgs() : encodeArgs(false))); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(~0)) == (v2 ? encodeArgs() : encodeArgs(false))); } BOOST_AUTO_TEST_CASE(add_owners) From b3d2413a1ff0cb1bac0d6e00a2c9166969dad918 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 13 Dec 2019 00:06:28 +0000 Subject: [PATCH 027/277] Remove unused features of FixedHash --- libsolutil/FixedHash.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/libsolutil/FixedHash.h b/libsolutil/FixedHash.h index d36f61e11..f5a042d4c 100644 --- a/libsolutil/FixedHash.h +++ b/libsolutil/FixedHash.h @@ -75,9 +75,6 @@ public: /// Convert to arithmetic type. operator Arith() const { return fromBigEndian(m_data); } - /// @returns true iff this is the empty hash. - explicit operator bool() const { return std::any_of(m_data.begin(), m_data.end(), [](uint8_t _b) { return _b != 0; }); } - // The obvious comparison operators. bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; } bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; } @@ -107,12 +104,6 @@ public: /// @returns a copy of the object's data as a byte vector. bytes asBytes() const { return bytes(data(), data() + N); } - /// @returns a mutable reference to the object's data as an STL array. - std::array& asArray() { return m_data; } - - /// @returns a constant reference to the object's data as an STL array. - std::array const& asArray() const { return m_data; } - /// Returns the index of the first bit set to one, or size() * 8 if no bits are set. inline unsigned firstBitSet() const { @@ -129,8 +120,6 @@ public: return ret; } - void clear() { m_data.fill(0); } - private: std::array m_data; ///< The binary data. }; From 22466acf39c41c168a9a24b05b2f00f48cbfa30e Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 9 Jan 2020 00:20:27 +0100 Subject: [PATCH 028/277] Experimental wasm rebuild scripts --- .../docker-scripts/genbytecode.sh | 98 ++++++++ .../docker-scripts/isolate_tests.py | 55 ++++ scripts/wasm-rebuild/docker-scripts/patch.sh | 7 + .../docker-scripts/rebuild_current.sh | 67 +++++ .../docker-scripts/rebuild_tags.sh | 238 ++++++++++++++++++ scripts/wasm-rebuild/rebuild.sh | 28 +++ 6 files changed, 493 insertions(+) create mode 100755 scripts/wasm-rebuild/docker-scripts/genbytecode.sh create mode 100755 scripts/wasm-rebuild/docker-scripts/isolate_tests.py create mode 100755 scripts/wasm-rebuild/docker-scripts/patch.sh create mode 100755 scripts/wasm-rebuild/docker-scripts/rebuild_current.sh create mode 100755 scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh create mode 100755 scripts/wasm-rebuild/rebuild.sh diff --git a/scripts/wasm-rebuild/docker-scripts/genbytecode.sh b/scripts/wasm-rebuild/docker-scripts/genbytecode.sh new file mode 100755 index 000000000..ef28d59a4 --- /dev/null +++ b/scripts/wasm-rebuild/docker-scripts/genbytecode.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Script used for cross-platform comparison as part of the travis automation. +# Splits all test source code into multiple files, generates bytecode and +# uploads the bytecode into github.com/ethereum/solidity-test-bytecode where +# another travis job is triggered to do the actual comparison. +# +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2017 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +SCRIPTDIR=$(dirname "$0") +SCRIPTDIR=$(realpath "${SCRIPTDIR}") + + +echo "Compiling all test contracts into bytecode..." +TMPDIR=$(mktemp -d) +( + cd "${TMPDIR}" + "${SCRIPTDIR}/isolate_tests.py" /src/test/ + + cat > solc < /tmp/report.txt +) +rm -rf "$TMPDIR" diff --git a/scripts/wasm-rebuild/docker-scripts/isolate_tests.py b/scripts/wasm-rebuild/docker-scripts/isolate_tests.py new file mode 100755 index 000000000..eab461bff --- /dev/null +++ b/scripts/wasm-rebuild/docker-scripts/isolate_tests.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python2 + +import sys +import re +import os +import hashlib +from os.path import join, isfile + + +def extract_test_cases(path): + lines = open(path, 'rb').read().splitlines() + + inside = False + delimiter = '' + tests = [] + + for l in lines: + if inside: + if l.strip().endswith(')' + delimiter + '";'): + tests[-1] += l.strip()[:-(3 + len(delimiter))] + inside = False + else: + tests[-1] += l + '\n' + else: + m = re.search(r'R"([^(]*)\((.*)$', l.strip()) + if m: + inside = True + delimiter = m.group(1) + tests += [m.group(2)] + + return tests + +def extract_and_write(f, path): + if f.endswith('.sol'): + cases = [open(path, 'r').read()] + else: + cases = extract_test_cases(path) + write_cases(f, cases) + +def write_cases(f, tests): + cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower() + for test in tests: + remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE) + open('test_%s_%s.sol' % (hashlib.sha256(test).hexdigest(), cleaned_filename), 'w').write(remainder) + + +if __name__ == '__main__': + path = sys.argv[1] + + for root, subdirs, files in os.walk(path): + if '_build' in subdirs: + subdirs.remove('_build') + for f in files: + path = join(root, f) + extract_and_write(f, path) diff --git a/scripts/wasm-rebuild/docker-scripts/patch.sh b/scripts/wasm-rebuild/docker-scripts/patch.sh new file mode 100755 index 000000000..a03fc73d0 --- /dev/null +++ b/scripts/wasm-rebuild/docker-scripts/patch.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +TAG="$1" +SOLJSON_JS="$2" + +# If we ever want to patch the binaries e.g. for compatibility with older solc-js versions, +# we can do that here. diff --git a/scripts/wasm-rebuild/docker-scripts/rebuild_current.sh b/scripts/wasm-rebuild/docker-scripts/rebuild_current.sh new file mode 100755 index 000000000..b48b37fc5 --- /dev/null +++ b/scripts/wasm-rebuild/docker-scripts/rebuild_current.sh @@ -0,0 +1,67 @@ +#!/bin/bash -e + +# Do not call this script directly. + +# This script is expected to be run inside the docker image trzeci/emscripten:sdk-tag-1.39.3-64bit and +# be called by ./rebuild_tags.sh. + +echo "========== STAGE 1: PREPARE ========== ($(date))" +COMMIT_DATE="$(git show -s --format=%cI HEAD)" +git rev-parse --short=8 HEAD >commit_hash.txt +echo -e "" >prerelease.txt +sed -i -e 's/-Wl,--gc-sections//' cmake/EthCompilerSettings.cmake +echo "set(CMAKE_CXX_FLAGS \"\${CMAKE_CXX_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap','addFunction','removeFunction','UTF8ToString','lengthBytesUTF8','_malloc','stringToUTF8','setValue'] -s WASM=1 -s WASM_ASYNC_COMPILATION=0 -s SINGLE_FILE=1 -Wno-almost-asm\")" >>cmake/EthCompilerSettings.cmake +# Needed for < 0.5.0. +sed -i -e 's/-Werror/-Wno-error/' cmake/EthCompilerSettings.cmake + +echo "========== STAGE 2: BUILD ========== ($(date))" +scripts/travis-emscripten/install_deps.sh +if [ -d cryptopp ]; then + # Needed for < 0.4.4. Will not affect >= 0.4.5. + # Unfortunately we need to update to the latest + # release in the 5.6 series for it to build. + # Hopefully we don't miss any bugs. + rm -rf cryptopp + git clone https://github.com/weidai11/cryptopp/ + ( + set -e + cd cryptopp + git checkout CRYPTOPP_5_6_5 + ln -s . src + ) +fi +if [ -d jsoncpp ]; then + # Needed for < 0.4.4. Will not affect >= 0.4.5. + ( + set -e + cd jsoncpp + # Checkout the latest commit at the time of our release. + git checkout $(git rev-list -1 --before=$COMMIT_DATE master) + ) +fi + +set +e +scripts/travis-emscripten/build_emscripten.sh +set -e + +mkdir -p upload + +if [ ! -f upload/soljson.js ]; then + if [ -f build/solc/soljson.js ]; then + cp build/solc/soljson.js upload + elif [ -f build/libsolc/soljson.js ]; then + cp build/libsolc/soljson.js upload + elif [ -f emscripten_build/solc/soljson.js ]; then + cp emscripten_build/solc/soljson.js upload + elif [ -f emscripten_build/libsolc/soljson.js ]; then + cp emscripten_build/libsolc/soljson.js upload + fi +fi + +if [ -f upload/soljson.js ]; then + echo "========== SUCCESS ========== ($(date))" + exit 0 +else + echo "========== FAILURE ========== ($(date))" + exit 1 +fi diff --git a/scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh b/scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh new file mode 100755 index 000000000..6a3065bca --- /dev/null +++ b/scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh @@ -0,0 +1,238 @@ +#!/bin/bash -e + +# This script is expected to be run inside the docker image trzeci/emscripten:sdk-tag-1.39.3-64bit. +# Its main purpose is to be called by ../rebuild.sh. + +# Usage: $0 [tagFilter] [outputDirectory] + +# The output directory must be outside the repository, +# since the script will prune the repository directory after +# each build. + +TAG_FILTER="$1" +OUTPUTDIR="$2" +RETEST=0 +shift +shift +while (( "$#" )); do + if [[ "$1" == "--retest" ]]; then + RETEST=1 + else + echo "Unrecognized option: $1" + exit 1 + fi + shift +done + +SOLIDITY_REPO_URL="https://github.com/ethereum/solidity" +SOLC_JS_REPO_URL="https://github.com/ethereum/solc-js" +SOLC_JS_BRANCH=wasmRebuildTests +RELEASE_URL="https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin" +RELEASE_COMMIT_LIST_URL="$RELEASE_URL/list.txt" + +SCRIPTDIR=$(dirname "$0") +SCRIPTDIR=$(realpath "${SCRIPTDIR}") +RED='\033[0;31m' +GREEN='\033[0;32m' +ORANGE='\033[0;33m' +CYAN='\033[0;36m' +RESET='\033[0m' + +function generate_bytecode_report() { + rm -rf /tmp/report.txt + + local EXIT_STATUS + + if semver -r "<0.4.12" $3 > /dev/null; then + set +e + "${SCRIPTDIR}/genbytecode.sh" "$1" >/dev/null 2>&1 + EXIT_STATUS=$? + set -e + else + set +e + ( + set -e + + git reset --hard HEAD --quiet + git clean -f -d -x --quiet + + for dir in build/solc build/libsolc emscripten_build/libsolc; do + mkdir -p $dir + rm -rf $dir/soljson.js + ln -sf "$1" $dir/soljson.js + done + + /tmp/storebytecode.sh >/dev/null 2>&1 + ) + EXIT_STATUS=$? + fi + + if [ $EXIT_STATUS -eq 0 ] && [ -f /tmp/report.txt ] && grep -q -v -c -e "ERROR" -e "NO BYTECODE" /tmp/report.txt; then + mv /tmp/report.txt "$2" + echo -e "${GREEN}SUCCESS${RESET}" + else + echo -e "${RED}FAILURE${RESET}" + fi +} +function clean_git_checkout() { + git submodule deinit --all -q + git reset --hard HEAD --quiet + git clean -f -d -x --quiet + git checkout "$1" --quiet + git submodule init -q + git submodule update -q +} +function process_tag() { + local TAG=$1 + cd /src + # Checkout the historic commit instead of the tag directly. + local HISTORIC_COMMIT_HASH="$(grep "${TAG}+" /tmp/release_commit_list.txt | cut -d '+' -f 2 | cut -d '.' -f 2)" + if [ "$(git cat-file -t ${HISTORIC_COMMIT_HASH} 2>/dev/null)" == "commit" ]; then + clean_git_checkout "$HISTORIC_COMMIT_HASH" + else + clean_git_checkout "${TAG}" + fi + + # compatibility symlink + ln -s . solidity + + local VERSION + if [ -f ./scripts/get_version.sh ]; then + VERSION=$(./scripts/get_version.sh) + else + VERSION=$(echo "$TAG" | cut -d v -f 2) + fi + + local COMMIT_HASH=$(git rev-parse --short=8 HEAD) + local FULL_VERSION_SUFFIX="${TAG}+commit.${COMMIT_HASH}" + local HISTORIC_VERSION_SUFFIX="${TAG}+commit.${HISTORIC_COMMIT_HASH}" + + if [ ! -f "${OUTPUTDIR}/bin/soljson-${FULL_VERSION_SUFFIX}.js" ]; then + echo -ne "BUILDING ${CYAN}${TAG}${RESET}... " + set +e + ( + set -e + "${SCRIPTDIR}/rebuild_current.sh" "${VERSION}" >"${OUTPUTDIR}/log/running/build-$TAG.txt" 2>&1 + "${SCRIPTDIR}/patch.sh" "$TAG" upload/soljson.js + cp upload/soljson.js "${OUTPUTDIR}/bin/soljson-${FULL_VERSION_SUFFIX}.js" + rm upload/soljson.js + ) + local EXIT_STATUS=$? + set -e + rm -f "${OUTPUTDIR}/log/success/build-$TAG.txt" + rm -f "${OUTPUTDIR}/log/fail/build-$TAG.txt" + if [ $EXIT_STATUS -eq 0 ]; then + mv "${OUTPUTDIR}/log/running/build-$TAG.txt" "${OUTPUTDIR}/log/success" + echo -e "${GREEN}SUCCESS${RESET}" + else + mv "${OUTPUTDIR}/log/running/build-$TAG.txt" "${OUTPUTDIR}/log/fail" + echo -e "${RED}FAIL${RESET}" + fi + else + echo -e "${CYAN}${TAG}${RESET} ALREADY EXISTS." + if [ $RETEST -eq 0 ]; then + return 0 + fi + fi + + if [ -f "${OUTPUTDIR}/bin/soljson-${FULL_VERSION_SUFFIX}.js" ]; then + + echo -ne "GENERATE BYTECODE REPORT FOR ${CYAN}${TAG}${RESET}... " + generate_bytecode_report "${OUTPUTDIR}/bin/soljson-${FULL_VERSION_SUFFIX}.js" "${OUTPUTDIR}"/log/reports/report-${TAG}.txt "${TAG}" + echo -ne "GENERATE BYTECODE REPORT FOR HISTORIC ${CYAN}${TAG}${RESET}... " + rm -rf /tmp/soljson.js + if wget -q "$RELEASE_URL/soljson-${HISTORIC_VERSION_SUFFIX}.js" -O /tmp/soljson.js; then + generate_bytecode_report /tmp/soljson.js "${OUTPUTDIR}"/log/reports/report-historic-${TAG}.txt "${TAG}" + else + echo -e "${ORANGE}CANNOT FETCH RELEASE${RESET}" + fi + rm -rf /tmp/soljson.js + + if [ -f "${OUTPUTDIR}/log/reports/report-${TAG}.txt" ] && [ -f "${OUTPUTDIR}/log/reports/report-historic-${TAG}.txt" ]; then + rm -rf "${OUTPUTDIR}/log/success/bytecode-${TAG}.txt" + rm -rf "${OUTPUTDIR}/log/fail/bytecode-${TAG}.txt" + if diff -q "${OUTPUTDIR}/log/reports/report-${TAG}.txt" "${OUTPUTDIR}/log/reports/report-historic-${TAG}.txt" >/dev/null 2>&1; then + echo -e "${GREEN}BYTECODE MATCHES FOR ${CYAN}${TAG}${RESET}" + grep -v -c -e "ERROR" -e "NO BYTECODE" "${OUTPUTDIR}/log/reports/report-${TAG}.txt" >"${OUTPUTDIR}/log/success/bytecode-${TAG}.txt" + else + echo -e "${RED}BYTECODE DOES NOT MATCH FOR ${CYAN}${TAG}${RESET}" + echo "MISMATCH" >"${OUTPUTDIR}/log/fail/bytecode-${TAG}.txt" + fi + fi + + echo -ne "TESTING ${CYAN}${TAG}${RESET}... " + cd /root/solc-js + npm version --allow-same-version --no-git-tag-version "${VERSION}" >/dev/null + sed -i -e "s/runTests(solc, .*)/runTests(solc, '${FULL_VERSION_SUFFIX}')/" test/compiler.js + ln -sf "${OUTPUTDIR}/bin/soljson-${FULL_VERSION_SUFFIX}.js" soljson.js + rm -f "${OUTPUTDIR}/log/success/test-$TAG.txt" + rm -f "${OUTPUTDIR}/log/fail/test-$TAG.txt" + if npm test >"${OUTPUTDIR}/log/running/test-$TAG.txt" 2>&1; then + mv "${OUTPUTDIR}/log/running/test-$TAG.txt" "${OUTPUTDIR}/log/success" + echo -e "${GREEN}SUCCESS${RESET}" + else + mv "${OUTPUTDIR}/log/running/test-$TAG.txt" "${OUTPUTDIR}/log/fail" + echo -e "${RED}FAIL${RESET}" + fi + fi +} + +cd /tmp + +echo "Check out solidity repository..." +if [ -d /root/project ]; then + echo "Solidity repo checkout already exists." +else + git clone "${SOLIDITY_REPO_URL}" /root/project --quiet +fi + +echo "Extract bytecode comparison scripts from v0.6.1..." +cd /root/project +git checkout v0.6.1 --quiet +cp scripts/bytecodecompare/storebytecode.sh /tmp +sed -i -e 's/rm -rf "\$TMPDIR"/cp "\$TMPDIR"\/report.txt \/tmp\/report.txt ; rm -rf "\$TMPDIR"/' /tmp/storebytecode.sh +sed -i -e 's/REPO_ROOT=.*/REPO_ROOT=\/src/' /tmp/storebytecode.sh +export SOLC_EMSCRIPTEN="On" + +echo "Check out solc-js repository..." +if [ -d /root/solc-js ]; then + echo "solc-js repo checkout already exists." +else + git clone --branch "${SOLC_JS_BRANCH}" "${SOLC_JS_REPO_URL}" /root/solc-js --quiet +fi + +echo "Create symbolic links for backwards compatibility with older emscripten docker images." +ln -sf /emsdk_portable/node/current/* /emsdk_portable/node/ +ln -sf /emsdk_portable/emscripten/sdk/ /emsdk_portable/ +ln -sf sdk /emsdk_portable/emscripten/bin +ln -sf /emsdk_portable/emscripten/bin/* /usr/local/bin +rm -rf /src +ln -sf /root/project /src + +apt-get -qq update >/dev/null 2>&1 +apt-get -qq install cmake >/dev/null 2>&1 + +echo "Create output directories." +mkdir -p "${OUTPUTDIR}" +mkdir -p "${OUTPUTDIR}"/log +mkdir -p "${OUTPUTDIR}"/log/success +mkdir -p "${OUTPUTDIR}"/log/fail +mkdir -p "${OUTPUTDIR}"/log/running +mkdir -p "${OUTPUTDIR}"/log/reports +mkdir -p "${OUTPUTDIR}"/bin + +echo "Prepare solc-js." +cd /root/solc-js +npm install >/dev/null 2>&1 + +echo "Install semver helper." +npm install -g semver >/dev/null 2>&1 + +echo "Fetching release commit list." +wget -q "${RELEASE_COMMIT_LIST_URL}" -O /tmp/release_commit_list.txt + +cd /src +TAGS=$(git tag --list "${TAG_FILTER}" | tac) +for TAG in ${TAGS}; do + process_tag "${TAG}" +done diff --git a/scripts/wasm-rebuild/rebuild.sh b/scripts/wasm-rebuild/rebuild.sh new file mode 100755 index 000000000..6ac275764 --- /dev/null +++ b/scripts/wasm-rebuild/rebuild.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# This script is expected to produce working builds for all compiler versions >= 0.3.6 and +# succeeding solc-js test runs for all compiler versions >= 0.5.0. + +if [ $# -lt 2 ]; then + echo "Usage: $0 [tagFilter] [outputDirectory] [options...]" + echo + echo " [tagFilter] will be passed to "git tag --list" to filter the tags to be built." + echo " [outputDirectory] will contain log files and the resulting soljson.js builds." + echo " --retest will re-run tests and bytecode comparisons, even if soljson.js is already built." + exit 1 +fi + +TAGS="$1" +OUTPUTDIR="$2" +shift +shift +SCRIPTDIR=$(dirname "$0") +SCRIPTDIR=$(realpath "${SCRIPTDIR}") + +if [ ! -d "${OUTPUTDIR}" ]; then + echo "Output directory ${OUTPUTDIR} does not exist!." + exit 1 +fi +OUTPUTDIR=$(realpath "${OUTPUTDIR}") + +docker run --rm -v "${OUTPUTDIR}":/tmp/output -v "${SCRIPTDIR}":/tmp/scripts:ro -it trzeci/emscripten:sdk-tag-1.39.3-64bit /tmp/scripts/docker-scripts/rebuild_tags.sh "${TAGS}" /tmp/output $@ From 1d6b42eaa400f0b59cb74c541fe2d3c63bdeffee Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 9 Jan 2020 18:35:22 +0100 Subject: [PATCH 029/277] Combine Yul documentation sections. --- docs/assembly.rst | 618 ++-------------------------- docs/contributing.rst | 2 +- docs/yul.rst | 908 ++++++++++++++++++++++++++++-------------- 3 files changed, 650 insertions(+), 878 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index 2bc89ad57..67c1ab6f6 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -1,41 +1,20 @@ -################# -Solidity Assembly -################# +.. _inline-assembly: + +############### +Inline Assembly +############### .. index:: ! assembly, ! asm, ! evmasm -Solidity defines an assembly language that you can use without Solidity and also -as "inline assembly" inside Solidity source code. This guide starts with describing -how to use inline assembly, how it differs from standalone assembly -(sometimes also referred to by its proper name "Yul"), and -specifies assembly itself. - -.. _inline-assembly: - -Inline Assembly -=============== You can interleave Solidity statements with inline assembly in a language close -to the one of the virtual machine. This gives you more fine-grained control, -especially when you are enhancing the language by writing libraries. +to the one of the Ethereum virtual machine. This gives you more fine-grained control, +which is especially useful when you are enhancing the language by writing libraries. -As the EVM is a stack machine, it is often hard to address the correct stack slot -and provide arguments to opcodes at the correct point on the stack. Solidity's inline -assembly helps you do this, and with other issues that arise when writing manual assembly. +The language used for inline assembly in Solidity is called `Yul `_ +and it is documented in its own section. This section will only cover +how the inline assembly code can interface with the surrounding Solidity code. -For inline assembly, the stack is actually not visible at all, but if you look -closer, there is always a very direct translation from inline assembly to -the stack based EVM opcode stream. - -Inline assembly has the following features: - -* functional-style opcodes: ``mul(1, add(2, 3))`` -* assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)`` -* access to external variables: ``function f(uint x) public { assembly { x := sub(x, 1) } }`` -* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` -* if statements: ``if slt(x, 0) { x := sub(0, x) }`` -* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }`` -* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }`` .. warning:: Inline assembly is a way to access the Ethereum Virtual Machine @@ -43,24 +22,14 @@ Inline assembly has the following features: features and checks of Solidity. You should only use it for tasks that need it, and only if you are confident with using it. -Syntax ------- -Assembly parses comments, literals and identifiers in the same way as Solidity, so you can use the -usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inline assembly can contain -``.``. Inline assembly is marked by ``assembly { ... }`` and inside -these curly braces, you can use the following (see the later sections for more details): +An inline assembly block is marked by ``assembly { ... }``, where the code inside +the curly braces is code in the `Yul `_ language. - - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - - opcodes in functional style, e.g. ``add(1, mload(0))`` - - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) - - identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)`` - - assignments, e.g. ``x := add(y, 3)`` - - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` +The inline assembly code can access local Solidity variables as explained below. -Inline assembly manages local variables and control-flow. Because of that, -opcodes that interfere with these features are not available. This includes -the ``dup`` and ``swap`` instructions as well as ``jump`` instructions and labels. +Different inline assembly blocks share no namespace, i.e. it is not possible +to call a Yul function or access a Yul variable defined in a different inline assembly block. Example ------- @@ -146,238 +115,20 @@ efficient code, for example: } -.. _opcodes: - -Opcodes -------- - -This document does not want to be a full description of the Ethereum virtual machine, but the -following list can be used as a quick reference of its opcodes. - -If an opcode takes arguments, they are given in parentheses. -Opcodes marked with ``-`` do not return a result, -those marked with ``*`` are special in a certain way and all others return exactly one value. -Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, -Byzantium, Constantinople or Istanbul, respectively. - -In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to -but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. - -In the grammar, opcodes are represented as pre-defined identifiers ("built-in functions"). - -+-------------------------+-----+---+-----------------------------------------------------------------+ -| Instruction | | | Explanation | -+=========================+=====+===+=================================================================+ -| stop() + `-` | F | stop execution, identical to return(0, 0) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| add(x, y) | | F | x + y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sub(x, y) | | F | x - y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mul(x, y) | | F | x * y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| div(x, y) | | F | x / y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sdiv(x, y) | | F | x / y, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mod(x, y) | | F | x % y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| smod(x, y) | | F | x % y, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| exp(x, y) | | F | x to the power of y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| not(x) | | F | ~x, every bit of x is negated | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| lt(x, y) | | F | 1 if x < y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gt(x, y) | | F | 1 if x > y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| slt(x, y) | | F | 1 if x < y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sgt(x, y) | | F | 1 if x > y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| eq(x, y) | | F | 1 if x == y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| iszero(x) | | F | 1 if x == 0, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| and(x, y) | | F | bitwise "and" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| or(x, y) | | F | bitwise "or" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| xor(x, y) | | F | bitwise "xor" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| shl(x, y) | | C | logical shift left y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| shr(x, y) | | C | logical shift right y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sar(x, y) | | C | signed arithmetic shift right y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mulmod(x, y, m) | | F | (x * y) % m with arbitrary precision arithmetic | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| signextend(i, x) | | F | sign extend from (i*8+7)th bit counting from least significant | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| keccak256(p, n) | | F | keccak(mem[p...(p+n))) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pc() | | F | current position in code | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pop(x) | `-` | F | discard value x | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mload(p) | | F | mem[p...(p+32)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mstore(p, v) | `-` | F | mem[p...(p+32)) := v | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mstore8(p, v) | `-` | F | mem[p] := v & 0xff (only modifies a single byte) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sload(p) | | F | storage[p] | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sstore(p, v) | `-` | F | storage[p] := v | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| msize() | | F | size of memory, i.e. largest accessed memory index | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gas() | | F | gas still available to execution | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| address() | | F | address of the current contract / execution context | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| balance(a) | | F | wei balance at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| selfbalance() | | I | equivalent to balance(address()), but cheaper | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| caller() | | F | call sender (excluding ``delegatecall``) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| callvalue() | | F | wei sent together with the current call | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldataload(p) | | F | call data starting from position p (32 bytes) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldatasize() | | F | size of call data in bytes | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| codesize() | | F | size of the code of the current contract / execution context | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodesize(a) | | F | size of the code at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| returndatasize() | | B | size of the last returndata | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodehash(a) | | C | code hash of address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | -| | | | and return the new address | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| create2(v, p, n, s) | | C | create new contract with code mem[p...(p+n)) at address | -| | | | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | -| | | | and send v wei and return the new address, where ``0xff`` is a | -| | | | 1 byte value, ``this`` is the current contract's address | -| | | | as a 20 byte value and ``s`` is a big-endian 256-bit value | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| call(g, a, v, in, | | F | call contract at address a with input mem[in...(in+insize)) | -| insize, out, outsize) | | | providing g gas and v wei and output area | -| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) | -| | | | and 1 on success | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay | -| insize, out, outsize) | | | in the context of the current contract otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` | -| insize, out, outsize) | | | and ``callvalue`` | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do | -| insize, out, outsize) | | | not allow state modifications | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| revert(p, s) | `-` | B | end execution, revert state changes, return data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| invalid() | `-` | F | end execution with invalid instruction | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log1(p, s, t1) | `-` | F | log with topic t1 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log2(p, s, t1, t2) | `-` | F | log with topics t1, t2 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log3(p, s, t1, t2, t3) | `-` | F | log with topics t1, t2, t3 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | -| t4) | | | | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| chainid() | | I | ID of the executing chain (EIP 1344) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| origin() | | F | transaction sender | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gasprice() | | F | gas price of the transaction | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| coinbase() | | F | current mining beneficiary | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| timestamp() | | F | timestamp of the current block in seconds since the epoch | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| number() | | F | current block number | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| difficulty() | | F | difficulty of the current block | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gaslimit() | | F | block gas limit of the current block | -+-------------------------+-----+---+-----------------------------------------------------------------+ - -Literals --------- - -You can use integer constants by typing them in decimal or hexadecimal notation and an -appropriate ``PUSHi`` instruction will automatically be generated. The following creates code -to add 2 and 3 resulting in 5 and then computes the bitwise ``AND`` with the string "abc". -The final value is assigned to a local variable called ``x``. -Strings are stored left-aligned and cannot be longer than 32 bytes. - -.. code:: - - assembly { let x := and("abc", add(3, 2)) } - - -Functional Style ------------------ - -For a sequence of opcodes, it is often hard to see what the actual -arguments for certain opcodes are. In the following example, -``3`` is added to the contents in memory at position ``0x80``. - -.. code:: - - 3 0x80 mload add 0x80 mstore - -Solidity inline assembly has a "functional style" notation where the same code -would be written as follows: - -.. code:: - - mstore(0x80, add(mload(0x80), 3)) - -If you read the code from right to left, you end up with exactly the same -sequence of constants and opcodes, but it is much clearer where the -values end up. - -If you care about the exact stack layout, just note that the -syntactically first argument for a function or opcode will be put at the -top of the stack. Access to External Variables, Functions and Libraries ----------------------------------------------------- You can access Solidity variables and other identifiers by using their name. -For variables stored in the memory data location, this pushes the address, and not the value -onto the stack. Variables stored in the storage data location are different, as they might not -occupy a full storage slot, so their "address" is composed of a slot and a byte-offset + +Local variables of value type are directly usable in inline assembly. + +Local variables that refer to memory or calldata evaluate to the +address of the variable in memory, resp. calldata, not the value itself. + +For local storage variables or state variables, a single Yul identifier +is not sufficient, since they do not necessarily occupy a single full storage slot. +Therefore, their "address" is composed of a slot and a byte-offset inside that slot. To retrieve the slot pointed to by the variable ``x``, you use ``x_slot``, and to retrieve the byte-offset you use ``x_offset``. @@ -391,7 +142,9 @@ Local Solidity variables are available for assignments, for example: uint b; function f(uint x) public view returns (uint r) { assembly { - r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero + // We ignore the storage slot offset, we know it is zero + // in this special case. + r := mul(x, sload(b_slot)) } } } @@ -407,177 +160,19 @@ Local Solidity variables are available for assignments, for example: To clean signed types, you can use the ``signextend`` opcode: ``assembly { signextend(, x) }`` -Declaring Assembly-Local Variables ----------------------------------- -You can use the ``let`` keyword to declare variables that are only visible in -inline assembly and actually only in the current ``{...}``-block. What happens -is that the ``let`` instruction will create a new stack slot that is reserved -for the variable and automatically removed again when the end of the block -is reached. You need to provide an initial value for the variable which can -be just ``0``, but it can also be a complex functional-style expression. - -Since 0.6.0 the name of a declared variable may not end in ``_offset`` or ``_slot`` +Since Solidity 0.6.0 the name of a inline assembly variable may not end in ``_offset`` or ``_slot`` and it may not shadow any declaration visible in the scope of the inline assembly block (including variable, contract and function declarations). Similarly, if the name of a declared variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any declaration visible in the scope of the inline assembly block. -.. code:: - - pragma solidity >=0.4.16 <0.7.0; - - contract C { - function f(uint x) public view returns (uint b) { - assembly { - let v := add(x, 1) - mstore(0x80, v) - { - let y := add(sload(v), 1) - b := y - } // y is "deallocated" here - b := add(b, v) - } // v is "deallocated" here - } - } - - -Assignments ------------ Assignments are possible to assembly-local variables and to function-local variables. Take care that when you assign to variables that point to memory or storage, you will only change the pointer and not the data. -Variables can only be assigned expressions that result in exactly one value. -If you want to assign the values returned from a function that has -multiple return parameters, you have to provide multiple variables. -.. code:: - - { - let v := 0 - let g := add(v, 2) - function f() -> a, b { } - let c, d := f() - } - -If --- - -The if statement can be used for conditionally executing code. -There is no "else" part, consider using "switch" (see below) if -you need multiple alternatives. - -.. code:: - - { - if eq(value, 0) { revert(0, 0) } - } - -The curly braces for the body are required. - -Switch ------- - -You can use a switch statement as a very basic version of "if/else". -It takes the value of an expression and compares it to several constants. -The branch corresponding to the matching constant is taken. Contrary to the -error-prone behaviour of some programming languages, control flow does -not continue from one case to the next. There can be a fallback or default -case called ``default``. - -.. code:: - - { - let x := 0 - switch calldataload(4) - case 0 { - x := calldataload(0x24) - } - default { - x := calldataload(0x44) - } - sstore(0, div(x, 2)) - } - -The list of cases does not require curly braces, but the body of a -case does require them. - -Loops ------ - -Assembly supports a simple for-style loop. For-style loops have -a header containing an initializing part, a condition and a post-iteration -part. The condition has to be a functional-style expression, while -the other two are blocks. If the initializing part -declares any variables, the scope of these variables is extended into the -body (including the condition and the post-iteration part). - -The ``break`` and ``continue`` statements can be used to exit the loop -or skip to the post-part, respectively. - -The following example computes the sum of an area in memory. - -.. code:: - - { - let x := 0 - for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } { - x := add(x, mload(i)) - } - } - -For loops can also be written so that they behave like while loops: -Simply leave the initialization and post-iteration parts empty. - -.. code:: - - { - let x := 0 - let i := 0 - for { } lt(i, 0x100) { } { // while(i < 0x100) - x := add(x, mload(i)) - i := add(i, 0x20) - } - } - -Functions ---------- - -Assembly allows the definition of low-level functions. These take their -arguments (and a return PC) from the stack and also put the results onto the -stack. Calling a function looks the same way as executing a functional-style -opcode. - -Functions can be defined anywhere and are visible in the block they are -declared in. Inside a function, you cannot access local variables -defined outside of that function. - -If you call a function that returns multiple values, you have to assign -them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``. - -The ``leave`` statement can be used to exit the current function. It -works like the ``return`` statement in other languages just that it does -not take a value to return, it just exits the functions and the function -will return whatever values are currently assigned to the return variable(s). - -The following example implements the power function by square-and-multiply. - -.. code:: - - { - function power(base, exponent) -> result { - switch exponent - case 0 { result := 1 } - case 1 { result := base } - default { - result := power(mul(base, base), div(exponent, 2)) - switch mod(exponent, 2) - case 1 { result := mul(base, result) } - } - } - } Things to Avoid --------------- @@ -593,7 +188,8 @@ Conventions in Solidity ----------------------- In contrast to EVM assembly, Solidity has types which are narrower than 256 bits, -e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256 +e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that +types can be shorter than 256 bits, and the higher-order bits are cleaned when necessary, i.e., shortly before they are written to memory or before comparisons are performed. This means that if you access such a variable @@ -630,157 +226,3 @@ first slot of the array and followed by the array elements. to allow better convertibility between statically- and dynamically-sized arrays, so do not rely on this. - -Standalone Assembly -=================== - -The assembly language described as inline assembly above can also be used -standalone and in fact, the plan is to use it as an intermediate language -for the Solidity compiler. In this form, it tries to achieve several goals: - -1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity. -2. The translation from assembly to bytecode should contain as few "surprises" as possible. -3. Control flow should be easy to detect to help in formal verification and optimization. - -In order to achieve the first and last goal, assembly provides high-level constructs -like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible -to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, -``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow -and the last two obfuscate control flow. Furthermore, functional statements of -the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like -``7 y x add mul`` because in the first form, it is much easier to see which -operand is used for which opcode. - -The second goal is achieved by compiling the -higher level constructs to bytecode in a very regular way. -The only non-local operation performed -by the assembler is name lookup of user-defined identifiers (functions, variables, ...), -which follow very simple and regular scoping rules and cleanup of local variables from the stack. - -Scoping: An identifier that is declared (label, variable, function, assembly) -is only visible in the block where it was declared (including nested blocks -inside the current block). It is not legal to access local variables across -function borders, even if they would be in scope. Shadowing is not allowed. -Local variables cannot be accessed before they were declared, but -functions and assemblies can. Assemblies are special blocks that are used -for e.g. returning runtime code or creating contracts. No identifier from an -outer assembly is visible in a sub-assembly. - -If control flow passes over the end of a block, pop instructions are inserted -that match the number of local variables declared in that block. -Whenever a local variable is referenced, the code generator needs -to know its current relative position in the stack and thus it needs to -keep track of the current so-called stack height. Since all local variables -are removed at the end of a block, the stack height before and after the block -should be the same. If this is not the case, compilation fails. - -Using ``switch``, ``for`` and functions, it should be possible to write -complex code without using ``jump`` or ``jumpi`` manually. This makes it much -easier to analyze the control flow, which allows for improved formal -verification and optimization. - -Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. -The position of all local variables on the stack needs to be known, otherwise -neither references to local variables nor removing local variables automatically -from the stack at the end of a block will work properly. - -Example: - -We will follow an example compilation from Solidity to assembly. -We consider the runtime bytecode of the following Solidity program:: - - pragma solidity >=0.4.16 <0.7.0; - - - contract C { - function f(uint x) public pure returns (uint y) { - y = 1; - for (uint i = 0; i < x; i++) - y = 2 * y; - } - } - -The following assembly will be generated:: - - { - mstore(0x40, 0x80) // store the "free memory pointer" - // function dispatcher - switch div(calldataload(0), exp(2, 226)) - case 0xb3de648b { - let r := f(calldataload(4)) - let ret := $allocate(0x20) - mstore(ret, r) - return(ret, 0x20) - } - default { revert(0, 0) } - // memory allocator - function $allocate(size) -> pos { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - // the contract function - function f(x) -> y { - y := 1 - for { let i := 0 } lt(i, x) { i := add(i, 1) } { - y := mul(2, y) - } - } - } - - -Assembly Grammar ----------------- - -The tasks of the parser are the following: - -- Turn the byte stream into a token stream, discarding C++-style comments - (a special comment exists for source references, but we will not explain it here). -- Turn the token stream into an AST according to the grammar below -- Register identifiers with the block they are defined in (annotation to the - AST node) and note from which point on, variables can be accessed. - -The assembly lexer follows the one defined by Solidity itself. - -Whitespace is used to delimit tokens and it consists of the characters -Space, Tab and Linefeed. Comments are regular JavaScript/C++ comments and -are interpreted in the same way as Whitespace. - -Grammar:: - - AssemblyBlock = '{' AssemblyItem* '}' - AssemblyItem = - Identifier | - AssemblyBlock | - AssemblyExpression | - AssemblyLocalDefinition | - AssemblyAssignment | - AssemblyIf | - AssemblySwitch | - AssemblyFunctionDefinition | - AssemblyFor | - 'break' | - 'continue' | - 'leave' | - SubAssembly - AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral - AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral - Identifier = [a-zA-Z_$] [a-zA-Z_0-9.]* - AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')' - AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )? - AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression - IdentifierOrList = Identifier | '(' IdentifierList ')' - IdentifierList = Identifier ( ',' Identifier)* - AssemblyIf = 'if' AssemblyExpression AssemblyBlock - AssemblySwitch = 'switch' AssemblyExpression AssemblyCase* - ( 'default' AssemblyBlock )? - AssemblyCase = 'case' AssemblyExpression AssemblyBlock - AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' - ( '->' '(' IdentifierList ')' )? AssemblyBlock - AssemblyFor = 'for' ( AssemblyBlock | AssemblyExpression ) - AssemblyExpression ( AssemblyBlock | AssemblyExpression ) AssemblyBlock - SubAssembly = 'assembly' Identifier AssemblyBlock - NumberLiteral = HexNumber | DecimalNumber - HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') - StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' - HexNumber = '0x' [0-9a-fA-F]+ - DecimalNumber = [0-9]+ diff --git a/docs/contributing.rst b/docs/contributing.rst index 1b50ccaaa..dbfcebe89 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -328,7 +328,7 @@ Whiskers compiler in various places to aid readability, and thus maintainability and verifiability, of the code. The syntax comes with a substantial difference to Mustache. The template markers ``{{`` and ``}}`` are -replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly` +replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`yul` (The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks). Another limitation is that lists are only resolved one depth and they do not recurse. This may change in the future. diff --git a/docs/yul.rst b/docs/yul.rst index 739a83996..9f0ff5283 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -1,53 +1,74 @@ +.. _yul: + ### Yul ### -.. _yul: - .. index:: ! assembly, ! asm, ! evmasm, ! yul, julia, iulia Yul (previously also called JULIA or IULIA) is an intermediate language that can be compiled to bytecode for different backends. -Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to be a usable common denominator of all three -platforms. It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler -will use Yul as an intermediate language. Yul is a good target for high-level optimisation stages that can benefit all target platforms equally. +Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to +be a usable common denominator of all three +platforms. It can already be used in stand-alone mode and +for "inline assembly" inside Solidity +and there is an experimental implementation of the Solidity compiler +that uses Yul as an intermediate language. Yul is a good target for +high-level optimisation stages that can benefit all target platforms equally. -With the "inline assembly" flavour, Yul can be used as a language setting -for the :ref:`standard-json interface `: +Motivation and High-level Description +===================================== -:: +The design of Yul tries to achieve several goals: - { - "language": "Yul", - "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, - "settings": { - "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, - "optimizer": { "enabled": true, "details": { "yul": true } } - } - } +1. Programs written in Yul should be readable, even if the code is generated by a compiler from Solidity or another high-level language. +2. Control flow should be easy to understand to help in manual inspection, formal verification and optimization. +3. The translation from Yul to bytecode should be as straightforward as possible. +4. Yul should be suitable for whole-program optimization. -And on the command line interface with the ``--strict-assembly`` parameter. +In order to achieve the first and second goal, Yul provides high-level constructs +like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should +be sufficient for adequately representing the control flow for assembly programs. +Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI`` +are provided, because the first two obfuscate the data flow +and the last two obfuscate control flow. Furthermore, functional statements of +the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like +``7 y x add mul`` because in the first form, it is much easier to see which +operand is used for which opcode. -.. warning:: +Even though it was designed for stack machines, Yul does not expose the complexity of the stack itself. +The programmer or auditor should not have to worry about the stack. - Yul is in active development and bytecode generation is fully implemented only for untyped Yul (everything is ``u256``) - and with EVM 1.0 as target, :ref:`EVM opcodes ` are used as built-in functions. +The third goal is achieved by compiling the +higher level constructs to bytecode in a very regular way. +The only non-local operation performed +by the assembler is name lookup of user-defined identifiers (functions, variables, ...) +and cleanup of local variables from the stack. -The core components of Yul are functions, blocks, variables, literals, -for-loops, if-statements, switch-statements, expressions and assignments to variables. +To avoid confusions between concepts like values and references, +Yul is statically typed. At the same time, there is a default type +(usually the integer word of the target machine) that can always +be omitted to help readability. -Yul is typed, both variables and literals must specify the type with postfix -notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``, -``u64``, ``s64``, ``u128``, ``s128``, ``u256`` and ``s256``. +To keep the language simple and flexible, Yul does not have +any built-in operations, functions or types in its pure form. +These are added together with their semantics when specifying a dialect of Yul, +which allows to specialize Yul to the requirements of different +target platforms and feature sets. -Yul in itself does not even provide operators. If the EVM is targeted, -opcodes will be available as built-in functions, but they can be reimplemented -if the backend changes. For a list of mandatory built-in functions, see the section below. +Currently, there is only one specified dialect of Yul. This dialect uses +the EVM opcodes as builtin functions +(see below) and defines only the type ``u256``, which is the native 256-bit +type of the EVM. Because of that, we will not provide types in the examples below. -The following example program assumes that the EVM opcodes ``mul``, ``div`` -and ``mod`` are available either natively or as functions and computes exponentiation. -As per the warning above, the following code is untyped and can be compiled using ``solc --strict-assembly``. + +Simple Example +============== + +The following example program is written in the EVM dialect and computes exponentiation. +It can be compiled using ``solc --strict-assembly``. The builtin functions +``mul`` and ``div`` compute product and division, respectively. .. code:: @@ -67,8 +88,8 @@ As per the warning above, the following code is untyped and can be compiled usin } It is also possible to implement the same function using a for-loop -instead of with recursion. Here, we need the EVM opcodes ``lt`` (less-than) -and ``add`` to be available. +instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``. +less-than comparison. .. code:: @@ -83,10 +104,326 @@ and ``add`` to be available. } } + + + +Stand-Alone Usage +================= + +You can use Yul in its stand-alone form in the EVM dialect using the Solidity compiler. +This will use the `Yul object notation `_ so that it is possible to refer +to code as data to deploy contracts. This Yul mode is available for the commandline compiler +(use ``--strict-assembly``) and for the :ref:`standard-json interface `: + +:: + + { + "language": "Yul", + "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, + "settings": { + "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, + "optimizer": { "enabled": true, "details": { "yul": true } } + } + } + +.. warning:: + + Yul is in active development and bytecode generation is only fully implemented for the EVM dialect of Yul + with EVM 1.0 as target. + + +Informal Description of Yul +=========================== + +In the following, we will talk about each individual aspect +of the Yul language. In examples, we will use the default EVM dialect. + +Syntax +------ + +Yul parses comments, literals and identifiers in the same way as Solidity, +so you can e.g. use ``//`` and ``/* */`` to denote comments. +There is one exception: Identifiers in Yul can contain dots: ``.``. + +Yul can specify "objects" that consist of code, data and sub-objects. +Please see `Yul Objects `_ below for details on that. +In this section, we are only concerned with the code part of such an object. +This code part always consists of a curly-braces +delimited block. Most tools support specifying just a code block +where an object is expected. + +Inside a code block, the following elements can be used +(see the later sections for more details): + + - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) + - calls to builtin functions, e.g. ``add(1, mload(0))`` + - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) + - identifiers (variables), e.g. ``add(3, x)`` + - assignments, e.g. ``x := add(y, 3)`` + - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` + - if statements, e.g. ``if lt(a, b) { sstore(0, 1) }`` + - switch statements, e.g. ``switch mload(0) case 0 { revert() } default { mstore(0, 1) }`` + - for loops, e.g. ``for { let i := 0} lt(i, 10) { i := add(i, 1) } { mstore(i, 7) }`` + - function definitions, e.g. ``function f(a, b) -> c { c := add(a, b) }``` + +Multiple syntactical elements can follow each other simply separated by +whitespace, i.e. there is no terminating ``;`` or newline required. + +Literals +-------- + +You can use integer constants in decimal or hexadecimal notation. +When compiling for the EVM, this will be translated into an +appropriate ``PUSHi`` instruction. In the following example, +``3`` and ``2`` are added resulting in 5 and then the +bitwise ``and`` with the string "abc" is computed. +The final value is assigned to a local variable called ``x``. +Strings are stored left-aligned and cannot be longer than 32 bytes. + +.. code:: + + let x := and("abc", add(3, 2)) + +Unless it is the default type, the type of a literal +has to be specified after a colon: + +.. code:: + + let x := and("abc":uint32, add(3:uint256, 2:uint256)) + + +Function Calls +-------------- + +Both built-in and user-defined functions (see below) can be called +in the same way as shown in the previous example. +If the function returns a single value, it can be directly used +inside an expression again. If it returns multiple values, +they have to be assigned to local variables. + +.. code:: + + mstore(0x80, add(mload(0x80), 3)) + // Here, the user-defined function `f` returns + // two values. The definition of the function + // is missing from the example. + let x, y := f(1, mload(0)) + +For built-in functions of the EVM, functional expressions +can be directly translated to a stream of opcodes: +You just read the expression from right to left to obtain the +opcodes. In the case of the first line in the example, this +is ``PUSH1 3 PUSH1 0x80 MLOAD ADD PUSH1 0x80 MSTORE``. + +For calls to user-defined functions, the arguments are also +put on the stack from right to left and this is the order +in which argument lists are evaluated. The return values, +though, are expected on the stack from left to right, +i.e. in this example, ``y`` is on top of the stack and ``x`` +is below it. + +Variable Declarations +--------------------- + +You can use the ``let`` keyword to declare variables. +A variable is only visible inside the +``{...}``-block it was defined in. When compiling to the EVM, +a new stack slot is created that is reserved +for the variable and automatically removed again when the end of the block +is reached. You can provide an initial value for the variable. +If you do not provide a value, the variable will be initialized to zero. + +Since variables are stored on the stack, they do not directly +influence memory or storage, but they can be used as pointers +to memory or storage locations in the built-in functions +``mstore``, ``mload``, ``sstore`` and ``sload``. +Future dialects migh introduce specific types for such pointers. + +When a variable is referenced, its current value is copied. +For the EVM, this translates to a ``DUP`` instruction. + +.. code:: + + { + let zero := 0 + let v := calldataload(zero) + { + let y := add(sload(v), 1) + v := y + } // y is "deallocated" here + sstore(v, zero) + } // v and zero are "deallocated" here + + +If the declared variable should have a type different from the default type, +you denote that following a colon. You can also declare multiple +variables in one statement when you assign from a function call +that returns multiple values. + +.. code:: + + { + let zero:uint32 := 0:uint32 + let v:uint256, t:uint32 := f() + let x, y := g() + } + +Depending on the optimiser settings, the compiler can free the stack slots +already after the variable has been used for +the last time, even though it is still in scope. + + +Assignments +----------- + +Variables can be assigned to after their definition using the +``:=`` operator. It is possible to assign multiple +variables at the same time. For this, the number and types of the +values have to match. +If you want to assign the values returned from a function that has +multiple return parameters, you have to provide multiple variables. + +.. code:: + + let v := 0 + // re-assign v + v := 2 + let t := add(v, 2) + function f() -> a, b { } + // assign multiple values + v, t := f() + + +If +-- + +The if statement can be used for conditionally executing code. +No "else" block can be defined. Consider using "switch" instead (see below) if +you need multiple alternatives. + +.. code:: + + if eq(value, 0) { revert(0, 0) } + +The curly braces for the body are required. + +Switch +------ + +You can use a switch statement as an extended version of the if statement. +It takes the value of an expression and compares it to several literal constants. +The branch corresponding to the matching constant is taken. +Contrary to other programming languages, for safety reasons, control flow does +not continue from one case to the next. There can be a fallback or default +case called ``default`` which is taken if none of the literal constants matches. + +.. code:: + + { + let x := 0 + switch calldataload(4) + case 0 { + x := calldataload(0x24) + } + default { + x := calldataload(0x44) + } + sstore(0, div(x, 2)) + } + +The list of cases is not enclosed by curly braces, but the body of a +case does require them. + +Loops +----- + +Yul supports for-loops which consist of +a header containing an initializing part, a condition, a post-iteration +part and a body. The condition has to be an expression, while +the other three are blocks. If the initializing part +declares any variables at the top level, the scope of these variables extends to all other +parts of the loop. + +The ``break`` and ``continue`` statements can be used in the body to exit the loop +or skip to the post-part, respectively. + +The following example computes the sum of an area in memory. + +.. code:: + + { + let x := 0 + for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } { + x := add(x, mload(i)) + } + } + +For loops can also be used as a replacement for while loops: +Simply leave the initialization and post-iteration parts empty. + +.. code:: + + { + let x := 0 + let i := 0 + for { } lt(i, 0x100) { } { // while(i < 0x100) + x := add(x, mload(i)) + i := add(i, 0x20) + } + } + +Function Declarations +--------------------- + +Yul allows the definition of functions. These should not be confused with functions +in Solidity since they are never part of an external interface of a contract and +are part of a namespace separate from the one for Solidity functions. + +For the EVM, Yul functions take their +arguments (and a return PC) from the stack and also put the results onto the +stack. User-defined functions and built-in functions are called in exactly the same way. + +Functions can be defined anywhere and are visible in the block they are +declared in. Inside a function, you cannot access local variables +defined outside of that function. + +Functions declare parameters and return variables, similar to Solidity. +To return a value, you assign it to the return variable(s). + +If you call a function that returns multiple values, you have to assign +them to multiple variables using ``a, b := f(x)`` or ``let a, b := f(x)``. + +The ``leave`` statement can be used to exit the current function. It +works like the ``return`` statement in other languages just that it does +not take a value to return, it just exits the functions and the function +will return whatever values are currently assigned to the return variable(s). + +Note that the EVM dialect has a built-in function called ``return`` that +quits the full execution context (internal message call) and not just +the current yul function. + +The following example implements the power function by square-and-multiply. + +.. code:: + + { + function power(base, exponent) -> result { + switch exponent + case 0 { result := 1 } + case 1 { result := base } + default { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + case 1 { result := mul(base, result) } + } + } + } + Specification of Yul ==================== -This chapter describes Yul code. It is usually placed inside a Yul object, which is described in the following chapter. +This chapter describes Yul code formally. Yul code is usually placed inside Yul objects, +which are explained in their own chapter. Grammar:: @@ -128,11 +465,10 @@ Grammar:: Identifier '(' ( Expression ( ',' Expression )* )? ')' Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]* IdentifierList = Identifier ( ',' Identifier)* - TypeName = Identifier | BuiltinTypeName - BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) - TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* + TypeName = Identifier + TypedIdentifierList = Identifier ( ':' TypeName )? ( ',' Identifier ( ':' TypeName )? )* Literal = - (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ':' TypeName + (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ( ':' TypeName )? NumberLiteral = HexNumber | DecimalNumber HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' @@ -141,19 +477,23 @@ Grammar:: HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ + Restrictions on the Grammar --------------------------- +Apart from those directly imposed by the grammar, the following +restrictions apply: + Switches must have at least one case (including the default case). -If all possible values of the expression are covered, a default case should -not be allowed (i.e. a switch with a ``bool`` expression that has both a -true and a false case should not allow a default case). All case values need to -have the same type. +All case values need to have the same type and distinct values. +If all possible values of the expression type are covered, a default case is +not allowed (i.e. a switch with a ``bool`` expression that has both a +true and a false case do not allow a default case). Every expression evaluates to zero or more values. Identifiers and Literals evaluate to exactly one value and function calls evaluate to a number of values equal to the -number of return values of the function called. +number of return variables of the function called. In variable declarations and assignments, the right-hand-side expression (if present) has to evaluate to a number of values equal to the number of @@ -168,13 +508,22 @@ In all other situations, expressions have to evaluate to exactly one value. The ``continue`` and ``break`` statements can only be used inside loop bodies and have to be in the same function as the loop (or both have to be at the -top level). -The ``leave`` statement can only be used inside a function. +top level). The ``continue`` and ``break`` statements cannot be used +in other parts of a loop, not even when it is scoped inside a second loop's body. + The condition part of the for-loop has to evaluate to exactly one value. + +The ``leave`` statement can only be used inside a function. + Functions cannot be defined anywhere inside for loop init blocks. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. +During assignments and function calls, the types of the respective values have to match. +There is no implicit type conversion. Type conversion in general can only be achieved +if the dialect provides an appropriate built-in function that takes a value of one +type and returns a value of a different type. + Scoping Rules ------------- @@ -186,9 +535,11 @@ introduce new identifiers into these scopes. Identifiers are visible in the block they are defined in (including all sub-nodes and sub-blocks). -As an exception, identifiers defined directly in the "init" part of the for-loop -(the first block) are visible in all other parts of the for-loop -(but not outside of the loop). +As an exception, the scope of the "init" part of the or-loop +(the first block) extends across all other parts of the for loop. +This means that variables declared in the init part (but not inside a +block inside the init part) are visible in all other parts of the for-loop. + Identifiers declared in the other parts of the for loop respect the regular syntactical scoping rules. @@ -197,7 +548,7 @@ to ``{ I... for {} C { P... } { B... } }``. The parameters and return parameters of functions are visible in the -function body and their names cannot overlap. +function body and their names have to be distinct. Variables can only be referenced after their declaration. In particular, variables cannot be referenced in the right hand side of their own variable @@ -215,13 +566,14 @@ Formal Specification -------------------- We formally specify Yul by providing an evaluation function E overloaded -on the various nodes of the AST. Any functions can have side effects, so +on the various nodes of the AST. As builtin functions can have side effects, E takes two state objects and the AST node and returns two new state objects and a variable number of other values. The two state objects are the global state object (which in the context of the EVM is the memory, storage and state of the blockchain) and the local state object (the state of local variables, i.e. a segment of the stack in the EVM). + If the AST node is a statement, E returns the two state objects and a "mode", which is used for the ``break``, ``continue`` and ``leave`` statements. If the AST node is an expression, E returns the two state objects and @@ -336,248 +688,207 @@ We will use a destructuring notation for the AST nodes. E(G, L, n: DecimalNumber) = G, L, dec(n), where dec is the decimal decoding function -Type Conversion Functions -------------------------- +.. _opcodes: -Yul has no support for implicit type conversion and therefore functions exist to provide explicit conversion. -When converting a larger type to a shorter type a runtime exception can occur in case of an overflow. +EVM Dialect +----------- -Truncating conversions are supported between the following types: - - ``bool`` - - ``u32`` - - ``u64`` - - ``u256`` - - ``s256`` +The default dialect of Yul currently is the EVM dialect for the currently selected version of the EVM. +with a version of the EVM. The only type available in this dialect +is ``u256``, the 256-bit native type of the Ethereum Virtual Machine. +Since it is the default type of this dialect, it can be omitted. -For each of these a type conversion function exists having the prototype in the form of ``to(x:) -> y:``, -such as ``u32tobool(x:u32) -> y:bool``, ``u256tou32(x:u256) -> y:u32`` or ``s256tou256(x:s256) -> y:u256``. +The following table lists all builtin functions +(depending on the EVM version) and provides a short description of the +semantics of the function / opcode. +This document does not want to be a full description of the Ethereum virtual machine. +Please refer to a different document if you are interested in the precise semantics. -.. note:: +Opcodes marked with ``-`` do not return a result and all others return exactly one value. +Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, +Byzantium, Constantinople or Istanbul, respectively. - ``u32tobool(x:u32) -> y:bool`` can be implemented as ``y := not(iszerou256(x))`` and - ``booltou32(x:bool) -> y:u32`` can be implemented as ``switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }`` +In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to +but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. -Low-level Functions -------------------- +Since Yul manages local variables and control-flow, +opcodes that interfere with these features are not available. This includes +the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels and the ``push`` instructions. -The following functions must be available: ++-------------------------+-----+---+-----------------------------------------------------------------+ +| Instruction | | | Explanation | ++=========================+=====+===+=================================================================+ +| stop() + `-` | F | stop execution, identical to return(0, 0) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| add(x, y) | | F | x + y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sub(x, y) | | F | x - y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mul(x, y) | | F | x * y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| div(x, y) | | F | x / y or 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sdiv(x, y) | | F | x / y, for signed numbers in two's complement, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mod(x, y) | | F | x % y, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| smod(x, y) | | F | x % y, for signed numbers in two's complement, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| exp(x, y) | | F | x to the power of y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| not(x) | | F | bitwise "not" of x (every bit of x is negated) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| lt(x, y) | | F | 1 if x < y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gt(x, y) | | F | 1 if x > y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| slt(x, y) | | F | 1 if x < y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sgt(x, y) | | F | 1 if x > y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| eq(x, y) | | F | 1 if x == y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| iszero(x) | | F | 1 if x == 0, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| and(x, y) | | F | bitwise "and" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| or(x, y) | | F | bitwise "or" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| xor(x, y) | | F | bitwise "xor" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| shl(x, y) | | C | logical shift left y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| shr(x, y) | | C | logical shift right y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sar(x, y) | | C | signed arithmetic shift right y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic, 0 if m == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mulmod(x, y, m) | | F | (x * y) % m with arbitrary precision arithmetic, 0 if m == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| signextend(i, x) | | F | sign extend from (i*8+7)th bit counting from least significant | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| keccak256(p, n) | | F | keccak(mem[p...(p+n))) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| pc() | | F | current position in code | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| pop(x) | `-` | F | discard value x | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mload(p) | | F | mem[p...(p+32)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mstore(p, v) | `-` | F | mem[p...(p+32)) := v | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mstore8(p, v) | `-` | F | mem[p] := v & 0xff (only modifies a single byte) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sload(p) | | F | storage[p] | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sstore(p, v) | `-` | F | storage[p] := v | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| msize() | | F | size of memory, i.e. largest accessed memory index | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gas() | | F | gas still available to execution | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| address() | | F | address of the current contract / execution context | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| balance(a) | | F | wei balance at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| selfbalance() | | I | equivalent to balance(address()), but cheaper | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| caller() | | F | call sender (excluding ``delegatecall``) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| callvalue() | | F | wei sent together with the current call | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldataload(p) | | F | call data starting from position p (32 bytes) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldatasize() | | F | size of call data in bytes | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| codesize() | | F | size of the code of the current contract / execution context | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodesize(a) | | F | size of the code at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| returndatasize() | | B | size of the last returndata | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodehash(a) | | C | code hash of address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | +| | | | and return the new address | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| create2(v, p, n, s) | | C | create new contract with code mem[p...(p+n)) at address | +| | | | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | +| | | | and send v wei and return the new address, where ``0xff`` is a | +| | | | 1 byte value, ``this`` is the current contract's address | +| | | | as a 20 byte value and ``s`` is a big-endian 256-bit value | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| call(g, a, v, in, | | F | call contract at address a with input mem[in...(in+insize)) | +| insize, out, outsize) | | | providing g gas and v wei and output area | +| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) | +| | | | and 1 on success | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay | +| insize, out, outsize) | | | in the context of the current contract otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` | +| insize, out, outsize) | | | and ``callvalue`` | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do | +| insize, out, outsize) | | | not allow state modifications | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| revert(p, s) | `-` | B | end execution, revert state changes, return data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| invalid() | `-` | F | end execution with invalid instruction | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log1(p, s, t1) | `-` | F | log with topic t1 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log2(p, s, t1, t2) | `-` | F | log with topics t1, t2 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log3(p, s, t1, t2, t3) | `-` | F | log with topics t1, t2, t3 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | +| t4) | | | | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| chainid() | | I | ID of the executing chain (EIP 1344) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| origin() | | F | transaction sender | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gasprice() | | F | gas price of the transaction | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| coinbase() | | F | current mining beneficiary | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| timestamp() | | F | timestamp of the current block in seconds since the epoch | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| number() | | F | current block number | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| difficulty() | | F | difficulty of the current block | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gaslimit() | | F | block gas limit of the current block | ++-------------------------+-----+---+-----------------------------------------------------------------+ -+---------------------------------------------------------------------------------------------------------------+ -| *Logic* | -+---------------------------------------------+-----------------------------------------------------------------+ -| not(x:bool) ‑> z:bool | logical not | -+---------------------------------------------+-----------------------------------------------------------------+ -| and(x:bool, y:bool) ‑> z:bool | logical and | -+---------------------------------------------+-----------------------------------------------------------------+ -| or(x:bool, y:bool) ‑> z:bool | logical or | -+---------------------------------------------+-----------------------------------------------------------------+ -| xor(x:bool, y:bool) ‑> z:bool | xor | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Arithmetic* | -+---------------------------------------------+-----------------------------------------------------------------+ -| addu256(x:u256, y:u256) ‑> z:u256 | x + y | -+---------------------------------------------+-----------------------------------------------------------------+ -| subu256(x:u256, y:u256) ‑> z:u256 | x - y | -+---------------------------------------------+-----------------------------------------------------------------+ -| mulu256(x:u256, y:u256) ‑> z:u256 | x * y | -+---------------------------------------------+-----------------------------------------------------------------+ -| divu256(x:u256, y:u256) ‑> z:u256 | x / y | -+---------------------------------------------+-----------------------------------------------------------------+ -| divs256(x:s256, y:s256) ‑> z:s256 | x / y, for signed numbers in two's complement | -+---------------------------------------------+-----------------------------------------------------------------+ -| modu256(x:u256, y:u256) ‑> z:u256 | x % y | -+---------------------------------------------+-----------------------------------------------------------------+ -| mods256(x:s256, y:s256) ‑> z:s256 | x % y, for signed numbers in two's complement | -+---------------------------------------------+-----------------------------------------------------------------+ -| signextendu256(i:u256, x:u256) ‑> z:u256 | sign extend from (i*8+7)th bit counting from least significant | -+---------------------------------------------+-----------------------------------------------------------------+ -| expu256(x:u256, y:u256) ‑> z:u256 | x to the power of y | -+---------------------------------------------+-----------------------------------------------------------------+ -| addmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x + y) % m with arbitrary precision arithmetic | -+---------------------------------------------+-----------------------------------------------------------------+ -| mulmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x * y) % m with arbitrary precision arithmetic | -+---------------------------------------------+-----------------------------------------------------------------+ -| ltu256(x:u256, y:u256) ‑> z:bool | true if x < y, false otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| gtu256(x:u256, y:u256) ‑> z:bool | true if x > y, false otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| lts256(x:s256, y:s256) ‑> z:bool | true if x < y, false otherwise | -| | (for signed numbers in two's complement) | -+---------------------------------------------+-----------------------------------------------------------------+ -| gts256(x:s256, y:s256) ‑> z:bool | true if x > y, false otherwise | -| | (for signed numbers in two's complement) | -+---------------------------------------------+-----------------------------------------------------------------+ -| equ256(x:u256, y:u256) ‑> z:bool | true if x == y, false otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| iszerou256(x:u256) ‑> z:bool | true if x == 0, false otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| notu256(x:u256) ‑> z:u256 | ~x, every bit of x is negated | -+---------------------------------------------+-----------------------------------------------------------------+ -| andu256(x:u256, y:u256) ‑> z:u256 | bitwise and of x and y | -+---------------------------------------------+-----------------------------------------------------------------+ -| oru256(x:u256, y:u256) ‑> z:u256 | bitwise or of x and y | -+---------------------------------------------+-----------------------------------------------------------------+ -| xoru256(x:u256, y:u256) ‑> z:u256 | bitwise xor of x and y | -+---------------------------------------------+-----------------------------------------------------------------+ -| shlu256(x:u256, y:u256) ‑> z:u256 | logical left shift of x by y | -+---------------------------------------------+-----------------------------------------------------------------+ -| shru256(x:u256, y:u256) ‑> z:u256 | logical right shift of x by y | -+---------------------------------------------+-----------------------------------------------------------------+ -| sars256(x:s256, y:u256) ‑> z:u256 | arithmetic right shift of x by y | -+---------------------------------------------+-----------------------------------------------------------------+ -| byte(n:u256, x:u256) ‑> v:u256 | nth byte of x, where the most significant byte is the 0th byte | -| | Cannot this be just replaced by and256(shr256(n, x), 0xff) and | -| | let it be optimised out by the EVM backend? | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Memory and storage* | -+---------------------------------------------+-----------------------------------------------------------------+ -| mload(p:u256) ‑> v:u256 | mem[p..(p+32)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| mstore(p:u256, v:u256) | mem[p..(p+32)) := v | -+---------------------------------------------+-----------------------------------------------------------------+ -| mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte | -+---------------------------------------------+-----------------------------------------------------------------+ -| sload(p:u256) ‑> v:u256 | storage[p] | -+---------------------------------------------+-----------------------------------------------------------------+ -| sstore(p:u256, v:u256) | storage[p] := v | -+---------------------------------------------+-----------------------------------------------------------------+ -| msize() ‑> size:u256 | size of memory, i.e. largest accessed memory index, albeit due | -| | due to the memory extension function, which extends by words, | -| | this will always be a multiple of 32 bytes | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Execution control* | -+---------------------------------------------+-----------------------------------------------------------------+ -| create(v:u256, p:u256, n:u256) | create new contract with code mem[p..(p+n)) and send v wei | -| | and return the new address | -+---------------------------------------------+-----------------------------------------------------------------+ -| create2(v:u256, p:u256, n:u256, s:u256) | create new contract with code mem[p...(p+n)) at address | -| | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | -| | and send v wei and return the new address, where ``0xff`` is a | -| | 8 byte value, ``this`` is the current contract's address | -| | as a 20 byte value and ``s`` is a big-endian 256-bit value | -+---------------------------------------------+-----------------------------------------------------------------+ -| call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) | -| insize:u256, out:u256, | providing g gas and v wei and output area | -| outsize:u256) | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | -| ‑> r:u256 | and 1 on success | -+---------------------------------------------+-----------------------------------------------------------------+ -| callcode(g:u256, a:u256, v:u256, in:u256, | identical to ``call`` but only use the code from a | -| insize:u256, out:u256, | and stay in the context of the | -| outsize:u256) ‑> r:u256 | current contract otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, | -| insize:u256, out:u256, | but also keep ``caller`` | -| outsize:u256) ‑> r:u256 | and ``callvalue`` | -+---------------------------------------------+-----------------------------------------------------------------+ -| abort() | abort (equals to invalid instruction on EVM) | -+---------------------------------------------+-----------------------------------------------------------------+ -| return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| revert(p:u256, s:u256) | end execution, revert state changes, return data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| selfdestruct(a:u256) | end execution, destroy current contract and send funds to a | -+---------------------------------------------+-----------------------------------------------------------------+ -| log0(p:u256, s:u256) | log without topics and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log1(p:u256, s:u256, t1:u256) | log with topic t1 and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log2(p:u256, s:u256, t1:u256, t2:u256) | log with topics t1, t2 and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log3(p:u256, s:u256, t1:u256, t2:u256, | log with topics t, t2, t3 and data mem[p..(p+s)) | -| t3:u256) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| log4(p:u256, s:u256, t1:u256, t2:u256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | -| t3:u256, t4:u256) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| *State queries* | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockcoinbase() ‑> address:u256 | current mining beneficiary | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockdifficulty() ‑> difficulty:u256 | difficulty of the current block | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockgaslimit() ‑> limit:u256 | block gas limit of the current block | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockhash(b:u256) ‑> hash:u256 | hash of block nr b - only for last 256 blocks excluding current | -+---------------------------------------------+-----------------------------------------------------------------+ -| blocknumber() ‑> block:u256 | current block number | -+---------------------------------------------+-----------------------------------------------------------------+ -| blocktimestamp() ‑> timestamp:u256 | timestamp of the current block in seconds since the epoch | -+---------------------------------------------+-----------------------------------------------------------------+ -| txorigin() ‑> address:u256 | transaction sender | -+---------------------------------------------+-----------------------------------------------------------------+ -| txgasprice() ‑> price:u256 | gas price of the transaction | -+---------------------------------------------+-----------------------------------------------------------------+ -| gasleft() ‑> gas:u256 | gas still available to execution | -+---------------------------------------------+-----------------------------------------------------------------+ -| balance(a:u256) ‑> v:u256 | wei balance at address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| this() ‑> address:u256 | address of the current contract / execution context | -+---------------------------------------------+-----------------------------------------------------------------+ -| caller() ‑> address:u256 | call sender (excluding delegatecall) | -+---------------------------------------------+-----------------------------------------------------------------+ -| callvalue() ‑> v:u256 | wei sent together with the current call | -+---------------------------------------------+-----------------------------------------------------------------+ -| calldataload(p:u256) ‑> v:u256 | call data starting from position p (32 bytes) | -+---------------------------------------------+-----------------------------------------------------------------+ -| calldatasize() ‑> v:u256 | size of call data in bytes | -+---------------------------------------------+-----------------------------------------------------------------+ -| calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t | -+---------------------------------------------+-----------------------------------------------------------------+ -| codesize() ‑> size:u256 | size of the code of the current contract / execution context | -+---------------------------------------------+-----------------------------------------------------------------+ -| codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t | -+---------------------------------------------+-----------------------------------------------------------------+ -| extcodesize(a:u256) ‑> size:u256 | size of the code at address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| extcodehash(a:u256) | code hash of address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Others* | -+---------------------------------------------+-----------------------------------------------------------------+ -| discard(unused:bool) | discard value | -+---------------------------------------------+-----------------------------------------------------------------+ -| discardu256(unused:u256) | discard value | -+---------------------------------------------+-----------------------------------------------------------------+ -| splitu256tou64(x:u256) ‑> (x1:u64, x2:u64, | split u256 to four u64's | -| x3:u64, x4:u64) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 | -| x4:u64) ‑> (x:u256) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| keccak256(p:u256, s:u256) ‑> v:u256 | keccak(mem[p...(p+s))) | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Object access* | | -+---------------------------------------------+-----------------------------------------------------------------+ -| datasize(name:string) ‑> size:u256 | size of the data object in bytes, name has to be string literal | -+---------------------------------------------+-----------------------------------------------------------------+ -| dataoffset(name:string) ‑> offset:u256 | offset of the data object inside the data area in bytes, | -| | name has to be string literal | -+---------------------------------------------+-----------------------------------------------------------------+ -| datacopy(dst:u256, src:u256, len:u256) | copy len bytes from the data area starting at offset src bytes | -| | to memory at position dst | -+---------------------------------------------+-----------------------------------------------------------------+ +There are three additional functions, ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, +which are used to access other parts of a Yul object. -Backends --------- +``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) +as arguments and return the size and offset in the data area, respectively. +For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. -Backends or targets are the translators from Yul to a specific bytecode. Each of the backends can expose functions -prefixed with the name of the backend. We reserve ``evm_`` and ``ewasm_`` prefixes for the two proposed backends. - -Backend: EVM ------------- - -The EVM target will have all the underlying EVM opcodes exposed with the `evm_` prefix. - -Backend: "EVM 1.5" ------------------- - -TBD - -Backend: eWASM --------------- - -TBD +.. _yul-object: Specification of Yul Object =========================== @@ -603,13 +914,16 @@ An example Yul Object is shown below: .. code:: - // Code consists of a single object. A single "code" node is the code of the object. + // A contract consists of a single object with sub-objects representing + // the code to be deployed or other contracts it can create. + // The single "code" node is the executable code of the object. // Every (other) named object or data section is serialized and // made accessible to the special built-in functions datacopy / dataoffset / datasize // Access to nested objects can be performed by joining the names using ``.``. // The current object and sub-objects and data items inside the current object // are in scope without nested access. object "Contract1" { + // This is the constructor code of the contract. code { function allocate(size) -> ptr { ptr := mload(0x40) @@ -620,15 +934,14 @@ An example Yul Object is shown below: // first create "runtime.Contract2" let size := datasize("runtime.Contract2") let offset := allocate(size) - // This will turn into a memory->memory copy for eWASM and - // a codecopy for EVM + // This will turn into codecopy for EVM datacopy(offset, dataoffset("runtime.Contract2"), size) // constructor parameter is a single number 0x1234 mstore(add(offset, size), 0x1234) pop(create(offset, add(size, 32), 0)) - // now return the runtime object (this is - // constructor code) + // now return the runtime object (the currently + // executing code is the constructor code) size := datasize("runtime") offset := allocate(size) // This will turn into a memory->memory copy for eWASM and @@ -651,8 +964,7 @@ An example Yul Object is shown below: let size := datasize("Contract2") let offset := allocate(size) - // This will turn into a memory->memory copy for eWASM and - // a codecopy for EVM + // This will turn into codecopy for EVM datacopy(offset, dataoffset("Contract2"), size) // constructor parameter is a single number 0x1234 mstore(add(offset, size), 0x1234) @@ -676,3 +988,21 @@ An example Yul Object is shown below: } } } + +Yul Optimizer +============= + +The Yul optimizer operates on Yul code and uses the same language for input, output and +intermediate states. This allows for easy debugging and verification of the optimizer. + +Please see the +`documentation in the source code `_ +for more details about its internals. + +If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``: + +:: + + solc --strict-assembly --optimize + +In Solidity mode, the Yul optimizer is activated together with the regular optimizer. From ee5ff4df4e87d1fd246a617e3347d0c3582292a7 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Tue, 14 Jan 2020 14:00:51 +0100 Subject: [PATCH 030/277] Clean up visibility via contract name and fix ICE on calling unimplemented base function. --- Changelog.md | 3 + libsolidity/analysis/NameAndTypeResolver.cpp | 2 +- libsolidity/analysis/TypeChecker.cpp | 16 +++-- libsolidity/ast/AST.cpp | 42 ++++-------- libsolidity/ast/AST.h | 33 ++++++++-- libsolidity/ast/ASTAnnotations.h | 5 +- libsolidity/ast/Types.cpp | 64 ++++++++++--------- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- .../145_external_base_visibility.sol | 2 +- .../call_unimplemented_internal_function.sol | 6 ++ .../assign_function_via_base_name_to_var.sol | 14 ++++ ...sign_function_via_contract_name_to_var.sol | 0 .../members/base_contract.sol | 11 ++++ .../members/base_contract_invalid.sol | 13 ++++ .../call_function_via_contract_name.sol | 6 +- .../members/call_unimplemented_base.sol | 10 +++ .../function_selector_via_contract_name.sol | 0 .../function_selector_via_interface_name.sol | 0 .../function_via_contract_name_internal.sol | 0 .../function_via_contract_name_overloaded.sol | 0 .../function_via_contract_name_private.sol | 0 .../function_via_contract_name_public.sol | 0 .../contractTypeType/members/modifier.sol | 10 +++ .../members/modifier_base.sol | 10 +++ .../contractTypeType/members/struct_enum.sol | 14 ++++ .../types/library_internal_call.sol | 4 ++ 26 files changed, 188 insertions(+), 79 deletions(-) create mode 100644 test/libsolidity/syntaxTests/types/call_unimplemented_internal_function.sol create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_base_name_to_var.sol rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/assign_function_via_contract_name_to_var.sol (100%) create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract.sol create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract_invalid.sol rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/call_function_via_contract_name.sol (54%) create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/call_unimplemented_base.sol rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_selector_via_contract_name.sol (100%) rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_selector_via_interface_name.sol (100%) rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_via_contract_name_internal.sol (100%) rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_via_contract_name_overloaded.sol (100%) rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_via_contract_name_private.sol (100%) rename test/libsolidity/syntaxTests/types/{function_types/definition => contractTypeType/members}/function_via_contract_name_public.sol (100%) create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/modifier.sol create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/modifier_base.sol create mode 100644 test/libsolidity/syntaxTests/types/contractTypeType/members/struct_enum.sol create mode 100644 test/libsolidity/syntaxTests/types/library_internal_call.sol diff --git a/Changelog.md b/Changelog.md index a4c2b11f4..0c700d1a6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,8 +11,11 @@ Compiler Features: * General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing. * Yul Optimizer: Apply penalty when trying to rematerialize into loops. + Bugfixes: * Commandline interface: Only activate yul optimizer if ``--optimize`` is given. + * Fixes internal compiler error on explicitly calling unimplemented base functions. + Build System: * Switch to building soljson.js with an embedded base64-encoded wasm binary. diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index c2e9124a7..76c388f3f 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -625,7 +625,6 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); m_currentFunction = &_function; - _function.annotation().contract = m_currentContract; return true; } @@ -760,6 +759,7 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter); _declaration.annotation().scope = m_currentScope; + _declaration.annotation().contract = m_currentContract; if (_opensScope) enterNewSubScope(_declaration); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5d8c7b85f..2c867b962 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1695,11 +1695,20 @@ void TypeChecker::typeCheckFunctionCall( { m_errorReporter.typeError( _functionCall.location(), - "Cannot call function via contract name." + "Cannot call function via contract type name." ); return; } - + if (_functionType->kind() == FunctionType::Kind::Internal && _functionType->hasDeclaration()) + if (auto const* functionDefinition = dynamic_cast(&_functionType->declaration())) + // functionDefinition->annotation().contract != m_scope ensures that this is a qualified access, + // e.g. ``A.f();`` instead of a simple function call like ``f();`` (the latter is valid for unimplemented + // functions). + if (functionDefinition->annotation().contract != m_scope && !functionDefinition->isImplemented()) + m_errorReporter.typeError( + _functionCall.location(), + "Cannot call unimplemented base function." + ); // Check for unsupported use of bare static call if ( @@ -2660,8 +2669,7 @@ bool TypeChecker::visit(Identifier const& _identifier) ); annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.type = annotation.referencedDeclaration->type(); - if (!annotation.type) - m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); + solAssert(annotation.type, "Declaration referenced before type could be determined."); if (auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast(annotation.referencedDeclaration)) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 74b042852..90084f9c4 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -91,6 +91,11 @@ vector ContractDefinition::stateVariablesIncludingIn return stateVars; } +bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const +{ + return util::contains(annotation().linearizedBaseContracts, &_base); +} + map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const { auto exportedFunctionList = interfaceFunctionList(); @@ -202,36 +207,6 @@ vector, FunctionTypePointer>> const& ContractDefinition: return *m_interfaceFunctionList; } -vector const& ContractDefinition::inheritableMembers() const -{ - if (!m_inheritableMembers) - { - m_inheritableMembers = make_unique>(); - auto addInheritableMember = [&](Declaration const* _decl) - { - solAssert(_decl, "addInheritableMember got a nullpointer."); - if (_decl->isVisibleInDerivedContracts()) - m_inheritableMembers->push_back(_decl); - }; - - for (FunctionDefinition const* f: definedFunctions()) - addInheritableMember(f); - - for (VariableDeclaration const* v: stateVariables()) - addInheritableMember(v); - - for (StructDefinition const* s: definedStructs()) - addInheritableMember(s); - - for (EnumDefinition const* e: definedEnums()) - addInheritableMember(e); - - for (EventDefinition const* e: events()) - addInheritableMember(e); - } - return *m_inheritableMembers; -} - TypePointer ContractDefinition::type() const { return TypeProvider::typeType(TypeProvider::contract(*this)); @@ -322,6 +297,13 @@ TypePointer FunctionDefinition::type() const return TypeProvider::function(*this, FunctionType::Kind::Internal); } +TypePointer FunctionDefinition::typeViaContractName() const +{ + if (annotation().contract->isLibrary()) + return FunctionType(*this).asCallableFunction(true); + return TypeProvider::function(*this, FunctionType::Kind::Declaration); +} + string FunctionDefinition::externalSignature() const { return TypeProvider::function(*this)->externalSignature(); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index a603a93fb..db0bd477c 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -217,19 +217,22 @@ public: Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; } bool isPublic() const { return visibility() >= Visibility::Public; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } - bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } + virtual bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; } + virtual bool isVisibleViaContractTypeAccess() const { return false; } virtual bool isLValue() const { return false; } virtual bool isPartOfExternalInterface() const { return false; } /// @returns the type of expressions referencing this declaration. - /// The current contract has to be given since this context can change the type, especially of - /// contract types. /// This can only be called once types of variable declarations have already been resolved. virtual TypePointer type() const = 0; + /// @returns the type for members of the containing contract type that refer to this declaration. + /// This can only be called once types of variable declarations have already been resolved. + virtual TypePointer typeViaContractName() const { return type(); } + /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. virtual FunctionTypePointer functionType(bool /*_internal*/) const { return {}; } @@ -420,13 +423,16 @@ public: bool isInterface() const { return m_contractKind == ContractKind::Interface; } bool isLibrary() const { return m_contractKind == ContractKind::Library; } + /// @returns true, if the contract derives from @arg _base. + bool derivesFrom(ContractDefinition const& _base) const; + /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. std::map, FunctionTypePointer> interfaceFunctions() const; std::vector, FunctionTypePointer>> const& interfaceFunctionList() const; - /// @returns a list of the inheritable members of this contract - std::vector const& inheritableMembers() const; + /// @returns a list of all declarations in this contract + std::vector declarations() const { return filteredNodes(m_subNodes); } /// Returns the constructor or nullptr if no constructor was specified. FunctionDefinition const* constructor() const; @@ -460,7 +466,6 @@ private: mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; - mutable std::unique_ptr> m_inheritableMembers; }; class InheritanceSpecifier: public ASTNode @@ -534,6 +539,9 @@ public: TypePointer type() const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return true; } + TypeDeclarationAnnotation& annotation() const override; private: @@ -553,6 +561,9 @@ public: void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return true; } + std::vector> const& members() const { return m_members; } TypePointer type() const override; @@ -715,6 +726,10 @@ public: { return Declaration::isVisibleInContract() && isOrdinary(); } + bool isVisibleViaContractTypeAccess() const override + { + return visibility() >= Visibility::Public; + } bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); } /// @returns the external signature of the function @@ -728,6 +743,7 @@ public: ContractKind inContractKind() const; TypePointer type() const override; + TypePointer typeViaContractName() const override; /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. @@ -880,6 +896,8 @@ public: TypePointer type() const override; + Visibility defaultVisibility() const override { return Visibility::Internal; } + ModifierDefinitionAnnotation& annotation() const override; private: @@ -942,6 +960,9 @@ public: TypePointer type() const override; FunctionTypePointer functionType(bool /*_internal*/) const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return false; /* TODO */ } + EventDefinitionAnnotation& annotation() const override; private: diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index ea7ca8590..ae447f884 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -78,6 +78,9 @@ struct ScopableAnnotation /// The scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. ASTNode const* scope = nullptr; + /// Pointer to the contract this declaration resides in. Can be nullptr if the current scope + /// is not part of a contract. Available only after name and type resolution step. + ContractDefinition const* contract = nullptr; }; struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation @@ -121,8 +124,6 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation { - /// Pointer to the contract this function is defined in - ContractDefinition const* contract = nullptr; }; struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 16f239df6..c64eee64e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2957,12 +2957,30 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const ); } -MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const +MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _scope) const { switch (m_kind) { case Kind::Declaration: - return {{"selector", TypeProvider::fixedBytes(4)}}; + if (declaration().isPartOfExternalInterface()) + return {{"selector", TypeProvider::fixedBytes(4)}}; + else + return MemberList::MemberMap(); + case Kind::Internal: + if ( + auto const* functionDefinition = dynamic_cast(m_declaration); + functionDefinition && + _scope && + functionDefinition->annotation().contract && + _scope != functionDefinition->annotation().contract && + functionDefinition->isPartOfExternalInterface() + ) + { + solAssert(_scope->derivesFrom(*functionDefinition->annotation().contract), ""); + return {{"selector", TypeProvider::fixedBytes(4)}}; + } + else + return MemberList::MemberMap(); case Kind::External: case Kind::Creation: case Kind::BareCall: @@ -3406,36 +3424,20 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current if (m_actualType->category() == Category::Contract) { ContractDefinition const& contract = dynamic_cast(*m_actualType).contractDefinition(); - bool isBase = false; - if (_currentScope != nullptr) + bool inDerivingScope = _currentScope && _currentScope->derivesFrom(contract); + + for (auto const* declaration: contract.declarations()) { - auto const& currentBases = _currentScope->annotation().linearizedBaseContracts; - isBase = (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()); - } - if (isBase) - { - // We are accessing the type of a base contract, so add all public and protected - // members. Note that this does not add inherited functions on purpose. - for (Declaration const* decl: contract.inheritableMembers()) - members.emplace_back(decl->name(), decl->type(), decl); - } - else - { - bool inLibrary = contract.isLibrary(); - for (FunctionDefinition const* function: contract.definedFunctions()) - if ( - (inLibrary && function->isVisibleAsLibraryMember()) || - (!inLibrary && function->isPartOfExternalInterface()) - ) - members.emplace_back( - function->name(), - FunctionType(*function).asCallableFunction(inLibrary), - function - ); - for (auto const& stru: contract.definedStructs()) - members.emplace_back(stru->name(), stru->type(), stru); - for (auto const& enu: contract.definedEnums()) - members.emplace_back(enu->name(), enu->type(), enu); + if (dynamic_cast(declaration)) + continue; + + if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts()) + members.emplace_back(declaration->name(), declaration->type(), declaration); + else if ( + (contract.isLibrary() && declaration->isVisibleAsLibraryMember()) || + declaration->isVisibleViaContractTypeAccess() + ) + members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration); } } else if (m_actualType->category() == Category::Enum) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2e1ef44dc..ff8c5fb65 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1300,7 +1300,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) functionType && member == "selector" ) { - if (functionType->kind() == FunctionType::Kind::Declaration) + if (functionType->hasDeclaration()) { m_context << functionType->externalIdentifier(); /// need to store it as bytes4 diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/145_external_base_visibility.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/145_external_base_visibility.sol index cf6804626..e1d4368e1 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/145_external_base_visibility.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/145_external_base_visibility.sol @@ -5,4 +5,4 @@ contract derived is base { function g() public { base.f(); } } // ---- -// TypeError: (100-106): Member "f" not found or not visible after argument-dependent lookup in type(contract base). +// TypeError: (100-108): Cannot call function via contract type name. diff --git a/test/libsolidity/syntaxTests/types/call_unimplemented_internal_function.sol b/test/libsolidity/syntaxTests/types/call_unimplemented_internal_function.sol new file mode 100644 index 000000000..9b1a88f48 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/call_unimplemented_internal_function.sol @@ -0,0 +1,6 @@ +abstract contract A { + function f() public virtual; + function g() public { + f(); + } +} diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_base_name_to_var.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_base_name_to_var.sol new file mode 100644 index 000000000..d26c51603 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_base_name_to_var.sol @@ -0,0 +1,14 @@ +contract A { + function f() external {} + function g() external pure {} +} + +contract B is A { + function h() external { + function() external f = A.f; + function() external pure g = A.g; + } +} +// ---- +// TypeError: (133-160): Type function A.f() is not implicitly convertible to expected type function () external. +// TypeError: (170-202): Type function A.g() pure is not implicitly convertible to expected type function () pure external. diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/assign_function_via_contract_name_to_var.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_contract_name_to_var.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/assign_function_via_contract_name_to_var.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/assign_function_via_contract_name_to_var.sol diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract.sol new file mode 100644 index 000000000..154a57d18 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract.sol @@ -0,0 +1,11 @@ +contract B { + function f() external {} + function g() public {} +} +contract C is B { + function h() public { + B.f.selector; + B.g.selector; + B.g(); + } +} diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract_invalid.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract_invalid.sol new file mode 100644 index 000000000..35c926eb3 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/base_contract_invalid.sol @@ -0,0 +1,13 @@ +contract B { + function f() external {} + function g() internal {} +} +contract C is B { + function i() public { + B.f(); + B.g.selector; + } +} +// ---- +// TypeError: (125-130): Cannot call function via contract type name. +// TypeError: (140-152): Member "selector" not found or not visible after argument-dependent lookup in function (). diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/call_function_via_contract_name.sol similarity index 54% rename from test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/call_function_via_contract_name.sol index 12b49007e..419080cbb 100644 --- a/test/libsolidity/syntaxTests/types/function_types/definition/call_function_via_contract_name.sol +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/call_function_via_contract_name.sol @@ -12,6 +12,6 @@ contract B { } } // ---- -// TypeError: (160-165): Cannot call function via contract name. -// TypeError: (175-180): Cannot call function via contract name. -// TypeError: (190-195): Cannot call function via contract name. +// TypeError: (160-165): Cannot call function via contract type name. +// TypeError: (175-180): Cannot call function via contract type name. +// TypeError: (190-195): Cannot call function via contract type name. diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/call_unimplemented_base.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/call_unimplemented_base.sol new file mode 100644 index 000000000..a785de63c --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/call_unimplemented_base.sol @@ -0,0 +1,10 @@ +abstract contract B { + function f() public virtual; +} +contract C is B { + function f() public override { + B.f(); + } +} +// ---- +// TypeError: (118-123): Cannot call unimplemented base function. diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_contract_name.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_selector_via_contract_name.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_contract_name.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_selector_via_contract_name.sol diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_interface_name.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_selector_via_interface_name.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_selector_via_interface_name.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_selector_via_interface_name.sol diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_internal.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_internal.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_internal.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_internal.sol diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_overloaded.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_overloaded.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_overloaded.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_overloaded.sol diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_private.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_private.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_private.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_private.sol diff --git a/test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_public.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_public.sol similarity index 100% rename from test/libsolidity/syntaxTests/types/function_types/definition/function_via_contract_name_public.sol rename to test/libsolidity/syntaxTests/types/contractTypeType/members/function_via_contract_name_public.sol diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier.sol new file mode 100644 index 000000000..496b16524 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier.sol @@ -0,0 +1,10 @@ +contract A { + modifier mod() { _; } +} +contract B { + function f() public { + A.mod; + } +} +// ---- +// TypeError: (88-93): Member "mod" not found or not visible after argument-dependent lookup in type(contract A). diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier_base.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier_base.sol new file mode 100644 index 000000000..85c0daec2 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/modifier_base.sol @@ -0,0 +1,10 @@ +contract A { + modifier mod() { _; } +} +contract B is A { + function f() public { + A.mod; + } +} +// ---- +// TypeError: (93-98): Member "mod" not found or not visible after argument-dependent lookup in type(contract A). diff --git a/test/libsolidity/syntaxTests/types/contractTypeType/members/struct_enum.sol b/test/libsolidity/syntaxTests/types/contractTypeType/members/struct_enum.sol new file mode 100644 index 000000000..1ecdfe58e --- /dev/null +++ b/test/libsolidity/syntaxTests/types/contractTypeType/members/struct_enum.sol @@ -0,0 +1,14 @@ +contract A { + struct S { uint256 a; } + enum E { V } +} +contract B { + A.S x; + A.E e; +} +contract C is A { + A.S x; + S y; + A.E e; + E f; +} diff --git a/test/libsolidity/syntaxTests/types/library_internal_call.sol b/test/libsolidity/syntaxTests/types/library_internal_call.sol new file mode 100644 index 000000000..0bb9b25f0 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_internal_call.sol @@ -0,0 +1,4 @@ +library L { + function a() public pure {} + function b() public pure { a(); } +} From 81569f720883735ee6eee0a16173df63d2180d12 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 16 Jan 2020 19:13:25 +0100 Subject: [PATCH 031/277] Remove Scope::Label. --- libyul/AsmAnalysis.cpp | 12 ---------- libyul/AsmScope.cpp | 8 ------- libyul/AsmScope.h | 5 +--- libyul/backends/evm/EVMCodeTransform.cpp | 30 ------------------------ libyul/backends/evm/EVMCodeTransform.h | 4 ---- 5 files changed, 1 insertion(+), 58 deletions(-) diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 5a88671ce..148eeabfe 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -123,10 +123,6 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) } ++m_stackHeight; }, - [&](Scope::Label const&) - { - ++m_stackHeight; - }, [&](Scope::Function const&) { m_errorReporter.typeError( @@ -293,14 +289,6 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) ); success = false; }, - [&](Scope::Label const&) - { - m_errorReporter.typeError( - _funCall.functionName.location, - "Attempt to call label instead of function." - ); - success = false; - }, [&](Scope::Function const& _fun) { /// TODO: compare types too diff --git a/libyul/AsmScope.cpp b/libyul/AsmScope.cpp index ae39fc10d..86dbce483 100644 --- a/libyul/AsmScope.cpp +++ b/libyul/AsmScope.cpp @@ -25,14 +25,6 @@ using namespace solidity; using namespace solidity::yul; using namespace solidity::util; -bool Scope::registerLabel(YulString _name) -{ - if (exists(_name)) - return false; - identifiers[_name] = Label(); - return true; -} - bool Scope::registerVariable(YulString _name, YulType const& _type) { if (exists(_name)) diff --git a/libyul/AsmScope.h b/libyul/AsmScope.h index a452e04ba..6da1ff1e0 100644 --- a/libyul/AsmScope.h +++ b/libyul/AsmScope.h @@ -37,20 +37,17 @@ namespace solidity::yul struct Scope { using YulType = YulString; - using LabelID = size_t; struct Variable { YulType type; }; - struct Label { }; struct Function { std::vector arguments; std::vector returns; }; - using Identifier = std::variant; + using Identifier = std::variant; bool registerVariable(YulString _name, YulType const& _type); - bool registerLabel(YulString _name); bool registerFunction( YulString _name, std::vector _arguments, diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 9f95d1257..f8ce12017 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -88,7 +88,6 @@ void VariableReferenceCounter::increaseRefIfFound(YulString _variableName) { ++m_context.variableReferences[&_var]; }, - [=](Scope::Label const&) { }, [=](Scope::Function const&) { } }); } @@ -286,7 +285,6 @@ void CodeTransform::operator()(FunctionCall const& _call) Scope::Function* function = nullptr; yulAssert(m_scope->lookup(_call.functionName.name, GenericVisitor{ [=](Scope::Variable&) { yulAssert(false, "Expected function name."); }, - [=](Scope::Label&) { yulAssert(false, "Expected function name."); }, [&](Scope::Function& _function) { function = &_function; } }), "Function name not found."); yulAssert(function, ""); @@ -323,10 +321,6 @@ void CodeTransform::operator()(Identifier const& _identifier) m_assembly.appendConstant(u256(0)); decreaseReference(_identifier.name, _var); }, - [=](Scope::Label& _label) - { - m_assembly.appendLabelReference(labelID(_label)); - }, [=](Scope::Function&) { yulAssert(false, "Function not removed during desugaring."); @@ -634,30 +628,6 @@ void CodeTransform::operator()(Block const& _block) BOOST_THROW_EXCEPTION(m_stackErrors.front()); } -AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier) -{ - AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1); - if (!m_scope->lookup(_identifier.name, GenericVisitor{ - [=](Scope::Variable&) { yulAssert(false, "Expected label"); }, - [&](Scope::Label& _label) - { - label = labelID(_label); - }, - [=](Scope::Function&) { yulAssert(false, "Expected label"); } - })) - { - yulAssert(false, "Identifier not found."); - } - return label; -} - -AbstractAssembly::LabelID CodeTransform::labelID(Scope::Label const& _label) -{ - if (!m_context->labelIDs.count(&_label)) - m_context->labelIDs[&_label] = m_assembly.newLabelId(); - return m_context->labelIDs[&_label]; -} - AbstractAssembly::LabelID CodeTransform::functionEntryID(YulString _name, Scope::Function const& _function) { if (!m_context->functionEntryIDs.count(&_function)) diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index 2c2c5991d..fb50be95f 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -53,7 +53,6 @@ struct StackTooDeepError: virtual YulException struct CodeTransformContext { - std::map labelIDs; std::map functionEntryIDs; std::map variableStackHeights; std::map variableReferences; @@ -187,9 +186,6 @@ public: private: AbstractAssembly::LabelID labelFromIdentifier(Identifier const& _identifier); - /// @returns the label ID corresponding to the given label, allocating a new one if - /// necessary. - AbstractAssembly::LabelID labelID(Scope::Label const& _label); AbstractAssembly::LabelID functionEntryID(YulString _name, Scope::Function const& _function); /// Generates code for an expression that is supposed to return a single value. void visitExpression(Expression const& _expression); From 4262eebdc492c9e2bfb55667daed089eb6c47d5e Mon Sep 17 00:00:00 2001 From: cameel Date: Fri, 17 Jan 2020 07:53:48 +0100 Subject: [PATCH 032/277] [yulopti] Fix typo: (f)flatten -> (f)latten --- test/tools/yulopti.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 67b34a784..2212d0b68 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -138,7 +138,7 @@ public: m_nameDispenser = make_shared(m_dialect, *m_ast, reservedIdentifiers); disambiguated = true; } - cout << "(q)quit/(f)flatten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; + cout << "(q)quit/(f)latten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; cout << " (e)xpr inline/(i)nline/(s)implify/varname c(l)eaner/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl; cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl; From 49ca1f85345e0df575be7c1b73caec7931243f6a Mon Sep 17 00:00:00 2001 From: cameel Date: Fri, 17 Jan 2020 08:00:41 +0100 Subject: [PATCH 033/277] [yulopti] Add missing ConditionalUnsimplifier to the list of available optimizations --- test/tools/yulopti.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 2212d0b68..76521b6fd 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -143,7 +144,7 @@ public: cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl; cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl; cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/(L)oad resolver/" << endl; - cout << " (C)onditional simplifier/loop-invariant code (M)otion?" << endl; + cout << " (C)onditional simplifier/conditional (U)nsimplifier/loop-invariant code (M)otion?" << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -171,6 +172,9 @@ public: case 'C': ConditionalSimplifier::run(context, *m_ast); break; + case 'U': + ConditionalUnsimplifier::run(context, *m_ast); + break; case 'd': VarDeclInitializer::run(context, *m_ast); break; From bf5e62931d7366a73a503d2bdfdb7c9bb12f3455 Mon Sep 17 00:00:00 2001 From: cameel Date: Fri, 17 Jan 2020 12:38:45 +0100 Subject: [PATCH 034/277] fixup! [yulopti] Fix typo: (f)flatten -> (f)latten --- test/tools/yulopti.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 76521b6fd..23e535734 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -139,7 +139,7 @@ public: m_nameDispenser = make_shared(m_dialect, *m_ast, reservedIdentifiers); disambiguated = true; } - cout << "(q)quit/(f)latten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; + cout << "(q)uit/(f)latten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; cout << " (e)xpr inline/(i)nline/(s)implify/varname c(l)eaner/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl; cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl; From 00f3c42d171cb8c1ae731ad0df0f7cb40726424a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 13 Dec 2019 00:35:55 +0000 Subject: [PATCH 035/277] Use multiprecision::msb() in GasMeter and remove FixedHash dependency --- libevmasm/GasMeter.cpp | 11 ++++++++--- libsolutil/FixedHash.h | 16 ---------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 358d67c3a..6a07e9905 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -19,8 +19,6 @@ #include -#include - using namespace std; using namespace solidity; using namespace solidity::util; @@ -182,7 +180,14 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::EXP: gas = GasCosts::expGas; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) - gas += GasCosts::expByteGas(m_evmVersion) * (32 - (h256(*value).firstBitSet() / 8)); + { + if (*value) + { + // Note: msb() counts from 0 and throws on 0 as input. + unsigned const significantByteCount = (boost::multiprecision::msb(*value) + 1 + 7) / 8; + gas += GasCosts::expByteGas(m_evmVersion) * significantByteCount; + } + } else gas += GasCosts::expByteGas(m_evmVersion) * 32; break; diff --git a/libsolutil/FixedHash.h b/libsolutil/FixedHash.h index f5a042d4c..4fa5a3072 100644 --- a/libsolutil/FixedHash.h +++ b/libsolutil/FixedHash.h @@ -104,22 +104,6 @@ public: /// @returns a copy of the object's data as a byte vector. bytes asBytes() const { return bytes(data(), data() + N); } - /// Returns the index of the first bit set to one, or size() * 8 if no bits are set. - inline unsigned firstBitSet() const - { - unsigned ret = 0; - for (auto d: m_data) - if (d) - { - for (;; ++ret, d <<= 1) - if (d & 0x80) - return ret; - } - else - ret += 8; - return ret; - } - private: std::array m_data; ///< The binary data. }; From 004f01a388c9420191b4d7137e61e0552ff93118 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 16 Jan 2020 14:34:45 +0000 Subject: [PATCH 036/277] Add test case for gas metering of exp(n, 0) --- test/libsolidity/GasMeter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index da2f79977..0020d80e4 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -261,6 +261,9 @@ BOOST_AUTO_TEST_CASE(exponent_size) { char const* sourceCode = R"( contract A { + function f(uint x) public returns (uint) { + return x ** 0; + } function g(uint x) public returns (uint) { return x ** 0x100; } @@ -270,6 +273,7 @@ BOOST_AUTO_TEST_CASE(exponent_size) } )"; testCreationTimeGas(sourceCode); + testRunTimeGas("f(uint256)", vector{encodeArgs(2)}); testRunTimeGas("g(uint256)", vector{encodeArgs(2)}); testRunTimeGas("h(uint256)", vector{encodeArgs(2)}); } From 9befb4bdf0e928905132816522ef1aed9883e946 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 18 Nov 2019 12:12:30 +0100 Subject: [PATCH 037/277] yul proto fuzzer: Do not generate infinite for loops and limit total number of for loops per test program to two --- test/tools/ossfuzz/protoToYul.cpp | 31 +++++++++++++++++++++++++++---- test/tools/ossfuzz/protoToYul.h | 9 +++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index cc7fde3ae..f5f67fc02 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -35,9 +35,18 @@ using namespace solidity; string ProtoConverter::dictionaryToken(HexPrefix _p) { - unsigned indexVar = m_inputSize * m_inputSize + counter(); - std::string token = hexDictionary[indexVar % hexDictionary.size()]; - yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large"); + std::string token; + // If dictionary constant is requested while converting + // for loop condition, then return zero so that we don't + // generate infinite for loops. + if (m_inForCond) + token = "0"; + else + { + unsigned indexVar = m_inputSize * m_inputSize + counter(); + token = hexDictionary[indexVar % hexDictionary.size()]; + yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large"); + } return _p == HexPrefix::Add ? "0x" + token : token; } @@ -173,7 +182,13 @@ void ProtoConverter::visit(Expression const& _x) visit(_x.varref()); break; case Expression::kCons: - m_output << visit(_x.cons()); + // If literal expression describes for-loop condition + // then force it to zero, so we don't generate infinite + // for loops + if (m_inForCond) + m_output << "0"; + else + m_output << visit(_x.cons()); break; case Expression::kBinop: visit(_x.binop()); @@ -964,17 +979,22 @@ void ProtoConverter::visit(StoreFunc const& _x) void ProtoConverter::visit(ForStmt const& _x) { + if (++m_numForLoops > s_maxForLoops) + return; bool wasInForBody = m_inForBodyScope; bool wasInForInit = m_inForInitScope; bool wasForInitScopeExtEnabled = m_forInitScopeExtEnabled; m_inForBodyScope = false; m_inForInitScope = true; m_forInitScopeExtEnabled = true; + m_inForCond = false; m_output << "for "; visit(_x.for_init()); m_inForInitScope = false; m_forInitScopeExtEnabled = wasForInitScopeExtEnabled; + m_inForCond = true; visit(_x.for_cond()); + m_inForCond = false; visit(_x.for_post()); m_inForBodyScope = true; visit(_x.for_body()); @@ -997,6 +1017,9 @@ void ProtoConverter::visit(ForStmt const& _x) void ProtoConverter::visit(BoundedForStmt const& _x) { + if (++m_numForLoops > s_maxForLoops) + return; + // Boilerplate for loop that limits the number of iterations to a maximum of 4. std::string loopVarName("i_" + std::to_string(m_numNestedForLoops++)); m_output << "for { let " << loopVarName << " := 0 } " diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 4150e7c64..37321c78c 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -42,7 +42,9 @@ public: m_globalVars = std::vector>{}; m_inForBodyScope = false; m_inForInitScope = false; + m_inForCond = false; m_numNestedForLoops = 0; + m_numForLoops = 0; m_counter = 0; m_inputSize = 0; m_inFunctionDef = false; @@ -339,11 +341,18 @@ private: /// Predicate to keep track of for body scope. If false, break/continue /// statements can not be created. bool m_inForBodyScope; + /// Maximum number of for loops that a test case may contain + static auto constexpr s_maxForLoops = 2; // Index used for naming loop variable of bounded for loops unsigned m_numNestedForLoops; + /// Counter for number of for loops + unsigned m_numForLoops; /// Predicate to keep track of for loop init scope. If true, variable /// or function declarations can not be created. bool m_inForInitScope; + /// Flag that is true while converting for loop condition, + /// false otherwise. + bool m_inForCond; /// Monotonically increasing counter unsigned m_counter; /// Size of protobuf input From e7c109898e1f03abd7fedd32c1646511f4241d61 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 20 Jan 2020 17:13:40 +0300 Subject: [PATCH 038/277] Fix the latest byte of metadata hash encoding in the docs --- docs/metadata.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/metadata.rst b/docs/metadata.rst index a91751858..0c8ae47f1 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -142,7 +142,7 @@ to the end of the deployed bytecode:: 0xa2 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> - 0x00 0x32 + 0x00 0x33 So in order to retrieve the data, the end of the deployed bytecode can be checked to match that pattern and use the IPFS hash to retrieve the file. From d48883ca17b3d00493e9adbd9165ce6a723f1ab5 Mon Sep 17 00:00:00 2001 From: cameel Date: Mon, 20 Jan 2020 11:20:18 +0100 Subject: [PATCH 039/277] Fix crashes in prepare_report.py caused by using str where bytes is expected and vice-versa 1) `Popen.communicate()` expects `bytes` (a raw, binary string) if `stdout`/`stderr` are open in binary mode but is given output from `json.loads()` which is str (an abstract unicode string). Encoding the `str` object into `bytes` using UTF-8 encoding fixes that. 2) `REPORT_FILE` gets opened in binary mode which means that functions like `write()` expect `bytes`. We're giving them `str` which results in an error. Changed mode to text solves the problem. --- scripts/bytecodecompare/prepare_report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index 9a3d86c8d..dad4dbbd4 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -6,7 +6,7 @@ import subprocess import json SOLC_BIN = sys.argv[1] -REPORT_FILE = open("report.txt", "wb") +REPORT_FILE = open("report.txt", "w") for optimize in [False, True]: for f in sorted(glob.glob("*.sol")): @@ -26,9 +26,9 @@ for optimize in [False, True]: if optimize: args += ['--optimize'] proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = proc.communicate(json.dumps(input_json)) + (out, err) = proc.communicate(json.dumps(input_json).encode('utf-8')) try: - result = json.loads(out.strip()) + result = json.loads(out.decode('utf-8').strip()) for filename in sorted(result['contracts'].keys()): for contractName in sorted(result['contracts'][filename].keys()): contractData = result['contracts'][filename][contractName] From 1cc8ce16562f94f156c0e97404c0d96ada1eb9f9 Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Thu, 2 Jan 2020 16:27:32 -0500 Subject: [PATCH 040/277] Don't prohibit interface inheritance --- libsolidity/analysis/TypeChecker.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2c867b962..646edeb1a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -237,9 +237,6 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); - if (m_scope->isInterface()) - m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit."); - if (base->isLibrary()) m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from."); From 573a054d5d304f9363f212199f658c34b1f0729c Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Thu, 2 Jan 2020 16:32:35 -0500 Subject: [PATCH 041/277] Prohibit interfaces inheriting from non-interfaces --- libsolidity/analysis/TypeChecker.cpp | 3 +++ .../syntaxTests/inheritance/interface/contract_base.sol | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/contract_base.sol diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 646edeb1a..153f4a774 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -237,6 +237,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); + if (m_scope->isInterface() && !base->isInterface()) + m_errorReporter.typeError(_inheritance.location(), "Interfaces can only inherit from other interfaces."); + if (base->isLibrary()) m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from."); diff --git a/test/libsolidity/syntaxTests/inheritance/interface/contract_base.sol b/test/libsolidity/syntaxTests/inheritance/interface/contract_base.sol new file mode 100644 index 000000000..1afb921c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/contract_base.sol @@ -0,0 +1,5 @@ +contract C {} +interface I is C {} + +// ---- +// TypeError: (29-30): Interfaces can only inherit from other interfaces. From 595f569e97e012ae00fc5259afb0bf4c1474f5e2 Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Thu, 2 Jan 2020 16:28:07 -0500 Subject: [PATCH 042/277] Update tests for interface inheritance --- .../interface_inheritance_conversions.sol | 39 +++++++++++++++++++ .../interface/diamond/diamond_no_relist.sol | 10 +++++ .../interface/diamond/diamond_with_relist.sol | 15 +++++++ .../interface/implementation/complete.sol | 17 ++++++++ .../interface/implementation/partial.sol | 19 +++++++++ .../linearization/invalid/lists_a.sol | 8 ++++ .../linearization/invalid/lists_b.sol | 8 ++++ .../linearization/invalid/lists_both.sol | 8 ++++ .../interface/linearization/valid.sol | 7 ++++ .../interface/multiple_parents.sol | 15 +++++++ .../interface/overrides_multiple.sol | 31 +++++++++++++++ .../interface/overrides_single.sol | 14 +++++++ .../inheritance/interface/single_parent.sol | 5 +++ .../418_interface_inheritance.sol | 2 - 14 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 test/libsolidity/semanticTests/interface_inheritance_conversions.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_no_relist.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_with_relist.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/implementation/complete.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/implementation/partial.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_a.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_b.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_both.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/linearization/valid.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/multiple_parents.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/overrides_multiple.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/overrides_single.sol create mode 100644 test/libsolidity/syntaxTests/inheritance/interface/single_parent.sol diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol new file mode 100644 index 000000000..4241afe81 --- /dev/null +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol @@ -0,0 +1,39 @@ +interface Parent { + function parentFun() external returns (uint256); +} + +interface SubA is Parent { + function subAFun() external returns (uint256); +} + +interface SubB is Parent { + function subBFun() external returns (uint256); +} + +contract Impl is SubA, SubB { + function parentFun() override external returns (uint256) { return 1; } + function subAFun() override external returns (uint256) { return 2; } + function subBFun() override external returns (uint256) { return 3; } +} + +contract C { + function convertParent() public returns (uint256) { + Parent p = new Impl(); + return p.parentFun(); + } + + function convertSubA() public returns (uint256, uint256) { + SubA sa = new Impl(); + return (sa.parentFun(), sa.subAFun()); + } + + function convertSubB() public returns (uint256, uint256) { + SubB sb = new Impl(); + return (sb.parentFun(), sb.subBFun()); + } +} + +// ---- +// convertParent() -> 1 +// convertSubA() -> 1, 2 +// convertSubB() -> 1, 3 diff --git a/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_no_relist.sol b/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_no_relist.sol new file mode 100644 index 000000000..5d525fde4 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_no_relist.sol @@ -0,0 +1,10 @@ +interface Parent { + function test() external returns (uint256); +} + +interface SubA is Parent {} +interface SubB is Parent {} + +contract C is SubA, SubB { + function test() external override returns (uint256) { return 42; } +} diff --git a/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_with_relist.sol b/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_with_relist.sol new file mode 100644 index 000000000..30ee63cc7 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/diamond/diamond_with_relist.sol @@ -0,0 +1,15 @@ +interface Parent { + function test() external returns (uint256); +} + +interface SubA is Parent { + function test() external override returns (uint256); +} + +interface SubB is Parent { + function test() external override returns (uint256); +} + +contract C is SubA, SubB { + function test() external override(SubA, SubB) returns (uint256) { return 42; } +} diff --git a/test/libsolidity/syntaxTests/inheritance/interface/implementation/complete.sol b/test/libsolidity/syntaxTests/inheritance/interface/implementation/complete.sol new file mode 100644 index 000000000..09334dafc --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/implementation/complete.sol @@ -0,0 +1,17 @@ +interface ParentA { + function testA() external returns (uint256); +} + +interface ParentB { + function testB() external returns (uint256); +} + +interface Sub is ParentA, ParentB { + function testSub() external returns (uint256); +} + +contract SubImpl is Sub { + function testA() external override returns (uint256) { return 12; } + function testB() external override(ParentB) returns (uint256) { return 42; } + function testSub() external override returns (uint256) { return 99; } +} diff --git a/test/libsolidity/syntaxTests/inheritance/interface/implementation/partial.sol b/test/libsolidity/syntaxTests/inheritance/interface/implementation/partial.sol new file mode 100644 index 000000000..378c3c41b --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/implementation/partial.sol @@ -0,0 +1,19 @@ +interface ParentA { + function testA() external returns (uint256); +} + +interface ParentB { + function testB() external returns (uint256); +} + +interface Sub is ParentA, ParentB { + function testSub() external returns (uint256); +} + +contract SubImpl is Sub { + function testA() external override returns (uint256) { return 12; } + function testSub() external override returns (uint256) { return 99; } +} + +// ---- +// TypeError: (234-407): Contract "SubImpl" should be marked as abstract. diff --git a/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_a.sol b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_a.sol new file mode 100644 index 000000000..0528af074 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_a.sol @@ -0,0 +1,8 @@ +interface ParentA {} +interface ParentB {} +interface Sub is ParentA, ParentB {} + +contract ListsA is Sub, ParentA {} + +// ---- +// TypeError: (80-114): Linearization of inheritance graph impossible diff --git a/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_b.sol b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_b.sol new file mode 100644 index 000000000..cea97889c --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_b.sol @@ -0,0 +1,8 @@ +interface ParentA {} +interface ParentB {} +interface Sub is ParentA, ParentB {} + +contract ListsB is Sub, ParentB {} + +// ---- +// TypeError: (80-114): Linearization of inheritance graph impossible diff --git a/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_both.sol b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_both.sol new file mode 100644 index 000000000..8bf99bd91 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/linearization/invalid/lists_both.sol @@ -0,0 +1,8 @@ +interface ParentA {} +interface ParentB {} +interface Sub is ParentA, ParentB {} + +contract ListsBoth is Sub, ParentA, ParentB {} + +// ---- +// TypeError: (80-126): Linearization of inheritance graph impossible diff --git a/test/libsolidity/syntaxTests/inheritance/interface/linearization/valid.sol b/test/libsolidity/syntaxTests/inheritance/interface/linearization/valid.sol new file mode 100644 index 000000000..4f725e1b7 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/linearization/valid.sol @@ -0,0 +1,7 @@ +interface ParentA {} +interface ParentB {} +interface Sub is ParentA, ParentB {} + +contract ListsA is ParentA, Sub {} +contract ListsB is ParentB, Sub {} +contract ListsBoth is ParentA, ParentB, Sub {} diff --git a/test/libsolidity/syntaxTests/inheritance/interface/multiple_parents.sol b/test/libsolidity/syntaxTests/inheritance/interface/multiple_parents.sol new file mode 100644 index 000000000..f3dcf6f0e --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/multiple_parents.sol @@ -0,0 +1,15 @@ +interface SuperA { + function test() external returns (uint256); + function testA() external returns (int128); +} + +interface SuperB { + function test() external returns (uint256); + function testB() external returns (int256); +} + +interface Sub is SuperA, SuperB { +} + +// ---- +// TypeError: (236-271): Derived contract must override function "test". Two or more base classes define function with same name and parameter types. diff --git a/test/libsolidity/syntaxTests/inheritance/interface/overrides_multiple.sol b/test/libsolidity/syntaxTests/inheritance/interface/overrides_multiple.sol new file mode 100644 index 000000000..36d76f3a8 --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/overrides_multiple.sol @@ -0,0 +1,31 @@ +interface SuperA { + function test1() external returns (uint256); + function test2() external returns (uint256); + function test3() external returns (uint256); + function test4() external returns (uint256); + function test5() external returns (uint256); +} + +interface SuperB { + function test1() external returns (uint256); + function test2() external returns (uint256); + function test3() external returns (uint256); + function test4() external returns (uint256); + function test5() external returns (uint256); +} + +interface Sub is SuperA, SuperB { + function test1() external returns (uint256); + function test2() external override returns (uint256); + function test3() external override(SuperA) returns (uint256); + function test4() external override(SuperB) returns (uint256); + function test5() external override(SuperA, SuperB) returns (uint256); +} + +// ---- +// TypeError: (572-616): Overriding function is missing "override" specifier. +// TypeError: (572-616): Overriding function is missing "override" specifier. +// TypeError: (572-616): Function needs to specify overridden contracts "SuperA" and "SuperB". +// TypeError: (647-655): Function needs to specify overridden contracts "SuperA" and "SuperB". +// TypeError: (705-721): Function needs to specify overridden contract "SuperB". +// TypeError: (771-787): Function needs to specify overridden contract "SuperA". diff --git a/test/libsolidity/syntaxTests/inheritance/interface/overrides_single.sol b/test/libsolidity/syntaxTests/inheritance/interface/overrides_single.sol new file mode 100644 index 000000000..1a68d980b --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/overrides_single.sol @@ -0,0 +1,14 @@ +interface Super { + function test1() external returns (uint256); + function test2() external returns (uint256); + function test3() external returns (uint256); +} + +interface Sub is Super { + function test1() external returns (uint256); + function test2() external override returns (uint256); + function test3() external override(Super) returns (uint256); +} + +// ---- +// TypeError: (197-241): Overriding function is missing "override" specifier. diff --git a/test/libsolidity/syntaxTests/inheritance/interface/single_parent.sol b/test/libsolidity/syntaxTests/inheritance/interface/single_parent.sol new file mode 100644 index 000000000..d6a7c747e --- /dev/null +++ b/test/libsolidity/syntaxTests/inheritance/interface/single_parent.sol @@ -0,0 +1,5 @@ +interface Super { + function test() external returns (uint256); +} + +interface Sub is Super {} diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/418_interface_inheritance.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/418_interface_inheritance.sol index 92683cda3..53a45636e 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/418_interface_inheritance.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/418_interface_inheritance.sol @@ -2,5 +2,3 @@ interface A { } interface I is A { } -// ---- -// TypeError: (31-32): Interfaces cannot inherit. From f492a0a3ef46e5466c717e314be459ade5b03981 Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Tue, 21 Jan 2020 08:32:04 -0500 Subject: [PATCH 043/277] Update changelog for interface inheritance --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 0c700d1a6..44e515d16 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Language Features: * Allow accessing external functions via contract and interface names to obtain their selector. * Inline Assembly: Support literals ``true`` and ``false``. + * Allow interfaces to inherit from other interfaces Compiler Features: From 86145adac8362f55e4cd1ccc903f02f909b67b06 Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Tue, 21 Jan 2020 08:42:58 -0500 Subject: [PATCH 044/277] Update documentation for interface inheritance --- docs/contracts/interfaces.rst | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index 69d4917cc..727809460 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -8,7 +8,7 @@ Interfaces Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: -- They cannot inherit other contracts or interfaces. +- They cannot inherit from other contracts, but they can inherit from other interfaces. - All declared functions must be external. - They cannot declare a constructor. - They cannot declare state variables. @@ -37,10 +37,31 @@ they can be overridden. This does not automatically mean that an overriding func can be overridden again - this is only possible if the overriding function is marked ``virtual``. +Interfaces can inherit from other interfaces. This has the same rules as normal +inheritance. + +:: + + pragma solidity >0.6.1 <0.7.0; + + interface ParentA { + function test() external returns (uint256); + } + + interface ParentB { + function test() external returns (uint256); + } + + interface SubInterface is ParentA, ParentB { + // Must redefine test in order to assert that the parent + // meanings are compatible. + function test() external override(ParentA, ParentB) returns (uint256); + } + Types defined inside interfaces and other contract-like structures can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. .. warning: Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make - sure the pragma version specifies this version as a minimum. \ No newline at end of file + sure the pragma version specifies this version as a minimum. From bb6fb675e0f7f31c6880226186605d5d6f49b880 Mon Sep 17 00:00:00 2001 From: pinkiebell <40266861+pinkiebell@users.noreply.github.com> Date: Wed, 22 Jan 2020 17:49:15 +0100 Subject: [PATCH 045/277] libsolidity/codegen: Use calldatacopy to cheaply zero memory instead of codecopy. Motiviation: Zero'ing memory is commonplace in contracts, but with the upcoming Layer-2 EVM translation layers and other on-chain verification mechanisms, using `codecopy` becomes a `costly` operation in those sandboxes. Using `calldatacopy` achieves the same thing, gas costs are also the same as codecopy, and is significantly cheaper in the `sandbox` situation. --- libsolidity/codegen/CompilerUtils.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index f9a0fbcec..c15d2537e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -605,7 +605,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) Whiskers templ(R"({ let size := mul(length, ) // cheap way of zero-initializing a memory range - codecopy(memptr, codesize(), size) + calldatacopy(memptr, calldatasize(), size) memptr := add(memptr, size) })"); templ("element_size", to_string(_type.memoryStride())); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index df89fb3f9..ee20e5b37 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -10179,7 +10179,7 @@ BOOST_AUTO_TEST_CASE(create_dynamic_array_with_zero_length) BOOST_AUTO_TEST_CASE(correctly_initialize_memory_array_in_constructor) { - // Memory arrays are initialized using codecopy past the size of the code. + // Memory arrays are initialized using calldatacopy past the size of the calldata. // This test checks that it also works in the constructor context. char const* sourceCode = R"( contract C { From dd035f8f48b65081268d9f6aa6746b83eab8d4e5 Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Wed, 22 Jan 2020 20:10:56 +0100 Subject: [PATCH 046/277] Increase scanner lookahead to two. --- liblangutil/Scanner.cpp | 55 ++++++++++++++++++++++------------------- liblangutil/Scanner.h | 41 +++++++++++++++--------------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 2db12225a..f9a8aab12 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -108,18 +108,18 @@ public: m_complete(false) { if (_type == LITERAL_TYPE_COMMENT) - m_scanner->m_nextSkippedComment.literal.clear(); + m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); else - m_scanner->m_nextToken.literal.clear(); + m_scanner->m_tokens[Scanner::NextNext].literal.clear(); } ~LiteralScope() { if (!m_complete) { if (m_type == LITERAL_TYPE_COMMENT) - m_scanner->m_nextSkippedComment.literal.clear(); + m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); else - m_scanner->m_nextToken.literal.clear(); + m_scanner->m_tokens[Scanner::NextNext].literal.clear(); } } void complete() { m_complete = true; } @@ -151,6 +151,7 @@ void Scanner::reset() skipWhitespace(); next(); next(); + next(); } void Scanner::setPosition(size_t _offset) @@ -158,6 +159,7 @@ void Scanner::setPosition(size_t _offset) m_char = m_source->setPosition(_offset); scanToken(); next(); + next(); } void Scanner::supportPeriodInIdentifier(bool _value) @@ -222,13 +224,14 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint) void Scanner::rescan() { size_t rollbackTo = 0; - if (m_skippedComment.literal.empty()) - rollbackTo = m_currentToken.location.start; + if (m_skippedComments[Current].literal.empty()) + rollbackTo = m_tokens[Current].location.start; else - rollbackTo = m_skippedComment.location.start; + rollbackTo = m_skippedComments[Current].location.start; m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo); next(); next(); + next(); } // Ensure that tokens can be stored in a byte. @@ -236,11 +239,14 @@ BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100); Token Scanner::next() { - m_currentToken = m_nextToken; - m_skippedComment = m_nextSkippedComment; + m_tokens[Current] = std::move(m_tokens[Next]); + m_tokens[Next] = std::move(m_tokens[NextNext]); + m_skippedComments[Current] = std::move(m_skippedComments[Next]); + m_skippedComments[Next] = std::move(m_skippedComments[NextNext]); + scanToken(); - return m_currentToken.token; + return m_tokens[Current].token; } Token Scanner::selectToken(char _next, Token _then, Token _else) @@ -421,10 +427,10 @@ Token Scanner::scanSlash() { // doxygen style /// comment Token comment; - m_nextSkippedComment.location.start = firstSlashPosition; + m_skippedComments[NextNext].location.start = firstSlashPosition; comment = scanSingleLineDocComment(); - m_nextSkippedComment.location.end = sourcePos(); - m_nextSkippedComment.token = comment; + m_skippedComments[NextNext].location.end = sourcePos(); + m_skippedComments[NextNext].token = comment; return Token::Whitespace; } else @@ -447,10 +453,10 @@ Token Scanner::scanSlash() } // we actually have a multiline documentation comment Token comment; - m_nextSkippedComment.location.start = firstSlashPosition; + m_skippedComments[NextNext].location.start = firstSlashPosition; comment = scanMultiLineDocComment(); - m_nextSkippedComment.location.end = sourcePos(); - m_nextSkippedComment.token = comment; + m_skippedComments[NextNext].location.end = sourcePos(); + m_skippedComments[NextNext].token = comment; if (comment == Token::Illegal) return Token::Illegal; // error already set else @@ -467,11 +473,8 @@ Token Scanner::scanSlash() void Scanner::scanToken() { - m_nextToken.error = ScannerError::NoError; - m_nextToken.literal.clear(); - m_nextToken.extendedTokenInfo = make_tuple(0, 0); - m_nextSkippedComment.literal.clear(); - m_nextSkippedComment.extendedTokenInfo = make_tuple(0, 0); + m_tokens[NextNext] = {}; + m_skippedComments[NextNext] = {}; Token token; // M and N are for the purposes of grabbing different type sizes @@ -480,7 +483,7 @@ void Scanner::scanToken() do { // Remember the position of the next token - m_nextToken.location.start = sourcePos(); + m_tokens[NextNext].location.start = sourcePos(); switch (m_char) { case '"': @@ -675,9 +678,9 @@ void Scanner::scanToken() // whitespace. } while (token == Token::Whitespace); - m_nextToken.location.end = sourcePos(); - m_nextToken.token = token; - m_nextToken.extendedTokenInfo = make_tuple(m, n); + m_tokens[NextNext].location.end = sourcePos(); + m_tokens[NextNext].token = token; + m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n); } bool Scanner::scanEscape() @@ -927,7 +930,7 @@ tuple Scanner::scanIdentifierOrKeyword() while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier)) addLiteralCharAndAdvance(); literal.complete(); - return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal); + return TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal); } } // namespace solidity::langutil diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 20dfbf29c..77af26357 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -121,32 +121,32 @@ public: /// @returns the current token Token currentToken() const { - return m_currentToken.token; + return m_tokens[Current].token; } ElementaryTypeNameToken currentElementaryTypeNameToken() const { unsigned firstSize; unsigned secondSize; - std::tie(firstSize, secondSize) = m_currentToken.extendedTokenInfo; - return ElementaryTypeNameToken(m_currentToken.token, firstSize, secondSize); + std::tie(firstSize, secondSize) = m_tokens[Current].extendedTokenInfo; + return ElementaryTypeNameToken(m_tokens[Current].token, firstSize, secondSize); } - SourceLocation currentLocation() const { return m_currentToken.location; } - std::string const& currentLiteral() const { return m_currentToken.literal; } - std::tuple const& currentTokenInfo() const { return m_currentToken.extendedTokenInfo; } + SourceLocation currentLocation() const { return m_tokens[Current].location; } + std::string const& currentLiteral() const { return m_tokens[Current].literal; } + std::tuple const& currentTokenInfo() const { return m_tokens[Current].extendedTokenInfo; } /// Retrieves the last error that occurred during lexical analysis. /// @note If no error occurred, the value is undefined. - ScannerError currentError() const noexcept { return m_currentToken.error; } + ScannerError currentError() const noexcept { return m_tokens[Current].error; } ///@} ///@{ ///@name Information about the current comment token - SourceLocation currentCommentLocation() const { return m_skippedComment.location; } - std::string const& currentCommentLiteral() const { return m_skippedComment.literal; } + SourceLocation currentCommentLocation() const { return m_skippedComments[Current].location; } + std::string const& currentCommentLiteral() const { return m_skippedComments[Current].literal; } /// Called by the parser during FunctionDefinition parsing to clear the current comment - void clearCurrentCommentLiteral() { m_skippedComment.literal.clear(); } + void clearCurrentCommentLiteral() { m_skippedComments[Current].literal.clear(); } ///@} @@ -154,9 +154,11 @@ public: ///@name Information about the next token /// @returns the next token without advancing input. - Token peekNextToken() const { return m_nextToken.token; } - SourceLocation peekLocation() const { return m_nextToken.location; } - std::string const& peekLiteral() const { return m_nextToken.literal; } + Token peekNextToken() const { return m_tokens[Next].token; } + SourceLocation peekLocation() const { return m_tokens[Next].location; } + std::string const& peekLiteral() const { return m_tokens[Next].literal; } + + Token peekNextNextToken() const { return m_tokens[NextNext].token; } ///@} ///@{ @@ -176,7 +178,7 @@ public: private: inline Token setError(ScannerError _error) noexcept { - m_nextToken.error = _error; + m_tokens[NextNext].error = _error; return Token::Illegal; } @@ -192,8 +194,8 @@ private: ///@{ ///@name Literal buffer support - inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); } - inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); } + inline void addLiteralChar(char c) { m_tokens[NextNext].literal.push_back(c); } + inline void addCommentLiteralChar(char c) { m_skippedComments[NextNext].literal.push_back(c); } inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } void addUnicodeAsUTF8(unsigned codepoint); ///@} @@ -252,11 +254,10 @@ private: bool m_supportPeriodInIdentifier = false; - TokenDesc m_skippedComment; // desc for current skipped comment - TokenDesc m_nextSkippedComment; // desc for next skipped comment + enum TokenIndex { Current, Next, NextNext }; - TokenDesc m_currentToken; // desc for current token (as returned by Next()) - TokenDesc m_nextToken; // desc for next token (one token look-ahead) + TokenDesc m_skippedComments[3] = {}; // desc for the current, next and nextnext skipped comment + TokenDesc m_tokens[3] = {}; // desc for the current, next and nextnext token std::shared_ptr m_source; From 8f36dd15719560f164c0c334641b97370af1b517 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Jan 2020 15:06:47 +0100 Subject: [PATCH 047/277] Fix extract tests on windows. --- scripts/bytecodecompare/prepare_report.py | 4 ++-- scripts/extract_test_cases.py | 4 ++-- scripts/isolate_tests.py | 8 ++++---- scripts/splitSources.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index dad4dbbd4..baeff7884 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -6,12 +6,12 @@ import subprocess import json SOLC_BIN = sys.argv[1] -REPORT_FILE = open("report.txt", "w") +REPORT_FILE = open("report.txt", mode="w", encoding='utf8') for optimize in [False, True]: for f in sorted(glob.glob("*.sol")): sources = {} - sources[f] = {'content': open(f, 'r').read()} + sources[f] = {'content': open(f, mode='r', encoding='utf8').read()} input_json = { 'language': 'Solidity', 'sources': sources, diff --git a/scripts/extract_test_cases.py b/scripts/extract_test_cases.py index 63314760c..46c80ef95 100755 --- a/scripts/extract_test_cases.py +++ b/scripts/extract_test_cases.py @@ -10,7 +10,7 @@ import sys import re def extract_test_cases(_path): - lines = open(_path, 'rb').read().splitlines() + lines = open(_path, mode='rb', encoding='utf8').read().splitlines() inside = False delimiter = '' @@ -22,7 +22,7 @@ def extract_test_cases(_path): for l in lines: if inside: if l.strip().endswith(')' + delimiter + '";'): - open('%03d_%s.sol' % (ctr, test_name), 'wb').write(test) + open('%03d_%s.sol' % (ctr, test_name), mode='wb', encoding='utf8').write(test) ctr += 1 inside = False test = '' diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index e1a9da46c..3a41d54c9 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -13,7 +13,7 @@ import hashlib from os.path import join, isfile def extract_test_cases(path): - lines = open(path, 'r').read().splitlines() + lines = open(path, mode='r', encoding='utf8').read().splitlines() inside = False delimiter = '' @@ -43,7 +43,7 @@ def extract_docs_cases(path): tests = [] # Collect all snippets of indented blocks - for l in open(path, 'r').read().splitlines(): + for l in open(path, mode='r', encoding='utf8').read().splitlines(): if l != '': if not inside and l.startswith(' '): # start new test @@ -72,14 +72,14 @@ def write_cases(f, tests): # so before checking remove 4 spaces from each line. remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE) sol_filename = 'test_%s_%s.sol' % (hashlib.sha256(test.encode("utf-8")).hexdigest(), cleaned_filename) - open(sol_filename, 'w').write(remainder) + open(sol_filename, mode='w', encoding='utf8').write(remainder) def extract_and_write(f, path): if docs: cases = extract_docs_cases(path) else: if f.endswith('.sol'): - cases = [open(path, 'r').read()] + cases = [open(path, mode='r', encoding='utf8').read()] else: cases = extract_test_cases(path) write_cases(f, cases) diff --git a/scripts/splitSources.py b/scripts/splitSources.py index dd6d62eb3..c14e5bf7b 100755 --- a/scripts/splitSources.py +++ b/scripts/splitSources.py @@ -31,7 +31,7 @@ def writeSourceToFile(lines): # print "filePath is", filePath if filePath != False: os.system("mkdir -p " + filePath) - f = open(srcName, 'a+') + f = open(srcName, mode='a+', encoding='utf8') createdSources.append(srcName) i = 0 for idx, line in enumerate(lines[1:]): @@ -48,7 +48,7 @@ def writeSourceToFile(lines): if __name__ == '__main__': filePath = sys.argv[1] # decide if file has multiple sources - lines = open(filePath, 'rb').read().splitlines() + lines = open(filePath, mode='rb', encoding='utf8').read().splitlines() if lines[0][:12] == "==== Source:": hasMultipleSources = True writeSourceToFile(lines) From 679f729f2f5850f7da0ec987b20024dda29cd147 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Jan 2020 17:38:57 +0100 Subject: [PATCH 048/277] Implement create2 for evmhost. --- test/EVMHost.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index fd5394fb5..3c9e6449e 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -172,6 +172,28 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept message.destination = convertToEVMC(createAddress); code = evmc::bytes(message.input_data, message.input_data + message.input_size); } + else if (message.kind == EVMC_CREATE2) + { + Address createAddress(keccak256( + bytes(1, 0xff) + + bytes(begin(message.sender.bytes), end(message.sender.bytes)) + + bytes(begin(message.create2_salt.bytes), end(message.create2_salt.bytes)) + + keccak256(bytes(message.input_data, message.input_data + message.input_size)).asBytes() + )); + message.destination = convertToEVMC(createAddress); + if (accounts.count(message.destination) && ( + accounts[message.destination].nonce > 0 || + !accounts[message.destination].code.empty() + )) + { + evmc::result result({}); + result.status_code = EVMC_OUT_OF_GAS; + accounts = stateBackup; + return result; + } + + code = evmc::bytes(message.input_data, message.input_data + message.input_size); + } else if (message.kind == EVMC_DELEGATECALL) { code = accounts[message.destination].code; @@ -184,7 +206,6 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept } else code = accounts[message.destination].code; - //TODO CREATE2 auto& destination = accounts[message.destination]; @@ -199,7 +220,7 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept evmc::result result = m_vm.execute(*this, m_evmRevision, message, code.data(), code.size()); m_currentAddress = currentAddress; - if (message.kind == EVMC_CREATE) + if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2) { result.gas_left -= evmasm::GasCosts::createDataGas * result.output_size; if (result.gas_left < 0) From a3f23d3158a833570aa30d8ba5187982677a877e Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Wed, 22 Jan 2020 15:42:50 +0100 Subject: [PATCH 049/277] Implement new with CREATE2 and function call options. --- libsolidity/analysis/TypeChecker.cpp | 118 ++++++++++++++++++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/AST.h | 29 +++++ libsolidity/ast/ASTJsonConverter.cpp | 17 +++ libsolidity/ast/ASTJsonConverter.h | 1 + libsolidity/ast/ASTVisitor.h | 4 + libsolidity/ast/AST_accept.h | 20 +++ libsolidity/ast/TypeProvider.cpp | 6 +- libsolidity/ast/TypeProvider.h | 3 +- libsolidity/ast/Types.cpp | 20 ++- libsolidity/ast/Types.h | 10 +- libsolidity/codegen/ExpressionCompiler.cpp | 74 ++++++++++- libsolidity/codegen/ExpressionCompiler.h | 1 + libsolidity/parsing/Parser.cpp | 72 +++++++---- libsolidity/parsing/Parser.h | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 34 +++++ .../functionCalls/calloptions_duplicated.sol | 17 +++ .../calloptions_on_delegatecall.sol | 8 ++ .../calloptions_on_internal_function.sol | 8 ++ .../functionCalls/calloptions_repeated.sol | 21 ++++ .../invalid_expressions_with_calloptions1.sol | 8 ++ .../invalid_expressions_with_calloptions2.sol | 8 ++ .../invalid_expressions_with_calloptions3.sol | 10 ++ .../functionCalls/lowlevel_call_options.sol | 7 ++ .../functionCalls/new_with_calloptions.sol | 12 ++ .../new_with_calloptions_unsupported.sol | 15 +++ .../new_with_invalid_calloptions.sol | 27 ++++ .../tryCatch/almost_call_options.sol | 14 +++ 28 files changed, 528 insertions(+), 38 deletions(-) create mode 100644 test/libsolidity/syntaxTests/functionCalls/calloptions_duplicated.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/calloptions_on_delegatecall.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/calloptions_on_internal_function.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/calloptions_repeated.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/invalid_expressions_with_calloptions1.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/invalid_expressions_with_calloptions2.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/invalid_expressions_with_calloptions3.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/lowlevel_call_options.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/new_with_calloptions.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/new_with_calloptions_unsupported.sol create mode 100644 test/libsolidity/syntaxTests/functionCalls/new_with_invalid_calloptions.sol create mode 100644 test/libsolidity/syntaxTests/tryCatch/almost_call_options.sol diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 153f4a774..82a362989 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2224,6 +2224,124 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) return false; } +bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) +{ + solAssert(_functionCallOptions.options().size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!"); + + _functionCallOptions.expression().accept(*this); + + auto expressionFunctionType = dynamic_cast(type(_functionCallOptions.expression())); + if (!expressionFunctionType) + { + m_errorReporter.fatalTypeError(_functionCallOptions.location(), "Expected callable expression before call options."); + return false; + } + + bool setSalt = false; + bool setValue = false; + bool setGas = false; + + FunctionType::Kind kind = expressionFunctionType->kind(); + if ( + kind != FunctionType::Kind::Creation && + kind != FunctionType::Kind::External && + kind != FunctionType::Kind::BareCall && + kind != FunctionType::Kind::BareCallCode && + kind != FunctionType::Kind::BareDelegateCall && + kind != FunctionType::Kind::BareStaticCall + ) + { + m_errorReporter.fatalTypeError( + _functionCallOptions.location(), + "Function call options can only be set on external function calls or contract creations." + ); + return false; + } + + auto setCheckOption = [&](bool& _option, string const&& _name, bool _alreadySet = false) + { + if (_option || _alreadySet) + m_errorReporter.typeError( + _functionCallOptions.location(), + _alreadySet ? + "Option \"" + std::move(_name) + "\" has already been set." : + "Duplicate option \"" + std::move(_name) + "\"." + ); + + _option = true; + }; + + for (size_t i = 0; i < _functionCallOptions.names().size(); ++i) + { + string const& name = *(_functionCallOptions.names()[i]); + if (name == "salt") + { + if (kind == FunctionType::Kind::Creation) + { + setCheckOption(setSalt, "salt", expressionFunctionType->saltSet()); + expectType(*_functionCallOptions.options()[i], *TypeProvider::fixedBytes(32)); + } + else + m_errorReporter.typeError( + _functionCallOptions.location(), + "Function call option \"salt\" can only be used with \"new\"." + ); + } + else if (name == "value") + { + if (kind == FunctionType::Kind::BareDelegateCall) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" for delegatecall." + ); + else if (kind == FunctionType::Kind::BareStaticCall) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" for staticcall." + ); + else if (!expressionFunctionType->isPayable()) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" on a non-payable function type." + ); + else + { + expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256()); + + setCheckOption(setValue, "value", expressionFunctionType->valueSet()); + } + } + else if (name == "gas") + { + if (kind == FunctionType::Kind::Creation) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Function call option \"gas\" cannot be used with \"new\"." + ); + else + { + expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256()); + + setCheckOption(setGas, "gas", expressionFunctionType->gasSet()); + } + } + else + m_errorReporter.typeError( + _functionCallOptions.location(), + "Unknown call option \"" + name + "\". Valid options are \"salt\", \"value\" and \"gas\"." + ); + } + + if (setSalt && !m_evmVersion.hasCreate2()) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Unsupported call option \"salt\" (requires Constantinople-compatible VMs)." + ); + + _functionCallOptions.annotation().type = expressionFunctionType->copyAndSetCallOptions(setGas, setValue, setSalt); + return false; +} + void TypeChecker::endVisit(NewExpression const& _newExpression) { TypePointer type = _newExpression.typeName().annotation().type; diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index c7443b29c..d428a6ac9 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -135,6 +135,7 @@ private: void endVisit(BinaryOperation const& _operation) override; bool visit(UnaryOperation const& _operation) override; bool visit(FunctionCall const& _functionCall) override; + bool visit(FunctionCallOptions const& _functionCallOptions) override; void endVisit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index db0bd477c..d5ef041f9 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1761,6 +1761,35 @@ private: std::vector> m_names; }; +/** + * Expression that annotates a function call / a new expression with extra + * options like gas, value, salt: new SomeContract{salt=123}(params) + */ +class FunctionCallOptions: public Expression +{ +public: + FunctionCallOptions( + int64_t _id, + SourceLocation const& _location, + ASTPointer const& _expression, + std::vector> const& _options, + std::vector> const& _names + ): + Expression(_id, _location), m_expression(_expression), m_options(_options), m_names(_names) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Expression const& expression() const { return *m_expression; } + std::vector> options() const { return {m_options.begin(), m_options.end()}; } + std::vector> const& names() const { return m_names; } + +private: + ASTPointer m_expression; + std::vector> m_options; + std::vector> m_names; + +}; + /** * Expression that creates a new contract or memory-array, * e.g. the "new SomeContract" part in "new SomeContract(1, 2)". diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 22eaaa246..71bb33f77 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -724,6 +724,23 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) return false; } +bool ASTJsonConverter::visit(FunctionCallOptions const& _node) +{ + Json::Value names(Json::arrayValue); + for (auto const& name: _node.names()) + names.append(Json::Value(*name)); + + std::vector> attributes = { + make_pair("expression", toJson(_node.expression())), + make_pair("names", std::move(names)), + make_pair("options", toJson(_node.options())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + + setJsonNode(_node, "FunctionCallOptions", std::move(attributes)); + return false; +} + bool ASTJsonConverter::visit(NewExpression const& _node) { std::vector> attributes = { diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index cf0d91f8b..2fd57fb12 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -111,6 +111,7 @@ public: bool visit(UnaryOperation const& _node) override; bool visit(BinaryOperation const& _node) override; bool visit(FunctionCall const& _node) override; + bool visit(FunctionCallOptions const& _node) override; bool visit(NewExpression const& _node) override; bool visit(MemberAccess const& _node) override; bool visit(IndexAccess const& _node) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 52e0db4bc..0ffbbd31a 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -84,6 +84,7 @@ public: virtual bool visit(UnaryOperation& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } virtual bool visit(FunctionCall& _node) { return visitNode(_node); } + virtual bool visit(FunctionCallOptions& _node) { return visitNode(_node); } virtual bool visit(NewExpression& _node) { return visitNode(_node); } virtual bool visit(MemberAccess& _node) { return visitNode(_node); } virtual bool visit(IndexAccess& _node) { return visitNode(_node); } @@ -134,6 +135,7 @@ public: virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionCallOptions& _node) { endVisitNode(_node); } virtual void endVisit(NewExpression& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); } @@ -197,6 +199,7 @@ public: virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } virtual bool visit(FunctionCall const& _node) { return visitNode(_node); } + virtual bool visit(FunctionCallOptions const& _node) { return visitNode(_node); } virtual bool visit(NewExpression const& _node) { return visitNode(_node); } virtual bool visit(MemberAccess const& _node) { return visitNode(_node); } virtual bool visit(IndexAccess const& _node) { return visitNode(_node); } @@ -247,6 +250,7 @@ public: virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionCallOptions const& _node) { endVisitNode(_node); } virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index ebbce88fb..4ca48e514 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -781,6 +781,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void FunctionCallOptions::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_expression->accept(_visitor); + listAccept(m_options, _visitor); + } + _visitor.endVisit(*this); +} + +void FunctionCallOptions::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_expression->accept(_visitor); + listAccept(m_options, _visitor); + } + _visitor.endVisit(*this); +} + void NewExpression::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 4cb3e781b..7dd895cbe 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -459,7 +459,8 @@ FunctionType const* TypeProvider::function( Declaration const* _declaration, bool _gasSet, bool _valueSet, - bool _bound + bool _bound, + bool _saltSet ) { return createAndGet( @@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function( _declaration, _gasSet, _valueSet, - _bound + _bound, + _saltSet ); } diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index cef182aed..38f2642eb 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -153,7 +153,8 @@ public: Declaration const* _declaration = nullptr, bool _gasSet = false, bool _valueSet = false, - bool _bound = false + bool _bound = false, + bool _saltSet = false ); /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c64eee64e..f23ae8a19 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2741,6 +2741,8 @@ string FunctionType::richIdentifier() const id += "gas"; if (m_valueSet) id += "value"; + if (m_saltSet) + id += "salt"; if (bound()) id += "bound_to" + identifierList(selfType()); return id; @@ -2918,6 +2920,8 @@ unsigned FunctionType::sizeOnStack() const size++; if (m_valueSet) size++; + if (m_saltSet) + size++; if (bound()) size += m_parameterTypes.front()->sizeOnStack(); return size; @@ -3001,7 +3005,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco "value", TypeProvider::function( parseElementaryTypeVector({"uint"}), - TypePointers{copyAndSetGasOrValue(false, true)}, + TypePointers{copyAndSetCallOptions(false, true, false)}, strings(1, ""), strings(1, ""), Kind::SetValue, @@ -3009,7 +3013,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco StateMutability::Pure, nullptr, m_gasSet, - m_valueSet + m_valueSet, + m_saltSet ) ); } @@ -3018,7 +3023,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco "gas", TypeProvider::function( parseElementaryTypeVector({"uint"}), - TypePointers{copyAndSetGasOrValue(true, false)}, + TypePointers{copyAndSetCallOptions(true, false, false)}, strings(1, ""), strings(1, ""), Kind::SetGas, @@ -3026,7 +3031,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco StateMutability::Pure, nullptr, m_gasSet, - m_valueSet + m_valueSet, + m_saltSet ) ); return members; @@ -3149,7 +3155,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con return false; //@todo this is ugly, but cannot be prevented right now - if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet) + if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet || m_saltSet != _other.m_saltSet) return false; if (bound() != _other.bound()) @@ -3250,7 +3256,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) return pointers; } -TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const +TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const { solAssert(m_kind != Kind::Declaration, ""); return TypeProvider::function( @@ -3264,6 +3270,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_declaration, m_gasSet || _setGas, m_valueSet || _setValue, + m_saltSet || _setSalt, m_bound ); } @@ -3304,6 +3311,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun m_declaration, m_gasSet, m_valueSet, + m_saltSet, _bound ); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 2c617efdc..7ad662dbb 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1055,7 +1055,7 @@ public: /// Refers to a function declaration without calling context /// (i.e. when accessed directly via the name of the containing contract). /// Cannot be called. - Declaration + Declaration, }; /// Creates the type of a function. @@ -1098,6 +1098,7 @@ public: Declaration const* _declaration = nullptr, bool _gasSet = false, bool _valueSet = false, + bool _saltSet = false, bool _bound = false ): m_parameterTypes(_parameterTypes), @@ -1110,7 +1111,8 @@ public: m_gasSet(_gasSet), m_valueSet(_valueSet), m_bound(_bound), - m_declaration(_declaration) + m_declaration(_declaration), + m_saltSet(_saltSet) { solAssert( m_parameterNames.size() == m_parameterTypes.size(), @@ -1235,11 +1237,12 @@ public: bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } + bool saltSet() const { return m_saltSet; } bool bound() const { return m_bound; } /// @returns a copy of this type, where gas or value are set manually. This will never set one /// of the parameters to false. - TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; + TypePointer copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const; /// @returns a copy of this function type where the location of reference types is changed /// from CallData to Memory. This is the type that would be used when the function is @@ -1264,6 +1267,7 @@ private: bool const m_valueSet = false; ///< true iff the value to be sent is on the stack bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn) Declaration const* m_declaration = nullptr; + bool m_saltSet = false; ///< true iff the salt value to be used is on the stack }; /** diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index ff8c5fb65..a2812ce51 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -620,6 +620,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); + // Stack: [salt], [value] + solAssert(!function.gasSet(), "Gas limit set for contract creation."); solAssert(function.returnParameterTypes().size() == 1, ""); TypePointers argumentTypes; @@ -633,16 +635,37 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().fetchFreeMemoryPointer(); utils().copyContractCodeToMemory(*contract, true); utils().abiEncode(argumentTypes, function.parameterTypes()); - // now on stack: memory_end_ptr - // need: size, offset, endowment + // now on stack: [salt], [value], memory_end_ptr + // need: [salt], size, offset, value + + if (function.saltSet()) + { + m_context << dupInstruction(2 + (function.valueSet() ? 1 : 0)); + m_context << Instruction::SWAP1; + } + + // now: [salt], [value], [salt], memory_end_ptr utils().toSizeAfterFreeMemoryPointer(); + + // now: [salt], [value], [salt], size, offset if (function.valueSet()) - m_context << dupInstruction(3); + m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0)); else m_context << u256(0); - m_context << Instruction::CREATE; + + // now: [salt], [value], [salt], size, offset, value + if (function.saltSet()) + m_context << Instruction::CREATE2; + else + m_context << Instruction::CREATE; + + // now: [salt], [value], address + if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; + if (function.saltSet()) + m_context << swapInstruction(1) << Instruction::POP; + // Check if zero (reverted) m_context << Instruction::DUP1 << Instruction::ISZERO; if (_functionCall.annotation().tryCall) @@ -1192,6 +1215,49 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) return false; } +bool ExpressionCompiler::visit(FunctionCallOptions const& _functionCallOptions) +{ + _functionCallOptions.expression().accept(*this); + + // Desired Stack: [salt], [gas], [value] + enum Option { Salt, Gas, Value }; + + vector