aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
authorwbt <wbt@users.noreply.github.com>2018-01-23 22:44:37 +0800
committerGitHub <noreply@github.com>2018-01-23 22:44:37 +0800
commit761eae2499b172334e5ea3e75573af99fae2a975 (patch)
tree3ec324015e8d97e9b7ca43cb4dcc3c46b1b3bf31 /libsolidity
parent7c69d88f937324b64ed8825f9e611e417421434b (diff)
parente5def2da3d9b6cffbff42e2c9e1941831d2fe5ea (diff)
downloaddexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.gz
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.bz2
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.lz
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.xz
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.tar.zst
dexon-solidity-761eae2499b172334e5ea3e75573af99fae2a975.zip
Update from official repo
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/analysis/ConstantEvaluator.cpp85
-rw-r--r--libsolidity/analysis/ConstantEvaluator.h18
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp11
-rw-r--r--libsolidity/analysis/TypeChecker.cpp40
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp18
-rw-r--r--libsolidity/ast/Types.h2
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp449
-rw-r--r--libsolidity/codegen/ABIFunctions.h49
-rw-r--r--libsolidity/codegen/CompilerContext.cpp12
-rw-r--r--libsolidity/codegen/CompilerContext.h4
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp17
-rw-r--r--libsolidity/codegen/CompilerUtils.h7
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp9
-rw-r--r--libsolidity/formal/SMTChecker.cpp265
-rw-r--r--libsolidity/formal/SMTChecker.h53
-rw-r--r--libsolidity/formal/SolverInterface.h9
-rw-r--r--libsolidity/formal/Z3Interface.cpp5
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp45
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h9
-rw-r--r--libsolidity/inlineasm/AsmData.h18
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h14
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp159
-rw-r--r--libsolidity/inlineasm/AsmParser.h16
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp13
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h1
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp11
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h1
-rw-r--r--libsolidity/interface/AssemblyStack.cpp23
-rw-r--r--libsolidity/interface/AssemblyStack.h2
-rw-r--r--libsolidity/interface/Exceptions.h12
-rw-r--r--libsolidity/interface/StandardCompiler.cpp125
-rw-r--r--libsolidity/parsing/Parser.cpp14
32 files changed, 1233 insertions, 283 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp
index 4d546e68..83f37f47 100644
--- a/libsolidity/analysis/ConstantEvaluator.cpp
+++ b/libsolidity/analysis/ConstantEvaluator.cpp
@@ -28,51 +28,42 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation)
void ConstantEvaluator::endVisit(UnaryOperation const& _operation)
{
- TypePointer const& subType = _operation.subExpression().annotation().type;
- if (!dynamic_cast<RationalNumberType const*>(subType.get()))
- m_errorReporter.fatalTypeError(_operation.subExpression().location(), "Invalid constant expression.");
- TypePointer t = subType->unaryOperatorResult(_operation.getOperator());
- _operation.annotation().type = t;
+ auto sub = type(_operation.subExpression());
+ if (sub)
+ setType(_operation, sub->unaryOperatorResult(_operation.getOperator()));
}
-/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation)
void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
{
- TypePointer const& leftType = _operation.leftExpression().annotation().type;
- TypePointer const& rightType = _operation.rightExpression().annotation().type;
- if (!dynamic_cast<RationalNumberType const*>(leftType.get()))
- m_errorReporter.fatalTypeError(_operation.leftExpression().location(), "Invalid constant expression.");
- if (!dynamic_cast<RationalNumberType const*>(rightType.get()))
- m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression.");
- TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
- if (!commonType)
+ auto left = type(_operation.leftExpression());
+ auto right = type(_operation.rightExpression());
+ if (left && right)
{
- m_errorReporter.typeError(
- _operation.location(),
- "Operator " +
- string(Token::toString(_operation.getOperator())) +
- " not compatible with types " +
- leftType->toString() +
- " and " +
- rightType->toString()
+ auto commonType = left->binaryOperatorResult(_operation.getOperator(), right);
+ if (!commonType)
+ m_errorReporter.fatalTypeError(
+ _operation.location(),
+ "Operator " +
+ string(Token::toString(_operation.getOperator())) +
+ " not compatible with types " +
+ left->toString() +
+ " and " +
+ right->toString()
+ );
+ setType(
+ _operation,
+ Token::isCompareOp(_operation.getOperator()) ?
+ make_shared<BoolType>() :
+ commonType
);
- commonType = leftType;
}
- _operation.annotation().commonType = commonType;
- _operation.annotation().type =
- Token::isCompareOp(_operation.getOperator()) ?
- make_shared<BoolType>() :
- commonType;
}
void ConstantEvaluator::endVisit(Literal const& _literal)
{
- _literal.annotation().type = Type::forLiteral(_literal);
- if (!_literal.annotation().type)
- m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
+ setType(_literal, Type::forLiteral(_literal));
}
void ConstantEvaluator::endVisit(Identifier const& _identifier)
@@ -81,18 +72,34 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier)
if (!variableDeclaration)
return;
if (!variableDeclaration->isConstant())
- m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant.");
+ return;
- ASTPointer<Expression> value = variableDeclaration->value();
+ ASTPointer<Expression> const& value = variableDeclaration->value();
if (!value)
- m_errorReporter.fatalTypeError(_identifier.location(), "Constant identifier declaration must have a constant value.");
-
- if (!value->annotation().type)
+ return;
+ else if (!m_types->count(value.get()))
{
if (m_depth > 32)
m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted).");
- ConstantEvaluator e(*value, m_errorReporter, m_depth + 1);
+ ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value);
}
- _identifier.annotation().type = value->annotation().type;
+ setType(_identifier, type(*value));
+}
+
+void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type)
+{
+ if (_type && _type->category() == Type::Category::RationalNumber)
+ (*m_types)[&_node] = _type;
+}
+
+TypePointer ConstantEvaluator::type(ASTNode const& _node)
+{
+ return (*m_types)[&_node];
+}
+
+TypePointer ConstantEvaluator::evaluate(Expression const& _expr)
+{
+ _expr.accept(*this);
+ return type(_expr);
}
diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h
index 6725d610..77a357b6 100644
--- a/libsolidity/analysis/ConstantEvaluator.h
+++ b/libsolidity/analysis/ConstantEvaluator.h
@@ -38,22 +38,32 @@ class TypeChecker;
class ConstantEvaluator: private ASTConstVisitor
{
public:
- ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter, size_t _newDepth = 0):
+ ConstantEvaluator(
+ ErrorReporter& _errorReporter,
+ size_t _newDepth = 0,
+ std::shared_ptr<std::map<ASTNode const*, TypePointer>> _types = std::make_shared<std::map<ASTNode const*, TypePointer>>()
+ ):
m_errorReporter(_errorReporter),
- m_depth(_newDepth)
+ m_depth(_newDepth),
+ m_types(_types)
{
- _expr.accept(*this);
}
+ TypePointer evaluate(Expression const& _expr);
+
private:
virtual void endVisit(BinaryOperation const& _operation);
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
virtual void endVisit(Identifier const& _identifier);
+ void setType(ASTNode const& _node, TypePointer const& _type);
+ TypePointer type(ASTNode const& _node);
+
ErrorReporter& m_errorReporter;
/// Current recursion depth.
- size_t m_depth;
+ size_t m_depth = 0;
+ std::shared_ptr<std::map<ASTNode const*, TypePointer>> m_types;
};
}
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index f22c95cc..540ffaf5 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -146,11 +146,12 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
if (Expression const* length = _typeName.length())
{
- if (!length->annotation().type)
- ConstantEvaluator e(*length, m_errorReporter);
- auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get());
+ TypePointer lengthTypeGeneric = length->annotation().type;
+ if (!lengthTypeGeneric)
+ lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
+ RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric.get());
if (!lengthType || !lengthType->mobileType())
- fatalTypeError(length->location(), "Invalid array length, expected integer literal.");
+ fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
else if (lengthType->isFractional())
fatalTypeError(length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
@@ -206,7 +207,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
- assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations());
+ assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations());
return false;
}
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 73047e76..191f78e9 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -171,13 +171,7 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con
ssl.append("Another declaration is here:", (*it)->location());
string msg = "More than one constructor defined.";
- size_t occurrences = ssl.infos.size();
- if (occurrences > 32)
- {
- ssl.infos.resize(32);
- msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
- }
-
+ ssl.limitSize(msg);
m_errorReporter.declarationError(
functions[_contract.name()].front()->location(),
ssl,
@@ -219,12 +213,7 @@ void TypeChecker::findDuplicateDefinitions(map<string, vector<T>> const& _defini
if (ssl.infos.size() > 0)
{
- size_t occurrences = ssl.infos.size();
- if (occurrences > 32)
- {
- ssl.infos.resize(32);
- _message += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
- }
+ ssl.limitSize(_message);
m_errorReporter.declarationError(
overloads[i]->location(),
@@ -570,6 +559,17 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions.");
+ if (
+ _function.visibility() > FunctionDefinition::Visibility::Internal &&
+ type(*var)->category() == Type::Category::Struct &&
+ !type(*var)->dataStoredIn(DataLocation::Storage) &&
+ !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)
+ )
+ m_errorReporter.typeError(
+ var->location(),
+ "Structs are only supported in the new experimental ABI encoder. "
+ "Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
+ );
var->accept(*this);
}
@@ -873,7 +873,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
- false,
+ assembly::AsmFlavour::Loose,
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))
@@ -1060,7 +1060,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
- " (absolute value too large or divison by zero)."
+ " (absolute value too large or division by zero)."
);
else
solAssert(false, "");
@@ -1551,8 +1551,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
{
+ bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
+
string msg =
- "Wrong argument count for function call: " +
+ "Wrong argument count for " +
+ string(isStructConstructorCall ? "struct constructor" : "function call") +
+ ": " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
@@ -1668,10 +1672,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
SecondarySourceLocation ssl;
for (auto function: contract->annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
+ string msg = "Trying to create an instance of an abstract contract.";
+ ssl.limitSize(msg);
m_errorReporter.typeError(
_newExpression.location(),
ssl,
- "Trying to create an instance of an abstract contract."
+ msg
);
}
if (!contract->constructorIsPublic())
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index 7e41fc16..6257ac6d 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -40,19 +40,20 @@ public:
void operator()(assembly::Label const&) { }
void operator()(assembly::Instruction const& _instruction)
{
- if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::NonPayable, _instruction.location);
- else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::View, _instruction.location);
+ checkInstruction(_instruction.location, _instruction.instruction);
}
void operator()(assembly::Literal const&) {}
void operator()(assembly::Identifier const&) {}
void operator()(assembly::FunctionalInstruction const& _instr)
{
- (*this)(_instr.instruction);
+ checkInstruction(_instr.location, _instr.instruction);
for (auto const& arg: _instr.arguments)
boost::apply_visitor(*this, arg);
}
+ void operator()(assembly::ExpressionStatement const& _expr)
+ {
+ boost::apply_visitor(*this, _expr.expression);
+ }
void operator()(assembly::StackAssignment const&) {}
void operator()(assembly::Assignment const& _assignment)
{
@@ -102,6 +103,13 @@ public:
private:
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
+ void checkInstruction(SourceLocation _location, solidity::Instruction _instruction)
+ {
+ if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
+ m_reportMutability(StateMutability::NonPayable, _location);
+ else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
+ m_reportMutability(StateMutability::View, _location);
+ }
};
}
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 635279ab..a54e4e09 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -257,7 +257,7 @@ public:
}
virtual u256 literalValue(Literal const*) const
{
- solAssert(false, "Literal value requested for type without literals.");
+ solAssert(false, "Literal value requested for type without literals: " + toString(false));
}
/// @returns a (simpler) type that is encoded in the same way for external function calls.
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index bb39cbbb..00f59065 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -22,9 +22,13 @@
#include <libsolidity/codegen/ABIFunctions.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/codegen/CompilerUtils.h>
+
#include <libdevcore/Whiskers.h>
-#include <libsolidity/ast/AST.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
@@ -99,6 +103,79 @@ string ABIFunctions::tupleEncoder(
});
}
+string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
+{
+ string functionName = string("abi_decode_tuple_");
+ for (auto const& t: _types)
+ functionName += t->identifier();
+ if (_fromMemory)
+ functionName += "_fromMemory";
+
+ solAssert(!_types.empty(), "");
+
+ return createFunction(functionName, [&]() {
+ TypePointers decodingTypes;
+ for (auto const& t: _types)
+ decodingTypes.emplace_back(t->decodingType());
+
+ Whiskers templ(R"(
+ function <functionName>(headStart, dataEnd) -> <valueReturnParams> {
+ if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
+ <decodeElements>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("minimumSize", to_string(headSize(decodingTypes)));
+
+ string decodeElements;
+ vector<string> valueReturnParams;
+ size_t headPos = 0;
+ size_t stackPos = 0;
+ for (size_t i = 0; i < _types.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ solAssert(decodingTypes[i], "");
+ size_t sizeOnStack = _types[i]->sizeOnStack();
+ solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
+ solAssert(sizeOnStack > 0, "");
+ vector<string> valueNamesLocal;
+ for (size_t j = 0; j < sizeOnStack; j++)
+ {
+ valueNamesLocal.push_back("value" + to_string(stackPos));
+ valueReturnParams.push_back("value" + to_string(stackPos));
+ stackPos++;
+ }
+ bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
+ Whiskers elementTempl(
+ dynamic ?
+ R"(
+ {
+ let offset := <load>(add(headStart, <pos>))
+ if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )" :
+ R"(
+ {
+ let offset := <pos>
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )"
+ );
+ elementTempl("load", _fromMemory ? "mload" : "calldataload");
+ elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
+ elementTempl("pos", to_string(headPos));
+ elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
+ decodeElements += elementTempl.render();
+ headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
+ }
+ templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
+ templ("decodeElements", decodeElements);
+
+ return templ.render();
+ });
+}
+
string ABIFunctions::requestedFunctions()
{
string result;
@@ -141,10 +218,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
- solAssert(false, "Array cleanup requested.");
- break;
case Type::Category::Struct:
- solAssert(false, "Struct cleanup requested.");
+ solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
+ templ("body", "cleaned := value");
break;
case Type::Category::FixedBytes:
{
@@ -367,6 +443,24 @@ string ABIFunctions::combineExternalFunctionIdFunction()
});
}
+string ABIFunctions::splitExternalFunctionIdFunction()
+{
+ string functionName = "split_external_function_id";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(combined) -> addr, selector {
+ combined := <shr64>(combined)
+ selector := and(combined, 0xffffffff)
+ addr := <shr32>(combined)
+ }
+ )")
+ ("functionName", functionName)
+ ("shr32", shiftRightFunction(32, false))
+ ("shr64", shiftRightFunction(64, false))
+ .render();
+ });
+}
+
string ABIFunctions::abiEncodingFunction(
Type const& _from,
Type const& _to,
@@ -963,6 +1057,307 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
});
}
+string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ // The decoding function has to perform bounds checks unless it decodes a value type.
+ // Conversely, bounds checks have to be performed before the decoding function
+ // of a value type is called.
+
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+
+ if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get()))
+ {
+ if (arrayType->dataStoredIn(DataLocation::CallData))
+ {
+ solAssert(!_fromMemory, "");
+ return abiDecodingFunctionCalldataArray(*arrayType);
+ }
+ else if (arrayType->isByteArray())
+ return abiDecodingFunctionByteArray(*arrayType, _fromMemory);
+ else
+ return abiDecodingFunctionArray(*arrayType, _fromMemory);
+ }
+ else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
+ return abiDecodingFunctionStruct(*structType, _fromMemory);
+ else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
+ return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
+ else
+ return abiDecodingFunctionValueType(_type, _fromMemory);
+}
+
+string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory)
+{
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+ solAssert(decodingType->sizeOnStack() == 1, "");
+ solAssert(decodingType->isValueType(), "");
+ solAssert(decodingType->calldataEncodedSize() == 32, "");
+ solAssert(!decodingType->isDynamicallyEncoded(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ function <functionName>(offset, end) -> value {
+ value := <cleanup>(<load>(offset))
+ }
+ )");
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ // Cleanup itself should use the type and not decodingType, because e.g.
+ // the decoding type of an enum is a plain int.
+ templ("cleanup", cleanupFunction(_type, true));
+ return templ.render();
+ });
+
+}
+
+string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(!_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solAssert(!_type.dataStoredIn(DataLocation::Storage), "");
+
+ return createFunction(functionName, [&]() {
+ string load = _fromMemory ? "mload" : "calldataload";
+ bool dynamicBase = _type.baseType()->isDynamicallyEncoded();
+ Whiskers templ(
+ R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> array {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ let length := <retrieveLength>
+ array := <allocate>(<allocationSize>(length))
+ let dst := array
+ <storeLength> // might update offset and dst
+ let src := offset
+ <staticBoundsCheck>
+ for { let i := 0 } lt(i, length) { i := add(i, 1) }
+ {
+ let elementPos := <retrieveElementPos>
+ mstore(dst, <decodingFun>(elementPos, end))
+ dst := add(dst, 0x20)
+ src := add(src, <baseEncodedSize>)
+ }
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ if (_type.isDynamicallySized())
+ templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
+ else
+ templ("storeLength", "");
+ if (dynamicBase)
+ {
+ templ("staticBoundsCheck", "");
+ templ("retrieveElementPos", "add(offset, " + load + "(src))");
+ templ("baseEncodedSize", "0x20");
+ }
+ else
+ {
+ string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
+ templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }");
+ templ("retrieveElementPos", "src");
+ templ("baseEncodedSize", baseEncodedSize);
+ }
+ templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
+{
+ // This does not work with arrays of complex types - the array access
+ // is not yet implemented in Solidity.
+ solAssert(_type.dataStoredIn(DataLocation::CallData), "");
+ if (!_type.isDynamicallySized())
+ solAssert(_type.length() < u256("0xffffffffffffffff"), "");
+ solAssert(!_type.baseType()->isDynamicallyEncoded(), "");
+ solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier();
+ return createFunction(functionName, [&]() {
+ string templ;
+ if (_type.isDynamicallySized())
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos, length {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ length := calldataload(offset)
+ if gt(length, 0xffffffffffffffff) { revert(0, 0) }
+ arrayPos := add(offset, 0x20)
+ if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
+ }
+ )";
+ else
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos {
+ arrayPos := offset
+ if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
+ }
+ )";
+ Whiskers w{templ};
+ w("functionName", functionName);
+ w("readableTypeName", _type.toString(true));
+ w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
+ w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length()));
+ return w.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(
+ R"(
+ function <functionName>(offset, end) -> array {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ let length := <load>(offset)
+ array := <allocate>(<allocationSize>(length))
+ mstore(array, length)
+ let src := add(offset, 0x20)
+ let dst := add(array, 0x20)
+ if gt(add(src, length), end) { revert(0, 0) }
+ <copyToMemFun>(src, dst, length)
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ templ("copyToMemFun", copyToMemoryFunction(!_fromMemory));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
+{
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ // <readableTypeName>
+ function <functionName>(headStart, end) -> value {
+ if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
+ value := <allocate>(<memorySize>)
+ <#members>
+ {
+ // <memberName>
+ <decode>
+ }
+ </members>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("allocate", allocationFunction());
+ solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
+ templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
+ size_t headPos = 0;
+ vector<map<string, string>> members;
+ for (auto const& member: _type.members(nullptr))
+ {
+ solAssert(member.type, "");
+ solAssert(member.type->canLiveOutsideStorage(), "");
+ auto decodingType = member.type->decodingType();
+ solAssert(decodingType, "");
+ bool dynamic = decodingType->isDynamicallyEncoded();
+ Whiskers memberTempl(
+ dynamic ?
+ R"(
+ let offset := <load>(add(headStart, <pos>))
+ if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )" :
+ R"(
+ let offset := <pos>
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )"
+ );
+ memberTempl("load", _fromMemory ? "mload" : "calldataload");
+ memberTempl("pos", to_string(headPos));
+ memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
+ memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false));
+
+ members.push_back({});
+ members.back()["decode"] = memberTempl.render();
+ members.back()["memberName"] = member.name;
+ headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
+ }
+ templ("members", members);
+ templ("minimumSize", toCompactHexWithPrefix(headPos));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ solAssert(_type.kind() == FunctionType::Kind::External, "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "") +
+ (_forUseOnStack ? "_onStack" : "");
+
+ return createFunction(functionName, [&]() {
+ if (_forUseOnStack)
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> addr, function_selector {
+ addr, function_selector := <splitExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("splitExtFun", splitExternalFunctionIdFunction())
+ .render();
+ }
+ else
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> fun {
+ fun := <cleanExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
+ .render();
+ }
+ });
+}
+
string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
@@ -1098,6 +1493,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
});
}
+string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ string functionName = "array_allocation_size_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers w(R"(
+ function <functionName>(length) -> size {
+ // Make sure we can allocate memory without overflow
+ if gt(length, 0xffffffffffffffff) { revert(0, 0) }
+ size := <allocationSize>
+ <addLengthSlot>
+ }
+ )");
+ w("functionName", functionName);
+ if (_type.isByteArray())
+ // Round up
+ w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
+ else
+ w("allocationSize", "mul(length, 0x20)");
+ if (_type.isDynamicallySized())
+ w("addLengthSlot", "size := add(size, 0x20)");
+ else
+ w("addLengthSlot", "");
+ return w.render();
+ });
+}
+
string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
@@ -1189,6 +1611,25 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
+string ABIFunctions::allocationFunction()
+{
+ string functionName = "allocateMemory";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(size) -> memPtr {
+ memPtr := mload(<freeMemoryPointer>)
+ let newFreePtr := add(memPtr, size)
+ // protect against overflow
+ if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
+ mstore(<freeMemoryPointer>, newFreePtr)
+ }
+ )")
+ ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
+ ("functionName", functionName)
+ .render();
+ });
+}
+
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
{
if (!m_requestedFunctions.count(_name))
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
index e61f68bc..2b582e84 100644
--- a/libsolidity/codegen/ABIFunctions.h
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -66,6 +66,16 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// @returns name of an assembly function to ABI-decode values of @a _types
+ /// into memory. If @a _fromMemory is true, decodes from memory instead of
+ /// from calldata.
+ /// Can allocate memory.
+ /// Inputs: <source_offset> <source_end> (layout reversed on stack)
+ /// Outputs: <value0> <value1> ... <valuen>
+ /// The values represent stack slots. If a type occupies more or less than one
+ /// stack slot, it takes exactly that number of values.
+ std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
+
/// @returns concatenation of all generated functions.
std::string requestedFunctions();
@@ -87,6 +97,10 @@ private:
/// for use in the ABI.
std::string combineExternalFunctionIdFunction();
+ /// @returns a function that splits the address and selector from a single value
+ /// for use in the ABI.
+ std::string splitExternalFunctionIdFunction();
+
/// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage
@@ -146,6 +160,31 @@ private:
bool _fromStack
);
+ /// @returns the name of the ABI decoding function for the given type
+ /// and queues the generation of the function to the requested functions.
+ /// The caller has to ensure that no out of bounds access (at least to the static
+ /// part) can happen inside this function.
+ /// @param _fromMemory if decoding from memory instead of from calldata
+ /// @param _forUseOnStack if the decoded value is stored on stack or in memory.
+ std::string abiDecodingFunction(
+ Type const& _Type,
+ bool _fromMemory,
+ bool _forUseOnStack
+ );
+
+ /// Part of @a abiDecodingFunction for value types.
+ std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for "regular" array types.
+ std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for calldata array types.
+ std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
+ /// Part of @a abiDecodingFunction for byte array types.
+ std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for struct types.
+ std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for array types.
+ std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
+
/// @returns a function that copies raw bytes of dynamic length from calldata
/// or memory to memory.
/// Pads with zeros and might write more than exactly length.
@@ -158,6 +197,10 @@ private:
std::string roundUpFunction();
std::string arrayLengthFunction(ArrayType const& _type);
+ /// @returns the name of a function that computes the number of bytes required
+ /// to store an array in memory given its length (internally encoded, not ABI encoded).
+ /// The function reverts for too large lengthes.
+ std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array
/// which is stored in that slot / memory area.
@@ -166,6 +209,12 @@ private:
/// Only works for memory arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type);
+ /// @returns the name of a function that allocates memory.
+ /// Modifies the "free memory pointer"
+ /// Arguments: size
+ /// Return value: pointer
+ std::string allocationFunction();
+
/// Helper function that uses @a _creator to create a function and add it to
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
/// cases.
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index ce9c3b7f..7a88475a 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly(
ErrorList errors;
ErrorReporter errorReporter(errors);
auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--");
- auto parserResult = assembly::Parser(errorReporter).parse(scanner);
+ auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner);
#ifdef SOL_OUTPUT_ASM
cout << assembly::AsmPrinter()(*parserResult) << endl;
#endif
assembly::AsmAnalysisInfo analysisInfo;
bool analyzerResult = false;
if (parserResult)
- analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult);
+ analyzerResult = assembly::AsmAnalyzer(
+ analysisInfo,
+ errorReporter,
+ assembly::AsmFlavour::Strict,
+ identifierAccess.resolve
+ ).analyze(*parserResult);
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
{
string message =
@@ -347,6 +352,9 @@ void CompilerContext::appendInlineAssembly(
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system);
+
+ // Reset the source location to the one of the node (instead of the CODEGEN source location)
+ updateSourceLocation();
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 7743fd3f..0e8b639c 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -187,8 +187,8 @@ public:
CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; }
CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; }
- /// Appends inline assembly. @a _replacements are string-matching replacements that are performed
- /// prior to parsing the inline assembly.
+ /// Appends inline assembly (strict mode).
+ /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly.
/// @param _localVariables assigns stack positions to variables with the last one being the stack top
/// @param _system if true, this is a "system-level" assembly where all functions use named labels.
void appendInlineAssembly(
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 053bed6a..533aca5c 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2(
m_context << ret.tag();
}
+void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
+{
+ // stack: <source_offset>
+ auto ret = m_context.pushNewTag();
+ m_context << Instruction::SWAP1;
+ if (_fromMemory)
+ // TODO pass correct size for the memory case
+ m_context << (u256(1) << 63);
+ else
+ m_context << Instruction::CALLDATASIZE;
+ m_context << Instruction::SWAP1;
+ string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
+ m_context.appendJumpTo(m_context.namedTag(decoderName));
+ m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
+ m_context << ret.tag();
+}
+
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
auto repeat = m_context.newTag();
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index ad3989ad..3cde281b 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -146,6 +146,13 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,
+ /// the data is taken from memory instead of from calldata.
+ /// Can allocate memory.
+ /// Stack pre: <source_offset>
+ /// Stack post: <value0> <value1> ... <valuen>
+ void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false);
+
/// Zero-initialises (the data part of) an already allocated memory array.
/// Length has to be nonzero!
/// Stack pre: <length> <memptr>
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 74565ae4..a81ba518 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -322,6 +322,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
{
// We do not check the calldata size, everything is zero-padded
+ if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
+ {
+ // Use the new JULIA-based decoding function
+ auto stackHeightBefore = m_context.stackHeight();
+ CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory);
+ solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, "");
+ return;
+ }
+
//@todo this does not yet support nested dynamic arrays
// Retain the offset pointer as base_offset, the point from which the data offsets are computed.
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index 1050621e..a64024b3 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -55,7 +55,7 @@ void SMTChecker::analyze(SourceUnit const& _source)
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
{
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
- assignment(_varDecl, *_varDecl.value());
+ assignment(_varDecl, *_varDecl.value(), _varDecl.location());
}
bool SMTChecker::visit(FunctionDefinition const& _function)
@@ -71,6 +71,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
m_interface->reset();
m_currentSequenceCounter.clear();
m_nextFreeSequenceCounter.clear();
+ m_pathConditions.clear();
m_conditionalExecutionHappened = false;
initializeLocalVariables(_function);
return true;
@@ -90,15 +91,16 @@ bool SMTChecker::visit(IfStatement const& _node)
checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE.");
- visitBranch(_node.trueStatement(), expr(_node.condition()));
+ auto countersEndFalse = m_currentSequenceCounter;
+ auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition()));
vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
if (_node.falseStatement())
{
- visitBranch(*_node.falseStatement(), !expr(_node.condition()));
+ countersEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition()));
touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement());
}
- resetVariables(touchedVariables);
+ mergeVariables(touchedVariables, expr(_node.condition()), countersEndTrue, countersEndFalse);
return false;
}
@@ -178,8 +180,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
else if (knownVariable(*_varDecl.declarations()[0]))
{
if (_varDecl.initialValue())
- // TODO more checks?
- assignment(*_varDecl.declarations()[0], *_varDecl.initialValue());
+ assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location());
}
else
m_errorReporter.warning(
@@ -208,7 +209,10 @@ void SMTChecker::endVisit(Assignment const& _assignment)
{
Declaration const* decl = identifier->annotation().referencedDeclaration;
if (knownVariable(*decl))
- assignment(*decl, _assignment.rightHandSide());
+ {
+ assignment(*decl, _assignment.rightHandSide(), _assignment.location());
+ defineExpr(_assignment, expr(_assignment.rightHandSide()));
+ }
else
m_errorReporter.warning(
_assignment.location(),
@@ -230,7 +234,81 @@ void SMTChecker::endVisit(TupleExpression const& _tuple)
"Assertion checker does not yet implement tules and inline arrays."
);
else
- m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0]));
+ defineExpr(_tuple, expr(*_tuple.components()[0]));
+}
+
+void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location)
+{
+ checkCondition(
+ _value < minValue(_type),
+ _location,
+ "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")",
+ "value",
+ &_value
+ );
+ checkCondition(
+ _value > maxValue(_type),
+ _location,
+ "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")",
+ "value",
+ &_value
+ );
+}
+
+void SMTChecker::endVisit(UnaryOperation const& _op)
+{
+ switch (_op.getOperator())
+ {
+ case Token::Not: // !
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Bool, "");
+ defineExpr(_op, !expr(_op.subExpression()));
+ break;
+ }
+ case Token::Inc: // ++ (pre- or postfix)
+ case Token::Dec: // -- (pre- or postfix)
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Integer, "");
+ solAssert(_op.subExpression().annotation().lValueRequested, "");
+ if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
+ {
+ Declaration const* decl = identifier->annotation().referencedDeclaration;
+ if (knownVariable(*decl))
+ {
+ auto innerValue = currentValue(*decl);
+ auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
+ assignment(*decl, newValue, _op.location());
+ defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such assignments."
+ );
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such increments / decrements."
+ );
+ break;
+ }
+ case Token::Add: // +
+ defineExpr(_op, expr(_op.subExpression()));
+ break;
+ case Token::Sub: // -
+ {
+ defineExpr(_op, 0 - expr(_op.subExpression()));
+ if (auto intType = dynamic_cast<IntegerType const*>(_op.annotation().type.get()))
+ checkUnderOverflow(expr(_op), *intType, _op.location());
+ break;
+ }
+ default:
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator."
+ );
+ }
}
void SMTChecker::endVisit(BinaryOperation const& _op)
@@ -268,16 +346,14 @@ void SMTChecker::endVisit(FunctionCall const& _funCall)
solAssert(args.size() == 1, "");
solAssert(args[0]->annotation().type->category() == Type::Category::Bool, "");
checkCondition(!(expr(*args[0])), _funCall.location(), "Assertion violation");
- m_interface->addAssertion(expr(*args[0]));
+ addPathImpliedExpression(expr(*args[0]));
}
else if (funType.kind() == FunctionType::Kind::Require)
{
solAssert(args.size() == 1, "");
solAssert(args[0]->annotation().type->category() == Type::Category::Bool, "");
- m_interface->addAssertion(expr(*args[0]));
- checkCondition(!(expr(*args[0])), _funCall.location(), "Unreachable code");
- // TODO is there something meaningful we can check here?
- // We can check whether the condition is always fulfilled or never fulfilled.
+ checkBooleanNotConstant(*args[0], "Condition is always $VALUE.");
+ addPathImpliedExpression(expr(*args[0]));
}
}
@@ -290,7 +366,7 @@ void SMTChecker::endVisit(Identifier const& _identifier)
// Will be translated as part of the node that requested the lvalue.
}
else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
- m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
+ defineExpr(_identifier, currentValue(*decl));
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{
if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require)
@@ -306,10 +382,10 @@ void SMTChecker::endVisit(Literal const& _literal)
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type))
solAssert(!rational->isFractional(), "");
- m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal)));
+ defineExpr(_literal, smt::Expression(type.literalValue(&_literal)));
}
else if (type.category() == Type::Category::Bool)
- m_interface->addAssertion(expr(_literal) == smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
+ defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
else
m_errorReporter.warning(
_literal.location(),
@@ -326,36 +402,30 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
case Token::Add:
case Token::Sub:
case Token::Mul:
+ case Token::Div:
{
solAssert(_op.annotation().commonType, "");
solAssert(_op.annotation().commonType->category() == Type::Category::Integer, "");
+ auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
smt::Expression left(expr(_op.leftExpression()));
smt::Expression right(expr(_op.rightExpression()));
Token::Value op = _op.getOperator();
smt::Expression value(
op == Token::Add ? left + right :
op == Token::Sub ? left - right :
+ op == Token::Div ? division(left, right, intType) :
/*op == Token::Mul*/ left * right
);
- // Overflow check
- auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
- checkCondition(
- value < minValue(intType),
- _op.location(),
- "Underflow (resulting value less than " + formatNumber(intType.minValue()) + ")",
- "value",
- &value
- );
- checkCondition(
- value > maxValue(intType),
- _op.location(),
- "Overflow (resulting value larger than " + formatNumber(intType.maxValue()) + ")",
- "value",
- &value
- );
+ if (_op.getOperator() == Token::Div)
+ {
+ checkCondition(right == 0, _op.location(), "Division by zero", "value", &right);
+ m_interface->addAssertion(right != 0);
+ }
+
+ checkUnderOverflow(value, intType, _op.location());
- m_interface->addAssertion(expr(_op) == value);
+ defineExpr(_op, value);
break;
}
default:
@@ -383,7 +453,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op)
/*op == Token::GreaterThanOrEqual*/ (left >= right)
);
// TODO: check that other values for op are not possible.
- m_interface->addAssertion(expr(_op) == value);
+ defineExpr(_op, value);
}
else
m_errorReporter.warning(
@@ -400,9 +470,9 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
{
// @TODO check that both of them are not constant
if (_op.getOperator() == Token::And)
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
else
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression()));
}
else
m_errorReporter.warning(
@@ -411,30 +481,50 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
);
}
-void SMTChecker::assignment(Declaration const& _variable, Expression const& _value)
+smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type)
{
- // TODO more checks?
- // TODO add restrictions about type (might be assignment from smaller type)
- m_interface->addAssertion(newValue(_variable) == expr(_value));
+ // Signed division in SMTLIB2 rounds differently for negative division.
+ if (_type.isSigned())
+ return (smt::Expression::ite(
+ _left >= 0,
+ smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
+ smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
+ ));
+ else
+ return _left / _right;
+}
+
+void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location)
+{
+ assignment(_variable, expr(_value), _location);
}
-void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition)
+void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location)
{
- visitBranch(_statement, &_condition);
+ TypePointer type = _variable.type();
+ if (auto const* intType = dynamic_cast<IntegerType const*>(type.get()))
+ checkUnderOverflow(_value, *intType, _location);
+ m_interface->addAssertion(newValue(_variable) == _value);
}
-void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition)
+SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition)
+{
+ return visitBranch(_statement, &_condition);
+}
+
+SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition)
{
VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
- m_interface->push();
if (_condition)
- m_interface->addAssertion(*_condition);
+ pushPathCondition(*_condition);
_statement.accept(*this);
- m_interface->pop();
+ if (_condition)
+ popPathCondition();
m_conditionalExecutionHappened = true;
- m_currentSequenceCounter = sequenceCountersStart;
+ std::swap(sequenceCountersStart, m_currentSequenceCounter);
+ return sequenceCountersStart;
}
void SMTChecker::checkCondition(
@@ -446,7 +536,7 @@ void SMTChecker::checkCondition(
)
{
m_interface->push();
- m_interface->addAssertion(_condition);
+ addPathConjoinedExpression(_condition);
vector<smt::Expression> expressionsToEvaluate;
vector<string> expressionNames;
@@ -472,7 +562,7 @@ void SMTChecker::checkCondition(
}
smt::CheckResult result;
vector<string> values;
- tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate);
+ tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate);
string conditionalComment;
if (m_conditionalExecutionHappened)
@@ -518,13 +608,13 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co
return;
m_interface->push();
- m_interface->addAssertion(expr(_condition));
- auto positiveResult = checkSatisifable();
+ addPathConjoinedExpression(expr(_condition));
+ auto positiveResult = checkSatisfiable();
m_interface->pop();
m_interface->push();
- m_interface->addAssertion(!expr(_condition));
- auto negatedResult = checkSatisifable();
+ addPathConjoinedExpression(!expr(_condition));
+ auto negatedResult = checkSatisfiable();
m_interface->pop();
if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
@@ -554,7 +644,7 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co
}
pair<smt::CheckResult, vector<string>>
-SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
+SMTChecker::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
{
smt::CheckResult result;
vector<string> values;
@@ -584,9 +674,9 @@ SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _exp
return make_pair(result, values);
}
-smt::CheckResult SMTChecker::checkSatisifable()
+smt::CheckResult SMTChecker::checkSatisfiable()
{
- return checkSatisifableAndGenerateModel({}).first;
+ return checkSatisfiableAndGenerateModel({}).first;
}
void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
@@ -614,6 +704,22 @@ void SMTChecker::resetVariables(vector<Declaration const*> _variables)
}
}
+void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse)
+{
+ set<Declaration const*> uniqueVars(_variables.begin(), _variables.end());
+ for (auto const* decl: uniqueVars)
+ {
+ int trueCounter = _countersEndTrue.at(decl);
+ int falseCounter = _countersEndFalse.at(decl);
+ solAssert(trueCounter != falseCounter, "");
+ m_interface->addAssertion(newValue(*decl) == smt::Expression::ite(
+ _condition,
+ valueAtSequence(*decl, trueCounter),
+ valueAtSequence(*decl, falseCounter))
+ );
+ }
+}
+
bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{
if (dynamic_cast<IntegerType const*>(_varDecl.type().get()))
@@ -696,6 +802,18 @@ smt::Expression SMTChecker::expr(Expression const& _e)
{
if (!m_expressions.count(&_e))
{
+ m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." );
+ createExpr(_e);
+ }
+ return m_expressions.at(&_e);
+}
+
+void SMTChecker::createExpr(Expression const& _e)
+{
+ if (m_expressions.count(&_e))
+ m_errorReporter.warning(_e.location(), "Internal error: Expression created twice in SMT solver." );
+ else
+ {
solAssert(_e.annotation().type, "");
switch (_e.annotation().type->category())
{
@@ -716,7 +834,12 @@ smt::Expression SMTChecker::expr(Expression const& _e)
solAssert(false, "Type not implemented.");
}
}
- return m_expressions.at(&_e);
+}
+
+void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value)
+{
+ createExpr(_e);
+ m_interface->addAssertion(expr(_e) == _value);
}
smt::Expression SMTChecker::var(Declaration const& _decl)
@@ -724,3 +847,31 @@ smt::Expression SMTChecker::var(Declaration const& _decl)
solAssert(m_variables.count(&_decl), "");
return m_variables.at(&_decl);
}
+
+void SMTChecker::popPathCondition()
+{
+ solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty.");
+ m_pathConditions.pop_back();
+}
+
+void SMTChecker::pushPathCondition(smt::Expression const& _e)
+{
+ m_pathConditions.push_back(currentPathConditions() && _e);
+}
+
+smt::Expression SMTChecker::currentPathConditions()
+{
+ if (m_pathConditions.size() == 0)
+ return smt::Expression(true);
+ return m_pathConditions.back();
+}
+
+void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e)
+{
+ m_interface->addAssertion(currentPathConditions() && _e);
+}
+
+void SMTChecker::addPathImpliedExpression(smt::Expression const& _e)
+{
+ m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e));
+}
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
index 8e07d74d..b57f0f96 100644
--- a/libsolidity/formal/SMTChecker.h
+++ b/libsolidity/formal/SMTChecker.h
@@ -26,6 +26,7 @@
#include <map>
#include <string>
+#include <vector>
namespace dev
{
@@ -57,6 +58,7 @@ private:
virtual void endVisit(ExpressionStatement const& _node) override;
virtual void endVisit(Assignment const& _node) override;
virtual void endVisit(TupleExpression const& _node) override;
+ virtual void endVisit(UnaryOperation const& _node) override;
virtual void endVisit(BinaryOperation const& _node) override;
virtual void endVisit(FunctionCall const& _node) override;
virtual void endVisit(Identifier const& _node) override;
@@ -66,12 +68,21 @@ private:
void compareOperation(BinaryOperation const& _op);
void booleanOperation(BinaryOperation const& _op);
- void assignment(Declaration const& _variable, Expression const& _value);
+ /// Division expression in the given type. Requires special treatment because
+ /// of rounding for signed division.
+ smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
- // Visits the branch given by the statement, pushes and pops the SMT checker.
- // @param _condition if present, asserts that this condition is true within the branch.
- void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr);
- void visitBranch(Statement const& _statement, smt::Expression _condition);
+ void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location);
+ void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location);
+
+ /// Maps a variable to an SSA index.
+ using VariableSequenceCounters = std::map<Declaration const*, int>;
+
+ /// Visits the branch given by the statement, pushes and pops the current path conditions.
+ /// @param _condition if present, asserts that this condition is true within the branch.
+ /// @returns the variable sequence counter after visiting the branch.
+ VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr);
+ VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression _condition);
/// Check that a condition can be satisfied.
void checkCondition(
@@ -88,14 +99,21 @@ private:
Expression const& _condition,
std::string const& _description
);
+ /// Checks that the value is in the range given by the type.
+ void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location);
+
std::pair<smt::CheckResult, std::vector<std::string>>
- checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
+ checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
- smt::CheckResult checkSatisifable();
+ smt::CheckResult checkSatisfiable();
void initializeLocalVariables(FunctionDefinition const& _function);
void resetVariables(std::vector<Declaration const*> _variables);
+ /// Given two different branches and the touched variables,
+ /// merge the touched variables into after-branch ite variables
+ /// using the branch condition as guard.
+ void mergeVariables(std::vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse);
/// Tries to create an uninitialized variable and returns true on success.
/// This fails if the type is not supported.
bool createVariable(VariableDeclaration const& _varDecl);
@@ -124,15 +142,27 @@ private:
static smt::Expression minValue(IntegerType const& _t);
static smt::Expression maxValue(IntegerType const& _t);
- using VariableSequenceCounters = std::map<Declaration const*, int>;
-
- /// Returns the expression corresponding to the AST node. Creates a new expression
- /// if it does not exist yet.
+ /// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e);
+ /// Creates the expression (value can be arbitrary)
+ void createExpr(Expression const& _e);
+ /// Creates the expression and sets its value.
+ void defineExpr(Expression const& _e, smt::Expression _value);
/// Returns the function declaration corresponding to the given variable.
/// The function takes one argument which is the "sequence number".
smt::Expression var(Declaration const& _decl);
+ /// Adds a new path condition
+ void pushPathCondition(smt::Expression const& _e);
+ /// Remove the last path condition
+ void popPathCondition();
+ /// Returns the conjunction of all path conditions or True if empty
+ smt::Expression currentPathConditions();
+ /// Conjoin the current path conditions with the given parameter and add to the solver
+ void addPathConjoinedExpression(smt::Expression const& _e);
+ /// Add to the solver: the given expression implied by the current path conditions
+ void addPathImpliedExpression(smt::Expression const& _e);
+
std::shared_ptr<smt::SolverInterface> m_interface;
std::shared_ptr<VariableUsage> m_variableUsage;
bool m_conditionalExecutionHappened = false;
@@ -140,6 +170,7 @@ private:
std::map<Declaration const*, int> m_nextFreeSequenceCounter;
std::map<Expression const*, smt::Expression> m_expressions;
std::map<Declaration const*, smt::Expression> m_variables;
+ std::vector<smt::Expression> m_pathConditions;
ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentFunction = nullptr;
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
index c9adf863..88487310 100644
--- a/libsolidity/formal/SolverInterface.h
+++ b/libsolidity/formal/SolverInterface.h
@@ -72,6 +72,11 @@ public:
}, _trueValue.sort);
}
+ static Expression implies(Expression _a, Expression _b)
+ {
+ return !std::move(_a) || std::move(_b);
+ }
+
friend Expression operator!(Expression _a)
{
return Expression("not", std::move(_a), Sort::Bool);
@@ -120,6 +125,10 @@ public:
{
return Expression("*", std::move(_a), std::move(_b), Sort::Int);
}
+ friend Expression operator/(Expression _a, Expression _b)
+ {
+ return Expression("/", std::move(_a), std::move(_b), Sort::Int);
+ }
Expression operator()(Expression _a) const
{
solAssert(
diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp
index e5c1aef4..769e6edb 100644
--- a/libsolidity/formal/Z3Interface.cpp
+++ b/libsolidity/formal/Z3Interface.cpp
@@ -127,7 +127,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
{">=", 2},
{"+", 2},
{"-", 2},
- {"*", 2}
+ {"*", 2},
+ {"/", 2}
};
string const& n = _expr.name;
if (m_functions.count(n))
@@ -173,6 +174,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return arguments[0] - arguments[1];
else if (n == "*")
return arguments[0] * arguments[1];
+ else if (n == "/")
+ return arguments[0] / arguments[1];
// Cannot reach here.
solAssert(false, "");
return arguments[0];
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index 2804ddfc..2d6e58de 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -54,14 +54,15 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
m_info.stackHeightInfo[&_label] = m_stackHeight;
+ warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
return true;
}
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instruction] = m_stackHeight;
@@ -140,22 +141,34 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour != AsmFlavour::IULIA, "");
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
if (!expectExpression(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;
+ auto const& info = instructionInfo(_instr.instruction);
+ solAssert(info.args == int(_instr.arguments.size()), "");
+ m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instr] = m_stackHeight;
+ warnOnInstructions(_instr.instruction, _instr.location);
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement)
+{
+ size_t initialStackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, _statement.expression);
+ if (m_flavour != AsmFlavour::Loose)
+ if (!expectDeposit(0, initialStackHeight, _statement.location))
+ success = false;
+ m_info.stackHeightInfo[&_statement] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
@@ -217,14 +230,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
{
expectValidType(var.type, var.location);
m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
}
int const stackHeight = m_stackHeight;
- m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
+ m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
bool success = (*this)(_funDef.body);
@@ -405,13 +418,13 @@ bool AsmAnalyzer::operator()(Block const& _block)
return success;
}
-bool AsmAnalyzer::expectExpression(Statement const& _statement)
+bool AsmAnalyzer::expectExpression(Expression const& _expr)
{
bool success = true;
int const initialHeight = m_stackHeight;
- if (!boost::apply_visitor(*this, _statement))
+ if (!boost::apply_visitor(*this, _expr))
success = false;
- if (!expectDeposit(1, initialHeight, locationOf(_statement)))
+ if (!expectDeposit(1, initialHeight, locationOf(_expr)))
success = false;
return success;
}
@@ -495,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
}
void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
{
- if (!m_julia)
+ if (m_flavour != AsmFlavour::IULIA)
return;
if (!builtinTypes.count(type))
@@ -522,11 +535,11 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio
"the Metropolis hard fork. Before that it acts as an invalid instruction."
);
- if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI)
+ if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST)
m_errorReporter.warning(
_location,
- "Jump instructions are low-level EVM features that can lead to "
+ "Jump instructions and labels are low-level EVM features that can lead to "
"incorrect stack access. Because of that they are discouraged. "
- "Please consider using \"switch\" or \"for\" statements instead."
+ "Please consider using \"switch\", \"if\" or \"for\" statements instead."
);
}
diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h
index e484b876..7a81dbf8 100644
--- a/libsolidity/inlineasm/AsmAnalysis.h
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -54,9 +54,9 @@ public:
explicit AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorReporter& _errorReporter,
- bool _julia = false,
+ AsmFlavour _flavour = AsmFlavour::Loose,
julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver()
- ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {}
+ ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {}
bool analyze(assembly::Block const& _block);
@@ -65,6 +65,7 @@ public:
bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::ExpressionStatement const&);
bool operator()(assembly::StackAssignment const&);
bool operator()(assembly::Assignment const& _assignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
@@ -77,7 +78,7 @@ public:
private:
/// Visits the statement and expects it to deposit one item onto the stack.
- bool expectExpression(Statement const& _statement);
+ bool expectExpression(Expression const& _expr);
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and has the same size
@@ -96,7 +97,7 @@ private:
std::set<Scope::Variable const*> m_activeVariables;
AsmAnalysisInfo& m_info;
ErrorReporter& m_errorReporter;
- bool m_julia = false;
+ AsmFlavour m_flavour = AsmFlavour::Loose;
};
}
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index a792a1b8..2982d5e0 100644
--- a/libsolidity/inlineasm/AsmData.h
+++ b/libsolidity/inlineasm/AsmData.h
@@ -58,23 +58,25 @@ struct StackAssignment { SourceLocation location; Identifier variableName; };
/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
/// a single stack slot and expects a single expression on the right hand returning
/// the same amount of items as the number of variables.
-struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; };
+struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; };
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
-struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
-struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
+struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector<Expression> arguments; };
+struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Expression> arguments; };
+/// Statement that contains only a single expression
+struct ExpressionStatement { SourceLocation location; Expression expression; };
/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted
-struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; };
+struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> value; };
/// Block that creates a scope (frees declared stack variables)
struct Block { SourceLocation location; std::vector<Statement> statements; };
/// Function definition ("function f(a, b) -> (d, e) { ... }")
-struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; };
+struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList parameters; TypedNameList returnVariables; Block body; };
/// Conditional execution without "else" part.
-struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; };
+struct If { SourceLocation location; std::shared_ptr<Expression> condition; Block body; };
/// Switch case or default case
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
/// Switch statement
-struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; };
-struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; };
+struct Switch { SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; };
+struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; };
struct LocationExtractor: boost::static_visitor<SourceLocation>
{
diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h
index d627b41a..3a9600fe 100644
--- a/libsolidity/inlineasm/AsmDataForward.h
+++ b/libsolidity/inlineasm/AsmDataForward.h
@@ -43,10 +43,22 @@ struct FunctionDefinition;
struct FunctionCall;
struct If;
struct Switch;
+struct Case;
struct ForLoop;
+struct ExpressionStatement;
struct Block;
-using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
+struct TypedName;
+
+using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
+using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
+
+enum class AsmFlavour
+{
+ Loose, // no types, EVM instructions as function, jumps and direct stack manipulations
+ Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations
+ IULIA // same as Strict mode with types
+};
}
}
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 8f171005..306b07e6 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -77,9 +77,7 @@ assembly::Statement Parser::parseStatement()
{
assembly::If _if = createWithLocation<assembly::If>();
m_scanner->next();
- _if.condition = make_shared<Statement>(parseExpression());
- if (_if.condition->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as conditions for if - try to append \"()\".");
+ _if.condition = make_shared<Expression>(parseExpression());
_if.body = parseBlock();
return _if;
}
@@ -87,9 +85,7 @@ assembly::Statement Parser::parseStatement()
{
assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
- _switch.expression = make_shared<Statement>(parseExpression());
- if (_switch.expression->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\".");
+ _switch.expression = make_shared<Expression>(parseExpression());
while (m_scanner->currentToken() == Token::Case)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)
@@ -107,14 +103,14 @@ assembly::Statement Parser::parseStatement()
return parseForLoop();
case Token::Assign:
{
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
break;
assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>();
advance();
expectToken(Token::Colon);
assignment.variableName.location = location();
assignment.variableName.name = currentLiteral();
- if (!m_julia && instructions().count(assignment.variableName.name))
+ if (instructions().count(assignment.variableName.name))
fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
@@ -127,18 +123,21 @@ assembly::Statement Parser::parseStatement()
// Simple instruction (might turn into functional),
// literal,
// identifier (might turn into label or functional assignment)
- Statement statement(parseElementaryOperation(false));
+ ElementaryOperation elementary(parseElementaryOperation());
switch (currentToken())
{
case Token::LParen:
- return parseCall(std::move(statement));
+ {
+ Expression expr = parseCall(std::move(elementary));
+ return ExpressionStatement{locationOf(expr), expr};
+ }
case Token::Comma:
{
// if a comma follows, a multiple assignment is assumed
- if (statement.type() != typeid(assembly::Identifier))
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \",\" (multiple assignment).");
- assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
Assignment assignment = createWithLocation<Assignment>(identifier.location);
assignment.variableNames.emplace_back(identifier);
@@ -146,43 +145,43 @@ assembly::Statement Parser::parseStatement()
do
{
expectToken(Token::Comma);
- statement = parseElementaryOperation(false);
- if (statement.type() != typeid(assembly::Identifier))
+ elementary = parseElementaryOperation();
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Variable name expected in multiple assignemnt.");
- assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement));
+ assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary));
}
while (currentToken() == Token::Comma);
expectToken(Token::Colon);
expectToken(Token::Assign);
- assignment.value.reset(new Statement(parseExpression()));
+ assignment.value.reset(new Expression(parseExpression()));
assignment.location.end = locationOf(*assignment.value).end;
return assignment;
}
case Token::Colon:
{
- if (statement.type() != typeid(assembly::Identifier))
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \":\".");
- assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
advance();
// identifier:=: should be parsed as identifier: =: (i.e. a label),
// while identifier:= (being followed by a non-colon) as identifier := (assignment).
if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
{
assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
- if (!m_julia && instructions().count(identifier.name))
+ if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names.");
advance();
assignment.variableNames.emplace_back(identifier);
- assignment.value.reset(new Statement(parseExpression()));
+ assignment.value.reset(new Expression(parseExpression()));
assignment.location.end = locationOf(*assignment.value).end;
return assignment;
}
else
{
// label
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
fatalParserError("Labels are not supported.");
Label label = createWithLocation<Label>(identifier.location);
label.name = identifier.name;
@@ -190,11 +189,25 @@ assembly::Statement Parser::parseStatement()
}
}
default:
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
fatalParserError("Call or assignment expected.");
break;
}
- return statement;
+ if (elementary.type() == typeid(assembly::Identifier))
+ {
+ Expression expr = boost::get<assembly::Identifier>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else if (elementary.type() == typeid(assembly::Literal))
+ {
+ Expression expr = boost::get<assembly::Literal>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else
+ {
+ solAssert(elementary.type() == typeid(assembly::Instruction), "Invalid elementary operation.");
+ return boost::get<assembly::Instruction>(elementary);
+ }
}
assembly::Case Parser::parseCase()
@@ -206,10 +219,10 @@ assembly::Case Parser::parseCase()
else if (m_scanner->currentToken() == Token::Case)
{
m_scanner->next();
- assembly::Statement statement = parseElementaryOperation();
- if (statement.type() != typeid(assembly::Literal))
+ ElementaryOperation literal = parseElementaryOperation();
+ if (literal.type() != typeid(assembly::Literal))
fatalParserError("Literal expected.");
- _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement)));
+ _case.value = make_shared<Literal>(boost::get<assembly::Literal>(std::move(literal)));
}
else
fatalParserError("Case or default case expected.");
@@ -224,22 +237,39 @@ assembly::ForLoop Parser::parseForLoop()
ForLoop forLoop = createWithLocation<ForLoop>();
expectToken(Token::For);
forLoop.pre = parseBlock();
- forLoop.condition = make_shared<Statement>(parseExpression());
- if (forLoop.condition->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as conditions for the for statement.");
+ forLoop.condition = make_shared<Expression>(parseExpression());
forLoop.post = parseBlock();
forLoop.body = parseBlock();
forLoop.location.end = forLoop.body.location.end;
return forLoop;
}
-assembly::Statement Parser::parseExpression()
+assembly::Expression Parser::parseExpression()
{
RecursionGuard recursionGuard(*this);
- Statement operation = parseElementaryOperation(true);
+ // In strict mode, this might parse a plain Instruction, but
+ // it will be converted to a FunctionalInstruction inside
+ // parseCall below.
+ ElementaryOperation operation = parseElementaryOperation();
if (operation.type() == typeid(Instruction))
{
Instruction const& instr = boost::get<Instruction>(operation);
+ // Disallow instructions returning multiple values (and DUP/SWAP) as expression.
+ if (
+ instructionInfo(instr.instruction).ret != 1 ||
+ isDupInstruction(instr.instruction) ||
+ isSwapInstruction(instr.instruction)
+ )
+ fatalParserError(
+ "Instruction \"" +
+ instructionNames().at(instr.instruction) +
+ "\" not allowed in this context."
+ );
+ if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen)
+ fatalParserError(
+ "Non-functional instructions are not allowed in this context."
+ );
+ // Enforce functional notation for instructions requiring multiple arguments.
int args = instructionInfo(instr.instruction).args;
if (args > 0 && currentToken() != Token::LParen)
fatalParserError(string(
@@ -252,8 +282,20 @@ assembly::Statement Parser::parseExpression()
}
if (currentToken() == Token::LParen)
return parseCall(std::move(operation));
+ else if (operation.type() == typeid(Instruction))
+ {
+ // Instructions not taking arguments are allowed as expressions.
+ solAssert(m_flavour == AsmFlavour::Loose, "");
+ Instruction& instr = boost::get<Instruction>(operation);
+ return FunctionalInstruction{std::move(instr.location), instr.instruction, {}};
+ }
+ else if (operation.type() == typeid(assembly::Identifier))
+ return boost::get<assembly::Identifier>(operation);
else
- return operation;
+ {
+ solAssert(operation.type() == typeid(assembly::Literal), "");
+ return boost::get<assembly::Literal>(operation);
+ }
}
std::map<string, dev::solidity::Instruction> const& Parser::instructions()
@@ -296,10 +338,10 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames()
return s_instructionNames;
}
-assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+Parser::ElementaryOperation Parser::parseElementaryOperation()
{
RecursionGuard recursionGuard(*this);
- Statement ret;
+ ElementaryOperation ret;
switch (currentToken())
{
case Token::Identifier:
@@ -317,15 +359,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else
literal = currentLiteral();
// first search the set of instructions.
- if (!m_julia && instructions().count(literal))
+ if (m_flavour != AsmFlavour::IULIA && instructions().count(literal))
{
dev::solidity::Instruction const& instr = instructions().at(literal);
- if (_onlySinglePusher)
- {
- InstructionInfo info = dev::solidity::instructionInfo(instr);
- if (info.ret != 1)
- fatalParserError("Instruction \"" + literal + "\" not allowed in this context.");
- }
ret = Instruction{location(), instr};
}
else
@@ -364,7 +400,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
""
};
advance();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
expectToken(Token::Colon);
literal.location.end = endPosition();
@@ -377,7 +413,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
}
default:
fatalParserError(
- m_julia ?
+ m_flavour == AsmFlavour::IULIA ?
"Literal or identifier expected." :
"Literal, identifier or instruction expected."
);
@@ -402,7 +438,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
expectToken(Token::Colon);
expectToken(Token::Assign);
- varDecl.value.reset(new Statement(parseExpression()));
+ varDecl.value.reset(new Expression(parseExpression()));
varDecl.location.end = locationOf(*varDecl.value).end;
}
else
@@ -419,7 +455,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::LParen);
while (currentToken() != Token::RParen)
{
- funDef.arguments.emplace_back(parseTypedName());
+ funDef.parameters.emplace_back(parseTypedName());
if (currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
@@ -431,7 +467,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::GreaterThan);
while (true)
{
- funDef.returns.emplace_back(parseTypedName());
+ funDef.returnVariables.emplace_back(parseTypedName());
if (currentToken() == Token::LBrace)
break;
expectToken(Token::Comma);
@@ -442,16 +478,17 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
return funDef;
}
-assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
+assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
{
RecursionGuard recursionGuard(*this);
- if (_instruction.type() == typeid(Instruction))
+ if (_initialOp.type() == typeid(Instruction))
{
- solAssert(!m_julia, "Instructions are invalid in JULIA");
+ solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA");
+ Instruction& instruction = boost::get<Instruction>(_initialOp);
FunctionalInstruction ret;
- ret.instruction = std::move(boost::get<Instruction>(_instruction));
- ret.location = ret.instruction.location;
- solidity::Instruction instr = ret.instruction.instruction;
+ ret.instruction = instruction.instruction;
+ ret.location = std::move(instruction.location);
+ solidity::Instruction instr = ret.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::isDupInstruction(instr))
fatalParserError("DUPi instructions not allowed for functional notation");
@@ -498,10 +535,10 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
expectToken(Token::RParen);
return ret;
}
- else if (_instruction.type() == typeid(Identifier))
+ else if (_initialOp.type() == typeid(Identifier))
{
FunctionCall ret;
- ret.functionName = std::move(boost::get<Identifier>(_instruction));
+ ret.functionName = std::move(boost::get<Identifier>(_initialOp));
ret.location = ret.functionName.location;
expectToken(Token::LParen);
while (currentToken() != Token::RParen)
@@ -517,7 +554,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
}
else
fatalParserError(
- m_julia ?
+ m_flavour == AsmFlavour::IULIA ?
"Function name expected." :
"Assembly instruction or function name required in front of \"(\")"
);
@@ -530,7 +567,7 @@ TypedName Parser::parseTypedName()
RecursionGuard recursionGuard(*this);
TypedName typedName = createWithLocation<TypedName>();
typedName.name = expectAsmIdentifier();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
expectToken(Token::Colon);
typedName.location.end = endPosition();
@@ -542,12 +579,18 @@ TypedName Parser::parseTypedName()
string Parser::expectAsmIdentifier()
{
string name = currentLiteral();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
- if (currentToken() == Token::Bool)
+ switch (currentToken())
{
+ case Token::Return:
+ case Token::Byte:
+ case Token::Address:
+ case Token::Bool:
advance();
return name;
+ default:
+ break;
}
}
else if (instructions().count(name))
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index e46d1732..015aeef3 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -37,13 +37,16 @@ namespace assembly
class Parser: public ParserBase
{
public:
- explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {}
+ explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose):
+ ParserBase(_errorReporter), m_flavour(_flavour) {}
/// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error.
std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner);
protected:
+ using ElementaryOperation = boost::variant<assembly::Instruction, assembly::Literal, assembly::Identifier>;
+
/// Creates an inline assembly node with the given source location.
template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const
{
@@ -65,20 +68,23 @@ protected:
Case parseCase();
ForLoop parseForLoop();
/// Parses a functional expression that has to push exactly one stack element
- Statement parseExpression();
+ assembly::Expression parseExpression();
static std::map<std::string, dev::solidity::Instruction> const& instructions();
static std::map<dev::solidity::Instruction, std::string> const& instructionNames();
- Statement parseElementaryOperation(bool _onlySinglePusher = false);
+ /// Parses an elementary operation, i.e. a literal, identifier or instruction.
+ /// This will parse instructions even in strict mode as part of the full parser
+ /// for FunctionalInstruction.
+ ElementaryOperation parseElementaryOperation();
VariableDeclaration parseVariableDeclaration();
FunctionDefinition parseFunctionDefinition();
- Statement parseCall(Statement&& _instruction);
+ assembly::Expression parseCall(ElementaryOperation&& _initialOp);
TypedName parseTypedName();
std::string expectAsmIdentifier();
static bool isValidNumberLiteral(std::string const& _literal);
private:
- bool m_julia = false;
+ AsmFlavour m_flavour = AsmFlavour::Loose;
};
}
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index 0f183244..bacd7a94 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -94,7 +94,7 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
{
solAssert(!m_julia, "");
return
- (*this)(_functionalInstruction.instruction) +
+ boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) +
"(" +
boost::algorithm::join(
_functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
@@ -102,6 +102,11 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
")";
}
+string AsmPrinter::operator()(ExpressionStatement const& _statement)
+{
+ return boost::apply_visitor(*this, _statement.expression);
+}
+
string AsmPrinter::operator()(assembly::Label const& _label)
{
solAssert(!m_julia, "");
@@ -144,17 +149,17 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin
{
string out = "function " + _functionDefinition.name + "(";
out += boost::algorithm::join(
- _functionDefinition.arguments | boost::adaptors::transformed(
+ _functionDefinition.parameters | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
);
out += ")";
- if (!_functionDefinition.returns.empty())
+ if (!_functionDefinition.returnVariables.empty())
{
out += " -> ";
out += boost::algorithm::join(
- _functionDefinition.returns | boost::adaptors::transformed(
+ _functionDefinition.returnVariables | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index eadf81d9..5bd87aba 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -42,6 +42,7 @@ public:
std::string operator()(assembly::Literal const& _literal);
std::string operator()(assembly::Identifier const& _identifier);
std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ std::string operator()(assembly::ExpressionStatement const& _expr);
std::string operator()(assembly::Label const& _label);
std::string operator()(assembly::StackAssignment const& _assignment);
std::string operator()(assembly::Assignment const& _assignment);
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
index 77ae9102..86f3809c 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.cpp
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -45,6 +45,11 @@ ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter):
m_currentScope = &scope(nullptr);
}
+bool ScopeFiller::operator()(ExpressionStatement const& _expr)
+{
+ return boost::apply_visitor(*this, _expr.expression);
+}
+
bool ScopeFiller::operator()(Label const& _item)
{
if (!m_currentScope->registerLabel(_item.name))
@@ -71,10 +76,10 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
vector<Scope::JuliaType> arguments;
- for (auto const& _argument: _funDef.arguments)
+ for (auto const& _argument: _funDef.parameters)
arguments.push_back(_argument.type);
vector<Scope::JuliaType> returns;
- for (auto const& _return: _funDef.returns)
+ for (auto const& _return: _funDef.returnVariables)
returns.push_back(_return.type);
if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
{
@@ -91,7 +96,7 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
varScope.superScope = m_currentScope;
m_currentScope = &varScope;
varScope.functionScope = true;
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
if (!registerVariable(var, _funDef.location, varScope))
success = false;
diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h
index ed28abbf..bb023f61 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.h
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -53,6 +53,7 @@ public:
bool operator()(assembly::Literal const&) { return true; }
bool operator()(assembly::Identifier const&) { return true; }
bool operator()(assembly::FunctionalInstruction const&) { return true; }
+ bool operator()(assembly::ExpressionStatement const& _expr);
bool operator()(assembly::Label const& _label);
bool operator()(assembly::StackAssignment const&) { return true; }
bool operator()(assembly::Assignment const&) { return true; }
diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp
index 504ad92c..1b4bd270 100644
--- a/libsolidity/interface/AssemblyStack.cpp
+++ b/libsolidity/interface/AssemblyStack.cpp
@@ -38,6 +38,25 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
+namespace
+{
+assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language)
+{
+ switch (_language)
+ {
+ case AssemblyStack::Language::Assembly:
+ return assembly::AsmFlavour::Loose;
+ case AssemblyStack::Language::StrictAssembly:
+ return assembly::AsmFlavour::Strict;
+ case AssemblyStack::Language::JULIA:
+ return assembly::AsmFlavour::IULIA;
+ }
+ solAssert(false, "");
+ return assembly::AsmFlavour::IULIA;
+}
+
+}
+
Scanner const& AssemblyStack::scanner() const
{
@@ -50,7 +69,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string
m_errors.clear();
m_analysisSuccessful = false;
m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName);
- m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner);
+ m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner);
if (!m_errorReporter.errors().empty())
return false;
solAssert(m_parserResult, "");
@@ -72,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann
bool AssemblyStack::analyzeParsed()
{
m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
- assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA);
+ assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language));
m_analysisSuccessful = analyzer.analyze(*m_parserResult);
return m_analysisSuccessful;
}
diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h
index 2ae596ed..6ae7e8d1 100644
--- a/libsolidity/interface/AssemblyStack.h
+++ b/libsolidity/interface/AssemblyStack.h
@@ -51,7 +51,7 @@ struct MachineAssemblyObject
class AssemblyStack
{
public:
- enum class Language { JULIA, Assembly };
+ enum class Language { JULIA, Assembly, StrictAssembly };
enum class Machine { EVM, EVM15, eWasm };
explicit AssemblyStack(Language _language = Language::Assembly):
diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h
index 09301b10..7c66d572 100644
--- a/libsolidity/interface/Exceptions.h
+++ b/libsolidity/interface/Exceptions.h
@@ -109,6 +109,18 @@ public:
infos.push_back(std::make_pair(_errMsg, _sourceLocation));
return *this;
}
+ /// Limits the number of secondary source locations to 32 and appends a notice to the
+ /// error message.
+ void limitSize(std::string& _message)
+ {
+ size_t occurrences = infos.size();
+ if (occurrences > 32)
+ {
+ infos.resize(32);
+ _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences.";
+ }
+ }
+
std::vector<errorSourceLocationInfo> infos;
};
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index 430739ac..04f5bd25 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -81,15 +81,15 @@ Json::Value formatErrorWithException(
else
message = _message;
+ Json::Value sourceLocation;
if (location && location->sourceName)
{
- Json::Value sourceLocation = Json::objectValue;
sourceLocation["file"] = *location->sourceName;
sourceLocation["start"] = location->start;
sourceLocation["end"] = location->end;
}
- return formatError(_warning, _type, _component, message, formattedMessage, location);
+ return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation);
}
set<string> requestedContractNames(Json::Value const& _outputSelection)
@@ -131,6 +131,61 @@ StringMap createSourceList(Json::Value const& _input)
return sources;
}
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
+{
+ for (auto const& artifact: _outputSelection)
+ /// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
+ if (artifact == "*" || artifact == _artifact)
+ return true;
+ return false;
+}
+
+///
+/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename,
+/// the second level is the contract name and the value is an array of artifact names to be requested for that contract.
+/// @a _file is the current file
+/// @a _contract is the current contract
+/// @a _artifact is the current artifact name
+///
+/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract.
+///
+/// In @a _outputSelection the use of '*' as a wildcard is permitted.
+///
+/// @TODO optimise this. Perhaps flatten the structure upfront.
+///
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact)
+{
+ if (!_outputSelection.isObject())
+ return false;
+
+ for (auto const& file: { _file, string("*") })
+ if (_outputSelection.isMember(file) && _outputSelection[file].isObject())
+ {
+ /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise
+ /// for Contract-level targets try both contract name and wildcard
+ vector<string> contracts{ _contract };
+ if (!_contract.empty())
+ contracts.push_back("*");
+ for (auto const& contract: contracts)
+ if (
+ _outputSelection[file].isMember(contract) &&
+ _outputSelection[file][contract].isArray() &&
+ isArtifactRequested(_outputSelection[file][contract], _artifact)
+ )
+ return true;
+ }
+
+ return false;
+}
+
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts)
+{
+ for (auto const& artifact: _artifacts)
+ if (isArtifactRequested(_outputSelection, _file, _contract, artifact))
+ return true;
+ return false;
+}
+
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@@ -138,7 +193,7 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
for (auto const& ref: linkReferences)
{
string const& fullname = ref.second;
- size_t colon = fullname.find(':');
+ size_t colon = fullname.rfind(':');
solAssert(colon != string::npos, "");
string file = fullname.substr(0, colon);
string name = fullname.substr(colon + 1);
@@ -396,43 +451,65 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
- sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
- sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "ast"))
+ sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST"))
+ sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult;
}
Json::Value contractsOutput = Json::objectValue;
for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>())
{
- size_t colon = contractName.find(':');
+ size_t colon = contractName.rfind(':');
solAssert(colon != string::npos, "");
string file = contractName.substr(0, colon);
string name = contractName.substr(colon + 1);
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
- contractData["abi"] = m_compilerStack.contractABI(contractName);
- contractData["metadata"] = m_compilerStack.metadata(contractName);
- contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
- contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "abi"))
+ contractData["abi"] = m_compilerStack.contractABI(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "metadata"))
+ contractData["metadata"] = m_compilerStack.metadata(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "userdoc"))
+ contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "devdoc"))
+ contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
// EVM
Json::Value evmData(Json::objectValue);
// @TODO: add ir
- evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
- evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
- evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
- evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
-
- evmData["bytecode"] = collectEVMObject(
- m_compilerStack.object(contractName),
- m_compilerStack.sourceMapping(contractName)
- );
-
- evmData["deployedBytecode"] = collectEVMObject(
- m_compilerStack.runtimeObject(contractName),
- m_compilerStack.runtimeSourceMapping(contractName)
- );
+ if (isArtifactRequested(outputSelection, file, name, "evm.assembly"))
+ evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly"))
+ evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers"))
+ evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates"))
+ evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
+ ))
+ evmData["bytecode"] = collectEVMObject(
+ m_compilerStack.object(contractName),
+ m_compilerStack.sourceMapping(contractName)
+ );
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
+ ))
+ evmData["deployedBytecode"] = collectEVMObject(
+ m_compilerStack.runtimeObject(contractName),
+ m_compilerStack.runtimeSourceMapping(contractName)
+ );
contractData["evm"] = evmData;
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index 821e81d2..05b877b5 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -644,15 +644,11 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
expectToken(Token::Event);
ASTPointer<ASTString> name(expectIdentifierToken());
- ASTPointer<ParameterList> parameters;
- if (m_scanner->currentToken() == Token::LParen)
- {
- VarDeclParserOptions options;
- options.allowIndexed = true;
- parameters = parseParameterList(options);
- }
- else
- parameters = createEmptyParameterList();
+
+ VarDeclParserOptions options;
+ options.allowIndexed = true;
+ ASTPointer<ParameterList> parameters = parseParameterList(options);
+
bool anonymous = false;
if (m_scanner->currentToken() == Token::Anonymous)
{