aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog.md2
-rw-r--r--docs/assembly.rst25
-rw-r--r--docs/miscellaneous.rst8
-rw-r--r--docs/using-the-compiler.rst10
-rw-r--r--libevmasm/ConstantOptimiser.cpp48
-rw-r--r--libevmasm/ConstantOptimiser.h13
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp44
-rw-r--r--libsolidity/analysis/TypeChecker.cpp111
-rw-r--r--libsolidity/ast/ASTAnnotations.h20
-rw-r--r--libsolidity/codegen/CompilerContext.cpp27
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp176
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp372
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h119
-rw-r--r--libsolidity/inlineasm/AsmAnalysisInfo.cpp26
-rw-r--r--libsolidity/inlineasm/AsmAnalysisInfo.h61
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.cpp200
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.h33
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp16
-rw-r--r--libsolidity/inlineasm/AsmParser.h5
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp2
-rw-r--r--libsolidity/inlineasm/AsmScope.cpp79
-rw-r--r--libsolidity/inlineasm/AsmScope.h128
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp130
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h89
-rw-r--r--libsolidity/inlineasm/AsmStack.cpp25
-rw-r--r--libsolidity/inlineasm/AsmStack.h29
-rw-r--r--libsolidity/interface/CompilerStack.cpp29
-rw-r--r--test/CMakeLists.txt2
-rwxr-xr-xtest/cmdlineTests.sh2
-rw-r--r--test/fuzzer.cpp137
-rw-r--r--test/libsolidity/InlineAssembly.cpp105
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp45
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp42
33 files changed, 1645 insertions, 515 deletions
diff --git a/Changelog.md b/Changelog.md
index 7b64f950..cd54aadb 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -7,6 +7,8 @@ Features:
* Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O.
* Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the
path(s) of the supplied source file(s) is always trusted.
+ * Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes.
+ * Inline Assembly: Disallow blocks with unbalanced stack.
* Static analyzer: Warn about statements without effects.
Bugfixes:
diff --git a/docs/assembly.rst b/docs/assembly.rst
index 30f7fc01..420cea17 100644
--- a/docs/assembly.rst
+++ b/docs/assembly.rst
@@ -29,7 +29,7 @@ arising when writing manual assembly by the following features:
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
* 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))) } }``
+* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }``
.. note::
Of the above, loops, function calls and switch statements are not yet implemented.
@@ -323,9 +323,12 @@ Access to External Variables and Functions
------------------------------------------
Solidity variables and other identifiers can be accessed by simply using their name.
-For storage and memory variables, this will push the address and not the value onto the
-stack. Also note that non-struct and non-array storage variable addresses occupy two slots
-on the stack: One for the address and one for the byte offset inside the storage slot.
+For memory variables, this will push the address and not the value onto the
+stack. Storage variables are different: Values in storage might not occupy a
+full storage slot, so 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
+used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``.
+
In assignments (see below), we can even use local Solidity variables to assign to.
Functions external to inline assembly can also be accessed: The assembly will
@@ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong.
.. code::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract C {
uint b;
function f(uint x) returns (uint r) {
assembly {
- b pop // remove the offset, we know it is zero
- sload
- x
- mul
- =: r // assign to return variable r
+ r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
}
}
}
@@ -567,7 +566,7 @@ The following example implements the power function by square-and-multiply.
.. code::
assembly {
- function power(base, exponent) -> (result) {
+ function power(base, exponent) -> result {
switch exponent
0: { result := 1 }
1: { result := base }
@@ -702,12 +701,12 @@ The following assembly will be generated::
}
default: { jump(invalidJumpLabel) }
// memory allocator
- function $allocate(size) -> (pos) {
+ function $allocate(size) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, size))
}
// the contract function
- function f(x) -> (y) {
+ function f(x) -> y {
y := 1
for { let i := 0 } lt(i, x) { i := add(i, 1) } {
y := mul(2, y)
diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst
index 2865d884..914dfacd 100644
--- a/docs/miscellaneous.rst
+++ b/docs/miscellaneous.rst
@@ -314,6 +314,14 @@ Comments are of course also not permitted and used here only for explanatory pur
}
}
+.. note::
+ Note the ABI definition above has no fixed order. It can change with compiler versions.
+
+.. note::
+ Since the bytecode of the resulting contract contains the metadata hash, any change to
+ the metadata will result in a change of the bytecode. Furthermore, since the metadata
+ includes a hash of all the sources used, a single whitespace change in any of the source
+ codes will result in a different metadata, and subsequently a different bytecode.
Encoding of the Metadata Hash in the Bytecode
=============================================
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
index deaa1329..b74da213 100644
--- a/docs/using-the-compiler.rst
+++ b/docs/using-the-compiler.rst
@@ -251,15 +251,15 @@ Output Description
// Function gas estimates
gasEstimates: {
creation: {
- dataCost: 420000,
- // -1 means infinite (aka. unknown)
- executionCost: -1
+ codeDepositCost: "420000",
+ executionCost: "infinite",
+ totalCost: "infinite"
},
external: {
- "delegate(address)": 25000
+ "delegate(address)": "25000"
},
internal: {
- "heavyLifting()": -1
+ "heavyLifting()": "infinite"
}
}
},
diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp
index a1dfd21c..d2ed4faf 100644
--- a/libevmasm/ConstantOptimiser.cpp
+++ b/libevmasm/ConstantOptimiser.cpp
@@ -232,6 +232,54 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
}
}
+bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine)
+{
+ // This is a tiny EVM that can only evaluate some instructions.
+ vector<u256> stack;
+ for (AssemblyItem const& item: _routine)
+ {
+ switch (item.type())
+ {
+ case Operation:
+ {
+ if (stack.size() < size_t(item.arguments()))
+ return false;
+ u256* sp = &stack.back();
+ switch (item.instruction())
+ {
+ case Instruction::MUL:
+ sp[-1] = sp[0] * sp[-1];
+ break;
+ case Instruction::EXP:
+ if (sp[-1] > 0xff)
+ return false;
+ sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
+ break;
+ case Instruction::ADD:
+ sp[-1] = sp[0] + sp[-1];
+ break;
+ case Instruction::SUB:
+ sp[-1] = sp[0] - sp[-1];
+ break;
+ case Instruction::NOT:
+ sp[0] = ~sp[0];
+ break;
+ default:
+ return false;
+ }
+ stack.resize(stack.size() + item.deposit());
+ break;
+ }
+ case Push:
+ stack.push_back(item.data());
+ break;
+ default:
+ return false;
+ }
+ }
+ return stack.size() == 1 && stack.front() == _value;
+}
+
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine)
{
size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP);
diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h
index 4f12c49f..85bdabac 100644
--- a/libevmasm/ConstantOptimiser.h
+++ b/libevmasm/ConstantOptimiser.h
@@ -21,10 +21,14 @@
#pragma once
-#include <vector>
+#include <libevmasm/Exceptions.h>
+
+#include <libdevcore/Assertions.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/CommonIO.h>
+#include <vector>
+
namespace dev
{
namespace eth
@@ -130,6 +134,11 @@ public:
ConstantOptimisationMethod(_params, _value)
{
m_routine = findRepresentation(m_value);
+ assertThrow(
+ checkRepresentation(m_value, m_routine),
+ OptimizerException,
+ "Invalid constant expression created."
+ );
}
virtual bigint gasNeeded() override { return gasNeeded(m_routine); }
@@ -141,6 +150,8 @@ public:
protected:
/// Tries to recursively find a way to compute @a _value.
AssemblyItems findRepresentation(u256 const& _value);
+ /// Recomputes the value from the calculated representation and checks for correctness.
+ bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine);
bigint gasNeeded(AssemblyItems const& _routine);
/// Counter for the complexity of optimization, will stop when it reaches zero.
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index 37bcb2d9..9433976a 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -25,9 +25,12 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
+#include <boost/algorithm/string.hpp>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
@@ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{
- // We need to perform a full code generation pass here as inline assembly does not distinguish
- // reference resolution and code generation.
// Errors created in this stage are completely ignored because we do not yet know
// the type and size of external identifiers, which would result in false errors.
+ // The only purpose of this step is to fill the inline assembly annotation with
+ // external references.
ErrorList errorsIgnored;
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored);
- codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) {
+ assembly::ExternalIdentifierAccess::Resolver resolver =
+ [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
+ bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
+ bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
+ if (isSlot || isOffset)
+ {
+ // special mode to access storage variables
+ if (!declarations.empty())
+ // the special identifier exists itself, we should not allow that.
+ return size_t(-1);
+ string realName = _identifier.name.substr(0, _identifier.name.size() - (
+ isSlot ?
+ string("_slot").size() :
+ string("_offset").size()
+ ));
+ declarations = m_resolver.nameFromCurrentScope(realName);
+ }
if (declarations.size() != 1)
- return false;
- _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front();
- // At this stage we neither know the code to generate nor the stack size of the identifier,
- // so we do not modify assembly.
- return true;
- });
+ return size_t(-1);
+ _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
+ _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
+ _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
+ return size_t(1);
+ };
+
+ // Will be re-generated later with correct information
+ assembly::AsmAnalysisInfo analysisInfo;
+ assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations());
return false;
}
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index b37db7b7..38cdc1f8 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -24,8 +24,9 @@
#include <memory>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/ast/AST.h>
-#include <libevmasm/Assembly.h> // needed for inline assembly
-#include <libsolidity/inlineasm/AsmCodeGen.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+#include <libsolidity/inlineasm/AsmData.h>
using namespace std;
using namespace dev;
@@ -628,65 +629,91 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
- // Inline assembly does not have its own type-checking phase, so we just run the
- // code-generator and see whether it produces any errors.
// External references have already been resolved in a prior stage and stored in the annotation.
- auto identifierAccess = [&](
+ // We run the resolve step again regardless.
+ assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier,
- eth::Assembly& _assembly,
- assembly::CodeGenerator::IdentifierContext _context
+ assembly::IdentifierContext _context
)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
- return false;
- Declaration const* declaration = ref->second;
+ return size_t(-1);
+ Declaration const* declaration = ref->second.declaration;
solAssert(!!declaration, "");
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
+ if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
- solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
- unsigned pushes = 0;
- if (dynamic_cast<FunctionDefinition const*>(declaration))
- pushes = 1;
- else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
+ if (ref->second.isSlot || ref->second.isOffset)
{
- if (var->isConstant())
- fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
- if (var->isLocalVariable())
- pushes = var->type()->sizeOnStack();
- else if (!var->type()->isValueType())
- pushes = 1;
- else
- pushes = 2; // slot number, intra slot offset
+ if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
+ {
+ typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
+ return size_t(-1);
+ }
+ else if (_context != assembly::IdentifierContext::RValue)
+ {
+ typeError(_identifier.location, "Storage variables cannot be assigned to.");
+ return size_t(-1);
+ }
}
- else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
+ else if (var->isConstant())
{
- if (!contract->isLibrary())
- return false;
- pushes = 1;
+ typeError(_identifier.location, "Constant variables not supported by inline assembly.");
+ return size_t(-1);
+ }
+ else if (!var->isLocalVariable())
+ {
+ typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
+ return size_t(-1);
+ }
+ else if (var->type()->dataStoredIn(DataLocation::Storage))
+ {
+ typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
+ return size_t(-1);
+ }
+ else if (var->type()->sizeOnStack() != 1)
+ {
+ typeError(_identifier.location, "Only types that use one stack slot are supported.");
+ return size_t(-1);
}
- else
- return false;
- for (unsigned i = 0; i < pushes; ++i)
- _assembly.append(u256(0)); // just to verify the stack height
}
- else
+ else if (_context == assembly::IdentifierContext::LValue)
+ {
+ typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
+ return size_t(-1);
+ }
+
+ if (_context == assembly::IdentifierContext::RValue)
{
- // lvalue context
- if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
+ solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
+ if (dynamic_cast<FunctionDefinition const*>(declaration))
+ {
+ }
+ else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
- if (!varDecl->isLocalVariable())
- return false; // only local variables are inline-assemlby lvalues
- for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i)
- _assembly.append(Instruction::POP); // remove value just to verify the stack height
+ }
+ else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
+ {
+ if (!contract->isLibrary())
+ {
+ typeError(_identifier.location, "Expected a library.");
+ return size_t(-1);
+ }
}
else
- return false;
+ return size_t(-1);
}
- return true;
+ ref->second.valueSize = 1;
+ return size_t(1);
};
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
- if (!codeGen.typeCheck(identifierAccess))
+ solAssert(!_inlineAssembly.annotation().analysisInfo, "");
+ _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
+ assembly::AsmAnalyzer analyzer(
+ *_inlineAssembly.annotation().analysisInfo,
+ m_errors,
+ identifierAccess
+ );
+ if (!analyzer.analyze(_inlineAssembly.operations()))
return false;
return true;
}
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index bd297f9e..a7d89248 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -22,11 +22,12 @@
#pragma once
+#include <libsolidity/ast/ASTForward.h>
+
#include <map>
#include <memory>
#include <vector>
#include <set>
-#include <libsolidity/ast/ASTForward.h>
namespace dev
{
@@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
namespace assembly
{
-struct Identifier; // forward
+ struct AsmAnalysisInfo;
+ struct Identifier;
}
struct InlineAssemblyAnnotation: StatementAnnotation
{
- /// Mapping containing resolved references to external identifiers.
- std::map<assembly::Identifier const*, Declaration const*> externalReferences;
+ struct ExternalIdentifierInfo
+ {
+ Declaration const* declaration = nullptr;
+ bool isSlot = false; ///< Whether the storage slot of a variable is queried.
+ bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
+ size_t valueSize = size_t(-1);
+ };
+
+ /// Mapping containing resolved references to external identifiers and their value size
+ std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences;
+ /// Information generated during analysis phase.
+ std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo;
};
struct ReturnAnnotation: StatementAnnotation
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index a8316109..51dd9fd2 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly(
}
unsigned startStackHeight = stackHeight();
- auto identifierAccess = [&](
+
+ assembly::ExternalIdentifierAccess identifierAccess;
+ identifierAccess.resolve = [&](
+ assembly::Identifier const& _identifier,
+ assembly::IdentifierContext
+ )
+ {
+ auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
+ return it == _localVariables.end() ? size_t(-1) : 1;
+ };
+ identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
- eth::Assembly& _assembly,
- assembly::CodeGenerator::IdentifierContext _context
- ) {
+ assembly::IdentifierContext _context,
+ eth::Assembly& _assembly
+ )
+ {
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
- if (it == _localVariables.end())
- return false;
+ solAssert(it != _localVariables.end(), "");
unsigned stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
- if (_context == assembly::CodeGenerator::IdentifierContext::LValue)
+ if (_context == assembly::IdentifierContext::LValue)
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_comment("Stack too deep, try removing local variables.")
);
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
+ if (_context == assembly::IdentifierContext::RValue)
_assembly.append(dupInstruction(stackDiff));
else
{
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
}
- return true;
};
solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block.");
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 6524bd03..34ef13c0 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -520,93 +520,129 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
ErrorList errors;
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors);
+ assembly::CodeGenerator codeGen(errors);
unsigned startStackHeight = m_context.stackHeight();
- codeGen.assemble(
- m_context.nonConstAssembly(),
- [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
- auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
- if (ref == _inlineAssembly.annotation().externalReferences.end())
- return false;
- Declaration const* decl = ref->second;
- solAssert(!!decl, "");
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
+ assembly::ExternalIdentifierAccess identifierAccess;
+ identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext)
+ {
+ auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
+ if (ref == _inlineAssembly.annotation().externalReferences.end())
+ return size_t(-1);
+ return ref->second.valueSize;
+ };
+ identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly)
+ {
+ auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
+ solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), "");
+ Declaration const* decl = ref->second.declaration;
+ solAssert(!!decl, "");
+ if (_context == assembly::IdentifierContext::RValue)
+ {
+ int const depositBefore = _assembly.deposit();
+ solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
+ if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
- solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
- if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ functionDef = &m_context.resolveVirtualFunction(*functionDef);
+ _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
+ // If there is a runtime context, we have to merge both labels into the same
+ // stack slot in case we store it in storage.
+ if (CompilerContext* rtc = m_context.runtimeContext())
{
- functionDef = &m_context.resolveVirtualFunction(*functionDef);
- _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
- // If there is a runtime context, we have to merge both labels into the same
- // stack slot in case we store it in storage.
- if (CompilerContext* rtc = m_context.runtimeContext())
- {
- _assembly.append(u256(1) << 32);
- _assembly.append(Instruction::MUL);
- _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
- _assembly.append(Instruction::OR);
- }
+ _assembly.append(u256(1) << 32);
+ _assembly.append(Instruction::MUL);
+ _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
+ _assembly.append(Instruction::OR);
}
- else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
+ }
+ else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
+ {
+ solAssert(!variable->isConstant(), "");
+ if (m_context.isStateVariable(decl))
{
- solAssert(!variable->isConstant(), "");
- if (m_context.isLocalVariable(variable))
- {
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
- if (stackDiff < 1 || stackDiff > 16)
- BOOST_THROW_EXCEPTION(
- CompilerError() <<
- errinfo_sourceLocation(_inlineAssembly.location()) <<
- errinfo_comment("Stack too deep, try removing local variables.")
- );
- for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i)
- _assembly.append(dupInstruction(stackDiff));
- }
+ auto const& location = m_context.storageLocationOfVariable(*decl);
+ if (ref->second.isSlot)
+ m_context << location.first;
+ else if (ref->second.isOffset)
+ m_context << u256(location.second);
else
+ solAssert(false, "");
+ }
+ else if (m_context.isLocalVariable(decl))
+ {
+ int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
+ if (ref->second.isSlot || ref->second.isOffset)
{
- solAssert(m_context.isStateVariable(variable), "Invalid variable type.");
- auto const& location = m_context.storageLocationOfVariable(*variable);
- if (!variable->type()->isValueType())
+ solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
+ unsigned size = variable->type()->sizeOnStack();
+ if (size == 2)
{
- solAssert(location.second == 0, "Intra-slot offest assumed to be zero.");
- _assembly.append(location.first);
+ // slot plus offset
+ if (ref->second.isOffset)
+ stackDiff--;
}
else
{
- _assembly.append(location.first);
- _assembly.append(u256(location.second));
+ solAssert(size == 1, "");
+ // only slot, offset is zero
+ if (ref->second.isOffset)
+ {
+ _assembly.append(u256(0));
+ return;
+ }
}
}
- }
- else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
- {
- solAssert(contract->isLibrary(), "");
- _assembly.appendLibraryAddress(contract->fullyQualifiedName());
+ else
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ if (stackDiff < 1 || stackDiff > 16)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ _assembly.append(dupInstruction(stackDiff));
}
else
- solAssert(false, "Invalid declaration type.");
- } else {
- // lvalue context
- auto variable = dynamic_cast<VariableDeclaration const*>(decl);
- solAssert(
- !!variable && m_context.isLocalVariable(variable),
- "Can only assign to stack variables in inline assembly."
- );
- unsigned size = variable->type()->sizeOnStack();
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size;
- if (stackDiff > 16 || stackDiff < 1)
- BOOST_THROW_EXCEPTION(
- CompilerError() <<
- errinfo_sourceLocation(_inlineAssembly.location()) <<
- errinfo_comment("Stack too deep, try removing local variables.")
- );
- for (unsigned i = 0; i < size; ++i) {
- _assembly.append(swapInstruction(stackDiff));
- _assembly.append(Instruction::POP);
- }
+ solAssert(false, "");
}
- return true;
+ else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
+ {
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ solAssert(contract->isLibrary(), "");
+ _assembly.appendLibraryAddress(contract->fullyQualifiedName());
+ }
+ else
+ solAssert(false, "Invalid declaration type.");
+ solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
}
+ else
+ {
+ // lvalue context
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ auto variable = dynamic_cast<VariableDeclaration const*>(decl);
+ solAssert(
+ !!variable && m_context.isLocalVariable(variable),
+ "Can only assign to stack variables in inline assembly."
+ );
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1;
+ if (stackDiff > 16 || stackDiff < 1)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
+ _assembly.append(swapInstruction(stackDiff));
+ _assembly.append(Instruction::POP);
+ }
+ };
+ solAssert(_inlineAssembly.annotation().analysisInfo, "");
+ codeGen.assemble(
+ _inlineAssembly.operations(),
+ *_inlineAssembly.annotation().analysisInfo,
+ m_context.nonConstAssembly(),
+ identifierAccess
);
solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight);
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index a3ddb61d..dad05a78 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -21,6 +21,9 @@
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
@@ -35,146 +38,363 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
-
-bool Scope::registerLabel(string const& _name)
+AsmAnalyzer::AsmAnalyzer(
+ AsmAnalysisInfo& _analysisInfo,
+ ErrorList& _errors,
+ ExternalIdentifierAccess::Resolver const& _resolver
+):
+ m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors)
{
- if (exists(_name))
- return false;
- identifiers[_name] = Label();
- return true;
}
-bool Scope::registerVariable(string const& _name)
+bool AsmAnalyzer::analyze(Block const& _block)
{
- if (exists(_name))
+ if (!(ScopeFiller(m_info.scopes, m_errors))(_block))
return false;
- identifiers[_name] = Variable();
- return true;
-}
-bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
-{
- if (exists(_name))
- return false;
- identifiers[_name] = Function(_arguments, _returns);
- return true;
+ return (*this)(_block);
}
-Scope::Identifier* Scope::lookup(string const& _name)
+bool AsmAnalyzer::operator()(Label const& _label)
{
- if (identifiers.count(_name))
- return &identifiers[_name];
- else if (superScope && !closedScope)
- return superScope->lookup(_name);
- else
- return nullptr;
-}
-
-bool Scope::exists(string const& _name)
-{
- if (identifiers.count(_name))
- return true;
- else if (superScope)
- return superScope->exists(_name);
- else
- return false;
+ m_info.stackHeightInfo[&_label] = m_stackHeight;
+ return true;
}
-AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors):
- m_scopes(_scopes), m_errors(_errors)
+bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
- // Make the Solidity ErrorTag available to inline assembly
- m_scopes[nullptr] = make_shared<Scope>();
- Scope::Label errorLabel;
- errorLabel.id = Scope::Label::errorLabelId;
- m_scopes[nullptr]->identifiers["invalidJumpLabel"] = errorLabel;
- m_currentScope = m_scopes[nullptr].get();
+ auto const& info = instructionInfo(_instruction.instruction);
+ m_stackHeight += info.ret - info.args;
+ m_info.stackHeightInfo[&_instruction] = m_stackHeight;
+ return true;
}
bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{
+ ++m_stackHeight;
if (!_literal.isNumber && _literal.value.size() > 32)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
- "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)"
+ "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)",
+ _literal.location
));
return false;
}
+ m_info.stackHeightInfo[&_literal] = m_stackHeight;
return true;
}
+bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
+{
+ size_t numErrorsBefore = m_errors.size();
+ bool success = true;
+ if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
+ [&](Scope::Variable const& _var)
+ {
+ if (!_var.active)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Variable " + _identifier.name + " used before it was declared.",
+ _identifier.location
+ ));
+ success = false;
+ }
+ ++m_stackHeight;
+ },
+ [&](Scope::Label const&)
+ {
+ ++m_stackHeight;
+ },
+ [&](Scope::Function const&)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Function " + _identifier.name + " used without being called.",
+ _identifier.location
+ ));
+ success = false;
+ }
+ )))
+ {
+ }
+ else
+ {
+ size_t stackSize(-1);
+ if (m_resolver)
+ stackSize = m_resolver(_identifier, IdentifierContext::RValue);
+ if (stackSize == size_t(-1))
+ {
+ // Only add an error message if the callback did not do it.
+ if (numErrorsBefore == m_errors.size())
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Identifier not found.",
+ _identifier.location
+ ));
+ success = false;
+ }
+ m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
+ }
+ m_info.stackHeightInfo[&_identifier] = m_stackHeight;
+ return success;
+}
+
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
+ {
+ int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg))
success = false;
+ if (!expectDeposit(1, stackHeight, locationOf(arg)))
+ success = false;
+ }
+ // Parser already checks that the number of arguments is correct.
+ solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
if (!(*this)(_instr.instruction))
success = false;
+ m_info.stackHeightInfo[&_instr] = m_stackHeight;
return success;
}
-bool AsmAnalyzer::operator()(Label const& _item)
+bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
- if (!m_currentScope->registerLabel(_item.name))
- {
- //@TODO secondary location
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Label name " + _item.name + " already taken in this scope.",
- _item.location
- ));
- return false;
- }
- return true;
+ bool success = checkAssignment(_assignment.variableName, size_t(-1));
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
}
+
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
{
- return boost::apply_visitor(*this, *_assignment.value);
+ int const stackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, *_assignment.value);
+ solAssert(m_stackHeight >= stackHeight, "Negative value size.");
+ if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
+ success = false;
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
}
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
{
+ int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_varDecl.value);
- if (!m_currentScope->registerVariable(_varDecl.name))
- {
- //@TODO secondary location
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Variable name " + _varDecl.name + " already taken in this scope.",
- _varDecl.location
- ));
- success = false;
- }
+ solAssert(m_stackHeight - stackHeight == 1, "Invalid value size.");
+ boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
+ m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
return success;
}
-bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&)
+bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{
- // TODO - we cannot throw an exception here because of some tests.
- return true;
+ Scope& bodyScope = scope(&_funDef.body);
+ for (auto const& var: _funDef.arguments + _funDef.returns)
+ boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
+
+ int const stackHeight = m_stackHeight;
+ m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
+ m_virtualVariablesInNextBlock = m_stackHeight;
+
+ bool success = (*this)(_funDef.body);
+
+ m_stackHeight = stackHeight;
+ m_info.stackHeightInfo[&_funDef] = m_stackHeight;
+ return success;
}
-bool AsmAnalyzer::operator()(assembly::FunctionCall const&)
+bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{
- // TODO - we cannot throw an exception here because of some tests.
- return true;
+ bool success = true;
+ size_t arguments = 0;
+ size_t returns = 0;
+ if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
+ [&](Scope::Variable const&)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Attempt to call variable instead of function.",
+ _funCall.functionName.location
+ ));
+ success = false;
+ },
+ [&](Scope::Label const&)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Attempt to call label instead of function.",
+ _funCall.functionName.location
+ ));
+ success = false;
+ },
+ [&](Scope::Function const& _fun)
+ {
+ arguments = _fun.arguments;
+ returns = _fun.returns;
+ }
+ )))
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Function not found.",
+ _funCall.functionName.location
+ ));
+ success = false;
+ }
+ if (success)
+ {
+ if (_funCall.arguments.size() != arguments)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Expected " +
+ boost::lexical_cast<string>(arguments) +
+ " arguments but got " +
+ boost::lexical_cast<string>(_funCall.arguments.size()) +
+ ".",
+ _funCall.functionName.location
+ ));
+ success = false;
+ }
+ }
+ for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
+ {
+ int const stackHeight = m_stackHeight;
+ if (!boost::apply_visitor(*this, arg))
+ success = false;
+ if (!expectDeposit(1, stackHeight, locationOf(arg)))
+ success = false;
+ }
+ m_stackHeight += int(returns) - int(arguments);
+ m_info.stackHeightInfo[&_funCall] = m_stackHeight;
+ return success;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;
- auto scope = make_shared<Scope>();
- scope->superScope = m_currentScope;
- m_scopes[&_block] = scope;
- m_currentScope = scope.get();
+ m_currentScope = &scope(&_block);
+
+ int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock;
+ m_virtualVariablesInNextBlock = 0;
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
+ for (auto const& identifier: scope(&_block).identifiers)
+ if (identifier.second.type() == typeid(Scope::Variable))
+ --m_stackHeight;
+
+ int const stackDiff = m_stackHeight - initialStackHeight;
+ if (stackDiff != 0)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Unbalanced stack at the end of a block: " +
+ (
+ stackDiff > 0 ?
+ to_string(stackDiff) + string(" surplus item(s).") :
+ to_string(-stackDiff) + string(" missing item(s).")
+ ),
+ _block.location
+ ));
+ success = false;
+ }
+
m_currentScope = m_currentScope->superScope;
+ m_info.stackHeightInfo[&_block] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
+{
+ bool success = true;
+ size_t numErrorsBefore = m_errors.size();
+ size_t variableSize(-1);
+ if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
+ {
+ // Check that it is a variable
+ if (var->type() != typeid(Scope::Variable))
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Assignment requires variable.",
+ _variable.location
+ ));
+ success = false;
+ }
+ else if (!boost::get<Scope::Variable>(*var).active)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Variable " + _variable.name + " used before it was declared.",
+ _variable.location
+ ));
+ success = false;
+ }
+ variableSize = 1;
+ }
+ else if (m_resolver)
+ variableSize = m_resolver(_variable, IdentifierContext::LValue);
+ if (variableSize == size_t(-1))
+ {
+ // Only add message if the callback did not.
+ if (numErrorsBefore == m_errors.size())
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Variable not found or variable not lvalue.",
+ _variable.location
+ ));
+ success = false;
+ }
+ if (_valueSize == size_t(-1))
+ _valueSize = variableSize == size_t(-1) ? 1 : variableSize;
+
+ m_stackHeight -= _valueSize;
+
+ if (_valueSize != variableSize && variableSize != size_t(-1))
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Variable size (" +
+ to_string(variableSize) +
+ ") and value size (" +
+ to_string(_valueSize) +
+ ") do not match.",
+ _variable.location
+ ));
+ success = false;
+ }
return success;
}
+
+bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location)
+{
+ int stackDiff = m_stackHeight - _oldHeight;
+ if (stackDiff != _deposit)
+ {
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::TypeError,
+ "Expected instruction(s) to deposit " +
+ boost::lexical_cast<string>(_deposit) +
+ " item(s) to the stack, but did deposit " +
+ boost::lexical_cast<string>(stackDiff) +
+ " item(s).",
+ _location
+ ));
+ return false;
+ }
+ else
+ return true;
+}
+
+Scope& AsmAnalyzer::scope(Block const* _block)
+{
+ auto scopePtr = m_info.scopes.at(_block);
+ solAssert(scopePtr, "Scope requested but not present.");
+ return *scopePtr;
+}
diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h
index 9726210d..426ee0d2 100644
--- a/libsolidity/inlineasm/AsmAnalysis.h
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -20,6 +20,8 @@
#pragma once
+#include <libsolidity/inlineasm/AsmStack.h>
+
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
@@ -46,101 +48,32 @@ struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
-template <class...>
-struct GenericVisitor{};
-
-template <class Visitable, class... Others>
-struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
-{
- using GenericVisitor<Others...>::operator ();
- explicit GenericVisitor(
- std::function<void(Visitable&)> _visitor,
- std::function<void(Others&)>... _otherVisitors
- ):
- GenericVisitor<Others...>(_otherVisitors...),
- m_visitor(_visitor)
- {}
-
- void operator()(Visitable& _v) const { m_visitor(_v); }
-
- std::function<void(Visitable&)> m_visitor;
-};
-template <>
-struct GenericVisitor<>: public boost::static_visitor<> {
- void operator()() const {}
-};
-
-
-struct Scope
-{
- struct Variable
- {
- int stackHeight = 0;
- bool active = false;
- };
-
- struct Label
- {
- size_t id = unassignedLabelId;
- static const size_t errorLabelId = -1;
- static const size_t unassignedLabelId = 0;
- };
-
- struct Function
- {
- Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
- size_t arguments = 0;
- size_t returns = 0;
- };
-
- using Identifier = boost::variant<Variable, Label, Function>;
- using Visitor = GenericVisitor<Variable const, Label const, Function const>;
- using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
-
- bool registerVariable(std::string const& _name);
- bool registerLabel(std::string const& _name);
- bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
-
- /// Looks up the identifier in this or super scopes (stops and function and assembly boundaries)
- /// and returns a valid pointer if found or a nullptr if not found.
- /// The pointer will be invalidated if the scope is modified.
- Identifier* lookup(std::string const& _name);
- /// Looks up the identifier in this and super scopes (stops and function and assembly boundaries)
- /// and calls the visitor, returns false if not found.
- template <class V>
- bool lookup(std::string const& _name, V const& _visitor)
- {
- if (Identifier* id = lookup(_name))
- {
- boost::apply_visitor(_visitor, *id);
- return true;
- }
- else
- return false;
- }
- /// @returns true if the name exists in this scope or in super scopes (also searches
- /// across function and assembly boundaries).
- bool exists(std::string const& _name);
- Scope* superScope = nullptr;
- /// If true, identifiers from the super scope are not visible here, but they are still
- /// taken into account to prevent shadowing.
- bool closedScope = false;
- std::map<std::string, Identifier> identifiers;
-};
+struct Scope;
+struct AsmAnalysisInfo;
+/**
+ * Performs the full analysis stage, calls the ScopeFiller internally, then resolves
+ * references and performs other checks.
+ * If all these checks pass, code generation should not throw errors.
+ */
class AsmAnalyzer: public boost::static_visitor<bool>
{
public:
- using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
- AsmAnalyzer(Scopes& _scopes, ErrorList& _errors);
+ AsmAnalyzer(
+ AsmAnalysisInfo& _analysisInfo,
+ ErrorList& _errors,
+ ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
+ );
+
+ bool analyze(assembly::Block const& _block);
- bool operator()(assembly::Instruction const&) { return true; }
+ bool operator()(assembly::Instruction const&);
bool operator()(assembly::Literal const& _literal);
- bool operator()(assembly::Identifier const&) { return true; }
+ bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
- bool operator()(assembly::Assignment const&) { return true; }
+ bool operator()(assembly::Assignment const&);
bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
@@ -148,8 +81,20 @@ public:
bool operator()(assembly::Block const& _block);
private:
+ /// Verifies that a variable to be assigned to exists and has the same size
+ /// as the value, @a _valueSize, unless that is equal to -1.
+ bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
+ bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
+ Scope& scope(assembly::Block const* _block);
+
+ /// This is used when we enter the body of a function definition. There, the parameters
+ /// and return parameters appear as variables which are already on the stack before
+ /// we enter the block.
+ int m_virtualVariablesInNextBlock = 0;
+ int m_stackHeight = 0;
+ ExternalIdentifierAccess::Resolver const& m_resolver;
Scope* m_currentScope = nullptr;
- Scopes& m_scopes;
+ AsmAnalysisInfo& m_info;
ErrorList& m_errors;
};
diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.cpp b/libsolidity/inlineasm/AsmAnalysisInfo.cpp
new file mode 100644
index 00000000..22318b12
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysisInfo.cpp
@@ -0,0 +1,26 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <ostream>
+
diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h
new file mode 100644
index 00000000..e21eb2c5
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysisInfo.h
@@ -0,0 +1,61 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#pragma once
+
+#include <boost/variant.hpp>
+
+#include <map>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Literal;
+struct Block;
+struct Label;
+struct FunctionalInstruction;
+struct FunctionalAssignment;
+struct VariableDeclaration;
+struct Instruction;
+struct Identifier;
+struct Assignment;
+struct FunctionDefinition;
+struct FunctionCall;
+
+struct Scope;
+
+using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
+
+struct AsmAnalysisInfo
+{
+ using StackHeightInfo = std::map<void const*, int>;
+ using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
+ Scopes scopes;
+ StackHeightInfo stackHeightInfo;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp
index fdfd9abe..9ef3e6e7 100644
--- a/libsolidity/inlineasm/AsmCodeGen.cpp
+++ b/libsolidity/inlineasm/AsmCodeGen.cpp
@@ -24,7 +24,9 @@
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/SourceLocation.h>
@@ -46,13 +48,8 @@ using namespace dev::solidity::assembly;
struct GeneratorState
{
- GeneratorState(ErrorList& _errors, eth::Assembly& _assembly):
- errors(_errors), assembly(_assembly) {}
-
- void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
- {
- errors.push_back(make_shared<Error>(_type, _description, _location));
- }
+ GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly):
+ errors(_errors), info(_analysisInfo), assembly(_assembly) {}
size_t newLabelId()
{
@@ -66,8 +63,8 @@ struct GeneratorState
return size_t(id);
}
- std::map<assembly::Block const*, shared_ptr<Scope>> scopes;
ErrorList& errors;
+ AsmAnalysisInfo info;
eth::Assembly& assembly;
};
@@ -80,13 +77,24 @@ public:
explicit CodeTransform(
GeneratorState& _state,
assembly::Block const& _block,
- assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess()
+ assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess()
+ ): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit())
+ {
+ }
+
+private:
+ CodeTransform(
+ GeneratorState& _state,
+ assembly::Block const& _block,
+ assembly::ExternalIdentifierAccess const& _identifierAccess,
+ int _initialDeposit
):
m_state(_state),
- m_scope(*m_state.scopes.at(&_block)),
- m_initialDeposit(m_state.assembly.deposit()),
- m_identifierAccess(_identifierAccess)
+ m_scope(*m_state.info.scopes.at(&_block)),
+ m_identifierAccess(_identifierAccess),
+ m_initialDeposit(_initialDeposit)
{
+ int blockStartDeposit = m_state.assembly.deposit();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
m_state.assembly.setSourceLocation(_block.location);
@@ -96,31 +104,16 @@ public:
if (identifier.second.type() == typeid(Scope::Variable))
m_state.assembly.append(solidity::Instruction::POP);
- int deposit = m_state.assembly.deposit() - m_initialDeposit;
-
- // issue warnings for stack height discrepancies
- if (deposit < 0)
- {
- m_state.addError(
- Error::Type::Warning,
- "Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.",
- _block.location
- );
- }
- else if (deposit > 0)
- {
- m_state.addError(
- Error::Type::Warning,
- "Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.",
- _block.location
- );
- }
+ int deposit = m_state.assembly.deposit() - blockStartDeposit;
+ solAssert(deposit == 0, "Invalid stack height at end of block.");
}
+public:
void operator()(assembly::Instruction const& _instruction)
{
m_state.assembly.setSourceLocation(_instruction.location);
m_state.assembly.append(_instruction.instruction);
+ checkStackHeight(&_instruction);
}
void operator()(assembly::Literal const& _literal)
{
@@ -132,6 +125,7 @@ public:
solAssert(_literal.value.size() <= 32, "");
m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
+ checkStackHeight(&_literal);
}
void operator()(assembly::Identifier const& _identifier)
{
@@ -153,20 +147,18 @@ public:
},
[=](Scope::Function&)
{
- solAssert(false, "Not yet implemented");
+ solAssert(false, "Function not removed during desugaring.");
}
)))
{
+ return;
}
- else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
- {
- m_state.addError(
- Error::Type::DeclarationError,
- "Identifier not found or not unique",
- _identifier.location
- );
- m_state.assembly.append(u256(0));
- }
+ solAssert(
+ m_identifierAccess.generateCode,
+ "Identifier not found and no external access available."
+ );
+ m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly);
+ checkStackHeight(&_identifier);
}
void operator()(FunctionalInstruction const& _instr)
{
@@ -174,9 +166,10 @@ public:
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *it);
- expectDeposit(1, height, locationOf(*it));
+ expectDeposit(1, height);
}
(*this)(_instr.instruction);
+ checkStackHeight(&_instr);
}
void operator()(assembly::FunctionCall const&)
{
@@ -186,36 +179,39 @@ public:
{
m_state.assembly.setSourceLocation(_label.location);
solAssert(m_scope.identifiers.count(_label.name), "");
- Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]);
+ Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
assignLabelIdIfUnset(label);
m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
+ checkStackHeight(&_label);
}
void operator()(assembly::Assignment const& _assignment)
{
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
+ checkStackHeight(&_assignment);
}
void operator()(FunctionalAssignment const& _assignment)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_assignment.value);
- expectDeposit(1, height, locationOf(*_assignment.value));
+ expectDeposit(1, height);
m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location);
+ checkStackHeight(&_assignment);
}
void operator()(assembly::VariableDeclaration const& _varDecl)
{
int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_varDecl.value);
- expectDeposit(1, height, locationOf(*_varDecl.value));
- solAssert(m_scope.identifiers.count(_varDecl.name), "");
- auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]);
+ expectDeposit(1, height);
+ auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
var.stackHeight = height;
var.active = true;
}
void operator()(assembly::Block const& _block)
{
- CodeTransform(m_state, _block, m_identifierAccess);
+ CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit);
+ checkStackHeight(&_block);
}
void operator()(assembly::FunctionDefinition const&)
{
@@ -225,35 +221,22 @@ public:
private:
void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
{
- if (m_scope.lookup(_variableName.name, Scope::Visitor(
- [=](Scope::Variable const& _var)
- {
- if (int heightDiff = variableHeightDiff(_var, _location, true))
- m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
- m_state.assembly.append(solidity::Instruction::POP);
- },
- [=](Scope::Label const&)
- {
- m_state.addError(
- Error::Type::DeclarationError,
- "Label \"" + string(_variableName.name) + "\" used as variable."
- );
- },
- [=](Scope::Function const&)
- {
- m_state.addError(
- Error::Type::DeclarationError,
- "Function \"" + string(_variableName.name) + "\" used as variable."
- );
- }
- )))
+ auto var = m_scope.lookup(_variableName.name);
+ if (var)
{
+ Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
+ if (int heightDiff = variableHeightDiff(_var, _location, true))
+ m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
+ m_state.assembly.append(solidity::Instruction::POP);
}
- else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
- m_state.addError(
- Error::Type::DeclarationError,
- "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue."
+ else
+ {
+ solAssert(
+ m_identifierAccess.generateCode,
+ "Identifier not found and no external access available."
);
+ m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly);
+ }
}
/// Determines the stack height difference to the given variables. Automatically generates
@@ -261,36 +244,33 @@ private:
/// errors and the (positive) stack height difference otherwise.
int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
{
- if (!_var.active)
- {
- m_state.addError( Error::Type::TypeError, "Variable used before it was declared", _location);
- return 0;
- }
int heightDiff = m_state.assembly.deposit() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
- m_state.addError(
+ //@TODO move this to analysis phase.
+ m_state.errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
_location
- );
+ ));
return 0;
}
else
return heightDiff;
}
- void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
+ void expectDeposit(int _deposit, int _oldHeight)
{
- if (m_state.assembly.deposit() != _oldHeight + 1)
- m_state.addError(Error::Type::TypeError,
- "Expected instruction(s) to deposit " +
- boost::lexical_cast<string>(_deposit) +
- " item(s) to the stack, but did deposit " +
- boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) +
- " item(s).",
- _location
- );
+ solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
+ }
+
+ void checkStackHeight(void const* _astElement)
+ {
+ solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
+ solAssert(
+ m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit,
+ "Stack height mismatch between analysis and code generation phase."
+ );
}
/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
@@ -305,35 +285,29 @@ private:
GeneratorState& m_state;
Scope& m_scope;
+ ExternalIdentifierAccess m_identifierAccess;
int const m_initialDeposit;
- assembly::CodeGenerator::IdentifierAccess m_identifierAccess;
};
-bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
-{
- size_t initialErrorLen = m_errors.size();
- eth::Assembly assembly;
- GeneratorState state(m_errors, assembly);
- if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
- return false;
- CodeTransform(state, m_parsedData, _identifierAccess);
- return m_errors.size() == initialErrorLen;
-}
-
-eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
+eth::Assembly assembly::CodeGenerator::assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ ExternalIdentifierAccess const& _identifierAccess
+)
{
eth::Assembly assembly;
- GeneratorState state(m_errors, assembly);
- if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
- solAssert(false, "Assembly error");
- CodeTransform(state, m_parsedData, _identifierAccess);
+ GeneratorState state(m_errors, _analysisInfo, assembly);
+ CodeTransform(state, _parsedData, _identifierAccess);
return assembly;
}
-void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
+void assembly::CodeGenerator::assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ ExternalIdentifierAccess const& _identifierAccess
+)
{
- GeneratorState state(m_errors, _assembly);
- if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
- solAssert(false, "Assembly error");
- CodeTransform(state, m_parsedData, _identifierAccess);
+ GeneratorState state(m_errors, _analysisInfo, _assembly);
+ CodeTransform(state, _parsedData, _identifierAccess);
}
diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h
index bd71812e..e830e047 100644
--- a/libsolidity/inlineasm/AsmCodeGen.h
+++ b/libsolidity/inlineasm/AsmCodeGen.h
@@ -22,9 +22,11 @@
#pragma once
-#include <functional>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/interface/Exceptions.h>
+#include <functional>
+
namespace dev
{
namespace eth
@@ -36,30 +38,27 @@ namespace solidity
namespace assembly
{
struct Block;
-struct Identifier;
class CodeGenerator
{
public:
- enum class IdentifierContext { LValue, RValue };
- /// Function type that is called for external identifiers. Such a function should search for
- /// the identifier and append appropriate assembly items to the assembly. If in lvalue context,
- /// the value to assign is assumed to be on the stack and an assignment is to be performed.
- /// If in rvalue context, the function is assumed to append instructions to
- /// push the value of the identifier onto the stack. On error, the function should return false.
- using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>;
- CodeGenerator(Block const& _parsedData, ErrorList& _errors):
- m_parsedData(_parsedData), m_errors(_errors) {}
- /// Performs type checks and @returns false on error.
- /// Actually runs the full code generation but discards the result.
- bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
+ CodeGenerator(ErrorList& _errors):
+ m_errors(_errors) {}
/// Performs code generation and @returns the result.
- eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess());
+ eth::Assembly assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
+ );
/// Performs code generation and appends generated to to _assembly.
- void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess());
+ void assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
+ );
private:
- Block const& m_parsedData;
ErrorList& m_errors;
};
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 0fc0a34f..d7f78958 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -24,6 +24,7 @@
#include <ctype.h>
#include <algorithm>
#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/interface/Exceptions.h>
using namespace std;
using namespace dev;
@@ -68,12 +69,14 @@ assembly::Statement Parser::parseStatement()
return parseBlock();
case Token::Assign:
{
+ if (m_julia)
+ break;
assembly::Assignment assignment = createWithLocation<assembly::Assignment>();
m_scanner->next();
expectToken(Token::Colon);
assignment.variableName.location = location();
assignment.variableName.name = m_scanner->currentLiteral();
- if (instructions().count(assignment.variableName.name))
+ if (!m_julia && instructions().count(assignment.variableName.name))
fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
@@ -105,7 +108,7 @@ assembly::Statement Parser::parseStatement()
{
// functional assignment
FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
- if (instructions().count(identifier.name))
+ if (!m_julia && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names.");
m_scanner->next();
funAss.variableName = identifier;
@@ -180,7 +183,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else
literal = m_scanner->currentLiteral();
// first search the set of instructions.
- if (instructions().count(literal))
+ if (!m_julia && instructions().count(literal))
{
dev::solidity::Instruction const& instr = instructions().at(literal);
if (_onlySinglePusher)
@@ -242,15 +245,13 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
{
expectToken(Token::Sub);
expectToken(Token::GreaterThan);
- expectToken(Token::LParen);
while (true)
{
funDef.returns.push_back(expectAsmIdentifier());
- if (m_scanner->currentToken() == Token::RParen)
+ if (m_scanner->currentToken() == Token::LBrace)
break;
expectToken(Token::Comma);
}
- expectToken(Token::RParen);
}
funDef.body = parseBlock();
funDef.location.end = funDef.body.location.end;
@@ -261,6 +262,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
{
if (_instruction.type() == typeid(Instruction))
{
+ solAssert(!m_julia, "Instructions are invalid in JULIA");
FunctionalInstruction ret;
ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location;
@@ -323,7 +325,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
string Parser::expectAsmIdentifier()
{
string name = m_scanner->currentLiteral();
- if (instructions().count(name))
+ if (!m_julia && instructions().count(name))
fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
return name;
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 4b4a24ae..c55fd2ac 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -37,7 +37,7 @@ namespace assembly
class Parser: public ParserBase
{
public:
- Parser(ErrorList& _errors): ParserBase(_errors) {}
+ explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {}
/// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error.
@@ -70,6 +70,9 @@ protected:
FunctionDefinition parseFunctionDefinition();
Statement parseFunctionalInstruction(Statement&& _instruction);
std::string expectAsmIdentifier();
+
+private:
+ bool m_julia = false;
};
}
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index a70b0b78..252e91f9 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -116,7 +116,7 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin
{
string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")";
if (!_functionDefinition.returns.empty())
- out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")";
+ out += " -> " + boost::algorithm::join(_functionDefinition.returns, ", ");
return out + "\n" + (*this)(_functionDefinition.body);
}
diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp
new file mode 100644
index 00000000..609dca16
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScope.cpp
@@ -0,0 +1,79 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+using namespace std;
+using namespace dev::solidity::assembly;
+
+
+bool Scope::registerLabel(string const& _name)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Label();
+ return true;
+}
+
+bool Scope::registerVariable(string const& _name)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Variable();
+ return true;
+}
+
+bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Function(_arguments, _returns);
+ return true;
+}
+
+Scope::Identifier* Scope::lookup(string const& _name)
+{
+ bool crossedFunctionBoundary = false;
+ for (Scope* s = this; s; s = s->superScope)
+ {
+ auto id = s->identifiers.find(_name);
+ if (id != s->identifiers.end())
+ {
+ if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable))
+ return nullptr;
+ else
+ return &id->second;
+ }
+
+ if (s->functionScope)
+ crossedFunctionBoundary = true;
+ }
+ return nullptr;
+}
+
+bool Scope::exists(string const& _name)
+{
+ if (identifiers.count(_name))
+ return true;
+ else if (superScope)
+ return superScope->exists(_name);
+ else
+ return false;
+}
diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h
new file mode 100644
index 00000000..37e0f0b8
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScope.h
@@ -0,0 +1,128 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/variant.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+template <class...>
+struct GenericVisitor{};
+
+template <class Visitable, class... Others>
+struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
+{
+ using GenericVisitor<Others...>::operator ();
+ explicit GenericVisitor(
+ std::function<void(Visitable&)> _visitor,
+ std::function<void(Others&)>... _otherVisitors
+ ):
+ GenericVisitor<Others...>(_otherVisitors...),
+ m_visitor(_visitor)
+ {}
+
+ void operator()(Visitable& _v) const { m_visitor(_v); }
+
+ std::function<void(Visitable&)> m_visitor;
+};
+template <>
+struct GenericVisitor<>: public boost::static_visitor<> {
+ void operator()() const {}
+};
+
+
+struct Scope
+{
+ struct Variable
+ {
+ /// Used during code generation to store the stack height. @todo move there.
+ int stackHeight = 0;
+ /// Used during analysis to check whether we already passed the declaration inside the block.
+ /// @todo move there.
+ bool active = false;
+ };
+
+ struct Label
+ {
+ size_t id = unassignedLabelId;
+ static const size_t errorLabelId = -1;
+ static const size_t unassignedLabelId = 0;
+ };
+
+ struct Function
+ {
+ Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
+ size_t arguments = 0;
+ size_t returns = 0;
+ };
+
+ using Identifier = boost::variant<Variable, Label, Function>;
+ using Visitor = GenericVisitor<Variable const, Label const, Function const>;
+ using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
+
+ bool registerVariable(std::string const& _name);
+ bool registerLabel(std::string const& _name);
+ bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
+
+ /// Looks up the identifier in this or super scopes and returns a valid pointer if found
+ /// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
+ /// will any lookups across assembly boundaries.
+ /// The pointer will be invalidated if the scope is modified.
+ /// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
+ Identifier* lookup(std::string const& _name);
+ /// Looks up the identifier in this and super scopes (will not find variables across function
+ /// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
+ /// false if not found.
+ template <class V>
+ bool lookup(std::string const& _name, V const& _visitor)
+ {
+ if (Identifier* id = lookup(_name))
+ {
+ boost::apply_visitor(_visitor, *id);
+ return true;
+ }
+ else
+ return false;
+ }
+ /// @returns true if the name exists in this scope or in super scopes (also searches
+ /// across function and assembly boundaries).
+ bool exists(std::string const& _name);
+
+ Scope* superScope = nullptr;
+ /// If true, variables from the super scope are not visible here (other identifiers are),
+ /// but they are still taken into account to prevent shadowing.
+ bool functionScope = false;
+ std::map<std::string, Identifier> identifiers;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
new file mode 100644
index 00000000..de6fbdaa
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -0,0 +1,130 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/Utils.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors):
+ m_scopes(_scopes), m_errors(_errors)
+{
+ // Make the Solidity ErrorTag available to inline assembly
+ Scope::Label errorLabel;
+ errorLabel.id = Scope::Label::errorLabelId;
+ scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel;
+ m_currentScope = &scope(nullptr);
+}
+
+bool ScopeFiller::operator()(Label const& _item)
+{
+ if (!m_currentScope->registerLabel(_item.name))
+ {
+ //@TODO secondary location
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Label name " + _item.name + " already taken in this scope.",
+ _item.location
+ ));
+ return false;
+ }
+ return true;
+}
+
+bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
+{
+ return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope);
+}
+
+bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
+{
+ bool success = true;
+ if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size()))
+ {
+ //@TODO secondary location
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Function name " + _funDef.name + " already taken in this scope.",
+ _funDef.location
+ ));
+ success = false;
+ }
+ Scope& body = scope(&_funDef.body);
+ body.superScope = m_currentScope;
+ body.functionScope = true;
+ for (auto const& var: _funDef.arguments + _funDef.returns)
+ if (!registerVariable(var, _funDef.location, body))
+ success = false;
+
+ if (!(*this)(_funDef.body))
+ success = false;
+
+ return success;
+}
+
+bool ScopeFiller::operator()(Block const& _block)
+{
+ bool success = true;
+ scope(&_block).superScope = m_currentScope;
+ m_currentScope = &scope(&_block);
+
+ for (auto const& s: _block.statements)
+ if (!boost::apply_visitor(*this, s))
+ success = false;
+
+ m_currentScope = m_currentScope->superScope;
+ return success;
+}
+
+bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope)
+{
+ if (!_scope.registerVariable(_name))
+ {
+ //@TODO secondary location
+ m_errors.push_back(make_shared<Error>(
+ Error::Type::DeclarationError,
+ "Variable name " + _name + " already taken in this scope.",
+ _location
+ ));
+ return false;
+ }
+ return true;
+}
+
+Scope& ScopeFiller::scope(Block const* _block)
+{
+ auto& scope = m_scopes[_block];
+ if (!scope)
+ scope = make_shared<Scope>();
+ return *scope;
+}
diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h
new file mode 100644
index 00000000..bb62948b
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -0,0 +1,89 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/variant.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Literal;
+struct Block;
+struct Label;
+struct FunctionalInstruction;
+struct FunctionalAssignment;
+struct VariableDeclaration;
+struct Instruction;
+struct Identifier;
+struct Assignment;
+struct FunctionDefinition;
+struct FunctionCall;
+
+struct Scope;
+
+/**
+ * Fills scopes with identifiers and checks for name clashes.
+ * Does not resolve references.
+ */
+class ScopeFiller: public boost::static_visitor<bool>
+{
+public:
+ using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
+ ScopeFiller(Scopes& _scopes, ErrorList& _errors);
+
+ bool operator()(assembly::Instruction const&) { return true; }
+ bool operator()(assembly::Literal const&) { return true; }
+ bool operator()(assembly::Identifier const&) { return true; }
+ bool operator()(assembly::FunctionalInstruction const&) { return true; }
+ bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::Assignment const&) { return true; }
+ bool operator()(assembly::FunctionalAssignment const&) { return true; }
+ bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ bool operator()(assembly::FunctionDefinition const& _functionDefinition);
+ bool operator()(assembly::FunctionCall const&) { return true; }
+ bool operator()(assembly::Block const& _block);
+
+private:
+ bool registerVariable(
+ std::string const& _name,
+ SourceLocation const& _location,
+ Scope& _scope
+ );
+
+ Scope& scope(assembly::Block const* _block);
+
+ Scope* m_currentScope = nullptr;
+ Scopes& m_scopes;
+ ErrorList& m_errors;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp
index 266136a1..c2a7d8ea 100644
--- a/libsolidity/inlineasm/AsmStack.cpp
+++ b/libsolidity/inlineasm/AsmStack.cpp
@@ -26,6 +26,7 @@
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmPrinter.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/parsing/Scanner.h>
@@ -39,7 +40,10 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
-bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
+bool InlineAssemblyStack::parse(
+ shared_ptr<Scanner> const& _scanner,
+ ExternalIdentifierAccess::Resolver const& _resolver
+)
{
m_parserResult = make_shared<Block>();
Parser parser(m_errors);
@@ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
return false;
*m_parserResult = std::move(*result);
- AsmAnalyzer::Scopes scopes;
- return (AsmAnalyzer(scopes, m_errors))(*m_parserResult);
+ AsmAnalysisInfo analysisInfo;
+ return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult);
}
string InlineAssemblyStack::toString()
@@ -59,14 +63,17 @@ string InlineAssemblyStack::toString()
eth::Assembly InlineAssemblyStack::assemble()
{
- CodeGenerator codeGen(*m_parserResult, m_errors);
- return codeGen.assemble();
+ AsmAnalysisInfo analysisInfo;
+ AsmAnalyzer analyzer(analysisInfo, m_errors);
+ solAssert(analyzer.analyze(*m_parserResult), "");
+ CodeGenerator codeGen(m_errors);
+ return codeGen.assemble(*m_parserResult, analysisInfo);
}
bool InlineAssemblyStack::parseAndAssemble(
string const& _input,
eth::Assembly& _assembly,
- CodeGenerator::IdentifierAccess const& _identifierAccess
+ ExternalIdentifierAccess const& _identifierAccess
)
{
ErrorList errors;
@@ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble(
auto parserResult = Parser(errors).parse(scanner);
if (!errors.empty())
return false;
+ solAssert(parserResult, "");
- CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess);
+ AsmAnalysisInfo analysisInfo;
+ AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve);
+ solAssert(analyzer.analyze(*parserResult), "");
+ CodeGenerator(errors).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess);
// At this point, the assembly might be messed up, but we should throw an
// internal compiler error anyway.
diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h
index 4d5a99a4..77a7e02a 100644
--- a/libsolidity/inlineasm/AsmStack.h
+++ b/libsolidity/inlineasm/AsmStack.h
@@ -22,10 +22,10 @@
#pragma once
+#include <libsolidity/interface/Exceptions.h>
+
#include <string>
#include <functional>
-#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
namespace dev
{
@@ -39,13 +39,34 @@ class Scanner;
namespace assembly
{
struct Block;
+struct Identifier;
+
+enum class IdentifierContext { LValue, RValue };
+
+/// Object that is used to resolve references and generate code for access to identifiers external
+/// to inline assembly (not used in standalone assembly mode).
+struct ExternalIdentifierAccess
+{
+ using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>;
+ /// Resolve a an external reference given by the identifier in the given context.
+ /// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
+ Resolver resolve;
+ using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>;
+ /// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
+ /// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
+ /// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
+ CodeGenerator generateCode;
+};
class InlineAssemblyStack
{
public:
/// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`.
/// @return false or error.
- bool parse(std::shared_ptr<Scanner> const& _scanner);
+ bool parse(
+ std::shared_ptr<Scanner> const& _scanner,
+ ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver()
+ );
/// Converts the parser result back into a string form (not necessarily the same form
/// as the source form, but it should parse into the same parsed form again).
std::string toString();
@@ -56,7 +77,7 @@ public:
bool parseAndAssemble(
std::string const& _input,
eth::Assembly& _assembly,
- CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess()
+ ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
ErrorList const& errors() const { return m_errors; }
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index bda7a882..79855060 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -686,8 +686,33 @@ void CompilerStack::compileContract(
cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
compiledContract.compiler = compiler;
- compiledContract.object = compiler->assembledObject();
- compiledContract.runtimeObject = compiler->runtimeObject();
+
+ try
+ {
+ compiledContract.object = compiler->assembledObject();
+ }
+ catch(eth::OptimizerException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode"));
+ }
+ catch(eth::AssemblyException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode"));
+ }
+
+ try
+ {
+ compiledContract.runtimeObject = compiler->runtimeObject();
+ }
+ catch(eth::OptimizerException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode"));
+ }
+ catch(eth::AssemblyException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode"));
+ }
+
compiledContract.onChainMetadata = onChainMetadata;
_compiledContracts[compiledContract.contract] = &compiler->assembly();
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4d56ec9d..01a9a188 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -21,4 +21,4 @@ include_directories(BEFORE ..)
target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
add_executable(solfuzzer fuzzer.cpp)
-target_link_libraries(solfuzzer soljson)
+target_link_libraries(solfuzzer soljson ${Boost_PROGRAM_OPTIONS_LIBRARIES})
diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh
index caf09a91..6dc3f77f 100755
--- a/test/cmdlineTests.sh
+++ b/test/cmdlineTests.sh
@@ -79,7 +79,7 @@ TMPDIR=$(mktemp -d)
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/
for f in *.sol
do
- "$REPO_ROOT"/build/test/solfuzzer < "$f"
+ "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f"
done
)
rm -rf "$TMPDIR"
diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp
index 410313c5..afa50671 100644
--- a/test/fuzzer.cpp
+++ b/test/fuzzer.cpp
@@ -16,21 +16,32 @@
*/
/**
* Executable for use with AFL <http://lcamtuf.coredump.cx/afl>.
- * Reads a single source from stdin and signals a failure for internal errors.
*/
+#include <libevmasm/Assembly.h>
+#include <libevmasm/ConstantOptimiser.h>
+
#include <json/json.h>
+#include <boost/program_options.hpp>
+
#include <string>
#include <iostream>
using namespace std;
+using namespace dev;
+using namespace dev::eth;
+namespace po = boost::program_options;
extern "C"
{
extern char const* compileJSON(char const* _input, bool _optimize);
+typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
+extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback);
}
+bool quiet = false;
+
string contains(string const& _haystack, vector<string> const& _needles)
{
for (string const& needle: _needles)
@@ -39,7 +50,42 @@ string contains(string const& _haystack, vector<string> const& _needles)
return "";
}
-int main()
+void testConstantOptimizer()
+{
+ if (!quiet)
+ cout << "Testing constant optimizer" << endl;
+ vector<u256> numbers;
+ while (!cin.eof())
+ {
+ h256 data;
+ cin.read(reinterpret_cast<char*>(data.data()), 32);
+ numbers.push_back(u256(data));
+ }
+ if (!quiet)
+ cout << "Got " << numbers.size() << " inputs:" << endl;
+
+ Assembly assembly;
+ for (u256 const& n: numbers)
+ {
+ if (!quiet)
+ cout << n << endl;
+ assembly.append(n);
+ }
+ for (bool isCreation: {false, true})
+ {
+ for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000})
+ {
+ ConstantOptimisationMethod::optimiseConstants(
+ isCreation,
+ runs,
+ assembly,
+ const_cast<AssemblyItems&>(assembly.items())
+ );
+ }
+ }
+}
+
+string readInput()
{
string input;
while (!cin.eof())
@@ -48,6 +94,41 @@ int main()
getline(cin, s);
input += s + '\n';
}
+ return input;
+}
+
+void testStandardCompiler()
+{
+ if (!quiet)
+ cout << "Testing compiler via JSON interface." << endl;
+ string input = readInput();
+ string outputString(compileStandard(input.c_str(), NULL));
+ Json::Value output;
+ if (!Json::Reader().parse(outputString, output))
+ {
+ cout << "Compiler produced invalid JSON output." << endl;
+ abort();
+ }
+ if (output.isMember("errors"))
+ for (auto const& error: output["errors"])
+ {
+ string invalid = contains(error["type"].asString(), vector<string>{
+ "Exception",
+ "InternalCompilerError"
+ });
+ if (!invalid.empty())
+ {
+ cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl;
+ abort();
+ }
+ }
+}
+
+void testCompiler()
+{
+ if (!quiet)
+ cout << "Testing compiler." << endl;
+ string input = readInput();
bool optimize = true;
string outputString(compileJSON(input.c_str(), optimize));
@@ -87,5 +168,57 @@ int main()
cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl;
abort();
}
+}
+
+int main(int argc, char** argv)
+{
+ po::options_description options(
+ R"(solfuzzer, fuzz-testing binary for use with AFL.
+Usage: solfuzzer [Options] < input
+Reads a single source from stdin, compiles it and signals a failure for internal errors.
+
+Allowed options)",
+ po::options_description::m_default_line_length,
+ po::options_description::m_default_line_length - 23);
+ options.add_options()
+ ("help", "Show this help screen.")
+ ("quiet", "Only output errors.")
+ (
+ "standard-json",
+ "Test via the standard-json interface, i.e. "
+ "input is expected to be JSON-encoded instead of "
+ "plain source file."
+ )
+ (
+ "const-opt",
+ "Run the constant optimizer instead of compiling. "
+ "Expects a binary string of up to 32 bytes on stdin."
+ );
+
+ po::variables_map arguments;
+ try
+ {
+ po::command_line_parser cmdLineParser(argc, argv);
+ cmdLineParser.options(options);
+ po::store(cmdLineParser.run(), arguments);
+ }
+ catch (po::error const& _exception)
+ {
+ cerr << _exception.what() << endl;
+ return false;
+ }
+
+ if (arguments.count("quiet"))
+ quiet = true;
+
+ if (arguments.count("help"))
+ cout << options;
+ else if (arguments.count("const-opt"))
+ testConstantOptimizer();
+ else if (arguments.count("standard-json"))
+ testStandardCompiler();
+ else
+ testCompiler();
+
return 0;
}
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index 9035599b..8bf4df8e 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -30,6 +30,7 @@
#include <libevmasm/Assembly.h>
#include <boost/optional.hpp>
+#include <boost/algorithm/string/replace.hpp>
#include <string>
#include <memory>
@@ -63,7 +64,7 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass
}
if (!success)
{
- BOOST_CHECK_EQUAL(stack.errors().size(), 1);
+ BOOST_REQUIRE_EQUAL(stack.errors().size(), 1);
return *stack.errors().front();
}
else
@@ -137,22 +138,22 @@ BOOST_AUTO_TEST_CASE(smoke_test)
BOOST_AUTO_TEST_CASE(simple_instructions)
{
- BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }"));
+ BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }"));
}
BOOST_AUTO_TEST_CASE(suicide_selfdestruct)
{
- BOOST_CHECK(successParse("{ suicide selfdestruct }"));
+ BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }"));
}
BOOST_AUTO_TEST_CASE(keywords)
{
- BOOST_CHECK(successParse("{ byte return address }"));
+ BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }"));
}
BOOST_AUTO_TEST_CASE(constants)
{
- BOOST_CHECK(successParse("{ 7 8 mul }"));
+ BOOST_CHECK(successParse("{ 7 8 mul pop }"));
}
BOOST_AUTO_TEST_CASE(vardecl)
@@ -162,37 +163,43 @@ BOOST_AUTO_TEST_CASE(vardecl)
BOOST_AUTO_TEST_CASE(assignment)
{
- BOOST_CHECK(successParse("{ 7 8 add =: x }"));
+ BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }"));
}
BOOST_AUTO_TEST_CASE(label)
{
- BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }"));
+ BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }"));
}
BOOST_AUTO_TEST_CASE(label_complex)
{
- BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }"));
+ BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }"));
}
BOOST_AUTO_TEST_CASE(functional)
{
- BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment)
{
- BOOST_CHECK(successParse("{ x := 7 }"));
+ BOOST_CHECK(successParse("{ let x := 2 x := 7 }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment_complex)
{
- BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }"));
}
BOOST_AUTO_TEST_CASE(vardecl_complex)
{
- BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }"));
+}
+
+BOOST_AUTO_TEST_CASE(variable_use_before_decl)
+{
+ CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared.");
+ CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
}
BOOST_AUTO_TEST_CASE(blocks)
@@ -202,17 +209,38 @@ BOOST_AUTO_TEST_CASE(blocks)
BOOST_AUTO_TEST_CASE(function_definitions)
{
- BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }"));
+ BOOST_CHECK(successParse("{ function f() { } function g(a) -> x { } }"));
}
BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
{
- BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }"));
+ BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> x, y { } }"));
}
BOOST_AUTO_TEST_CASE(function_calls)
{
- BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }"));
+ BOOST_CHECK(successParse("{ function f(a) -> b {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }"));
+}
+
+BOOST_AUTO_TEST_CASE(opcode_for_functions)
+{
+ CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names.");
+}
+
+BOOST_AUTO_TEST_CASE(opcode_for_function_args)
+{
+ CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names.");
+ CHECK_PARSE_ERROR("{ function f() -> gas { } }", ParserError, "Cannot use instruction names for identifier names.");
+}
+
+BOOST_AUTO_TEST_CASE(name_clashes)
+{
+ CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope");
+}
+
+BOOST_AUTO_TEST_CASE(variable_access_cross_functions)
+{
+ CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found.");
}
BOOST_AUTO_TEST_SUITE_END()
@@ -226,7 +254,7 @@ BOOST_AUTO_TEST_CASE(print_smoke)
BOOST_AUTO_TEST_CASE(print_instructions)
{
- parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n}");
+ parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n pop\n}");
}
BOOST_AUTO_TEST_CASE(print_subblock)
@@ -236,7 +264,7 @@ BOOST_AUTO_TEST_CASE(print_subblock)
BOOST_AUTO_TEST_CASE(print_functional)
{
- parsePrintCompare("{\n mul(sload(0x12), 7)\n}");
+ parsePrintCompare("{\n let x := mul(sload(0x12), 7)\n}");
}
BOOST_AUTO_TEST_CASE(print_label)
@@ -251,13 +279,13 @@ BOOST_AUTO_TEST_CASE(print_assignments)
BOOST_AUTO_TEST_CASE(print_string_literals)
{
- parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}");
+ parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}");
}
BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
{
- string source = "{ \"\\u1bac\" }";
- string parsed = "{\n \"\\xe1\\xae\\xac\"\n}";
+ string source = "{ let x := \"\\u1bac\" }";
+ string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}";
assembly::InlineAssemblyStack stack;
BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source))));
BOOST_REQUIRE(stack.errors().empty());
@@ -267,12 +295,26 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
{
- parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}");
+ parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}");
}
BOOST_AUTO_TEST_CASE(function_calls)
{
- parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}");
+ string source = R"({
+ function y()
+ {
+ }
+ function f(a) -> b
+ {
+ }
+ function g(a, b, c)
+ {
+ }
+ g(1, mul(2, address), f(mul(2, caller)))
+ y()
+})";
+ boost::replace_all(source, "\t", " ");
+ parsePrintCompare(source);
}
BOOST_AUTO_TEST_SUITE_END()
@@ -291,27 +333,32 @@ BOOST_AUTO_TEST_CASE(oversize_string_literals)
BOOST_AUTO_TEST_CASE(assignment_after_tag)
{
- BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }"));
+ BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }"));
}
BOOST_AUTO_TEST_CASE(magic_variables)
{
- CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique");
- CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique");
- BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }"));
+ CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found");
+ CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found");
+ BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }"));
+}
+
+BOOST_AUTO_TEST_CASE(stack_variables)
+{
+ BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }"));
}
BOOST_AUTO_TEST_CASE(imbalanced_stack)
{
BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false));
- CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves");
- CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes");
+ CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
+ CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false));
}
BOOST_AUTO_TEST_CASE(error_tag)
{
- BOOST_CHECK(successAssemble("{ invalidJumpLabel }"));
+ BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }"));
}
BOOST_AUTO_TEST_CASE(designated_invalid_instruction)
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index 8dd5042a..f2f4b8b0 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -7426,18 +7426,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
uint16 x;
uint16 public y;
uint public z;
- function f() {
- // we know that z is aligned because it is too large, so we just discard its
- // intra-slot offset value
- assembly { 7 z pop sstore }
+ function f() returns (bool) {
+ uint off1;
+ uint off2;
+ assembly {
+ sstore(z_slot, 7)
+ off1 := z_offset
+ off2 := y_offset
+ }
+ assert(off1 == 0);
+ assert(off2 == 2);
+ return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
- BOOST_CHECK(callContractFunction("f()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
+BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer)
+{
+ char const* sourceCode = R"(
+ contract C {
+ struct Data { uint contents; }
+ uint public separator;
+ Data public a;
+ uint public separator2;
+ function f() returns (bool) {
+ Data x = a;
+ uint off;
+ assembly {
+ sstore(x_slot, 7)
+ off := x_offset
+ }
+ assert(off == 0);
+ return true;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7)));
+ BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0)));
+}
+
BOOST_AUTO_TEST_CASE(inline_assembly_jumps)
{
char const* sourceCode = R"(
@@ -7474,6 +7508,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
assembly {
_x
jump(g)
+ pop
}
}
}
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index b98c3706..f88f600a 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -4997,7 +4997,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack)
}
}
)";
- CHECK_WARNING(text, "Inline assembly block is not balanced");
+ CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
@@ -5011,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
}
}
)";
- CHECK_WARNING(text, "Inline assembly block is not balanced");
+ CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
@@ -5024,7 +5024,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
}
}
)";
- CHECK_WARNING(text, "Inline assembly block is not balanced");
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier)
@@ -5053,12 +5053,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage)
function f() {
assembly {
x := 2
- pop
}
}
}
)";
- CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue.");
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
@@ -5069,7 +5068,6 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
modifier m {
assembly {
x := 2
- pop
}
_;
}
@@ -5077,7 +5075,37 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
}
}
)";
- CHECK_ERROR(text, DeclarationError, "");
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign)
+{
+ char const* text = R"(
+ contract test {
+ uint constant x = 1;
+ function f() {
+ assembly {
+ x := 2
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
+{
+ char const* text = R"(
+ contract test {
+ uint constant x = 1;
+ function f() {
+ assembly {
+ let y := x
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
}
BOOST_AUTO_TEST_CASE(invalid_mobile_type)