aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/CMakeLists.txt35
-rw-r--r--libsolidity/analysis/ControlFlowAnalyzer.cpp15
-rw-r--r--libsolidity/analysis/ControlFlowBuilder.cpp9
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp22
-rw-r--r--libsolidity/analysis/DeclarationContainer.h5
-rw-r--r--libsolidity/analysis/DocStringAnalyser.cpp34
-rw-r--r--libsolidity/analysis/DocStringAnalyser.h11
-rw-r--r--libsolidity/analysis/GlobalContext.cpp12
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp48
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h6
-rw-r--r--libsolidity/analysis/PostTypeChecker.cpp5
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp270
-rw-r--r--libsolidity/analysis/ReferencesResolver.h1
-rw-r--r--libsolidity/analysis/StaticAnalyzer.cpp72
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h3
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp164
-rw-r--r--libsolidity/analysis/SyntaxChecker.h11
-rw-r--r--libsolidity/analysis/TypeChecker.cpp915
-rw-r--r--libsolidity/analysis/TypeChecker.h14
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp146
-rw-r--r--libsolidity/analysis/ViewPureChecker.h26
-rw-r--r--libsolidity/ast/AST.cpp142
-rw-r--r--libsolidity/ast/AST.h68
-rw-r--r--libsolidity/ast/ASTAnnotations.h7
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp25
-rw-r--r--libsolidity/ast/ExperimentalFeatures.h5
-rw-r--r--libsolidity/ast/Types.cpp697
-rw-r--r--libsolidity/ast/Types.h122
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp61
-rw-r--r--libsolidity/codegen/ABIFunctions.h4
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp96
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/Compiler.cpp13
-rw-r--r--libsolidity/codegen/Compiler.h6
-rw-r--r--libsolidity/codegen/CompilerContext.cpp27
-rw-r--r--libsolidity/codegen/CompilerContext.h6
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp153
-rw-r--r--libsolidity/codegen/CompilerUtils.h12
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp198
-rw-r--r--libsolidity/codegen/ContractCompiler.h32
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp364
-rw-r--r--libsolidity/formal/CVC4Interface.cpp27
-rw-r--r--libsolidity/formal/CVC4Interface.h17
-rw-r--r--libsolidity/formal/SMTChecker.cpp166
-rw-r--r--libsolidity/formal/SMTChecker.h28
-rw-r--r--libsolidity/formal/SMTLib2Interface.cpp50
-rw-r--r--libsolidity/formal/SMTLib2Interface.h9
-rw-r--r--libsolidity/formal/SMTPortfolio.cpp152
-rw-r--r--libsolidity/formal/SMTPortfolio.h67
-rw-r--r--libsolidity/formal/SSAVariable.cpp2
-rw-r--r--libsolidity/formal/SolverInterface.h19
-rw-r--r--libsolidity/formal/SymbolicIntVariable.cpp14
-rw-r--r--libsolidity/formal/VariableUsage.cpp4
-rw-r--r--libsolidity/formal/VariableUsage.h6
-rw-r--r--libsolidity/formal/Z3Interface.cpp23
-rw-r--r--libsolidity/formal/Z3Interface.h6
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp22
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h4
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp35
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp10
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h4
-rw-r--r--libsolidity/inlineasm/AsmScope.cpp4
-rw-r--r--libsolidity/inlineasm/AsmScope.h14
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp4
-rw-r--r--libsolidity/interface/AssemblyStack.cpp14
-rw-r--r--libsolidity/interface/AssemblyStack.h6
-rw-r--r--libsolidity/interface/CompilerStack.cpp180
-rw-r--r--libsolidity/interface/CompilerStack.h72
-rw-r--r--libsolidity/interface/ErrorReporter.h6
-rw-r--r--libsolidity/interface/Exceptions.h2
-rw-r--r--libsolidity/interface/Natspec.cpp74
-rw-r--r--libsolidity/interface/Natspec.h8
-rw-r--r--libsolidity/interface/SourceReferenceFormatter.cpp10
-rw-r--r--libsolidity/interface/StandardCompiler.cpp21
-rw-r--r--libsolidity/interface/StandardCompiler.h6
-rw-r--r--libsolidity/parsing/Parser.cpp202
-rw-r--r--libsolidity/parsing/Parser.h16
-rw-r--r--libsolidity/parsing/Scanner.cpp45
-rw-r--r--libsolidity/parsing/Scanner.h2
-rw-r--r--libsolidity/parsing/Token.cpp4
-rw-r--r--libsolidity/parsing/Token.h35
81 files changed, 3184 insertions, 2073 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
index 0bdec4b4..91a4678a 100644
--- a/libsolidity/CMakeLists.txt
+++ b/libsolidity/CMakeLists.txt
@@ -7,24 +7,22 @@ if (${Z3_FOUND})
include_directories(${Z3_INCLUDE_DIR})
add_definitions(-DHAVE_Z3)
message("Z3 SMT solver found. This enables optional SMT checking with Z3.")
- list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp")
else()
list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/Z3Interface.cpp")
- find_package(GMP QUIET)
- find_package(CVC4 QUIET)
- if (${CVC4_FOUND})
- if (${GMP_FOUND})
- include_directories(${CVC4_INCLUDE_DIR})
- add_definitions(-DHAVE_CVC4)
- message("CVC4 SMT solver and GMP found. This enables optional SMT checking with CVC4.")
- else()
- message("CVC4 SMT solver found but its dependency GMP was NOT found. Optional SMT checking with CVC4 will not be available. Please install GMP if it is desired.")
- list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp")
- endif()
- else()
- message("No SMT solver found (Z3 or CVC4). Optional SMT checking will not be available. Please install Z3 or CVC4 if it is desired.")
- list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp")
- endif()
+endif()
+
+find_package(CVC4 QUIET)
+if (${CVC4_FOUND})
+ include_directories(${CVC4_INCLUDE_DIR})
+ add_definitions(-DHAVE_CVC4)
+ message("CVC4 SMT solver found. This enables optional SMT checking with CVC4.")
+else()
+ list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp")
+endif()
+
+if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
+ message("No SMT solver found (or it has been forcefully disabled). Optional SMT checking will not be available.\
+ \nPlease install Z3 or CVC4 or remove the option disabling them (USE_Z3, USE_CVC4).")
endif()
add_library(solidity ${sources} ${headers})
@@ -34,7 +32,6 @@ if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC ${Z3_LIBRARY})
endif()
-if (${CVC4_FOUND} AND ${GMP_FOUND})
- target_link_libraries(solidity PUBLIC ${CVC4_LIBRARY})
- target_link_libraries(solidity PUBLIC ${GMP_LIBRARY})
+if (${CVC4_FOUND})
+ target_link_libraries(solidity PUBLIC ${CVC4_LIBRARIES})
endif()
diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp
index 6edf7986..ab6569be 100644
--- a/libsolidity/analysis/ControlFlowAnalyzer.cpp
+++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp
@@ -75,7 +75,10 @@ void ControlFlowAnalyzer::checkUnassignedStorageReturnValues(
{
auto& unassignedAtFunctionEntry = unassigned[_functionEntry];
for (auto const& returnParameter: _function.returnParameterList()->parameters())
- if (returnParameter->type()->dataStoredIn(DataLocation::Storage))
+ if (
+ returnParameter->type()->dataStoredIn(DataLocation::Storage) ||
+ returnParameter->type()->category() == Type::Category::Mapping
+ )
unassignedAtFunctionEntry.insert(returnParameter.get());
}
@@ -144,12 +147,12 @@ void ControlFlowAnalyzer::checkUnassignedStorageReturnValues(
ssl.append("Problematic end of function:", _function.location());
}
- m_errorReporter.warning(
+ m_errorReporter.typeError(
returnVal->location(),
- "This variable is of storage pointer type and might be returned without assignment. "
- "This can cause storage corruption. Assign the variable (potentially from itself) "
- "to remove this warning.",
- ssl
+ ssl,
+ "This variable is of storage pointer type and might be returned without assignment and "
+ "could be used uninitialized. Assign the variable (potentially from itself) "
+ "to fix this error."
);
}
}
diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp
index 35d7687c..5bd39da3 100644
--- a/libsolidity/analysis/ControlFlowBuilder.cpp
+++ b/libsolidity/analysis/ControlFlowBuilder.cpp
@@ -159,15 +159,14 @@ bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
{
auto afterWhile = newLabel();
auto whileBody = createLabelHere();
+ auto condition = newLabel();
{
- // Note that "continue" in this case currently indeed jumps to whileBody
- // and not to the condition. This is inconsistent with JavaScript and C and
- // therefore a bug. This will be fixed in the future (planned for 0.5.0)
- // and the Control Flow Graph will have to be adjusted accordingly.
- BreakContinueScope scope(*this, afterWhile, whileBody);
+ BreakContinueScope scope(*this, afterWhile, condition);
appendControlFlow(_whileStatement.body());
}
+
+ placeAndConnectLabel(condition);
appendControlFlow(_whileStatement.condition());
connect(m_currentNode, whileBody);
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index 786272e4..5f980788 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -49,16 +49,10 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
dynamic_cast<MagicVariableDeclaration const*>(&_declaration)
)
{
- // check that all other declarations with the same name are functions or a public state variable or events.
- // And then check that the signatures are different.
+ // check that all other declarations are of the same kind (in which
+ // case the type checker will ensure that the signatures are different)
for (Declaration const* declaration: declarations)
{
- if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration))
- {
- if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic())
- continue;
- return declaration;
- }
if (
dynamic_cast<FunctionDefinition const*>(&_declaration) &&
!dynamic_cast<FunctionDefinition const*>(declaration)
@@ -96,6 +90,11 @@ void DeclarationContainer::activateVariable(ASTString const& _name)
m_invisibleDeclarations.erase(_name);
}
+bool DeclarationContainer::isInvisible(ASTString const& _name) const
+{
+ return m_invisibleDeclarations.count(_name);
+}
+
bool DeclarationContainer::registerDeclaration(
Declaration const& _declaration,
ASTString const* _name,
@@ -139,19 +138,22 @@ vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _n
vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const
{
static size_t const MAXIMUM_EDIT_DISTANCE = 2;
+ // because the function below has quadratic runtime - it will not magically improve once a better algorithm is discovered ;)
+ // since 80 is the suggested line length limit, we use 80^2 as length threshold
+ static size_t const MAXIMUM_LENGTH_THRESHOLD = 80 * 80;
vector<ASTString> similar;
for (auto const& declaration: m_declarations)
{
string const& declarationName = declaration.first;
- if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
+ if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD))
similar.push_back(declarationName);
}
for (auto const& declaration: m_invisibleDeclarations)
{
string const& declarationName = declaration.first;
- if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
+ if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD))
similar.push_back(declarationName);
}
diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h
index e4b3320a..9d7a17a3 100644
--- a/libsolidity/analysis/DeclarationContainer.h
+++ b/libsolidity/analysis/DeclarationContainer.h
@@ -58,10 +58,13 @@ public:
/// @returns whether declaration is valid, and if not also returns previous declaration.
Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const;
- /// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
+ /// Activates a previously inactive (invisible) variable. To be used in C99 scoping for
/// VariableDeclarationStatements.
void activateVariable(ASTString const& _name);
+ /// @returns true if declaration is currently invisible.
+ bool isInvisible(ASTString const& _name) const;
+
/// @returns existing declaration names similar to @a _name.
/// Searches this and all parent containers.
std::vector<ASTString> similarNames(ASTString const& _name) const;
diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp
index b3fb5258..c1b97def 100644
--- a/libsolidity/analysis/DocStringAnalyser.cpp
+++ b/libsolidity/analysis/DocStringAnalyser.cpp
@@ -48,7 +48,10 @@ bool DocStringAnalyser::visit(ContractDefinition const& _contract)
bool DocStringAnalyser::visit(FunctionDefinition const& _function)
{
- handleCallable(_function, _function, _function.annotation());
+ if (_function.isConstructor())
+ handleConstructor(_function, _function, _function.annotation());
+ else
+ handleCallable(_function, _function, _function.annotation());
return true;
}
@@ -66,15 +69,11 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
return true;
}
-void DocStringAnalyser::handleCallable(
+void DocStringAnalyser::checkParameters(
CallableDeclaration const& _callable,
- Documented const& _node,
DocumentedAnnotation& _annotation
)
{
- static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"};
- parseDocStrings(_node, _annotation, validTags, "functions");
-
set<string> validParams;
for (auto const& p: _callable.parameters())
validParams.insert(p->name());
@@ -89,6 +88,29 @@ void DocStringAnalyser::handleCallable(
i->second.paramName +
"\" not found in the parameter list of the function."
);
+
+}
+
+void DocStringAnalyser::handleConstructor(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+)
+{
+ static const set<string> validTags = set<string>{"author", "dev", "notice", "param"};
+ parseDocStrings(_node, _annotation, validTags, "constructor");
+ checkParameters(_callable, _annotation);
+}
+
+void DocStringAnalyser::handleCallable(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+)
+{
+ static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"};
+ parseDocStrings(_node, _annotation, validTags, "functions");
+ checkParameters(_callable, _annotation);
}
void DocStringAnalyser::parseDocStrings(
diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h
index 158b4060..5d339428 100644
--- a/libsolidity/analysis/DocStringAnalyser.h
+++ b/libsolidity/analysis/DocStringAnalyser.h
@@ -48,6 +48,17 @@ private:
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual bool visit(EventDefinition const& _event) override;
+ void checkParameters(
+ CallableDeclaration const& _callable,
+ DocumentedAnnotation& _annotation
+ );
+
+ void handleConstructor(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+ );
+
void handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp
index 756bb540..cba2655c 100644
--- a/libsolidity/analysis/GlobalContext.cpp
+++ b/libsolidity/analysis/GlobalContext.cpp
@@ -42,7 +42,7 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("blockhash", make_shared<FunctionType>(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)),
- make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("log0", make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
make_shared<MagicVariableDeclaration>("log1", make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)),
make_shared<MagicVariableDeclaration>("log2", make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)),
@@ -55,11 +55,11 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
- make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)),
- make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
- make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),
- make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
- make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
+ make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
+ make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction))
})
{
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 0a356f04..b452a49a 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -54,11 +54,10 @@ NameAndTypeResolver::NameAndTypeResolver(
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
{
- bool useC99Scoping = _sourceUnit.annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
- DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, useC99Scoping, m_errorReporter, _currentScope);
+ DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
}
catch (FatalError const&)
{
@@ -157,7 +156,13 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
void NameAndTypeResolver::activateVariable(string const& _name)
{
solAssert(m_currentScope, "");
- m_currentScope->activateVariable(_name);
+ // Scoped local variables are invisible before activation.
+ // When a local variable is activated, its name is removed
+ // from a scope's invisible variables.
+ // This is used to avoid activation of variables of same name
+ // in the same scope (an error is returned).
+ if (m_currentScope->isInvisible(_name))
+ m_currentScope->activateVariable(_name);
}
vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const
@@ -226,7 +231,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
shared_ptr<FunctionType const> newFunctionType { d->functionType(false) };
if (!newFunctionType)
newFunctionType = d->functionType(true);
- return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType);
+ return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
}
))
uniqueFunctions.push_back(declaration);
@@ -290,10 +295,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
{
setScope(contract);
if (!resolveNamesAndTypes(*node, false))
- {
success = false;
- break;
- }
}
if (!success)
@@ -449,11 +451,9 @@ string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
- bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope
):
- m_useC99Scoping(_useC99Scoping),
m_scopes(_scopes),
m_currentScope(_currentScope),
m_errorReporter(_errorReporter)
@@ -626,32 +626,39 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&)
closeCurrentScope();
}
+bool DeclarationRegistrationHelper::visit(FunctionTypeName& _funTypeName)
+{
+ enterNewSubScope(_funTypeName);
+ return true;
+}
+
+void DeclarationRegistrationHelper::endVisit(FunctionTypeName&)
+{
+ closeCurrentScope();
+}
+
bool DeclarationRegistrationHelper::visit(Block& _block)
{
_block.setScope(m_currentScope);
- if (m_useC99Scoping)
- enterNewSubScope(_block);
+ enterNewSubScope(_block);
return true;
}
void DeclarationRegistrationHelper::endVisit(Block&)
{
- if (m_useC99Scoping)
- closeCurrentScope();
+ closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ForStatement& _for)
{
_for.setScope(m_currentScope);
- if (m_useC99Scoping)
- enterNewSubScope(_for);
+ enterNewSubScope(_for);
return true;
}
void DeclarationRegistrationHelper::endVisit(ForStatement&)
{
- if (m_useC99Scoping)
- closeCurrentScope();
+ closeCurrentScope();
}
void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement)
@@ -711,14 +718,9 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
dynamic_cast<EventDefinition const*>(m_currentScope)
)
warnAboutShadowing = false;
- // Do not warn about the constructor shadowing the contract.
- if (auto fun = dynamic_cast<FunctionDefinition const*>(&_declaration))
- if (fun->isConstructor())
- warnAboutShadowing = false;
- // Register declaration as inactive if we are in block scope and C99 mode.
+ // Register declaration as inactive if we are in block scope.
bool inactive =
- m_useC99Scoping &&
(dynamic_cast<Block const*>(m_currentScope) || dynamic_cast<ForStatement const*>(m_currentScope));
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h
index 3d10fbd8..a72c21e3 100644
--- a/libsolidity/analysis/NameAndTypeResolver.h
+++ b/libsolidity/analysis/NameAndTypeResolver.h
@@ -69,7 +69,7 @@ public:
/// that create their own scope.
/// @returns false in case of error.
bool updateDeclaration(Declaration const& _declaration);
- /// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
+ /// Activates a previously inactive (invisible) variable. To be used in C99 scoping for
/// VariableDeclarationStatements.
void activateVariable(std::string const& _name);
@@ -142,7 +142,6 @@ public:
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
- bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope = nullptr
);
@@ -172,6 +171,8 @@ private:
void endVisit(FunctionDefinition& _function) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
+ bool visit(FunctionTypeName& _funTypeName) override;
+ void endVisit(FunctionTypeName& _funTypeName) override;
bool visit(Block& _block) override;
void endVisit(Block& _block) override;
bool visit(ForStatement& _forLoop) override;
@@ -190,7 +191,6 @@ private:
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
- bool m_useC99Scoping = false;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp
index 19d0b708..240d7973 100644
--- a/libsolidity/analysis/PostTypeChecker.cpp
+++ b/libsolidity/analysis/PostTypeChecker.cpp
@@ -91,8 +91,11 @@ bool PostTypeChecker::visit(Identifier const& _identifier)
VariableDeclaration const* PostTypeChecker::findCycle(VariableDeclaration const& _startingFrom)
{
- auto visitor = [&](VariableDeclaration const& _variable, CycleDetector<VariableDeclaration>& _cycleDetector)
+ auto visitor = [&](VariableDeclaration const& _variable, CycleDetector<VariableDeclaration>& _cycleDetector, size_t _depth)
{
+ if (_depth >= 256)
+ m_errorReporter.fatalDeclarationError(_variable.location(), "Variable definition exhausting cyclic dependency validator.");
+
// Iterating through the dependencies needs to be deterministic and thus cannot
// depend on the memory layout.
// Because of that, we sort by AST node id.
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index f91eaf6e..8a576e2e 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -30,7 +30,10 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/ErrorReporter.h>
+#include <libdevcore/StringUtils.h>
+
#include <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/transformed.hpp>
using namespace std;
using namespace dev;
@@ -47,10 +50,7 @@ bool ReferencesResolver::visit(Block const& _block)
{
if (!m_resolveInsideCode)
return false;
- m_experimental050Mode = _block.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- // C99-scoped variables
- if (m_experimental050Mode)
- m_resolver.setScope(&_block);
+ m_resolver.setScope(&_block);
return true;
}
@@ -59,19 +59,14 @@ void ReferencesResolver::endVisit(Block const& _block)
if (!m_resolveInsideCode)
return;
- // C99-scoped variables
- if (m_experimental050Mode)
- m_resolver.setScope(_block.scope());
+ m_resolver.setScope(_block.scope());
}
bool ReferencesResolver::visit(ForStatement const& _for)
{
if (!m_resolveInsideCode)
return false;
- m_experimental050Mode = _for.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- // C99-scoped variables
- if (m_experimental050Mode)
- m_resolver.setScope(&_for);
+ m_resolver.setScope(&_for);
return true;
}
@@ -79,18 +74,16 @@ void ReferencesResolver::endVisit(ForStatement const& _for)
{
if (!m_resolveInsideCode)
return;
- if (m_experimental050Mode)
- m_resolver.setScope(_for.scope());
+ m_resolver.setScope(_for.scope());
}
void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (!m_resolveInsideCode)
return;
- if (m_experimental050Mode)
- for (auto const& var: _varDeclStatement.declarations())
- if (var)
- m_resolver.activateVariable(var->name());
+ for (auto const& var: _varDeclStatement.declarations())
+ if (var)
+ m_resolver.activateVariable(var->name());
}
bool ReferencesResolver::visit(Identifier const& _identifier)
@@ -99,9 +92,14 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
if (declarations.empty())
{
string suggestions = m_resolver.similarNameSuggestions(_identifier.name());
- string errorMessage =
- "Undeclared identifier." +
- (suggestions.empty()? "": " Did you mean " + std::move(suggestions) + "?");
+ string errorMessage = "Undeclared identifier.";
+ if (!suggestions.empty())
+ {
+ if ("\"" + _identifier.name() + "\"" == suggestions)
+ errorMessage += " " + std::move(suggestions) + " is not (or not yet) visible at this point.";
+ else
+ errorMessage += " Did you mean " + std::move(suggestions) + "?";
+ }
declarationError(_identifier.location(), errorMessage);
}
else if (declarations.size() == 1)
@@ -114,7 +112,28 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
{
- _typeName.annotation().type = Type::fromElementaryTypeName(_typeName.typeName());
+ if (!_typeName.annotation().type)
+ {
+ _typeName.annotation().type = Type::fromElementaryTypeName(_typeName.typeName());
+ if (_typeName.stateMutability().is_initialized())
+ {
+ // for non-address types this was already caught by the parser
+ solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
+ switch(*_typeName.stateMutability())
+ {
+ case StateMutability::Payable:
+ case StateMutability::NonPayable:
+ _typeName.annotation().type = make_shared<AddressType>(*_typeName.stateMutability());
+ break;
+ default:
+ m_errorReporter.typeError(
+ _typeName.location(),
+ "Address types can only be payable or non-payable."
+ );
+ break;
+ }
+ }
+ }
return true;
}
@@ -147,7 +166,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
if (!declaration)
{
- declarationError(_typeName.location(), "Identifier not found or not unique.");
+ fatalDeclarationError(_typeName.location(), "Identifier not found or not unique.");
return;
}
@@ -160,7 +179,10 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = make_shared<ContractType>(*contract);
else
+ {
+ _typeName.annotation().type = make_shared<TupleType>();
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
+ }
}
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
@@ -171,13 +193,13 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
case VariableDeclaration::Visibility::External:
break;
default:
- typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
+ fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
return;
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
{
- typeError(_typeName.location(), "Only external function types can be payable.");
+ fatalTypeError(_typeName.location(), "Only external function types can be payable.");
return;
}
@@ -187,7 +209,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
solAssert(t->annotation().type, "Type not set for parameter.");
if (!t->annotation().type->canBeUsedExternally(false))
{
- typeError(t->location(), "Internal type cannot be used for external function type.");
+ fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
return;
}
}
@@ -261,10 +283,18 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
string("_slot").size() :
string("_offset").size()
));
+ if (realName.empty())
+ {
+ declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix.");
+ return size_t(-1);
+ }
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() != 1)
+ {
+ declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported.");
return size_t(-1);
+ }
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && _crossesFunctionBoundary)
{
@@ -280,7 +310,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
// We use the latest EVM version because we will re-run it anyway.
assembly::AsmAnalysisInfo analysisInfo;
- boost::optional<Error::Type> errorTypeForLoose = m_experimental050Mode ? Error::Type::SyntaxError : Error::Type::Warning;
+ boost::optional<Error::Type> errorTypeForLoose = Error::Type::SyntaxError;
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), errorTypeForLoose, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations());
return false;
}
@@ -297,112 +327,106 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
if (_variable.annotation().type)
return;
- TypePointer type;
- if (_variable.typeName())
+ if (_variable.isConstant() && !_variable.isStateVariable())
+ m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
+
+ if (!_variable.typeName())
+ {
+ // This can still happen in very unusual cases where a developer uses constructs, such as
+ // `var a;`, however, such code will have generated errors already.
+ // However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
+ // invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
+ // after this step.
+ return;
+ }
+ using Location = VariableDeclaration::Location;
+ Location varLoc = _variable.referenceLocation();
+ DataLocation typeLoc = DataLocation::Memory;
+
+ set<Location> allowedDataLocations = _variable.allowedDataLocations();
+ if (!allowedDataLocations.count(varLoc))
{
- type = _variable.typeName()->annotation().type;
- using Location = VariableDeclaration::Location;
- Location varLoc = _variable.referenceLocation();
- DataLocation typeLoc = DataLocation::Memory;
- // References are forced to calldata for external function parameters (not return)
- // and memory for parameters (also return) of publicly visible functions.
- // They default to memory for function parameters and storage for local variables.
- // As an exception, "storage" is allowed for library functions.
- if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
+ auto locationToString = [](VariableDeclaration::Location _location) -> string
{
- bool isPointer = true;
- if (_variable.isExternalCallableParameter())
- {
- auto const& contract = dynamic_cast<ContractDefinition const&>(
- *dynamic_cast<Declaration const&>(*_variable.scope()).scope()
- );
- if (contract.isLibrary())
- {
- if (varLoc == Location::Memory)
- fatalTypeError(_variable.location(),
- "Location has to be calldata or storage for external "
- "library functions (remove the \"memory\" keyword)."
- );
- }
- else
- {
- // force location of external function parameters (not return) to calldata
- if (varLoc != Location::Default)
- fatalTypeError(_variable.location(),
- "Location has to be calldata for external functions "
- "(remove the \"memory\" or \"storage\" keyword)."
- );
- }
- if (varLoc == Location::Default)
- typeLoc = DataLocation::CallData;
- else
- typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
- }
- else if (_variable.isCallableParameter() && dynamic_cast<Declaration const&>(*_variable.scope()).isPublic())
+ switch (_location)
{
- auto const& contract = dynamic_cast<ContractDefinition const&>(
- *dynamic_cast<Declaration const&>(*_variable.scope()).scope()
- );
- // force locations of public or external function (return) parameters to memory
- if (varLoc == Location::Storage && !contract.isLibrary())
- fatalTypeError(_variable.location(),
- "Location has to be memory for publicly visible functions "
- "(remove the \"storage\" keyword)."
- );
- if (varLoc == Location::Default || !contract.isLibrary())
- typeLoc = DataLocation::Memory;
- else
- typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
+ case Location::Memory: return "\"memory\"";
+ case Location::Storage: return "\"storage\"";
+ case Location::CallData: return "\"calldata\"";
+ case Location::Unspecified: return "none";
}
+ return {};
+ };
+
+ string errorString;
+ if (!_variable.hasReferenceOrMappingType())
+ errorString = "Data location can only be specified for array, struct or mapping types";
+ else
+ {
+ errorString = "Data location must be " +
+ joinHumanReadable(
+ allowedDataLocations | boost::adaptors::transformed(locationToString),
+ ", ",
+ " or "
+ );
+ if (_variable.isCallableParameter())
+ errorString +=
+ " for " +
+ string(_variable.isReturnParameter() ? "return " : "") +
+ "parameter in" +
+ string(_variable.isExternalCallableParameter() ? " external" : "") +
+ " function";
else
- {
- if (_variable.isConstant())
- {
- if (varLoc != Location::Default && varLoc != Location::Memory)
- fatalTypeError(
- _variable.location(),
- "Storage location has to be \"memory\" (or unspecified) for constants."
- );
- typeLoc = DataLocation::Memory;
- }
- else if (varLoc == Location::Default)
- {
- if (_variable.isCallableParameter())
- typeLoc = DataLocation::Memory;
- else
- {
- typeLoc = DataLocation::Storage;
- if (_variable.isLocalVariable())
- {
- if (_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
- typeError(
- _variable.location(),
- "Storage location must be specified as either \"memory\" or \"storage\"."
- );
- else
- m_errorReporter.warning(
- _variable.location(),
- "Variable is declared as a storage pointer. "
- "Use an explicit \"storage\" keyword to silence this warning."
- );
- }
- }
- }
- else
- typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
- isPointer = !_variable.isStateVariable();
- }
+ errorString += " for variable";
+ }
+ errorString += ", but " + locationToString(varLoc) + " was given.";
+ typeError(_variable.location(), errorString);
- type = ref->copyForLocation(typeLoc, isPointer);
+ solAssert(!allowedDataLocations.empty(), "");
+ varLoc = *allowedDataLocations.begin();
+ }
+
+ // Find correct data location.
+ if (_variable.isEventParameter())
+ {
+ solAssert(varLoc == Location::Unspecified, "");
+ typeLoc = DataLocation::Memory;
+ }
+ else if (_variable.isStateVariable())
+ {
+ solAssert(varLoc == Location::Unspecified, "");
+ typeLoc = _variable.isConstant() ? DataLocation::Memory : DataLocation::Storage;
+ }
+ else if (
+ dynamic_cast<StructDefinition const*>(_variable.scope()) ||
+ dynamic_cast<EnumDefinition const*>(_variable.scope())
+ )
+ // The actual location will later be changed depending on how the type is used.
+ typeLoc = DataLocation::Storage;
+ else
+ switch (varLoc)
+ {
+ case Location::Memory:
+ typeLoc = DataLocation::Memory;
+ break;
+ case Location::Storage:
+ typeLoc = DataLocation::Storage;
+ break;
+ case Location::CallData:
+ typeLoc = DataLocation::CallData;
+ break;
+ case Location::Unspecified:
+ solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
- else if (varLoc != Location::Default && !ref)
- typeError(_variable.location(), "Storage location can only be given for array or struct types.");
- _variable.annotation().type = type;
+ TypePointer type = _variable.typeName()->annotation().type;
+ if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
+ {
+ bool isPointer = !_variable.isStateVariable();
+ type = ref->copyForLocation(typeLoc, isPointer);
}
- else if (!_variable.canHaveAutoType())
- typeError(_variable.location(), "Explicit type needed.");
- // otherwise we have a "var"-declaration whose type is resolved by the first assignment
+
+ _variable.annotation().type = type;
}
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h
index 4e8f54b5..24ec4643 100644
--- a/libsolidity/analysis/ReferencesResolver.h
+++ b/libsolidity/analysis/ReferencesResolver.h
@@ -94,7 +94,6 @@ private:
std::vector<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode;
bool m_errorOccurred = false;
- bool m_experimental050Mode = false;
};
}
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
index 00a581d0..487a5cca 100644
--- a/libsolidity/analysis/StaticAnalyzer.cpp
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -51,22 +51,11 @@ void StaticAnalyzer::endVisit(ContractDefinition const&)
bool StaticAnalyzer::visit(FunctionDefinition const& _function)
{
- const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface;
-
- if (_function.noVisibilitySpecified())
- m_errorReporter.warning(
- _function.location(),
- "No visibility specified. Defaulting to \"" +
- Declaration::visibilityToString(_function.visibility()) +
- "\". " +
- (isInterface ? "In interfaces it defaults to external." : "")
- );
if (_function.isImplemented())
m_currentFunction = &_function;
else
solAssert(!m_currentFunction, "");
solAssert(m_localVarUseCount.empty(), "");
- m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
m_constructor = _function.isConstructor();
return true;
}
@@ -74,7 +63,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function)
void StaticAnalyzer::endVisit(FunctionDefinition const&)
{
m_currentFunction = nullptr;
- m_nonPayablePublic = false;
m_constructor = false;
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
@@ -150,61 +138,27 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
{
- bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
{
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
- {
- if (v050)
- m_errorReporter.typeError(
- _memberAccess.location(),
- "\"msg.gas\" has been deprecated in favor of \"gasleft()\""
- );
- else
- m_errorReporter.warning(
- _memberAccess.location(),
- "\"msg.gas\" has been deprecated in favor of \"gasleft()\""
- );
- }
- if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash")
- {
- if (v050)
- m_errorReporter.typeError(
- _memberAccess.location(),
- "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
- );
- else
- m_errorReporter.warning(
- _memberAccess.location(),
- "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
- );
- }
+ m_errorReporter.typeError(
+ _memberAccess.location(),
+ "\"msg.gas\" has been deprecated in favor of \"gasleft()\""
+ );
+ else if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash")
+ m_errorReporter.typeError(
+ _memberAccess.location(),
+ "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
+ );
}
- if (m_nonPayablePublic && !m_library)
- if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
- if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value")
- m_errorReporter.warning(
- _memberAccess.location(),
- "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?"
- );
-
if (_memberAccess.memberName() == "callcode")
if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
if (type->kind() == FunctionType::Kind::BareCallCode)
- {
- if (v050)
- m_errorReporter.typeError(
- _memberAccess.location(),
- "\"callcode\" has been deprecated in favour of \"delegatecall\"."
- );
- else
- m_errorReporter.warning(
- _memberAccess.location(),
- "\"callcode\" has been deprecated in favour of \"delegatecall\"."
- );
- }
+ m_errorReporter.typeError(
+ _memberAccess.location(),
+ "\"callcode\" has been deprecated in favour of \"delegatecall\"."
+ );
if (m_constructor)
{
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index 2a62e391..7f5c743a 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -75,9 +75,6 @@ private:
/// Flag that indicates whether the current contract definition is a library.
bool m_library = false;
- /// Flag that indicates whether a public function does not contain the "payable" modifier.
- bool m_nonPayablePublic = false;
-
/// Number of uses of each (named) local variable in a function, counter is initialized with zero.
/// Pairs of AST ids and pointers are used as keys to ensure a deterministic order
/// when traversing.
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index 396058f4..0bc20f2e 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -22,6 +22,10 @@
#include <libsolidity/analysis/SemVerHandler.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
+#include <boost/algorithm/cxx11/all_of.hpp>
+
+#include <boost/algorithm/string.hpp>
+#include <string>
using namespace std;
using namespace dev;
@@ -134,9 +138,25 @@ void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
m_placeholderFound = false;
}
-bool SyntaxChecker::visit(WhileStatement const&)
+void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _statement)
+{
+ auto varDecl = dynamic_cast<VariableDeclarationStatement const*>(&_statement);
+ if (varDecl)
+ m_errorReporter.syntaxError(_statement.location(), "Variable declarations can only be used inside blocks.");
+}
+
+bool SyntaxChecker::visit(IfStatement const& _ifStatement)
+{
+ checkSingleStatementVariableDeclaration(_ifStatement.trueStatement());
+ if (Statement const* _statement = _ifStatement.falseStatement())
+ checkSingleStatementVariableDeclaration(*_statement);
+ return true;
+}
+
+bool SyntaxChecker::visit(WhileStatement const& _whileStatement)
{
m_inLoopDepth++;
+ checkSingleStatementVariableDeclaration(_whileStatement.body());
return true;
}
@@ -145,9 +165,10 @@ void SyntaxChecker::endVisit(WhileStatement const&)
m_inLoopDepth--;
}
-bool SyntaxChecker::visit(ForStatement const&)
+bool SyntaxChecker::visit(ForStatement const& _forStatement)
{
m_inLoopDepth++;
+ checkSingleStatementVariableDeclaration(_forStatement.body());
return true;
}
@@ -174,33 +195,58 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
bool SyntaxChecker::visit(Throw const& _throwStatement)
{
- bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
- if (v050)
- m_errorReporter.syntaxError(
- _throwStatement.location(),
- "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
- );
- else
- m_errorReporter.warning(
- _throwStatement.location(),
- "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
- );
+ m_errorReporter.syntaxError(
+ _throwStatement.location(),
+ "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
+ );
return true;
}
-bool SyntaxChecker::visit(UnaryOperation const& _operation)
+bool SyntaxChecker::visit(Literal const& _literal)
{
- bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+ if (_literal.token() != Token::Number)
+ return true;
- if (_operation.getOperator() == Token::Add)
+ ASTString const& value = _literal.value();
+ solAssert(!value.empty(), "");
+
+ // Generic checks no matter what base this number literal is of:
+ if (value.back() == '_')
{
- if (v050)
- m_errorReporter.syntaxError(_operation.location(), "Use of unary + is deprecated.");
- else
- m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated.");
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No trailing underscores allowed.");
+ return true;
+ }
+
+ if (value.find("__") != ASTString::npos)
+ {
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. Only one consecutive underscores between digits allowed.");
+ return true;
+ }
+
+ if (!_literal.isHexNumber()) // decimal literal
+ {
+ if (value.find("._") != ASTString::npos)
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed.");
+
+ if (value.find("_.") != ASTString::npos)
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed.");
+
+ if (value.find("_e") != ASTString::npos)
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscore at the end of the mantissa allowed.");
+
+ if (value.find("e_") != ASTString::npos)
+ m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No underscore in front of exponent allowed.");
}
+
+ return true;
+}
+
+bool SyntaxChecker::visit(UnaryOperation const& _operation)
+{
+ if (_operation.getOperator() == Token::Add)
+ m_errorReporter.syntaxError(_operation.location(), "Use of unary + is disallowed.");
+
return true;
}
@@ -210,40 +256,34 @@ bool SyntaxChecker::visit(PlaceholderStatement const&)
return true;
}
-bool SyntaxChecker::visit(FunctionDefinition const& _function)
+bool SyntaxChecker::visit(ContractDefinition const& _contract)
{
- bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
- if (v050 && _function.noVisibilitySpecified())
- m_errorReporter.syntaxError(_function.location(), "No visibility specified.");
+ m_isInterface = _contract.contractKind() == ContractDefinition::ContractKind::Interface;
- if (_function.isOldStyleConstructor())
- {
- if (v050)
- m_errorReporter.syntaxError(
- _function.location(),
+ ASTString const& contractName = _contract.name();
+ for (FunctionDefinition const* function: _contract.definedFunctions())
+ if (function->name() == contractName)
+ m_errorReporter.syntaxError(function->location(),
"Functions are not allowed to have the same name as the contract. "
"If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it."
);
- else
- m_errorReporter.warning(
- _function.location(),
- "Defining constructors as functions with the same name as the contract is deprecated. "
- "Use \"constructor(...) { ... }\" instead."
- );
- }
- if (!_function.isImplemented() && !_function.modifiers().empty())
+ return true;
+}
+
+bool SyntaxChecker::visit(FunctionDefinition const& _function)
+{
+ if (_function.noVisibilitySpecified())
{
- if (v050)
- m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
- else
- m_errorReporter.warning(_function.location(), "Modifiers of functions without implementation are ignored." );
- }
- if (_function.name() == "constructor")
- m_errorReporter.warning(_function.location(),
- "This function is named \"constructor\" but is not the constructor of the contract. "
- "If you intend this to be a constructor, use \"constructor(...) { ... }\" without the \"function\" keyword to define it."
+ string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public";
+ m_errorReporter.syntaxError(
+ _function.location(),
+ "No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"
);
+ }
+
+ if (!_function.isImplemented() && !_function.modifiers().empty())
+ m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
+
return true;
}
@@ -255,35 +295,27 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node)
for (auto const& decl: _node.returnParameterTypeList()->parameters())
if (!decl->name().empty())
- m_errorReporter.warning(decl->location(), "Naming function type return parameters is deprecated.");
+ m_errorReporter.syntaxError(decl->location(), "Return parameters in function types may not be named.");
return true;
}
-bool SyntaxChecker::visit(VariableDeclaration const& _declaration)
+bool SyntaxChecker::visit(VariableDeclarationStatement const& _statement)
{
- bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+ // Report if none of the variable components in the tuple have a name (only possible via deprecated "var")
+ if (boost::algorithm::all_of_equal(_statement.declarations(), nullptr))
+ m_errorReporter.syntaxError(
+ _statement.location(),
+ "The use of the \"var\" keyword is disallowed. The declaration part of the statement can be removed, since it is empty."
+ );
- if (!_declaration.typeName())
- {
- if (v050)
- m_errorReporter.syntaxError(_declaration.location(), "Use of the \"var\" keyword is deprecated.");
- else
- m_errorReporter.warning(_declaration.location(), "Use of the \"var\" keyword is deprecated.");
- }
return true;
}
bool SyntaxChecker::visit(StructDefinition const& _struct)
{
- bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
if (_struct.members().empty())
- {
- if (v050)
- m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed.");
- else
- m_errorReporter.warning(_struct.location(), "Defining empty structs is deprecated.");
- }
+ m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed.");
+
return true;
}
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index 1579df57..f5716bf9 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -52,6 +52,12 @@ private:
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual void endVisit(ModifierDefinition const& _modifier) override;
+ /// Reports an error if _statement is a VariableDeclarationStatement.
+ /// Used by if/while/for to check for single statement variable declarations
+ /// without a block.
+ void checkSingleStatementVariableDeclaration(ASTNode const& _statement);
+
+ virtual bool visit(IfStatement const& _ifStatement) override;
virtual bool visit(WhileStatement const& _whileStatement) override;
virtual void endVisit(WhileStatement const& _whileStatement) override;
virtual bool visit(ForStatement const& _forStatement) override;
@@ -66,12 +72,14 @@ private:
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
+ virtual bool visit(ContractDefinition const& _contract) override;
virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(FunctionTypeName const& _node) override;
- virtual bool visit(VariableDeclaration const& _declaration) override;
+ virtual bool visit(VariableDeclarationStatement const& _statement) override;
virtual bool visit(StructDefinition const& _struct) override;
+ virtual bool visit(Literal const& _literal) override;
ErrorReporter& m_errorReporter;
@@ -82,6 +90,7 @@ private:
bool m_versionPragmaFound = false;
int m_inLoopDepth = 0;
+ bool m_isInterface = false;
SourceUnit const* m_sourceUnit = nullptr;
};
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 2062458e..e023dc38 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -22,13 +22,16 @@
#include <libsolidity/analysis/TypeChecker.h>
#include <memory>
+#include <boost/algorithm/cxx11/all_of.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/ast/AST.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/ErrorReporter.h>
+#include <libdevcore/Algorithms.h>
using namespace std;
using namespace dev;
@@ -41,15 +44,13 @@ bool typeSupportedByOldABIEncoder(Type const& _type)
{
if (_type.dataStoredIn(DataLocation::Storage))
return true;
- else if (_type.category() == Type::Category::Struct)
+ if (_type.category() == Type::Category::Struct)
return false;
- else if (_type.category() == Type::Category::Array)
+ if (_type.category() == Type::Category::Array)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(_type);
auto base = arrayType.baseType();
- if (!typeSupportedByOldABIEncoder(*base))
- return false;
- else if (base->category() == Type::Category::Array && base->isDynamicallySized())
+ if (!typeSupportedByOldABIEncoder(*base) || (base->category() == Type::Category::Array && base->isDynamicallySized()))
return false;
}
return true;
@@ -125,10 +126,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
m_errorReporter.typeError(function->parameterList().location(), "Fallback function cannot take parameters.");
if (!function->returnParameters().empty())
m_errorReporter.typeError(function->returnParameterList()->location(), "Fallback function cannot return values.");
- if (
- _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) &&
- function->visibility() != FunctionDefinition::Visibility::External
- )
+ if (function->visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(function->location(), "Fallback function must be defined as \"external\".");
}
@@ -216,7 +214,7 @@ void TypeChecker::findDuplicateDefinitions(map<string, vector<T>> const& _defini
SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j)
- if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
+ if (FunctionType(*overloads[i]).hasEqualParameterTypes(FunctionType(*overloads[j])))
{
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
@@ -254,7 +252,7 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
FunctionTypePointer funType = make_shared<FunctionType>(*function);
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
{
- return funType->hasEqualArgumentTypes(*_funAndFlag.first);
+ return funType->hasEqualParameterTypes(*_funAndFlag.first);
});
if (it == overloads.end())
overloads.push_back(make_pair(funType, function->isImplemented()));
@@ -281,8 +279,6 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
void TypeChecker::checkContractBaseConstructorArguments(ContractDefinition const& _contract)
{
- bool const v050 = _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
// Determine the arguments that are used for the base constructors.
@@ -290,27 +286,19 @@ void TypeChecker::checkContractBaseConstructorArguments(ContractDefinition const
{
if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers())
- {
- auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(*modifier->name()));
- if (modifier->arguments())
+ if (auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(*modifier->name())))
{
- if (baseContract && baseContract->constructor())
- annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get());
- }
- else
- {
- if (v050)
- m_errorReporter.declarationError(
- modifier->location(),
- "Modifier-style base constructor call without arguments."
- );
+ if (modifier->arguments())
+ {
+ if (baseContract->constructor())
+ annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get());
+ }
else
- m_errorReporter.warning(
+ m_errorReporter.declarationError(
modifier->location(),
"Modifier-style base constructor call without arguments."
);
}
- }
for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts())
{
@@ -337,8 +325,6 @@ void TypeChecker::annotateBaseConstructorArguments(
ASTNode const* _argumentNode
)
{
- bool const v050 = _currentContract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
solAssert(_baseConstructor, "");
solAssert(_argumentNode, "");
@@ -351,7 +337,7 @@ void TypeChecker::annotateBaseConstructorArguments(
SourceLocation const* mainLocation = nullptr;
SecondarySourceLocation ssl;
-
+
if (
_currentContract.location().contains(previousNode->location()) ||
_currentContract.location().contains(_argumentNode->location())
@@ -367,18 +353,11 @@ void TypeChecker::annotateBaseConstructorArguments(
ssl.append("Second constructor call is here: ", previousNode->location());
}
- if (v050)
- m_errorReporter.declarationError(
- *mainLocation,
- ssl,
- "Base constructor arguments given twice."
- );
- else
- m_errorReporter.warning(
- *mainLocation,
- "Base constructor arguments given twice.",
- ssl
- );
+ m_errorReporter.declarationError(
+ *mainLocation,
+ ssl,
+ "Base constructor arguments given twice."
+ );
}
}
@@ -425,7 +404,7 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
FunctionType functionType(function);
FunctionType superType(super);
- if (!functionType.hasEqualArgumentTypes(superType))
+ if (!functionType.hasEqualParameterTypes(superType))
return;
if (!function.annotation().superFunction)
@@ -462,7 +441,7 @@ void TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefi
{
m_errorReporter.typeError(
function.location(),
- SecondarySourceLocation().append("Overriden function is here:", super.location()),
+ SecondarySourceLocation().append("Overridden function is here:", super.location()),
message
);
}
@@ -496,7 +475,7 @@ void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _co
for (auto const& it: externalDeclarations)
for (size_t i = 0; i < it.second.size(); ++i)
for (size_t j = i + 1; j < it.second.size(); ++j)
- if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second))
+ if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second))
m_errorReporter.typeError(
it.second[j].first->location(),
"Function overload clash during conversion to external types for arguments."
@@ -519,7 +498,12 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide()));
TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide()));
- bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front());
+ if (lhs.components().size() != rhs.components().size())
+ {
+ solAssert(m_errorReporter.hasErrors(), "");
+ return;
+ }
+
size_t storageToStorageCopies = 0;
size_t toStorageCopies = 0;
for (size_t i = 0; i < lhs.components().size(); ++i)
@@ -527,10 +511,8 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get());
if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer())
continue;
- size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i);
- solAssert(rhsPos < rhs.components().size(), "");
toStorageCopies++;
- if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage))
+ if (rhs.components()[i]->dataStoredIn(DataLocation::Storage))
storageToStorageCopies++;
}
if (storageToStorageCopies >= 1 && toStorageCopies >= 2)
@@ -543,6 +525,76 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
);
}
+TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2)
+{
+ vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
+ if (arguments.size() != 2)
+ m_errorReporter.typeError(
+ _functionCall.location(),
+ "This function takes two arguments, but " +
+ toString(arguments.size()) +
+ " were provided."
+ );
+ if (arguments.size() >= 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
+ m_errorReporter.typeError(
+ arguments.front()->location(),
+ "Invalid type for argument in function call. "
+ "Invalid implicit conversion from " +
+ type(*arguments.front())->toString() +
+ " to bytes memory requested."
+ );
+
+ if (arguments.size() < 2)
+ return {};
+
+ // The following is a rather syntactic restriction, but we check it here anyway:
+ // The second argument has to be a tuple expression containing type names.
+ TupleExpression const* tupleExpression = dynamic_cast<TupleExpression const*>(arguments[1].get());
+ if (!tupleExpression)
+ {
+ m_errorReporter.typeError(
+ arguments[1]->location(),
+ "The second argument to \"abi.decode\" has to be a tuple of types."
+ );
+ return {};
+ }
+
+ TypePointers components;
+ for (auto const& typeArgument: tupleExpression->components())
+ {
+ solAssert(typeArgument, "");
+ if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument).get()))
+ {
+ TypePointer actualType = argTypeType->actualType();
+ solAssert(actualType, "");
+ // We force memory because the parser currently cannot handle
+ // data locations. Furthermore, storage can be a little dangerous and
+ // calldata is not really implemented anyway.
+ actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType);
+ // We force address payable for address types.
+ if (actualType->category() == Type::Category::Address)
+ actualType = make_shared<AddressType>(StateMutability::Payable);
+ solAssert(
+ !actualType->dataStoredIn(DataLocation::CallData) &&
+ !actualType->dataStoredIn(DataLocation::Storage),
+ ""
+ );
+ if (!actualType->fullEncodingType(false, _abiEncoderV2, false))
+ m_errorReporter.typeError(
+ typeArgument->location(),
+ "Decoding type " + actualType->toString(false) + " not supported."
+ );
+ components.push_back(actualType);
+ }
+ else
+ {
+ m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name.");
+ components.push_back(make_shared<TupleType>());
+ }
+ }
+ return components;
+}
+
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
@@ -562,33 +614,18 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
if (arguments)
{
- bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
if (parameterTypes.size() != arguments->size())
{
- if (arguments->size() == 0 && !v050)
- m_errorReporter.warning(
- _inheritance.location(),
- "Wrong argument count for constructor call: " +
- toString(arguments->size()) +
- " arguments given but expected " +
- toString(parameterTypes.size()) +
- "."
- );
- else
- {
- m_errorReporter.typeError(
- _inheritance.location(),
- "Wrong argument count for constructor call: " +
- toString(arguments->size()) +
- " arguments given but expected " +
- toString(parameterTypes.size()) +
- "."
- );
- return;
- }
+ m_errorReporter.typeError(
+ _inheritance.location(),
+ "Wrong argument count for constructor call: " +
+ toString(arguments->size()) +
+ " arguments given but expected " +
+ toString(parameterTypes.size()) +
+ ". Remove parentheses if you do not want to provide arguments here."
+ );
}
- for (size_t i = 0; i < arguments->size(); ++i)
+ for (size_t i = 0; i < std::min(arguments->size(), parameterTypes.size()); ++i)
if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
m_errorReporter.typeError(
(*arguments)[i]->location(),
@@ -613,41 +650,44 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
bool TypeChecker::visit(StructDefinition const& _struct)
{
- if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
- m_errorReporter.typeError(_struct.location(), "Structs cannot be defined in interfaces.");
-
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
if (!type(*member)->canBeStored())
m_errorReporter.typeError(member->location(), "Type cannot be used in struct.");
// Check recursion, fatal error if detected.
- using StructPointer = StructDefinition const*;
- using StructPointersSet = set<StructPointer>;
- function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents)
- {
- if (_parents.count(_struct))
- m_errorReporter.fatalTypeError(_struct->location(), "Recursive struct definition.");
- StructPointersSet parents = _parents;
- parents.insert(_struct);
- for (ASTPointer<VariableDeclaration> const& member: _struct->members())
- if (type(*member)->category() == Type::Category::Struct)
+ auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t _depth)
+ {
+ if (_depth >= 256)
+ m_errorReporter.fatalDeclarationError(_struct.location(), "Struct definition exhausting cyclic dependency validator.");
+
+ for (ASTPointer<VariableDeclaration> const& member: _struct.members())
+ {
+ Type const* memberType = type(*member).get();
+ while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
{
- auto const& typeName = dynamic_cast<UserDefinedTypeName const&>(*member->typeName());
- check(&dynamic_cast<StructDefinition const&>(*typeName.annotation().referencedDeclaration), parents);
+ if (arrayType->isDynamicallySized())
+ break;
+ memberType = arrayType->baseType().get();
}
+ if (auto structType = dynamic_cast<StructType const*>(memberType))
+ if (_cycleDetector.run(structType->structDefinition()))
+ return;
+ }
};
- check(&_struct, StructPointersSet{});
+ if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
+ m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition.");
+ bool insideStruct = true;
+ swap(insideStruct, m_insideStruct);
ASTNode::listAccept(_struct.members(), *this);
+ m_insideStruct = insideStruct;
return false;
}
bool TypeChecker::visit(FunctionDefinition const& _function)
{
- bool isLibraryFunction =
- dynamic_cast<ContractDefinition const*>(_function.scope()) &&
- dynamic_cast<ContractDefinition const*>(_function.scope())->isLibrary();
+ bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library;
if (_function.isPayable())
{
if (isLibraryFunction)
@@ -657,7 +697,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
}
for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters())
{
- if (!type(*var)->canLiveOutsideStorage())
+ if (
+ type(*var)->category() == Type::Category::Mapping &&
+ !type(*var)->dataStoredIn(DataLocation::Storage)
+ )
+ m_errorReporter.typeError(var->location(), "Mapping types can only have a data location of \"storage\".");
+ else if (
+ !type(*var)->canLiveOutsideStorage() &&
+ _function.visibility() > FunctionDefinition::Visibility::Internal
+ )
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.");
@@ -696,18 +744,10 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
{
if (_function.isImplemented())
m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
- if (_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
- {
- if (_function.visibility() != FunctionDefinition::Visibility::External)
- m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external.");
- }
- else
- {
- if (_function.visibility() < FunctionDefinition::Visibility::Public)
- m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
- else if (_function.visibility() != FunctionDefinition::Visibility::External)
- m_errorReporter.warning(_function.location(), "Functions in interfaces should be declared external.");
- }
+
+ if (_function.visibility() != FunctionDefinition::Visibility::External)
+ m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external.");
+
if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
}
@@ -726,10 +766,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
// Forbid any variable declarations inside interfaces unless they are part of
- // a function's input/output parameters.
+ // * a function's input/output parameters,
+ // * or inside of a struct definition.
if (
m_scope->contractKind() == ContractDefinition::ContractKind::Interface
&& !_variable.isCallableParameter()
+ && !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@@ -742,12 +784,11 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// TypeChecker at the VariableDeclarationStatement level.
TypePointer varType = _variable.annotation().type;
solAssert(!!varType, "Failed to infer variable type.");
+
if (_variable.value())
expectType(*_variable.value(), *varType);
if (_variable.isConstant())
{
- if (!_variable.isStateVariable())
- m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
if (!_variable.type()->isValueType())
{
bool allowed = false;
@@ -760,19 +801,10 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!_variable.value())
m_errorReporter.typeError(_variable.location(), "Uninitialized \"constant\" variable.");
else if (!_variable.value()->annotation().isPure)
- {
- if (_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
- m_errorReporter.typeError(
- _variable.value()->location(),
- "Initial value for constant variable has to be compile-time constant."
- );
- else
- m_errorReporter.warning(
- _variable.value()->location(),
- "Initial value for constant variable has to be compile-time constant. "
- "This will fail to compile with the next breaking version change."
- );
- }
+ m_errorReporter.typeError(
+ _variable.value()->location(),
+ "Initial value for constant variable has to be compile-time constant."
+ );
}
if (!_variable.isStateVariable())
{
@@ -786,7 +818,9 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
)
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
- if (varType->category() == Type::Category::Array)
+ switch (varType->category())
+ {
+ case Type::Category::Array:
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
if (
((arrayType->location() == DataLocation::Memory) ||
@@ -794,17 +828,22 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
!arrayType->validForCalldata()
)
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
+ break;
+ case Type::Category::Mapping:
+ if (auto mappingType = dynamic_cast<MappingType const*>(varType.get()))
+ if (
+ mappingType->keyType()->isDynamicallySized() &&
+ _variable.visibility() == Declaration::Visibility::Public
+ )
+ m_errorReporter.typeError(_variable.location(), "Dynamically-sized keys for public mappings are not supported.");
+ break;
+ default:
+ break;
+ }
return false;
}
-bool TypeChecker::visit(EnumDefinition const& _enum)
-{
- if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
- m_errorReporter.typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
- return false;
-}
-
void TypeChecker::visitManually(
ModifierInvocation const& _modifier,
vector<ContractDefinition const*> const& _bases
@@ -924,6 +963,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
return size_t(-1);
Declaration const* declaration = ref->second.declaration;
solAssert(!!declaration, "");
+ bool requiresStorage = ref->second.isSlot || ref->second.isOffset;
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (var->isConstant())
@@ -931,7 +971,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly.");
return size_t(-1);
}
- else if (ref->second.isSlot || ref->second.isOffset)
+ else if (requiresStorage)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
@@ -951,7 +991,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
}
else if (var->type()->dataStoredIn(DataLocation::Storage))
{
- m_errorReporter.typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
+ m_errorReporter.typeError(_identifier.location, "You have to use the _slot or _offset suffix to access storage reference variables.");
return size_t(-1);
}
else if (var->type()->sizeOnStack() != 1)
@@ -963,6 +1003,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
return size_t(-1);
}
}
+ else if (requiresStorage)
+ {
+ m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
+ return size_t(-1);
+ }
else if (_context == julia::IdentifierContext::LValue)
{
m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
@@ -994,15 +1039,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
};
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
_inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
- boost::optional<Error::Type> errorTypeForLoose =
- m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) ?
- Error::Type::SyntaxError :
- Error::Type::Warning;
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
m_evmVersion,
- errorTypeForLoose,
+ Error::Type::SyntaxError,
assembly::AsmFlavour::Loose,
identifierAccess
);
@@ -1041,9 +1082,13 @@ bool TypeChecker::visit(ForStatement const& _forStatement)
void TypeChecker::endVisit(Return const& _return)
{
+ ParameterList const* params = _return.annotation().functionReturnParameters;
if (!_return.expression())
+ {
+ if (params && !params->parameters().empty())
+ m_errorReporter.typeError(_return.location(), "Return arguments required.");
return;
- ParameterList const* params = _return.annotation().functionReturnParameters;
+ }
if (!params)
{
m_errorReporter.typeError(_return.location(), "Return arguments not allowed.");
@@ -1094,29 +1139,87 @@ void TypeChecker::endVisit(EmitStatement const& _emit)
m_insideEmitStatement = false;
}
+namespace
+{
+/**
+ * @returns a suggested left-hand-side of a multi-variable declaration contairing
+ * the variable declarations given in @a _decls.
+ */
+string createTupleDecl(vector<ASTPointer<VariableDeclaration>> const& _decls)
+{
+ vector<string> components;
+ for (ASTPointer<VariableDeclaration> const& decl: _decls)
+ if (decl)
+ {
+ solAssert(decl->annotation().type, "");
+ components.emplace_back(decl->annotation().type->toString(false) + " " + decl->name());
+ }
+ else
+ components.emplace_back();
+
+ if (_decls.size() == 1)
+ return components.front();
+ else
+ return "(" + boost::algorithm::join(components, ", ") + ")";
+}
+
+bool typeCanBeExpressed(vector<ASTPointer<VariableDeclaration>> const& decls)
+{
+ for (ASTPointer<VariableDeclaration> const& decl: decls)
+ {
+ // skip empty tuples (they can be expressed of course)
+ if (!decl)
+ continue;
+
+ if (!decl->annotation().type)
+ return false;
+
+ if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type.get()))
+ if (
+ functionType->kind() != FunctionType::Kind::Internal &&
+ functionType->kind() != FunctionType::Kind::External
+ )
+ return false;
+ }
+
+ return true;
+}
+}
+
bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
{
- bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (!_statement.initialValue())
{
// No initial value is only permitted for single variables with specified type.
if (_statement.declarations().size() != 1 || !_statement.declarations().front())
- m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
+ {
+ if (boost::algorithm::all_of_equal(_statement.declarations(), nullptr))
+ {
+ // The syntax checker has already generated an error for this case (empty LHS tuple).
+ solAssert(m_errorReporter.hasErrors(), "");
+
+ // It is okay to return here, as there are no named components on the
+ // left-hand-side that could cause any damage later.
+ return false;
+ }
+ else
+ // Bailing out *fatal* here, as those (untyped) vars may be used later, and diagnostics wouldn't be helpful then.
+ m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
+ }
+
VariableDeclaration const& varDecl = *_statement.declarations().front();
if (!varDecl.annotation().type)
- m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
+ m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
+
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get()))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
string errorText{"Uninitialized storage pointer."};
- if (varDecl.referenceLocation() == VariableDeclaration::Location::Default)
+ if (varDecl.referenceLocation() == VariableDeclaration::Location::Unspecified)
errorText += " Did you mean '<type> memory " + varDecl.name() + "'?";
solAssert(m_scope, "");
- if (v050)
- m_errorReporter.declarationError(varDecl.location(), errorText);
- else
- m_errorReporter.warning(varDecl.location(), errorText);
+ m_errorReporter.declarationError(varDecl.location(), errorText);
}
}
else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
@@ -1138,85 +1241,34 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
else
valueTypes = TypePointers{type(*_statement.initialValue())};
- // Determine which component is assigned to which variable.
- // If numbers do not match, fill up if variables begin or end empty (not both).
- vector<VariableDeclaration const*>& assignments = _statement.annotation().assignments;
- assignments.resize(valueTypes.size(), nullptr);
vector<ASTPointer<VariableDeclaration>> const& variables = _statement.declarations();
if (variables.empty())
- {
- if (!valueTypes.empty())
- m_errorReporter.fatalTypeError(
- _statement.location(),
- "Too many components (" +
- toString(valueTypes.size()) +
- ") in value for variable assignment (0) needed"
- );
- }
+ // We already have an error for this in the SyntaxChecker.
+ solAssert(m_errorReporter.hasErrors(), "");
else if (valueTypes.size() != variables.size())
- {
- if (v050)
- m_errorReporter.fatalTypeError(
- _statement.location(),
- "Different number of components on the left hand side (" +
- toString(variables.size()) +
- ") than on the right hand side (" +
- toString(valueTypes.size()) +
- ")."
- );
- else if (!variables.front() && !variables.back())
- m_errorReporter.fatalTypeError(
- _statement.location(),
- "Wildcard both at beginning and end of variable declaration list is only allowed "
- "if the number of components is equal."
- );
- else
- m_errorReporter.warning(
- _statement.location(),
- "Different number of components on the left hand side (" +
- toString(variables.size()) +
- ") than on the right hand side (" +
- toString(valueTypes.size()) +
- ")."
- );
- }
- size_t minNumValues = variables.size();
- if (!variables.empty() && (!variables.back() || !variables.front()))
- --minNumValues;
- if (valueTypes.size() < minNumValues)
- m_errorReporter.fatalTypeError(
- _statement.location(),
- "Not enough components (" +
- toString(valueTypes.size()) +
- ") in value to assign all variables (" +
- toString(minNumValues) + ")."
- );
- if (valueTypes.size() > variables.size() && variables.front() && variables.back())
- m_errorReporter.fatalTypeError(
+ m_errorReporter.typeError(
_statement.location(),
- "Too many components (" +
+ "Different number of components on the left hand side (" +
+ toString(variables.size()) +
+ ") than on the right hand side (" +
toString(valueTypes.size()) +
- ") in value for variable assignment (" +
- toString(minNumValues) +
- " needed)."
+ ")."
);
- bool fillRight = !variables.empty() && (!variables.back() || variables.front());
- for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i)
- if (fillRight)
- assignments[i] = variables[i].get();
- else
- assignments[assignments.size() - i - 1] = variables[variables.size() - i - 1].get();
- for (size_t i = 0; i < assignments.size(); ++i)
+ bool autoTypeDeductionNeeded = false;
+
+ for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i)
{
- if (!assignments[i])
+ if (!variables[i])
continue;
- VariableDeclaration const& var = *assignments[i];
+ VariableDeclaration const& var = *variables[i];
solAssert(!var.value(), "Value has to be tied to statement.");
TypePointer const& valueComponentType = valueTypes[i];
solAssert(!!valueComponentType, "");
if (!var.annotation().type)
{
+ autoTypeDeductionNeeded = true;
+
// Infer type from value.
solAssert(!var.typeName(), "");
var.annotation().type = valueComponentType->mobileType();
@@ -1260,14 +1312,6 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
}
else
solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type.get()), "Unknown type.");
-
- m_errorReporter.warning(
- _statement.location(),
- "The type of this variable was inferred as " +
- typeName +
- extension +
- ". This is probably not desired. Use an explicit type to silence this warning."
- );
}
var.accept(*this);
@@ -1304,6 +1348,23 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
}
}
}
+
+ if (autoTypeDeductionNeeded)
+ {
+ if (!typeCanBeExpressed(variables))
+ m_errorReporter.syntaxError(
+ _statement.location(),
+ "Use of the \"var\" keyword is disallowed. "
+ "Type cannot be expressed in syntax."
+ );
+ else
+ m_errorReporter.syntaxError(
+ _statement.location(),
+ "Use of the \"var\" keyword is disallowed. "
+ "Use explicit declaration `" + createTupleDecl(variables) + " = ...´ instead."
+ );
+ }
+
return false;
}
@@ -1321,7 +1382,8 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
if (
kind == FunctionType::Kind::BareCall ||
kind == FunctionType::Kind::BareCallCode ||
- kind == FunctionType::Kind::BareDelegateCall
+ kind == FunctionType::Kind::BareDelegateCall ||
+ kind == FunctionType::Kind::BareStaticCall
)
m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used.");
else if (kind == FunctionType::Kind::Send)
@@ -1375,12 +1437,41 @@ bool TypeChecker::visit(Conditional const& _conditional)
return false;
}
+void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& _expression)
+{
+ if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
+ {
+ auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
+ auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { _type.shared_from_this() };
+
+ solAssert(tupleExpression->components().size() == types.size(), "");
+ for (size_t i = 0; i < types.size(); i++)
+ if (types[i])
+ {
+ solAssert(!!tupleExpression->components()[i], "");
+ checkExpressionAssignment(*types[i], *tupleExpression->components()[i]);
+ }
+ }
+ else if (_type.category() == Type::Category::Mapping)
+ {
+ bool isLocalOrReturn = false;
+ if (auto const* identifier = dynamic_cast<Identifier const*>(&_expression))
+ if (auto const *variableDeclaration = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
+ if (variableDeclaration->isLocalOrReturn())
+ isLocalOrReturn = true;
+ if (!isLocalOrReturn)
+ m_errorReporter.typeError(_expression.location(), "Mappings cannot be assigned to.");
+ }
+}
+
bool TypeChecker::visit(Assignment const& _assignment)
{
- bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
requireLValue(_assignment.leftHandSide());
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
+
+ checkExpressionAssignment(*t, _assignment.leftHandSide());
+
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
{
if (_assignment.assignmentOperator() != Token::Assign)
@@ -1394,30 +1485,8 @@ bool TypeChecker::visit(Assignment const& _assignment)
expectType(_assignment.rightHandSide(), *tupleType);
// expectType does not cause fatal errors, so we have to check again here.
- if (TupleType const* rhsType = dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get()))
- {
+ if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get()))
checkDoubleStorageAssignment(_assignment);
- // @todo For 0.5.0, this code shoud move to TupleType::isImplicitlyConvertibleTo,
- // but we cannot do it right now.
- if (rhsType->components().size() != tupleType->components().size())
- {
- string message =
- "Different number of components on the left hand side (" +
- toString(tupleType->components().size()) +
- ") than on the right hand side (" +
- toString(rhsType->components().size()) +
- ").";
- if (v050)
- m_errorReporter.typeError(_assignment.location(), message);
- else
- m_errorReporter.warning(_assignment.location(), message);
- }
- }
- }
- else if (t->category() == Type::Category::Mapping)
- {
- m_errorReporter.typeError(_assignment.location(), "Mappings cannot be assigned to.");
- _assignment.rightHandSide().accept(*this);
}
else if (_assignment.assignmentOperator() == Token::Assign)
expectType(_assignment.rightHandSide(), *t);
@@ -1469,14 +1538,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
}
else
{
- bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
bool isPure = true;
TypePointer inlineArrayType;
for (size_t i = 0; i < components.size(); ++i)
{
- // Outside of an lvalue-context, the only situation where a component can be empty is (x,).
- if (!components[i] && !(i == 1 && components.size() == 2))
+ if (!components[i])
m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
else if (components[i])
{
@@ -1488,10 +1555,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
if (_tuple.isInlineArray())
m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty.");
- if (v050)
- m_errorReporter.fatalTypeError(components[i]->location(), "Tuple component cannot be empty.");
- else
- m_errorReporter.warning(components[i]->location(), "Tuple component cannot be empty.");
+ m_errorReporter.typeError(components[i]->location(), "Tuple component cannot be empty.");
}
// Note: code generation will visit each of the expression even if they are not assigned from.
@@ -1529,11 +1593,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
if (components.size() == 1)
_tuple.annotation().type = type(*components[0]);
else
- {
- if (components.size() == 2 && !components[1])
- types.pop_back();
_tuple.annotation().type = make_shared<TupleType>(types);
- }
}
}
@@ -1667,19 +1727,47 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else
{
TypePointer const& argType = type(*arguments.front());
+ // Resulting data location is memory unless we are converting from a reference
+ // type with a different data location.
+ // (data location cannot yet be specified for type conversions)
+ DataLocation dataLoc = DataLocation::Memory;
if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get()))
- // do not change the data location when converting
- // (data location cannot yet be specified for type conversions)
- resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType);
+ dataLoc = argRefType->location();
+ resultType = ReferenceType::copyForLocationIfReference(dataLoc, resultType);
if (!argType->isExplicitlyConvertibleTo(*resultType))
- m_errorReporter.typeError(
- _functionCall.location(),
- "Explicit type conversion not allowed from \"" +
- argType->toString() +
- "\" to \"" +
- resultType->toString() +
- "\"."
- );
+ {
+ if (resultType->category() == Type::Category::Contract && argType->category() == Type::Category::Address)
+ {
+ solAssert(dynamic_cast<ContractType const*>(resultType.get())->isPayable(), "");
+ solAssert(dynamic_cast<AddressType const*>(argType.get())->stateMutability() < StateMutability::Payable, "");
+ SecondarySourceLocation ssl;
+ if (auto const* identifier = dynamic_cast<Identifier const*>(arguments.front().get()))
+ if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
+ ssl.append("Did you mean to declare this variable as \"address payable\"?", variableDeclaration->location());
+ m_errorReporter.typeError(
+ _functionCall.location(), ssl,
+ "Explicit type conversion not allowed from non-payable \"address\" to \"" +
+ resultType->toString() +
+ "\", which has a payable fallback function."
+ );
+ }
+ else
+ m_errorReporter.typeError(
+ _functionCall.location(),
+ "Explicit type conversion not allowed from \"" +
+ argType->toString() +
+ "\" to \"" +
+ resultType->toString() +
+ "\"."
+ );
+ }
+ if (resultType->category() == Type::Category::Address)
+ {
+ bool payable = true;
+ if (auto const* contractType = dynamic_cast<ContractType const*>(argType.get()))
+ payable = contractType->isPayable();
+ resultType = make_shared<AddressType>(payable ? StateMutability::Payable : StateMutability::NonPayable);
+ }
}
_functionCall.annotation().type = resultType;
_functionCall.annotation().isPure = isPure;
@@ -1715,39 +1803,18 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
return false;
}
- auto returnTypes =
- allowDynamicTypes ?
- functionType->returnParameterTypes() :
- functionType->returnParameterTypesWithoutDynamicTypes();
- if (returnTypes.size() == 1)
- _functionCall.annotation().type = returnTypes.front();
- else
- _functionCall.annotation().type = make_shared<TupleType>(returnTypes);
-
- bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+ if (functionType->kind() == FunctionType::Kind::BareStaticCall && !m_evmVersion.hasStaticCall())
+ m_errorReporter.typeError(_functionCall.location(), "\"staticcall\" is not supported by the VM version.");
if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
- string msg;
- if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3)
- msg = "\"sha3\" has been deprecated in favour of \"keccak256\"";
+ if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::KECCAK256)
+ m_errorReporter.typeError(_functionCall.location(), "\"sha3\" has been deprecated in favour of \"keccak256\"");
else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct)
- msg = "\"suicide\" has been deprecated in favour of \"selfdestruct\"";
- if (!msg.empty())
- {
- if (v050)
- m_errorReporter.typeError(_functionCall.location(), msg);
- else
- m_errorReporter.warning(_functionCall.location(), msg);
- }
+ m_errorReporter.typeError(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\"");
}
if (!m_insideEmitStatement && functionType->kind() == FunctionType::Kind::Event)
- {
- if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
- m_errorReporter.typeError(_functionCall.location(), "Event invocations have to be prefixed by \"emit\".");
- else
- m_errorReporter.warning(_functionCall.location(), "Invoking events without \"emit\" prefix is deprecated.");
- }
+ m_errorReporter.typeError(_functionCall.location(), "Event invocations have to be prefixed by \"emit\".");
TypePointers parameterTypes = functionType->parameterTypes();
@@ -1758,58 +1825,31 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
auto const& argType = type(*arguments[i]);
if (auto literal = dynamic_cast<RationalNumberType const*>(argType.get()))
{
- /* If no mobile type is available an error will be raised elsewhere. */
if (literal->mobileType())
+ m_errorReporter.typeError(
+ arguments[i]->location(),
+ "Cannot perform packed encoding for a literal. Please convert it to an explicit type first."
+ );
+ else
{
- if (v050)
- m_errorReporter.typeError(
- arguments[i]->location(),
- "Cannot perform packed encoding for a literal. Please convert it to an explicit type first."
- );
- else
- m_errorReporter.warning(
- arguments[i]->location(),
- "The type of \"" +
- argType->toString() +
- "\" was inferred as " +
- literal->mobileType()->toString() +
- ". This is probably not desired. Use an explicit type to silence this warning."
- );
+ /* If no mobile type is available an error will be raised elsewhere. */
+ solAssert(m_errorReporter.hasErrors(), "");
}
}
}
}
- if (functionType->takesSinglePackedBytesParameter())
- {
- if (
- (arguments.size() > 1) ||
- (arguments.size() == 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
- )
- {
- string msg =
- "This function only accepts a single \"bytes\" argument. Please use "
- "\"abi.encodePacked(...)\" or a similar function to encode the data.";
- if (v050)
- m_errorReporter.typeError(_functionCall.location(), msg);
- else
- m_errorReporter.warning(_functionCall.location(), msg);
- }
+ bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
- if (arguments.size() == 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
- {
- string msg =
- "The provided argument of type " +
- type(*arguments.front())->toString() +
- " is not implicitly convertible to expected type bytes memory.";
- if (v050)
- m_errorReporter.typeError(_functionCall.location(), msg);
- else
- m_errorReporter.warning(_functionCall.location(), msg);
- }
- }
+ // Will be assigned to .type at the end (turning multi-elements into a tuple).
+ TypePointers returnTypes =
+ allowDynamicTypes ?
+ functionType->returnParameterTypes() :
+ functionType->returnParameterTypesWithoutDynamicTypes();
- if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
+ if (functionType->kind() == FunctionType::Kind::ABIDecode)
+ returnTypes = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2);
+ else if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
{
solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
m_errorReporter.typeError(
@@ -1840,12 +1880,31 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
+ else if (
+ functionType->kind() == FunctionType::Kind::BareCall ||
+ functionType->kind() == FunctionType::Kind::BareCallCode ||
+ functionType->kind() == FunctionType::Kind::BareDelegateCall ||
+ functionType->kind() == FunctionType::Kind::BareStaticCall
+ )
+ {
+ if (arguments.empty())
+ msg += " This function requires a single bytes argument. Use \"\" as argument to provide empty calldata.";
+ else
+ msg += " This function requires a single bytes argument. If all your arguments are value types, you can use abi.encode(...) to properly generate it.";
+ }
+ else if (
+ functionType->kind() == FunctionType::Kind::KECCAK256 ||
+ functionType->kind() == FunctionType::Kind::SHA256 ||
+ functionType->kind() == FunctionType::Kind::RIPEMD160
+ )
+ msg +=
+ " This function requires a single bytes argument."
+ " Use abi.encodePacked(...) to obtain the pre-0.5.0 behaviour"
+ " or abi.encode(...) to use ABI encoding.";
m_errorReporter.typeError(_functionCall.location(), msg);
}
else if (isPositionalCall)
{
- bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
-
for (size_t i = 0; i < arguments.size(); ++i)
{
auto const& argType = type(*arguments[i]);
@@ -1858,33 +1917,36 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
errored = true;
}
- if (!errored)
- {
- TypePointer encodingType;
- if (
- argType->mobileType() &&
- argType->mobileType()->interfaceType(false) &&
- argType->mobileType()->interfaceType(false)->encodingType()
- )
- encodingType = argType->mobileType()->interfaceType(false)->encodingType();
- // Structs are fine as long as ABIV2 is activated and we do not do packed encoding.
- if (!encodingType || (
- dynamic_cast<StructType const*>(encodingType.get()) &&
- !(abiEncodeV2 && functionType->padArguments())
- ))
- m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded.");
- }
+ if (!errored && !argType->fullEncodingType(false, abiEncoderV2, !functionType->padArguments()))
+ m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded.");
}
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
- m_errorReporter.typeError(
- arguments[i]->location(),
+ {
+ string msg =
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*arguments[i])->toString() +
" to " +
parameterTypes[i]->toString() +
- " requested."
- );
+ " requested.";
+ if (
+ functionType->kind() == FunctionType::Kind::BareCall ||
+ functionType->kind() == FunctionType::Kind::BareCallCode ||
+ functionType->kind() == FunctionType::Kind::BareDelegateCall ||
+ functionType->kind() == FunctionType::Kind::BareStaticCall
+ )
+ msg += " This function requires a single bytes argument. If all your arguments are value types, you can use abi.encode(...) to properly generate it.";
+ else if (
+ functionType->kind() == FunctionType::Kind::KECCAK256 ||
+ functionType->kind() == FunctionType::Kind::SHA256 ||
+ functionType->kind() == FunctionType::Kind::RIPEMD160
+ )
+ msg +=
+ " This function requires a single bytes argument."
+ " Use abi.encodePacked(...) to obtain the pre-0.5.0 behaviour"
+ " or abi.encode(...) to use ABI encoding.";
+ m_errorReporter.typeError(arguments[i]->location(), msg);
+ }
}
}
else
@@ -1894,7 +1956,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (functionType->takesArbitraryParameters())
m_errorReporter.typeError(
_functionCall.location(),
- "Named arguments cannnot be used for functions that take arbitrary parameters."
+ "Named arguments cannot be used for functions that take arbitrary parameters."
);
else if (parameterNames.size() > argumentNames.size())
m_errorReporter.typeError(_functionCall.location(), "Some argument names are missing.");
@@ -1938,12 +2000,17 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (!found)
m_errorReporter.typeError(
_functionCall.location(),
- "Named argument does not match function declaration."
+ "Named argument \"" + *argumentNames[i] + "\" does not match function declaration."
);
}
}
}
+ if (returnTypes.size() == 1)
+ _functionCall.annotation().type = returnTypes.front();
+ else
+ _functionCall.annotation().type = make_shared<TupleType>(returnTypes);
+
return false;
}
@@ -2040,6 +2107,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
else
++it;
}
+
+ auto& annotation = _memberAccess.annotation();
+
if (possibleMembers.size() == 0)
{
if (initialMemberCount == 0)
@@ -2057,11 +2127,21 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
" outside of storage."
);
}
+ string errorMsg = "Member \"" + memberName + "\" not found or not visible "
+ "after argument-dependent lookup in " + exprType->toString() +
+ (memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".");
+ if (exprType->category() == Type::Category::Contract)
+ for (auto const& addressMember: AddressType(StateMutability::Payable).nativeMembers(nullptr))
+ if (addressMember.name == memberName)
+ {
+ Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
+ string varName = var ? var->name() : "...";
+ errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member.";
+ break;
+ }
m_errorReporter.fatalTypeError(
_memberAccess.location(),
- "Member \"" + memberName + "\" not found or not visible "
- "after argument-dependent lookup in " + exprType->toString() +
- (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "")
+ errorMsg
);
}
else if (possibleMembers.size() > 1)
@@ -2069,10 +2149,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
_memberAccess.location(),
"Member \"" + memberName + "\" not unique "
"after argument-dependent lookup in " + exprType->toString() +
- (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "")
+ (memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".")
);
- auto& annotation = _memberAccess.annotation();
annotation.referencedDeclaration = possibleMembers.front().declaration;
annotation.type = possibleMembers.front().type;
@@ -2081,7 +2160,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
m_errorReporter.typeError(
_memberAccess.location(),
"Function \"" + memberName + "\" cannot be called on an object of type " +
- exprType->toString() + " (expected " + funType->selfType()->toString() + ")"
+ exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
);
if (exprType->category() == Type::Category::Struct)
@@ -2105,20 +2184,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (exprType->category() == Type::Category::Contract)
{
- // Warn about using address members on contracts
- bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr))
- if (addressMember.name == memberName && *annotation.type == *addressMember.type)
- {
- solAssert(!v050, "Address member still present on contract in v0.5.0.");
- m_errorReporter.warning(
- _memberAccess.location(),
- "Using contract member \"" + memberName +"\" inherited from the address type is deprecated." +
- " Convert the contract to \"address\" type to access the member,"
- " for example use \"address(contract)." + memberName + "\" instead."
- );
- }
-
// Warn about using send or transfer with a non-payable fallback function.
if (auto callType = dynamic_cast<FunctionType const*>(type(_memberAccess).get()))
{
@@ -2172,12 +2237,13 @@ bool TypeChecker::visit(IndexAccess const& _access)
else
{
expectType(*index, IntegerType(256));
- if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index).get()))
- {
- if (!numberType->isFractional()) // error is reported above
+ if (!m_errorReporter.hasErrors())
+ if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index).get()))
+ {
+ solAssert(!numberType->isFractional(), "");
if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr))
m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
- }
+ }
}
resultType = actualType.baseType();
isLValue = actualType.location() != DataLocation::CallData;
@@ -2313,51 +2379,46 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
void TypeChecker::endVisit(Literal const& _literal)
{
- bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
-
if (_literal.looksLikeAddress())
{
- if (_literal.passesAddressChecksum())
- _literal.annotation().type = make_shared<IntegerType>(160, IntegerType::Modifier::Address);
- else
- m_errorReporter.warning(
+ // Assign type here if it even looks like an address. This prevents double errors for invalid addresses
+ _literal.annotation().type = make_shared<AddressType>(StateMutability::Payable);
+
+ string msg;
+ if (_literal.value().length() != 42) // "0x" + 40 hex digits
+ // looksLikeAddress enforces that it is a hex literal starting with "0x"
+ msg =
+ "This looks like an address but is not exactly 40 hex digits. It is " +
+ to_string(_literal.value().length() - 2) +
+ " hex digits.";
+ else if (!_literal.passesAddressChecksum())
+ {
+ msg = "This looks like an address but has an invalid checksum.";
+ if (!_literal.getChecksummedAddress().empty())
+ msg += " Correct checksummed address: \"" + _literal.getChecksummedAddress() + "\".";
+ }
+
+ if (!msg.empty())
+ m_errorReporter.syntaxError(
_literal.location(),
- "This looks like an address but has an invalid checksum. "
- "If this is not used as an address, please prepend '00'. " +
- (!_literal.getChecksummedAddress().empty() ? "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " : "") +
+ msg +
+ " If this is not used as an address, please prepend '00'. " +
"For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals"
);
}
if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None)
- {
- if (v050)
- m_errorReporter.fatalTypeError(
- _literal.location(),
- "Hexadecimal numbers cannot be used with unit denominations. "
- "You can use an expression of the form \"0x1234 * 1 day\" instead."
- );
- else
- m_errorReporter.warning(
- _literal.location(),
- "Hexadecimal numbers with unit denominations are deprecated. "
- "You can use an expression of the form \"0x1234 * 1 day\" instead."
- );
- }
+ m_errorReporter.fatalTypeError(
+ _literal.location(),
+ "Hexadecimal numbers cannot be used with unit denominations. "
+ "You can use an expression of the form \"0x1234 * 1 day\" instead."
+ );
if (_literal.subDenomination() == Literal::SubDenomination::Year)
- {
- if (v050)
- m_errorReporter.typeError(
- _literal.location(),
- "Using \"years\" as a unit denomination is deprecated."
- );
- else
- m_errorReporter.warning(
- _literal.location(),
- "Using \"years\" as a unit denomination is deprecated."
- );
- }
+ m_errorReporter.typeError(
+ _literal.location(),
+ "Using \"years\" as a unit denomination is deprecated."
+ );
if (!_literal.annotation().type)
_literal.annotation().type = Type::forLiteral(_literal);
@@ -2426,22 +2487,6 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
"."
);
}
-
- if (
- type(_expression)->category() == Type::Category::RationalNumber &&
- _expectedType.category() == Type::Category::FixedBytes
- )
- {
- auto literal = dynamic_cast<Literal const*>(&_expression);
-
- if (literal && !literal->isHexNumber())
- m_errorReporter.warning(
- _expression.location(),
- "Decimal literal assigned to bytesXX variable will be left-aligned. "
- "Use an explicit conversion to silence this warning."
- );
- }
-
}
void TypeChecker::requireLValue(Expression const& _expression)
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index 2245abd6..8d25a88e 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -68,7 +68,7 @@ private:
void checkContractDuplicateFunctions(ContractDefinition const& _contract);
void checkContractDuplicateEvents(ContractDefinition const& _contract);
void checkContractIllegalOverrides(ContractDefinition const& _contract);
- /// Reports a type error with an appropiate message if overriden function signature differs.
+ /// Reports a type error with an appropriate message if overridden function signature differs.
/// Also stores the direct super function in the AST annotations.
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
@@ -87,13 +87,20 @@ private:
/// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage.
/// Should only be called if the left hand side is tuple-typed.
void checkDoubleStorageAssignment(Assignment const& _assignment);
+ // Checks whether the expression @arg _expression can be assigned from type @arg _type
+ // and reports an error, if not.
+ void checkExpressionAssignment(Type const& _type, Expression const& _expression);
+
+ /// Performs type checks for ``abi.decode(bytes memory, (...))`` and returns the
+ /// vector of return types (which is basically the second argument) if successful. It returns
+ /// the empty vector on error.
+ TypePointers typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2);
virtual void endVisit(InheritanceSpecifier const& _inheritance) override;
virtual void endVisit(UsingForDirective const& _usingFor) override;
virtual bool visit(StructDefinition const& _struct) override;
virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(VariableDeclaration const& _variable) override;
- virtual bool visit(EnumDefinition const& _enum) override;
/// We need to do this manually because we want to pass the bases of the current contract in
/// case this is a base constructor call.
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);
@@ -147,6 +154,9 @@ private:
/// Flag indicating whether we are currently inside an EmitStatement.
bool m_insideEmitStatement = false;
+ /// Flag indicating whether we are currently inside a StructDefinition.
+ bool m_insideStruct = false;
+
ErrorReporter& m_errorReporter;
};
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index d9843012..113a3177 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -116,31 +116,22 @@ private:
bool ViewPureChecker::check()
{
- // The bool means "enforce view with errors".
- map<ContractDefinition const*, bool> contracts;
+ vector<ContractDefinition const*> contracts;
for (auto const& node: m_ast)
{
SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get());
solAssert(source, "");
- bool enforceView = source->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- for (ContractDefinition const* c: source->filteredNodes<ContractDefinition>(source->nodes()))
- contracts[c] = enforceView;
+ contracts += source->filteredNodes<ContractDefinition>(source->nodes());
}
// Check modifiers first to infer their state mutability.
for (auto const& contract: contracts)
- {
- m_enforceViewWithError = contract.second;
- for (ModifierDefinition const* mod: contract.first->functionModifiers())
+ for (ModifierDefinition const* mod: contract->functionModifiers())
mod->accept(*this);
- }
for (auto const& contract: contracts)
- {
- m_enforceViewWithError = contract.second;
- contract.first->accept(*this);
- }
+ contract->accept(*this);
return !m_errors;
}
@@ -151,7 +142,7 @@ bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
{
solAssert(!m_currentFunction, "");
m_currentFunction = &_funDef;
- m_currentBestMutability = StateMutability::Pure;
+ m_bestMutabilityAndLocation = {StateMutability::Pure, _funDef.location()};
return true;
}
@@ -159,7 +150,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
{
solAssert(m_currentFunction == &_funDef, "");
if (
- m_currentBestMutability < _funDef.stateMutability() &&
+ m_bestMutabilityAndLocation.mutability < _funDef.stateMutability() &&
_funDef.stateMutability() != StateMutability::Payable &&
_funDef.isImplemented() &&
!_funDef.isConstructor() &&
@@ -168,22 +159,22 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
)
m_errorReporter.warning(
_funDef.location(),
- "Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability)
+ "Function state mutability can be restricted to " + stateMutabilityToString(m_bestMutabilityAndLocation.mutability)
);
m_currentFunction = nullptr;
}
-bool ViewPureChecker::visit(ModifierDefinition const&)
+bool ViewPureChecker::visit(ModifierDefinition const& _modifier)
{
solAssert(m_currentFunction == nullptr, "");
- m_currentBestMutability = StateMutability::Pure;
+ m_bestMutabilityAndLocation = {StateMutability::Pure, _modifier.location()};
return true;
}
void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef)
{
solAssert(m_currentFunction == nullptr, "");
- m_inferredMutability[&_modifierDef] = m_currentBestMutability;
+ m_inferredMutability[&_modifierDef] = std::move(m_bestMutabilityAndLocation);
}
void ViewPureChecker::endVisit(Identifier const& _identifier)
@@ -228,39 +219,72 @@ void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
}(_inlineAssembly.operations());
}
-void ViewPureChecker::reportMutability(StateMutability _mutability, SourceLocation const& _location)
+void ViewPureChecker::reportMutability(
+ StateMutability _mutability,
+ SourceLocation const& _location,
+ boost::optional<SourceLocation> const& _nestedLocation
+)
{
- if (m_currentFunction && m_currentFunction->stateMutability() < _mutability)
+ if (_mutability > m_bestMutabilityAndLocation.mutability)
+ m_bestMutabilityAndLocation = MutabilityAndLocation{_mutability, _location};
+ if (!m_currentFunction || _mutability <= m_currentFunction->stateMutability())
+ return;
+
+ // Check for payable here, because any occurrence of `msg.value`
+ // will set mutability to payable.
+ if (_mutability == StateMutability::View || (
+ _mutability == StateMutability::Payable &&
+ m_currentFunction->stateMutability() == StateMutability::Pure
+ ))
{
- string text;
- if (_mutability == StateMutability::View)
- text =
- "Function declared as pure, but this expression (potentially) reads from the "
- "environment or state and thus requires \"view\".";
- else if (_mutability == StateMutability::NonPayable)
- text =
- "Function declared as " +
- stateMutabilityToString(m_currentFunction->stateMutability()) +
- ", but this expression (potentially) modifies the state and thus "
- "requires non-payable (the default) or payable.";
- else
- solAssert(false, "");
-
- solAssert(
- m_currentFunction->stateMutability() == StateMutability::View ||
- m_currentFunction->stateMutability() == StateMutability::Pure,
- ""
+ m_errorReporter.typeError(
+ _location,
+ "Function declared as pure, but this expression (potentially) reads from the "
+ "environment or state and thus requires \"view\"."
);
- if (!m_enforceViewWithError && m_currentFunction->stateMutability() == StateMutability::View)
- m_errorReporter.warning(_location, text);
- else
+ m_errors = true;
+ }
+ else if (_mutability == StateMutability::NonPayable)
+ {
+ m_errorReporter.typeError(
+ _location,
+ "Function declared as " +
+ stateMutabilityToString(m_currentFunction->stateMutability()) +
+ ", but this expression (potentially) modifies the state and thus "
+ "requires non-payable (the default) or payable."
+ );
+ m_errors = true;
+ }
+ else if (_mutability == StateMutability::Payable)
+ {
+ // We do not warn for library functions because they cannot be payable anyway.
+ // Also internal functions should be allowed to use `msg.value`.
+ if (m_currentFunction->isPublic() && m_currentFunction->inContractKind() != ContractDefinition::ContractKind::Library)
{
+ if (_nestedLocation)
+ m_errorReporter.typeError(
+ _location,
+ SecondarySourceLocation().append("\"msg.value\" appears here inside the modifier.", *_nestedLocation),
+ "This modifier uses \"msg.value\" and thus the function has to be payable or internal."
+ );
+ else
+ m_errorReporter.typeError(
+ _location,
+ "\"msg.value\" can only be used in payable public functions. Make the function "
+ "\"payable\" or use an internal function to avoid this error."
+ );
m_errors = true;
- m_errorReporter.typeError(_location, text);
}
}
- if (_mutability > m_currentBestMutability)
- m_currentBestMutability = _mutability;
+ else
+ solAssert(false, "");
+
+ solAssert(
+ m_currentFunction->stateMutability() == StateMutability::View ||
+ m_currentFunction->stateMutability() == StateMutability::Pure ||
+ m_currentFunction->stateMutability() == StateMutability::NonPayable,
+ ""
+ );
}
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
@@ -299,19 +323,34 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
ASTString const& member = _memberAccess.memberName();
switch (_memberAccess.expression().annotation().type->category())
{
- case Type::Category::Contract:
- case Type::Category::Integer:
- if (member == "balance" && !_memberAccess.annotation().referencedDeclaration)
+ case Type::Category::Address:
+ if (member == "balance")
mutability = StateMutability::View;
break;
case Type::Category::Magic:
{
- // we can ignore the kind of magic and only look at the name of the member
- set<string> static const pureMembers{
- "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash"
+ using MagicMember = pair<MagicType::Kind, string>;
+ set<MagicMember> static const pureMembers{
+ {MagicType::Kind::ABI, "decode"},
+ {MagicType::Kind::ABI, "encode"},
+ {MagicType::Kind::ABI, "encodePacked"},
+ {MagicType::Kind::ABI, "encodeWithSelector"},
+ {MagicType::Kind::ABI, "encodeWithSignature"},
+ {MagicType::Kind::Block, "blockhash"},
+ {MagicType::Kind::Message, "data"},
+ {MagicType::Kind::Message, "sig"}
};
- if (!pureMembers.count(member))
+ set<MagicMember> static const payableMembers{
+ {MagicType::Kind::Message, "value"}
+ };
+
+ auto const& type = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type);
+ MagicMember magicMember(type.kind(), member);
+
+ if (!pureMembers.count(magicMember))
mutability = StateMutability::View;
+ if (payableMembers.count(magicMember))
+ mutability = StateMutability::Payable;
break;
}
case Type::Category::Struct:
@@ -351,7 +390,8 @@ void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
if (ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration))
{
solAssert(m_inferredMutability.count(mod), "");
- reportMutability(m_inferredMutability.at(mod), _modifier.location());
+ auto const& mutAndLocation = m_inferredMutability.at(mod);
+ reportMutability(mutAndLocation.mutability, _modifier.location(), mutAndLocation.location);
}
else
solAssert(dynamic_cast<ContractDefinition const*>(_modifier.name()->annotation().referencedDeclaration), "");
diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h
index 0b882cd8..faa5b698 100644
--- a/libsolidity/analysis/ViewPureChecker.h
+++ b/libsolidity/analysis/ViewPureChecker.h
@@ -31,16 +31,6 @@ namespace dev
namespace solidity
{
-class ASTNode;
-class FunctionDefinition;
-class ModifierDefinition;
-class Identifier;
-class MemberAccess;
-class IndexAccess;
-class ModifierInvocation;
-class FunctionCall;
-class InlineAssembly;
-
class ViewPureChecker: private ASTConstVisitor
{
public:
@@ -50,6 +40,11 @@ public:
bool check();
private:
+ struct MutabilityAndLocation
+ {
+ StateMutability mutability;
+ SourceLocation location;
+ };
virtual bool visit(FunctionDefinition const& _funDef) override;
virtual void endVisit(FunctionDefinition const& _funDef) override;
@@ -65,16 +60,19 @@ private:
/// Called when an element of mutability @a _mutability is encountered.
/// Creates appropriate warnings and errors and sets @a m_currentBestMutability.
- void reportMutability(StateMutability _mutability, SourceLocation const& _location);
+ void reportMutability(
+ StateMutability _mutability,
+ SourceLocation const& _location,
+ boost::optional<SourceLocation> const& _nestedLocation = {}
+ );
std::vector<std::shared_ptr<ASTNode>> const& m_ast;
ErrorReporter& m_errorReporter;
bool m_errors = false;
- bool m_enforceViewWithError = false;
- StateMutability m_currentBestMutability = StateMutability::Payable;
+ MutabilityAndLocation m_bestMutabilityAndLocation = MutabilityAndLocation{StateMutability::Payable, SourceLocation()};
FunctionDefinition const* m_currentFunction = nullptr;
- std::map<ModifierDefinition const*, StateMutability> m_inferredMutability;
+ std::map<ModifierDefinition const*, MutabilityAndLocation> m_inferredMutability;
};
}
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 80f5d642..8e7a81a6 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -312,7 +312,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
case Declaration::Visibility::External:
return {};
default:
- solAssert(false, "visibility() should not return a Visibility");
+ solAssert(false, "visibility() should return a Visibility");
}
}
else
@@ -328,7 +328,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
case Declaration::Visibility::External:
return make_shared<FunctionType>(*this, _internal);
default:
- solAssert(false, "visibility() should not return a Visibility");
+ solAssert(false, "visibility() should return a Visibility");
}
}
@@ -347,15 +347,6 @@ string FunctionDefinition::externalSignature() const
return FunctionType(*this).externalSignature();
}
-string FunctionDefinition::fullyQualifiedName() const
-{
- auto const* contract = dynamic_cast<ContractDefinition const*>(scope());
- solAssert(contract, "Enclosing scope of function definition was not set.");
-
- auto fname = name().empty() ? "<fallback>" : name();
- return sourceUnitName() + ":" + contract->name() + "." + fname;
-}
-
FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
{
if (!m_annotation)
@@ -406,7 +397,7 @@ SourceUnit const& Scopable::sourceUnit() const
{
ASTNode const* s = scope();
solAssert(s, "");
- // will not always be a declaratoion
+ // will not always be a declaration
while (dynamic_cast<Scopable const*>(s) && dynamic_cast<Scopable const*>(s)->scope())
s = dynamic_cast<Scopable const*>(s)->scope();
return dynamic_cast<SourceUnit const&>(*s);
@@ -427,6 +418,7 @@ bool VariableDeclaration::isLocalVariable() const
{
auto s = scope();
return
+ dynamic_cast<FunctionTypeName const*>(s) ||
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<ForStatement const*>(s);
@@ -434,14 +426,18 @@ bool VariableDeclaration::isLocalVariable() const
bool VariableDeclaration::isCallableParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable)
- return false;
- for (auto const& variable: callable->parameters())
- if (variable.get() == this)
- return true;
- if (callable->returnParameterList())
- for (auto const& variable: callable->returnParameterList()->parameters())
+ if (isReturnParameter())
+ return true;
+
+ vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ parameters = &funTypeName->parameterTypes();
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ parameters = &callable->parameters();
+
+ if (parameters)
+ for (auto const& variable: *parameters)
if (variable.get() == this)
return true;
return false;
@@ -454,11 +450,16 @@ bool VariableDeclaration::isLocalOrReturn() const
bool VariableDeclaration::isReturnParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable)
- return false;
- if (callable->returnParameterList())
- for (auto const& variable: callable->returnParameterList()->parameters())
+ vector<ASTPointer<VariableDeclaration>> const* returnParameters = nullptr;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ returnParameters = &funTypeName->returnParameterTypes();
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ if (callable->returnParameterList())
+ returnParameters = &callable->returnParameterList()->parameters();
+
+ if (returnParameters)
+ for (auto const& variable: *returnParameters)
if (variable.get() == this)
return true;
return false;
@@ -466,18 +467,86 @@ bool VariableDeclaration::isReturnParameter() const
bool VariableDeclaration::isExternalCallableParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable || callable->visibility() != Declaration::Visibility::External)
+ if (!isCallableParameter())
return false;
- for (auto const& variable: callable->parameters())
- if (variable.get() == this)
- return true;
+
+ if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ if (callable->visibility() == Declaration::Visibility::External)
+ return !isReturnParameter();
+
return false;
}
-bool VariableDeclaration::canHaveAutoType() const
+bool VariableDeclaration::isInternalCallableParameter() const
{
- return isLocalVariable() && !isCallableParameter();
+ if (!isCallableParameter())
+ return false;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ return funTypeName->visibility() == Declaration::Visibility::Internal;
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ return callable->visibility() <= Declaration::Visibility::Internal;
+ return false;
+}
+
+bool VariableDeclaration::isLibraryFunctionParameter() const
+{
+ if (!isCallableParameter())
+ return false;
+ if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
+ return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
+ else
+ return false;
+}
+
+bool VariableDeclaration::isEventParameter() const
+{
+ return dynamic_cast<EventDefinition const*>(scope()) != nullptr;
+}
+
+bool VariableDeclaration::hasReferenceOrMappingType() const
+{
+ solAssert(typeName(), "");
+ solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
+ TypePointer const& type = typeName()->annotation().type;
+ return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type.get());
+}
+
+set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
+{
+ using Location = VariableDeclaration::Location;
+
+ if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
+ return set<Location>{ Location::Unspecified };
+ else if (isStateVariable() && isConstant())
+ return set<Location>{ Location::Memory };
+ else if (isExternalCallableParameter())
+ {
+ set<Location> locations{ Location::CallData };
+ if (isLibraryFunctionParameter())
+ locations.insert(Location::Storage);
+ return locations;
+ }
+ else if (isCallableParameter())
+ {
+ set<Location> locations{ Location::Memory };
+ if (isInternalCallableParameter() || isLibraryFunctionParameter())
+ locations.insert(Location::Storage);
+ return locations;
+ }
+ else if (isLocalVariable())
+ {
+ solAssert(typeName(), "");
+ solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
+ if (typeName()->annotation().type->category() == Type::Category::Mapping)
+ return set<Location>{ Location::Storage };
+ else
+ // TODO: add Location::Calldata once implemented for local variables.
+ return set<Location>{ Location::Memory, Location::Storage };
+ }
+ else
+ // Struct members etc.
+ return set<Location>{ Location::Unspecified };
}
TypePointer VariableDeclaration::type() const
@@ -535,13 +604,6 @@ ReturnAnnotation& Return::annotation() const
return dynamic_cast<ReturnAnnotation&>(*m_annotation);
}
-VariableDeclarationStatementAnnotation& VariableDeclarationStatement::annotation() const
-{
- if (!m_annotation)
- m_annotation = new VariableDeclarationStatementAnnotation();
- return dynamic_cast<VariableDeclarationStatementAnnotation&>(*m_annotation);
-}
-
ExpressionAnnotation& Expression::annotation() const
{
if (!m_annotation)
@@ -601,7 +663,7 @@ bool Literal::passesAddressChecksum() const
return dev::passesAddressChecksum(value(), true);
}
-std::string Literal::getChecksummedAddress() const
+string Literal::getChecksummedAddress() const
{
solAssert(isHexNumber(), "Expected hex number");
/// Pad literal to be a proper hex address.
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index fa0d6921..f3464f92 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -206,7 +206,6 @@ public:
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
- std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
@@ -406,6 +405,8 @@ public:
/// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const;
+ std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
+
virtual TypePointer type() const override;
virtual ContractDefinitionAnnotation& annotation() const override;
@@ -614,13 +615,11 @@ public:
StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; }
- bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); }
bool isFallback() const { return !m_isConstructor && name().empty(); }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
- std::string fullyQualifiedName() const;
virtual bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !isFallback();
@@ -656,7 +655,7 @@ private:
class VariableDeclaration: public Declaration
{
public:
- enum Location { Default, Storage, Memory };
+ enum Location { Unspecified, Storage, Memory, CallData };
VariableDeclaration(
SourceLocation const& _sourceLocation,
@@ -667,7 +666,7 @@ public:
bool _isStateVar = false,
bool _isIndexed = false,
bool _isConstant = false,
- Location _referenceLocation = Location::Default
+ Location _referenceLocation = Location::Unspecified
):
Declaration(_sourceLocation, _name, _visibility),
m_typeName(_type),
@@ -686,6 +685,8 @@ public:
virtual bool isLValue() const override;
virtual bool isPartOfExternalInterface() const override { return isPublic(); }
+ /// @returns true iff this variable is the parameter (or return parameter) of a function
+ /// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
@@ -694,14 +695,27 @@ public:
/// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
+ /// This excludes parameters of external function type names.
bool isExternalCallableParameter() const;
+ /// @returns true if this variable is a parameter or return parameter of an internal function
+ /// or a function type of internal visibility.
+ bool isInternalCallableParameter() const;
+ /// @returns true iff this variable is a parameter(or return parameter of a library function
+ bool isLibraryFunctionParameter() const;
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
/// in the body of a function or modifier.
- bool canHaveAutoType() const;
+ /// @returns true if this variable is a parameter of an event.
+ bool isEventParameter() const;
+ /// @returns true if the type of the variable is a reference or mapping type, i.e.
+ /// array, struct or mapping. These types can take a data location (and often require it).
+ /// Can only be called after reference resolution.
+ bool hasReferenceOrMappingType() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
Location referenceLocation() const { return m_location; }
+ /// @returns a set of allowed storage locations for the variable.
+ std::set<Location> allowedDataLocations() const;
virtual TypePointer type() const override;
@@ -862,23 +876,31 @@ public:
};
/**
- * Any pre-defined type name represented by a single keyword, i.e. it excludes mappings,
- * contracts, functions, etc.
+ * Any pre-defined type name represented by a single keyword (and possibly a state mutability for address types),
+ * i.e. it excludes mappings, contracts, functions, etc.
*/
class ElementaryTypeName: public TypeName
{
public:
- ElementaryTypeName(SourceLocation const& _location, ElementaryTypeNameToken const& _elem):
- TypeName(_location), m_type(_elem)
- {}
+ ElementaryTypeName(
+ SourceLocation const& _location,
+ ElementaryTypeNameToken const& _elem,
+ boost::optional<StateMutability> _stateMutability = {}
+ ): TypeName(_location), m_type(_elem), m_stateMutability(_stateMutability)
+ {
+ solAssert(!_stateMutability.is_initialized() || _elem.token() == Token::Address, "");
+ }
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeNameToken const& typeName() const { return m_type; }
+ boost::optional<StateMutability> const& stateMutability() const { return m_stateMutability; }
+
private:
ElementaryTypeNameToken m_type;
+ boost::optional<StateMutability> m_stateMutability; ///< state mutability for address type
};
/**
@@ -1169,11 +1191,11 @@ public:
Statement const& body() const { return *m_body; }
private:
- /// For statement's initialization expresion. for(XXX; ; ). Can be empty
+ /// For statement's initialization expression. for(XXX; ; ). Can be empty
ASTPointer<Statement> m_initExpression;
- /// For statement's condition expresion. for(; XXX ; ). Can be empty
+ /// For statement's condition expression. for(; XXX ; ). Can be empty
ASTPointer<Expression> m_condExpression;
- /// For statement's loop expresion. for(;;XXX). Can be empty
+ /// For statement's loop expression. for(;;XXX). Can be empty
ASTPointer<ExpressionStatement> m_loopExpression;
/// The body of the loop
ASTPointer<Statement> m_body;
@@ -1250,13 +1272,12 @@ private:
};
/**
- * Definition of a variable as a statement inside a function. It requires a type name (which can
- * also be "var") but the actual assignment can be missing.
- * Examples: var a = 2; uint256 a;
- * As a second form, multiple variables can be declared, cannot have a type and must be assigned
- * right away. If the first or last component is unnamed, it can "consume" an arbitrary number
- * of components.
- * Examples: var (a, b) = f(); var (a,,,c) = g(); var (a,) = d();
+ * Definition of one or more variables as a statement inside a function.
+ * If multiple variables are declared, a value has to be assigned directly.
+ * If only a single variable is declared, the value can be missing.
+ * Examples:
+ * uint[] memory a; uint a = 2;
+ * (uint a, bytes32 b, ) = f(); (, uint a, , StructName storage x) = g();
*/
class VariableDeclarationStatement: public Statement
{
@@ -1271,13 +1292,14 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
- VariableDeclarationStatementAnnotation& annotation() const override;
-
std::vector<ASTPointer<VariableDeclaration>> const& declarations() const { return m_variables; }
Expression const* initialValue() const { return m_initialValue.get(); }
private:
/// List of variables, some of which can be empty pointers (unnamed components).
+ /// Note that the ``m_value`` member of these is unused. Instead, ``m_initialValue``
+ /// below is used, because the initial value can be a single expression assigned
+ /// to all variables.
std::vector<ASTPointer<VariableDeclaration>> m_variables;
/// The assigned expression / initial value.
ASTPointer<Expression> m_initialValue;
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index 5cbe42bd..e0b3f492 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -164,13 +164,6 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation
ContractDefinition const* contractScope = nullptr;
};
-struct VariableDeclarationStatementAnnotation: StatementAnnotation
-{
- /// Information about which component of the value is assigned to which variable.
- /// The pointer can be null to signify that the component is discarded.
- std::vector<VariableDeclaration const*> assignments;
-};
-
struct ExpressionAnnotation: ASTAnnotation
{
/// Inferred type of the expression.
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index b8e00b60..8d52851a 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -126,7 +126,7 @@ string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location)
int length = -1;
if (_location.start >= 0 && _location.end >= 0)
length = _location.end - _location.start;
- return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex);
+ return to_string(_location.start) + ":" + to_string(length) + ":" + to_string(sourceIndex);
}
string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath)
@@ -325,9 +325,6 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
- // FIXME: remove with next breaking release
- make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
- make_pair("payable", _node.isPayable()),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("superFunction", idOrNull(_node.annotation().superFunction)),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
@@ -397,10 +394,15 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{
- setJsonNode(_node, "ElementaryTypeName", {
+ std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.typeName().toString()),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
- });
+ };
+
+ if (_node.stateMutability())
+ attributes.emplace_back(make_pair("stateMutability", stateMutabilityToString(*_node.stateMutability())));
+
+ setJsonNode(_node, "ElementaryTypeName", std::move(attributes));
return false;
}
@@ -418,11 +420,8 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
bool ASTJsonConverter::visit(FunctionTypeName const& _node)
{
setJsonNode(_node, "FunctionTypeName", {
- make_pair("payable", _node.isPayable()),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
- // FIXME: remove with next breaking release
- make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
make_pair("parameterTypes", toJson(*_node.parameterTypeList())),
make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
@@ -555,8 +554,8 @@ bool ASTJsonConverter::visit(EmitStatement const& _node)
bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node)
{
Json::Value varDecs(Json::arrayValue);
- for (auto const& v: _node.annotation().assignments)
- appendMove(varDecs, idOrNull(v));
+ for (auto const& v: _node.declarations())
+ appendMove(varDecs, idOrNull(v.get()));
setJsonNode(_node, "VariableDeclarationStatement", {
make_pair("assignments", std::move(varDecs)),
make_pair("declarations", toJson(_node.declarations())),
@@ -745,12 +744,14 @@ string ASTJsonConverter::location(VariableDeclaration::Location _location)
{
switch (_location)
{
- case VariableDeclaration::Location::Default:
+ case VariableDeclaration::Location::Unspecified:
return "default";
case VariableDeclaration::Location::Storage:
return "storage";
case VariableDeclaration::Location::Memory:
return "memory";
+ case VariableDeclaration::Location::CallData:
+ return "calldata";
default:
solAssert(false, "Unknown declaration location.");
}
diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h
index 30ea7ec5..54aad573 100644
--- a/libsolidity/ast/ExperimentalFeatures.h
+++ b/libsolidity/ast/ExperimentalFeatures.h
@@ -29,9 +29,8 @@ namespace solidity
enum class ExperimentalFeature
{
- ABIEncoderV2, // new ABI encoder that makes use of JULIA
+ ABIEncoderV2, // new ABI encoder that makes use of Yul
SMTChecker,
- V050, // v0.5.0 breaking changes
Test,
TestOnlyAnalysis
};
@@ -40,14 +39,12 @@ static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis
{
{ ExperimentalFeature::SMTChecker, true },
{ ExperimentalFeature::TestOnlyAnalysis, true },
- { ExperimentalFeature::V050, true }
};
static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames =
{
{ "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 },
{ "SMTChecker", ExperimentalFeature::SMTChecker },
- { "v0.5.0", ExperimentalFeature::V050 },
{ "__test", ExperimentalFeature::Test },
{ "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis },
};
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 60e3183c..25702f19 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -33,10 +33,13 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/adaptor/sliced.hpp>
#include <boost/range/adaptor/transformed.hpp>
+#include <boost/algorithm/string.hpp>
#include <limits>
@@ -167,15 +170,6 @@ pair<u256, unsigned> const* StorageOffsets::offset(size_t _index) const
return nullptr;
}
-MemberList& MemberList::operator=(MemberList&& _other)
-{
- solAssert(&_other != this, "");
-
- m_memberTypes = move(_other.m_memberTypes);
- m_storageOffsets = move(_other.m_storageOffsets);
- return *this;
-}
-
void MemberList::combine(MemberList const & _other)
{
m_memberTypes += _other.m_memberTypes;
@@ -261,6 +255,17 @@ string Type::escapeIdentifier(string const& _identifier)
return ret;
}
+string Type::identifier() const
+{
+ string ret = escapeIdentifier(richIdentifier());
+ solAssert(ret.find_first_of("0123456789") != 0, "Identifier cannot start with a number.");
+ solAssert(
+ ret.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMONPQRSTUVWXYZ_$") == string::npos,
+ "Identifier contains invalid characters."
+ );
+ return ret;
+}
+
TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
{
solAssert(Token::isElementaryTypeName(_type.token()),
@@ -294,7 +299,7 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
case Token::Byte:
return make_shared<FixedBytesType>(1);
case Token::Address:
- return make_shared<IntegerType>(160, IntegerType::Modifier::Address);
+ return make_shared<AddressType>(StateMutability::NonPayable);
case Token::Bool:
return make_shared<BoolType>();
case Token::Bytes:
@@ -312,22 +317,45 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
TypePointer Type::fromElementaryTypeName(string const& _name)
{
- string name = _name;
- DataLocation location = DataLocation::Storage;
- if (boost::algorithm::ends_with(name, " memory"))
- {
- name = name.substr(0, name.length() - 7);
- location = DataLocation::Memory;
- }
- unsigned short firstNum;
- unsigned short secondNum;
+ vector<string> nameParts;
+ boost::split(nameParts, _name, boost::is_any_of(" "));
+ solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name);
Token::Value token;
- tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(name);
+ unsigned short firstNum, secondNum;
+ tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(nameParts[0]);
auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
if (auto* ref = dynamic_cast<ReferenceType const*>(t.get()))
+ {
+ DataLocation location = DataLocation::Storage;
+ if (nameParts.size() == 2)
+ {
+ if (nameParts[1] == "storage")
+ location = DataLocation::Storage;
+ else if (nameParts[1] == "calldata")
+ location = DataLocation::CallData;
+ else if (nameParts[1] == "memory")
+ location = DataLocation::Memory;
+ else
+ solAssert(false, "Unknown data location: " + nameParts[1]);
+ }
return ref->copyForLocation(location, true);
+ }
+ else if (t->category() == Type::Category::Address)
+ {
+ if (nameParts.size() == 2)
+ {
+ if (nameParts[1] == "payable")
+ return make_shared<AddressType>(StateMutability::Payable);
+ else
+ solAssert(false, "Invalid state mutability for address type: " + nameParts[1]);
+ }
+ return make_shared<AddressType>(StateMutability::NonPayable);
+ }
else
+ {
+ solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types");
return t;
+ }
}
TypePointer Type::forLiteral(Literal const& _literal)
@@ -338,13 +366,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
case Token::FalseLiteral:
return make_shared<BoolType>();
case Token::Number:
- {
- tuple<bool, rational> validLiteral = RationalNumberType::isValidLiteral(_literal);
- if (get<0>(validLiteral) == true)
- return make_shared<RationalNumberType>(get<1>(validLiteral));
- else
- return TypePointer();
- }
+ return RationalNumberType::forLiteral(_literal);
case Token::StringLiteral:
return make_shared<StringLiteralType>(_literal);
default:
@@ -376,6 +398,27 @@ MemberList const& Type::members(ContractDefinition const* _currentScope) const
return *m_members[_currentScope];
}
+TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _packed) const
+{
+ TypePointer encodingType = mobileType();
+ if (encodingType)
+ encodingType = encodingType->interfaceType(_inLibraryCall);
+ if (encodingType)
+ encodingType = encodingType->encodingType();
+ // Structs are fine in the following circumstances:
+ // - ABIv2 without packed encoding or,
+ // - storage struct for a library
+ if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage))
+ return encodingType;
+ TypePointer baseType = encodingType;
+ while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
+ baseType = arrayType->baseType();
+ if (dynamic_cast<StructType const*>(baseType.get()))
+ if (!_encoderV2 || _packed)
+ return TypePointer();
+ return encodingType;
+}
+
MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope)
{
// Normalise data location of type.
@@ -407,6 +450,98 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
return members;
}
+AddressType::AddressType(StateMutability _stateMutability):
+ m_stateMutability(_stateMutability)
+{
+ solAssert(m_stateMutability == StateMutability::Payable || m_stateMutability == StateMutability::NonPayable, "");
+}
+
+string AddressType::richIdentifier() const
+{
+ if (m_stateMutability == StateMutability::Payable)
+ return "t_address_payable";
+ else
+ return "t_address";
+}
+
+bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const
+{
+ if (_other.category() != category())
+ return false;
+ AddressType const& other = dynamic_cast<AddressType const&>(_other);
+
+ return other.m_stateMutability <= m_stateMutability;
+}
+
+bool AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
+ return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable();
+ return isImplicitlyConvertibleTo(_convertTo) ||
+ _convertTo.category() == Category::Integer ||
+ (_convertTo.category() == Category::FixedBytes && 160 == dynamic_cast<FixedBytesType const&>(_convertTo).numBytes() * 8);
+}
+
+string AddressType::toString(bool) const
+{
+ if (m_stateMutability == StateMutability::Payable)
+ return "address payable";
+ else
+ return "address";
+}
+
+string AddressType::canonicalName() const
+{
+ return "address";
+}
+
+u256 AddressType::literalValue(Literal const* _literal) const
+{
+ solAssert(_literal, "");
+ solAssert(_literal->value().substr(0, 2) == "0x", "");
+ return u256(_literal->value());
+}
+
+TypePointer AddressType::unaryOperatorResult(Token::Value _operator) const
+{
+ return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
+}
+
+
+TypePointer AddressType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ // Addresses can only be compared.
+ if (!Token::isCompareOp(_operator))
+ return TypePointer();
+
+ return Type::commonType(shared_from_this(), _other);
+}
+
+bool AddressType::operator==(Type const& _other) const
+{
+ if (_other.category() != category())
+ return false;
+ AddressType const& other = dynamic_cast<AddressType const&>(_other);
+ return other.m_stateMutability == m_stateMutability;
+}
+
+MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const
+{
+ MemberList::MemberMap members = {
+ {"balance", make_shared<IntegerType>(256)},
+ {"call", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)},
+ {"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)},
+ {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false)},
+ {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}
+ };
+ if (m_stateMutability == StateMutability::Payable)
+ {
+ members.emplace_back(MemberList::Member{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)});
+ members.emplace_back(MemberList::Member{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)});
+ }
+ return members;
+}
+
namespace
{
@@ -416,7 +551,7 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
if (_operator == Token::SHR)
return false;
else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType))
- return !otherInt->isAddress();
+ return true;
else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType))
return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned();
else
@@ -428,8 +563,6 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier):
m_bits(_bits), m_modifier(_modifier)
{
- if (isAddress())
- solAssert(m_bits == 160, "");
solAssert(
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(m_bits)
@@ -438,10 +571,7 @@ IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier):
string IntegerType::richIdentifier() const
{
- if (isAddress())
- return "t_address";
- else
- return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits());
+ return "t_" + string(isSigned() ? "" : "u") + "int" + to_string(numBits());
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -451,8 +581,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (convertTo.m_bits < m_bits)
return false;
- if (isAddress())
- return convertTo.isAddress();
else if (isSigned())
return convertTo.isSigned();
else
@@ -461,11 +589,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
else if (_convertTo.category() == Category::FixedPoint)
{
FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo);
-
- if (isAddress())
- return false;
- else
- return maxValue() <= convertTo.maxIntegerValue() && minValue() >= convertTo.minIntegerValue();
+ return maxValue() <= convertTo.maxIntegerValue() && minValue() >= convertTo.minIntegerValue();
}
else
return false;
@@ -474,9 +598,10 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
return _convertTo.category() == category() ||
+ _convertTo.category() == Category::Address ||
_convertTo.category() == Category::Contract ||
_convertTo.category() == Category::Enum ||
- _convertTo.category() == Category::FixedBytes ||
+ (_convertTo.category() == Category::FixedBytes && numBits() == dynamic_cast<FixedBytesType const&>(_convertTo).numBytes() * 8) ||
_convertTo.category() == Category::FixedPoint;
}
@@ -485,10 +610,7 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
// "delete" is ok for all integer types
if (_operator == Token::Delete)
return make_shared<TupleType>();
- // no further unary operators for addresses
- else if (isAddress())
- return TypePointer();
- // for non-address integers, we allow +, -, ++ and --
+ // we allow +, -, ++ and --
else if (_operator == Token::Add || _operator == Token::Sub ||
_operator == Token::Inc || _operator == Token::Dec ||
_operator == Token::BitNot)
@@ -507,20 +629,10 @@ bool IntegerType::operator==(Type const& _other) const
string IntegerType::toString(bool) const
{
- if (isAddress())
- return "address";
string prefix = isSigned() ? "int" : "uint";
return prefix + dev::toString(m_bits);
}
-u256 IntegerType::literalValue(Literal const* _literal) const
-{
- solAssert(m_modifier == Modifier::Address, "");
- solAssert(_literal, "");
- solAssert(_literal->value().substr(0, 2) == "0x", "");
- return u256(_literal->value());
-}
-
bigint IntegerType::minValue() const
{
if (isSigned())
@@ -548,8 +660,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
if (Token::isShiftOp(_operator))
{
// Shifts are not symmetric with respect to the type
- if (isAddress())
- return TypePointer();
if (isValidShiftAndAmountType(_operator, *_other))
return shared_from_this();
else
@@ -567,9 +677,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
return TypePointer();
if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType))
{
- // Nothing else can be done with addresses
- if (intType->isAddress())
- return TypePointer();
// Signed EXP is not allowed
if (Token::Exp == _operator && intType->isSigned())
return TypePointer();
@@ -580,21 +687,6 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
return commonType;
}
-MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) const
-{
- if (isAddress())
- return {
- {"balance", make_shared<IntegerType>(256)},
- {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, StateMutability::Payable)},
- {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, StateMutability::Payable)},
- {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)},
- {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
- {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
- };
- else
- return MemberList::MemberMap();
-}
-
FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, FixedPointType::Modifier _modifier):
m_totalBits(_totalBits), m_fractionalDigits(_fractionalDigits), m_modifier(_modifier)
{
@@ -607,7 +699,7 @@ FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits,
string FixedPointType::richIdentifier() const
{
- return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(m_totalBits) + "x" + std::to_string(m_fractionalDigits);
+ return "t_" + string(isSigned() ? "" : "u") + "fixed" + to_string(m_totalBits) + "x" + to_string(m_fractionalDigits);
}
bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -625,8 +717,7 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- return _convertTo.category() == category() ||
- (_convertTo.category() == Category::Integer && !dynamic_cast<IntegerType const&>(_convertTo).isAddress());
+ return _convertTo.category() == category() || _convertTo.category() == Category::Integer;
}
TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const
@@ -664,7 +755,7 @@ string FixedPointType::toString(bool) const
bigint FixedPointType::maxIntegerValue() const
{
bigint maxValue = (bigint(1) << (m_totalBits - (isSigned() ? 1 : 0))) - 1;
- return maxValue / pow(bigint(10), m_fractionalDigits);
+ return maxValue / boost::multiprecision::pow(bigint(10), m_fractionalDigits);
}
bigint FixedPointType::minIntegerValue() const
@@ -672,7 +763,7 @@ bigint FixedPointType::minIntegerValue() const
if (isSigned())
{
bigint minValue = -(bigint(1) << (m_totalBits - (isSigned() ? 1 : 0)));
- return minValue / pow(bigint(10), m_fractionalDigits);
+ return minValue / boost::multiprecision::pow(bigint(10), m_fractionalDigits);
}
else
return bigint(0);
@@ -741,36 +832,66 @@ tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
}
}
+TypePointer RationalNumberType::forLiteral(Literal const& _literal)
+{
+ solAssert(_literal.token() == Token::Number, "");
+ tuple<bool, rational> validLiteral = isValidLiteral(_literal);
+ if (get<0>(validLiteral))
+ {
+ TypePointer compatibleBytesType;
+ if (_literal.isHexNumber())
+ {
+ size_t digitCount = count_if(
+ _literal.value().begin() + 2, // skip "0x"
+ _literal.value().end(),
+ [](unsigned char _c) -> bool { return isxdigit(_c); }
+ );
+ // require even number of digits
+ if (!(digitCount & 1))
+ compatibleBytesType = make_shared<FixedBytesType>(digitCount / 2);
+ }
+
+ return make_shared<RationalNumberType>(get<1>(validLiteral), compatibleBytesType);
+ }
+ return TypePointer();
+}
+
tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
{
rational value;
try
{
- auto expPoint = find(_literal.value().begin(), _literal.value().end(), 'e');
- if (expPoint == _literal.value().end())
- expPoint = find(_literal.value().begin(), _literal.value().end(), 'E');
+ ASTString valueString = _literal.value();
+ boost::erase_all(valueString, "_");// Remove underscore separators
- if (boost::starts_with(_literal.value(), "0x"))
+ auto expPoint = find(valueString.begin(), valueString.end(), 'e');
+ if (expPoint == valueString.end())
+ expPoint = find(valueString.begin(), valueString.end(), 'E');
+
+ if (boost::starts_with(valueString, "0x"))
{
// process as hex
- value = bigint(_literal.value());
+ value = bigint(valueString);
}
- else if (expPoint != _literal.value().end())
+ else if (expPoint != valueString.end())
{
- // Parse base and exponent. Checks numeric limit.
- bigint exp = bigint(string(expPoint + 1, _literal.value().end()));
+ // Parse mantissa and exponent. Checks numeric limit.
+ tuple<bool, rational> mantissa = parseRational(string(valueString.begin(), expPoint));
- if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
+ if (!get<0>(mantissa))
return make_tuple(false, rational(0));
+ value = get<1>(mantissa);
- uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>();
-
+ // 0E... is always zero.
+ if (value == 0)
+ return make_tuple(true, rational(0));
- tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint));
+ bigint exp = bigint(string(expPoint + 1, valueString.end()));
- if (!get<0>(base))
+ if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
return make_tuple(false, rational(0));
- value = get<1>(base);
+
+ uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>();
if (exp < 0)
{
@@ -794,7 +915,7 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
else
{
// parse as rational number
- tuple<bool, rational> tmp = parseRational(_literal.value());
+ tuple<bool, rational> tmp = parseRational(valueString);
if (!get<0>(tmp))
return tmp;
value = get<1>(tmp);
@@ -842,49 +963,53 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
- if (_convertTo.category() == Category::Integer)
+ switch (_convertTo.category())
+ {
+ case Category::Integer:
{
- if (m_value == rational(0))
- return true;
if (isFractional())
return false;
IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo);
+ if (m_value == rational(0))
+ return true;
unsigned forSignBit = (targetType.isSigned() ? 1 : 0);
if (m_value > rational(0))
{
if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit)))
return true;
+ return false;
+ }
+ if (targetType.isSigned())
+ {
+ if (-m_value.numerator() <= (u256(1) << (targetType.numBits() - forSignBit)))
+ return true;
}
- else if (targetType.isSigned() && -m_value.numerator() <= (u256(1) << (targetType.numBits() - forSignBit)))
- return true;
return false;
}
- else if (_convertTo.category() == Category::FixedPoint)
+ case Category::FixedPoint:
{
if (auto fixed = fixedPointType())
return fixed->isImplicitlyConvertibleTo(_convertTo);
- else
- return false;
+ return false;
}
- else if (_convertTo.category() == Category::FixedBytes)
- {
- FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo);
- if (!isFractional())
- {
- if (integerType())
- return fixedBytes.numBytes() * 8 >= integerType()->numBits();
- return false;
- }
- else
- return false;
+ case Category::FixedBytes:
+ return (m_value == rational(0)) || (m_compatibleBytesType && *m_compatibleBytesType == _convertTo);
+ default:
+ return false;
}
- return false;
}
bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- TypePointer mobType = mobileType();
- return mobType && mobType->isExplicitlyConvertibleTo(_convertTo);
+ if (isImplicitlyConvertibleTo(_convertTo))
+ return true;
+ else if (_convertTo.category() != Category::FixedBytes)
+ {
+ TypePointer mobType = mobileType();
+ return (mobType && mobType->isExplicitlyConvertibleTo(_convertTo));
+ }
+ else
+ return false;
}
TypePointer RationalNumberType::unaryOperatorResult(Token::Value _operator) const
@@ -926,7 +1051,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
RationalNumberType const& other = dynamic_cast<RationalNumberType const&>(*_other);
if (Token::isCompareOp(_operator))
{
- // Since we do not have a "BoolConstantType", we have to do the acutal comparison
+ // Since we do not have a "BoolConstantType", we have to do the actual comparison
// at runtime and convert to mobile typse first. Such a comparison is not a very common
// use-case and will be optimized away.
TypePointer thisMobile = mobileType();
@@ -982,10 +1107,9 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
}
else
value = m_value.numerator() % other.m_value.numerator();
- break;
+ break;
case Token::Exp:
{
- using boost::multiprecision::pow;
if (other.isFractional())
return TypePointer();
solAssert(other.m_value.denominator() == 1, "");
@@ -1019,7 +1143,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
else if (_base == -1)
return 1 - 2 * int(_exponent & 1);
else
- return pow(_base, _exponent);
+ return boost::multiprecision::pow(_base, _exponent);
};
bigint numerator = optimizedPow(m_value.numerator(), absExp);
@@ -1035,7 +1159,6 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
}
case Token::SHL:
{
- using boost::multiprecision::pow;
if (fractional)
return TypePointer();
else if (other.m_value < 0)
@@ -1049,7 +1172,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent))
return TypePointer();
- value = m_value.numerator() * pow(bigint(2), exponent);
+ value = m_value.numerator() * boost::multiprecision::pow(bigint(2), exponent);
}
break;
}
@@ -1057,7 +1180,6 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
// determines the resulting type and the type of shift (SAR or SHR).
case Token::SAR:
{
- namespace mp = boost::multiprecision;
if (fractional)
return TypePointer();
else if (other.m_value < 0)
@@ -1069,10 +1191,22 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
else
{
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
- if (exponent > mostSignificantBit(mp::abs(m_value.numerator())))
- value = 0;
+ if (exponent > mostSignificantBit(boost::multiprecision::abs(m_value.numerator())))
+ value = m_value.numerator() < 0 ? -1 : 0;
else
- value = rational(m_value.numerator() / mp::pow(bigint(2), exponent), 1);
+ {
+ if (m_value.numerator() < 0)
+ // Add 1 to the negative value before dividing to get a result that is strictly too large,
+ // then subtract 1 afterwards to round towards negative infinity.
+ // This is the same algorithm as used in ExpressionCompiler::appendShiftOperatorCode(...).
+ // To see this note that for negative x, xor(x,all_ones) = (-x-1) and
+ // therefore xor(div(xor(x,all_ones), exp(2, shift_amount)), all_ones) is
+ // -(-x - 1) / 2^shift_amount - 1, which is the same as
+ // (x + 1) / 2^shift_amount - 1.
+ value = rational((m_value.numerator() + 1) / boost::multiprecision::pow(bigint(2), exponent) - bigint(1), 1);
+ else
+ value = rational(m_value.numerator() / boost::multiprecision::pow(bigint(2), exponent), 1);
+ }
}
break;
}
@@ -1090,7 +1224,14 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
string RationalNumberType::richIdentifier() const
{
- return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str();
+ // rational seemingly will put the sign always on the numerator,
+ // but let just make it deterministic here.
+ bigint numerator = abs(m_value.numerator());
+ bigint denominator = abs(m_value.denominator());
+ if (m_value < 0)
+ return "t_rational_minus_" + numerator.str() + "_by_" + denominator.str();
+ else
+ return "t_rational_" + numerator.str() + "_by_" + denominator.str();
}
bool RationalNumberType::operator==(Type const& _other) const
@@ -1128,7 +1269,7 @@ u256 RationalNumberType::literalValue(Literal const*) const
// its value.
u256 value;
- bigint shiftedValue;
+ bigint shiftedValue;
if (!isFractional())
shiftedValue = m_value.numerator();
@@ -1137,7 +1278,7 @@ u256 RationalNumberType::literalValue(Literal const*) const
auto fixed = fixedPointType();
solAssert(fixed, "");
int fractionalDigits = fixed->fractionalDigits();
- shiftedValue = m_value.numerator() * pow(bigint(10), fractionalDigits) / m_value.denominator();
+ shiftedValue = m_value.numerator() * boost::multiprecision::pow(bigint(10), fractionalDigits) / m_value.denominator();
}
// we ignore the literal and hope that the type was correctly determined
@@ -1180,7 +1321,7 @@ shared_ptr<FixedPointType const> RationalNumberType::fixedPointType() const
bool negative = (m_value < 0);
unsigned fractionalDigits = 0;
rational value = abs(m_value); // We care about the sign later.
- rational maxValue = negative ?
+ rational maxValue = negative ?
rational(bigint(1) << 255, 1):
rational((bigint(1) << 256) - 1, 1);
@@ -1189,12 +1330,13 @@ shared_ptr<FixedPointType const> RationalNumberType::fixedPointType() const
value *= 10;
fractionalDigits++;
}
-
+
if (value > maxValue)
return shared_ptr<FixedPointType const>();
// This means we round towards zero for positive and negative values.
bigint v = value.numerator() / value.denominator();
- if (negative)
+
+ if (negative && v != 0)
// modify value to satisfy bit requirements for negative numbers:
// add one bit for sign and decrement because negative numbers can be larger
v = (v - 1) << 1;
@@ -1281,7 +1423,8 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- return _convertTo.category() == Category::Integer ||
+ return (_convertTo.category() == Category::Integer && numBytes() * 8 == dynamic_cast<IntegerType const&>(_convertTo).numBits()) ||
+ (_convertTo.category() == Category::Address && numBytes() == 20) ||
_convertTo.category() == Category::FixedPoint ||
_convertTo.category() == category();
}
@@ -1325,7 +1468,7 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c
string FixedBytesType::richIdentifier() const
{
- return "t_bytes" + std::to_string(m_bytes);
+ return "t_bytes" + to_string(m_bytes);
}
bool FixedBytesType::operator==(Type const& _other) const
@@ -1358,7 +1501,7 @@ TypePointer BoolType::binaryOperatorResult(Token::Value _operator, TypePointer c
{
if (category() != _other->category())
return TypePointer();
- if (Token::isCompareOp(_operator) || _operator == Token::And || _operator == Token::Or)
+ if (_operator == Token::Equal || _operator == Token::NotEqual || _operator == Token::And || _operator == Token::Or)
return _other;
else
return TypePointer();
@@ -1368,25 +1511,24 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (*this == _convertTo)
return true;
- if (_convertTo.category() == Category::Integer)
- return dynamic_cast<IntegerType const&>(_convertTo).isAddress();
if (_convertTo.category() == Category::Contract)
{
auto const& bases = contractDefinition().annotation().linearizedBaseContracts;
if (m_super && bases.size() <= 1)
return false;
- return find(m_super ? ++bases.begin() : bases.begin(), bases.end(),
- &dynamic_cast<ContractType const&>(_convertTo).contractDefinition()) != bases.end();
+ return find(
+ m_super ? ++bases.begin() : bases.begin(), bases.end(),
+ &dynamic_cast<ContractType const&>(_convertTo).contractDefinition()
+ ) != bases.end();
}
return false;
}
bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- return
- isImplicitlyConvertibleTo(_convertTo) ||
- _convertTo.category() == Category::Integer ||
- _convertTo.category() == Category::Contract;
+ if (auto const* addressType = dynamic_cast<AddressType const*>(&_convertTo))
+ return isPayable() || (addressType->stateMutability() < StateMutability::Payable);
+ return isImplicitlyConvertibleTo(_convertTo);
}
bool ContractType::isPayable() const
@@ -1416,8 +1558,6 @@ TypePointer ReferenceType::unaryOperatorResult(Token::Value _operator) const
return make_shared<TupleType>();
case DataLocation::Storage:
return m_isPointer ? TypePointer() : make_shared<TupleType>();
- default:
- solAssert(false, "");
}
return TypePointer();
}
@@ -1452,12 +1592,18 @@ string ReferenceType::stringForReferencePart() const
string ReferenceType::identifierLocationSuffix() const
{
string id;
- if (location() == DataLocation::Storage)
+ switch (location())
+ {
+ case DataLocation::Storage:
id += "_storage";
- else if (location() == DataLocation::Memory)
+ break;
+ case DataLocation::Memory:
id += "_memory";
- else
+ break;
+ case DataLocation::CallData:
id += "_calldata";
+ break;
+ }
if (isPointer())
id += "_ptr";
return id;
@@ -1672,6 +1818,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
{
members.push_back({"length", make_shared<IntegerType>(256)});
if (isDynamicallySized() && location() == DataLocation::Storage)
+ {
members.push_back({"push", make_shared<FunctionType>(
TypePointers{baseType()},
TypePointers{make_shared<IntegerType>(256)},
@@ -1679,6 +1826,14 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
strings{string()},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
)});
+ members.push_back({"pop", make_shared<FunctionType>(
+ TypePointers{},
+ TypePointers{},
+ strings{string()},
+ strings{string()},
+ FunctionType::Kind::ArrayPop
+ )});
+ }
}
return members;
}
@@ -1752,7 +1907,7 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer)
string ContractType::richIdentifier() const
{
- return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id());
+ return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
}
bool ContractType::operator==(Type const& _other) const
@@ -1799,7 +1954,7 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con
continue;
auto memberType = dynamic_cast<FunctionType const*>(member.type.get());
solAssert(!!memberType, "Override changes type.");
- if (!memberType->hasEqualArgumentTypes(*functionType))
+ if (!memberType->hasEqualParameterTypes(*functionType))
continue;
functionWithEqualArgumentsFound = true;
break;
@@ -1821,47 +1976,9 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con
&it.second->declaration()
));
}
- // In 0.5.0 address members are not populated into the contract.
- if (!_contract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
- addNonConflictingAddressMembers(members);
return members;
}
-void ContractType::addNonConflictingAddressMembers(MemberList::MemberMap& _members)
-{
- MemberList::MemberMap addressMembers = IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr);
- for (auto const& addressMember: addressMembers)
- {
- bool clash = false;
- for (auto const& member: _members)
- {
- if (
- member.name == addressMember.name &&
- (
- // Members with different types are not allowed
- member.type->category() != addressMember.type->category() ||
- // Members must overload functions without clash
- (
- member.type->category() == Type::Category::Function &&
- dynamic_cast<FunctionType const&>(*member.type).hasEqualArgumentTypes(dynamic_cast<FunctionType const&>(*addressMember.type))
- )
- )
- )
- {
- clash = true;
- break;
- }
- }
-
- if (!clash)
- _members.push_back(MemberList::Member(
- addressMember.name,
- addressMember.type,
- addressMember.declaration
- ));
- }
-}
-
shared_ptr<FunctionType const> const& ContractType::newExpressionType() const
{
if (!m_constructorType)
@@ -1904,7 +2021,7 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
string StructType::richIdentifier() const
{
- return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix();
+ return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + to_string(m_struct.id()) + identifierLocationSuffix();
}
bool StructType::operator==(Type const& _other) const
@@ -2106,7 +2223,7 @@ bool StructType::recursive() const
{
if (!m_recursive.is_initialized())
{
- auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector)
+ auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t /*_depth*/)
{
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
{
@@ -2130,7 +2247,7 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
string EnumType::richIdentifier() const
{
- return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id());
+ return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + to_string(m_enum.id());
}
bool EnumType::operator==(Type const& _other) const
@@ -2189,25 +2306,13 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
TypePointers const& targets = tupleType->components();
if (targets.empty())
return components().empty();
- if (components().size() != targets.size() && !targets.front() && !targets.back())
- return false; // (,a,) = (1,2,3,4) - unable to position `a` in the tuple.
- size_t minNumValues = targets.size();
- if (!targets.back() || !targets.front())
- --minNumValues; // wildcards can also match 0 components
- if (components().size() < minNumValues)
+ if (components().size() != targets.size())
return false;
- if (components().size() > targets.size() && targets.front() && targets.back())
- return false; // larger source and no wildcard
- bool fillRight = !targets.back() || targets.front();
- for (size_t i = 0; i < min(targets.size(), components().size()); ++i)
- {
- auto const& s = components()[fillRight ? i : components().size() - i - 1];
- auto const& t = targets[fillRight ? i : targets.size() - i - 1];
- if (!s && t)
+ for (size_t i = 0; i < targets.size(); ++i)
+ if (!components()[i] && targets[i])
return false;
- else if (s && t && !s->isImplicitlyConvertibleTo(*t))
+ else if (components()[i] && targets[i] && !components()[i]->isImplicitlyConvertibleTo(*targets[i]))
return false;
- }
return true;
}
else
@@ -2273,16 +2378,14 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
{
solAssert(!!_targetType, "");
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components();
- bool fillRight = !targetComponents.empty() && (!targetComponents.back() || targetComponents.front());
+ solAssert(components().size() == targetComponents.size(), "");
TypePointers tempComponents(targetComponents.size());
- for (size_t i = 0; i < min(targetComponents.size(), components().size()); ++i)
+ for (size_t i = 0; i < targetComponents.size(); ++i)
{
- size_t si = fillRight ? i : components().size() - i - 1;
- size_t ti = fillRight ? i : targetComponents.size() - i - 1;
- if (components()[si] && targetComponents[ti])
+ if (components()[i] && targetComponents[i])
{
- tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]);
- solAssert(tempComponents[ti], "");
+ tempComponents[i] = components()[i]->closestTemporaryType(targetComponents[i]);
+ solAssert(tempComponents[i], "");
}
}
return make_shared<TupleType>(tempComponents);
@@ -2446,7 +2549,14 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const
{
TypePointers returnParameterTypes = m_returnParameterTypes;
- if (m_kind == Kind::External || m_kind == Kind::CallCode || m_kind == Kind::DelegateCall)
+ if (
+ m_kind == Kind::External ||
+ m_kind == Kind::DelegateCall ||
+ m_kind == Kind::BareCall ||
+ m_kind == Kind::BareCallCode ||
+ m_kind == Kind::BareDelegateCall ||
+ m_kind == Kind::BareStaticCall
+ )
for (auto& param: returnParameterTypes)
if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage))
param = make_shared<InaccessibleDynamicType>();
@@ -2468,15 +2578,15 @@ string FunctionType::richIdentifier() const
{
case Kind::Internal: id += "internal"; break;
case Kind::External: id += "external"; break;
- case Kind::CallCode: id += "callcode"; break;
case Kind::DelegateCall: id += "delegatecall"; break;
case Kind::BareCall: id += "barecall"; break;
case Kind::BareCallCode: id += "barecallcode"; break;
case Kind::BareDelegateCall: id += "baredelegatecall"; break;
+ case Kind::BareStaticCall: id += "barestaticcall"; break;
case Kind::Creation: id += "creation"; break;
case Kind::Send: id += "send"; break;
case Kind::Transfer: id += "transfer"; break;
- case Kind::SHA3: id += "sha3"; break;
+ case Kind::KECCAK256: id += "keccak256"; break;
case Kind::Selfdestruct: id += "selfdestruct"; break;
case Kind::Revert: id += "revert"; break;
case Kind::ECRecover: id += "ecrecover"; break;
@@ -2495,6 +2605,7 @@ string FunctionType::richIdentifier() const
case Kind::AddMod: id += "addmod"; break;
case Kind::MulMod: id += "mulmod"; break;
case Kind::ArrayPush: id += "arraypush"; break;
+ case Kind::ArrayPop: id += "arraypop"; break;
case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; break;
case Kind::Assert: id += "assert"; break;
@@ -2503,7 +2614,7 @@ string FunctionType::richIdentifier() const
case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
- default: solAssert(false, "Unknown function location."); break;
+ case Kind::ABIDecode: id += "abidecode"; break;
}
id += "_" + stateMutabilityToString(m_stateMutability);
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
@@ -2520,43 +2631,46 @@ bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
-
FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
- if (
- m_kind != other.m_kind ||
- m_stateMutability != other.stateMutability() ||
- m_parameterTypes.size() != other.m_parameterTypes.size() ||
- m_returnParameterTypes.size() != other.m_returnParameterTypes.size()
- )
+ if (!equalExcludingStateMutability(other))
return false;
-
- auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
- if (
- !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) ||
- !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare)
- )
- return false;
- //@todo this is ugly, but cannot be prevented right now
- if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
- return false;
- if (bound() != other.bound())
- return false;
- if (bound() && *selfType() != *other.selfType())
+ if (m_stateMutability != other.stateMutability())
return false;
return true;
}
bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- if (m_kind == Kind::External && _convertTo.category() == Category::Integer)
- {
- IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
- if (convertTo.isAddress())
+ if (m_kind == Kind::External && _convertTo.category() == Category::Address)
return true;
- }
return _convertTo.category() == category();
}
+bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (_convertTo.category() != category())
+ return false;
+
+ FunctionType const& convertTo = dynamic_cast<FunctionType const&>(_convertTo);
+
+ if (!equalExcludingStateMutability(convertTo))
+ return false;
+
+ // non-payable should not be convertible to payable
+ if (m_stateMutability != StateMutability::Payable && convertTo.stateMutability() == StateMutability::Payable)
+ return false;
+
+ // payable should be convertible to non-payable, because you are free to pay 0 ether
+ if (m_stateMutability == StateMutability::Payable && convertTo.stateMutability() == StateMutability::NonPayable)
+ return true;
+
+ // e.g. pure should be convertible to view, but not the other way around.
+ if (m_stateMutability > convertTo.stateMutability())
+ return false;
+
+ return true;
+}
+
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
{
if (_operator == Token::Value::Delete)
@@ -2640,15 +2754,16 @@ unsigned FunctionType::sizeOnStack() const
switch(kind)
{
case Kind::External:
- case Kind::CallCode:
case Kind::DelegateCall:
size = 2;
break;
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
case Kind::Internal:
case Kind::ArrayPush:
+ case Kind::ArrayPop:
case Kind::ByteArrayPush:
size = 1;
break;
@@ -2713,6 +2828,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
{
MemberList::MemberMap members;
if (m_kind == Kind::External)
@@ -2801,7 +2917,7 @@ bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePoin
);
}
-bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
+bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const
{
if (m_parameterTypes.size() != _other.m_parameterTypes.size())
return false;
@@ -2813,6 +2929,38 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
);
}
+bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const
+{
+ if (m_returnParameterTypes.size() != _other.m_returnParameterTypes.size())
+ return false;
+ return equal(
+ m_returnParameterTypes.cbegin(),
+ m_returnParameterTypes.cend(),
+ _other.m_returnParameterTypes.cbegin(),
+ [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }
+ );
+}
+
+bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) const
+{
+ if (m_kind != _other.m_kind)
+ return false;
+
+ if (!hasEqualParameterTypes(_other) || !hasEqualReturnTypes(_other))
+ return false;
+
+ //@todo this is ugly, but cannot be prevented right now
+ if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet)
+ return false;
+
+ if (bound() != _other.bound())
+ return false;
+
+ solAssert(!bound() || *selfType() == *_other.selfType(), "");
+
+ return true;
+}
+
bool FunctionType::isBareCall() const
{
switch (m_kind)
@@ -2820,6 +2968,7 @@ bool FunctionType::isBareCall() const
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
case Kind::ECRecover:
case Kind::SHA256:
case Kind::RIPEMD160:
@@ -2833,6 +2982,16 @@ string FunctionType::externalSignature() const
{
solAssert(m_declaration != nullptr, "External signature of function needs declaration");
solAssert(!m_declaration->name().empty(), "Fallback function has no signature.");
+ switch (kind())
+ {
+ case Kind::Internal:
+ case Kind::External:
+ case Kind::DelegateCall:
+ case Kind::Event:
+ break;
+ default:
+ solAssert(false, "Invalid function type for requesting external signature.");
+ }
bool const inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
FunctionTypePointer external = interfaceFunctionType();
@@ -2859,7 +3018,7 @@ bool FunctionType::isPure() const
// FIXME: replace this with m_stateMutability == StateMutability::Pure once
// the callgraph analyzer is in place
return
- m_kind == Kind::SHA3 ||
+ m_kind == Kind::KECCAK256 ||
m_kind == Kind::ECRecover ||
m_kind == Kind::SHA256 ||
m_kind == Kind::RIPEMD160 ||
@@ -2869,7 +3028,8 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncode ||
m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector ||
- m_kind == Kind::ABIEncodeWithSignature;
+ m_kind == Kind::ABIEncodeWithSignature ||
+ m_kind == Kind::ABIDecode;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
@@ -2954,6 +3114,26 @@ ASTPointer<ASTString> FunctionType::documentation() const
return ASTPointer<ASTString>();
}
+bool FunctionType::padArguments() const
+{
+ // No padding only for hash functions, low-level calls and the packed encoding function.
+ switch (m_kind)
+ {
+ case Kind::BareCall:
+ case Kind::BareCallCode:
+ case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
+ case Kind::SHA256:
+ case Kind::RIPEMD160:
+ case Kind::KECCAK256:
+ case Kind::ABIEncodePacked:
+ return false;
+ default:
+ return true;
+ }
+ return true;
+}
+
string MappingType::richIdentifier() const
{
return "t_mapping" + identifierList(m_keyType, m_valueType);
@@ -3093,7 +3273,7 @@ string ModifierType::toString(bool _short) const
string ModuleType::richIdentifier() const
{
- return "t_module_" + std::to_string(m_sourceUnit.id());
+ return "t_module_" + to_string(m_sourceUnit.id());
}
bool ModuleType::operator==(Type const& _other) const
@@ -3129,8 +3309,6 @@ string MagicType::richIdentifier() const
return "t_magic_transaction";
case Kind::ABI:
return "t_magic_abi";
- default:
- solAssert(false, "Unknown kind of magic");
}
return "";
}
@@ -3149,7 +3327,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{
case Kind::Block:
return MemberList::MemberMap({
- {"coinbase", make_shared<IntegerType>(160, IntegerType::Modifier::Address)},
+ {"coinbase", make_shared<AddressType>(StateMutability::Payable)},
{"timestamp", make_shared<IntegerType>(256)},
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)},
{"difficulty", make_shared<IntegerType>(256)},
@@ -3158,7 +3336,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
});
case Kind::Message:
return MemberList::MemberMap({
- {"sender", make_shared<IntegerType>(160, IntegerType::Modifier::Address)},
+ {"sender", make_shared<AddressType>(StateMutability::Payable)},
{"gas", make_shared<IntegerType>(256)},
{"value", make_shared<IntegerType>(256)},
{"data", make_shared<ArrayType>(DataLocation::CallData)},
@@ -3166,7 +3344,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
});
case Kind::Transaction:
return MemberList::MemberMap({
- {"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)},
+ {"origin", make_shared<AddressType>(StateMutability::Payable)},
{"gasprice", make_shared<IntegerType>(256)}
});
case Kind::ABI:
@@ -3206,6 +3384,15 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
FunctionType::Kind::ABIEncodeWithSignature,
true,
StateMutability::Pure
+ )},
+ {"decode", make_shared<FunctionType>(
+ TypePointers(),
+ TypePointers(),
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIDecode,
+ true,
+ StateMutability::Pure
)}
});
default:
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 95821634..a2d18b0a 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -95,9 +95,7 @@ public:
using MemberMap = std::vector<Member>;
- MemberList() {}
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
- MemberList& operator=(MemberList&& _other);
void combine(MemberList const& _other);
TypePointer memberType(std::string const& _name) const
{
@@ -132,6 +130,8 @@ private:
mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
};
+static_assert(std::is_nothrow_move_constructible<MemberList>::value, "MemberList should be noexcept move constructible");
+
/**
* Abstract base class that forms the root of the type hierarchy.
*/
@@ -141,7 +141,7 @@ public:
virtual ~Type() = default;
enum class Category
{
- Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
+ Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
@@ -151,7 +151,8 @@ public:
/// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(ElementaryTypeNameToken const& _type);
- /// Converts a given elementary type name with optional suffix " memory" to a type pointer.
+ /// Converts a given elementary type name with optional data location
+ /// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage".
static TypePointer fromElementaryTypeName(std::string const& _name);
/// @}
@@ -171,7 +172,7 @@ public:
/// only if they have the same identifier.
/// The identifier should start with "t_".
/// Will not contain any character which would be invalid as an identifier.
- std::string identifier() const { return escapeIdentifier(richIdentifier()); }
+ std::string identifier() const;
/// More complex identifier strings use "parentheses", where $_ is interpreted as as
/// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that
@@ -279,6 +280,11 @@ public:
/// This for example returns address for contract types.
/// If there is no such type, returns an empty shared pointer.
virtual TypePointer encodingType() const { return TypePointer(); }
+ /// @returns the encoding type used under the given circumstances for the type of an expression
+ /// when used for e.g. abi.encode(...) or the empty pointer if the object
+ /// cannot be encoded.
+ /// This is different from encodingType since it takes implicit conversions into account.
+ TypePointer fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _packed) const;
/// @returns a (simpler) type that is used when decoding this type in calldata.
virtual TypePointer decodingType() const { return encodingType(); }
/// @returns a type that will be used outside of Solidity for e.g. function signatures.
@@ -308,14 +314,52 @@ protected:
};
/**
- * Any kind of integer type (signed, unsigned, address).
+ * Type for addresses.
+ */
+class AddressType: public Type
+{
+public:
+ virtual Category category() const override { return Category::Address; }
+
+ explicit AddressType(StateMutability _stateMutability);
+
+ virtual std::string richIdentifier() const override;
+ virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
+ virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
+
+ virtual bool operator==(Type const& _other) const override;
+
+ virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; }
+ virtual unsigned storageBytes() const override { return 160 / 8; }
+ virtual bool isValueType() const override { return true; }
+
+ virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
+
+ virtual std::string toString(bool _short) const override;
+ virtual std::string canonicalName() const override;
+
+ virtual u256 literalValue(Literal const* _literal) const override;
+
+ virtual TypePointer encodingType() const override { return shared_from_this(); }
+ virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
+
+ StateMutability stateMutability(void) const { return m_stateMutability; }
+
+private:
+ StateMutability m_stateMutability;
+};
+
+/**
+ * Any kind of integer type (signed, unsigned).
*/
class IntegerType: public Type
{
public:
enum class Modifier
{
- Unsigned, Signed, Address
+ Unsigned, Signed
};
virtual Category category() const override { return Category::Integer; }
@@ -333,17 +377,12 @@ public:
virtual unsigned storageBytes() const override { return m_bits / 8; }
virtual bool isValueType() const override { return true; }
- virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
-
virtual std::string toString(bool _short) const override;
- virtual u256 literalValue(Literal const* _literal) const override;
-
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
unsigned numBits() const { return m_bits; }
- bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
bigint minValue() const;
@@ -417,12 +456,12 @@ public:
virtual Category category() const override { return Category::RationalNumber; }
- /// @returns true if the literal is a valid integer.
- static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
+ static TypePointer forLiteral(Literal const& _literal);
- explicit RationalNumberType(rational const& _value):
- m_value(_value)
+ explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()):
+ m_value(_value), m_compatibleBytesType(_compatibleBytesType)
{}
+
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -440,7 +479,8 @@ public:
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
std::shared_ptr<IntegerType const> integerType() const;
- /// @returns the smallest fixed type that can hold the value or incurs the least precision loss.
+ /// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
+ /// unless the value was truncated, then a suitable type will be chosen to indicate such event.
/// If the integer part does not fit, returns an empty pointer.
std::shared_ptr<FixedPointType const> fixedPointType() const;
@@ -456,6 +496,13 @@ public:
private:
rational m_value;
+ /// Bytes type to which the rational can be explicitly converted.
+ /// Empty for all rationals that are not directly parsed from hex literals.
+ TypePointer m_compatibleBytesType;
+
+ /// @returns true if the literal is a valid integer.
+ static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
+
/// @returns true if the literal is a valid rational number.
static std::tuple<bool, rational> parseRational(std::string const& _value);
@@ -691,9 +738,9 @@ public:
virtual Category category() const override { return Category::Contract; }
explicit ContractType(ContractDefinition const& _contract, bool _super = false):
m_contract(_contract), m_super(_super) {}
- /// Contracts can be implicitly converted to super classes and to addresses.
+ /// Contracts can be implicitly converted only to base contracts.
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
- /// Contracts can be converted to themselves and to integers.
+ /// Contracts can only be explicitly converted to address types and base contracts.
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual std::string richIdentifier() const override;
@@ -715,7 +762,7 @@ public:
{
if (isSuper())
return TypePointer{};
- return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address);
+ return std::make_shared<AddressType>(isPayable() ? StateMutability::Payable : StateMutability::NonPayable);
}
virtual TypePointer interfaceType(bool _inLibrary) const override
{
@@ -739,8 +786,6 @@ public:
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
private:
- static void addNonConflictingAddressMembers(MemberList::MemberMap& _members);
-
ContractDefinition const& m_contract;
/// If true, it is the "super" type of the current contract, i.e. it contains only inherited
/// members.
@@ -887,15 +932,15 @@ public:
{
Internal, ///< stack-call using plain JUMP
External, ///< external call using CALL
- CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage
DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage
BareCall, ///< CALL without function hash
BareCallCode, ///< CALLCODE without function hash
BareDelegateCall, ///< DELEGATECALL without function hash
+ BareStaticCall, ///< STATICCALL without function hash
Creation, ///< external call using CREATE
Send, ///< CALL, but without data and gas
Transfer, ///< CALL, but without data and throws on error
- SHA3, ///< SHA3
+ KECCAK256, ///< KECCAK256
Selfdestruct, ///< SELFDESTRUCT
Revert, ///< REVERT
ECRecover, ///< CALL to special contract for ecrecover
@@ -913,6 +958,7 @@ public:
AddMod, ///< ADDMOD
MulMod, ///< MULMOD
ArrayPush, ///< .push() to a dynamically sized array in storage
+ ArrayPop, ///< .pop() from a dynamically sized array in storage
ByteArrayPush, ///< .push() to a dynamically sized byte array in storage
ObjectCreation, ///< array creation using new
Assert, ///< assert()
@@ -921,7 +967,8 @@ public:
ABIEncodePacked,
ABIEncodeWithSelector,
ABIEncodeWithSignature,
- GasLeft ///< gasleft()
+ ABIDecode,
+ GasLeft, ///< gasleft()
};
virtual Category category() const override { return Category::Function; }
@@ -1000,6 +1047,7 @@ public:
virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) const override;
+ virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
@@ -1029,10 +1077,14 @@ public:
/// @param _selfType if the function is bound, this has to be supplied and is the type of the
/// expression the function is called on.
bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const;
- /// @returns true if the types of parameters are equal (does't check return parameter types)
- bool hasEqualArgumentTypes(FunctionType const& _other) const;
-
- /// @returns true if the ABI is used for this call (only meaningful for external calls)
+ /// @returns true if the types of parameters are equal (does not check return parameter types)
+ bool hasEqualParameterTypes(FunctionType const& _other) const;
+ /// @returns true iff the return types are equal (does not check parameter types)
+ bool hasEqualReturnTypes(FunctionType const& _other) const;
+ /// @returns true iff the function type is equal to the given type, ignoring state mutability differences.
+ bool equalExcludingStateMutability(FunctionType const& _other) const;
+
+ /// @returns true if the ABI is NOT used for this call (only meaningful for external calls)
bool isBareCall() const;
Kind const& kind() const { return m_kind; }
StateMutability stateMutability() const { return m_stateMutability; }
@@ -1056,18 +1108,22 @@ public:
ASTPointer<ASTString> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
- bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160 || m_kind == Kind::ABIEncodePacked); }
+ /// The only functions that do not pad are hash functions, the low-level call functions
+ /// and abi.encodePacked.
+ bool padArguments() const;
bool takesArbitraryParameters() const { return m_arbitraryParameters; }
/// true iff the function takes a single bytes parameter and it is passed on without padding.
- /// @todo until 0.5.0, this is just a "recommendation".
bool takesSinglePackedBytesParameter() const
{
- // @todo add the call kinds here with 0.5.0 and perhaps also log0.
switch (m_kind)
{
- case FunctionType::Kind::SHA3:
+ case FunctionType::Kind::KECCAK256:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
+ case FunctionType::Kind::BareCall:
+ case FunctionType::Kind::BareCallCode:
+ case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
return true;
default:
return false;
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index 3e3aa0ae..6c27533c 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -17,7 +17,7 @@
/**
* @author Christian <chris@ethereum.org>
* @date 2017
- * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ * Routines that generate Yul code related to ABI encoding, decoding and type conversions.
*/
#include <libsolidity/codegen/ABIFunctions.h>
@@ -197,6 +197,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
templ("functionName", functionName);
switch (_type.category())
{
+ case Type::Category::Address:
+ templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)");
+ break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
@@ -228,7 +231,8 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
if (type.numBytes() == 32)
templ("body", "cleaned := value");
else if (type.numBytes() == 0)
- templ("body", "cleaned := 0");
+ // This is disallowed in the type system.
+ solAssert(false, "");
else
{
size_t numBits = type.numBytes() * 8;
@@ -238,8 +242,14 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
break;
}
case Type::Category::Contract:
- templ("body", "cleaned := " + cleanupFunction(IntegerType(160, IntegerType::Modifier::Address)) + "(value)");
+ {
+ AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
+ StateMutability::Payable :
+ StateMutability::NonPayable
+ );
+ templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
break;
+ }
case Type::Category::Enum:
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
@@ -283,6 +293,12 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
auto fromCategory = _from.category();
switch (fromCategory)
{
+ case Type::Category::Address:
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(IntegerType(160), _to))
+ .render();
+ break;
case Type::Category::Integer:
case Type::Category::RationalNumber:
case Type::Category::Contract:
@@ -313,16 +329,19 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
.render();
}
else if (toCategory == Type::Category::FixedPoint)
- {
solUnimplemented("Not yet implemented - FixedPointType.");
- }
+ else if (toCategory == Type::Category::Address)
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(_from, IntegerType(160)))
+ .render();
else
{
solAssert(
toCategory == Type::Category::Integer ||
toCategory == Type::Category::Contract,
"");
- IntegerType const addressType(160, IntegerType::Modifier::Address);
+ IntegerType const addressType(160);
IntegerType const& to =
toCategory == Type::Category::Integer ?
dynamic_cast<IntegerType const&>(_to) :
@@ -374,6 +393,11 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
("shift", shiftRightFunction(256 - from.numBytes() * 8))
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
.render();
+ else if (toCategory == Type::Category::Address)
+ body =
+ Whiskers("converted := <convert>(value)")
+ ("convert", conversionFunction(_from, IntegerType(160)))
+ .render();
else
{
// clear for conversion to longer bytes
@@ -409,7 +433,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
solAssert(false, "");
}
- solAssert(!body.empty(), "");
+ solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
templ("body", body);
return templ.render();
});
@@ -471,13 +495,8 @@ string ABIFunctions::abiEncodingFunction(
bool _fromStack
)
{
- solUnimplementedAssert(
- _to.mobileType() &&
- _to.mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + _to.toString() + "\" not yet implemented."
- );
- TypePointer toInterface = _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer toInterface = _to.fullEncodingType(_encodeAsLibraryTypes, true, false);
+ solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented.");
Type const& to = *toInterface;
if (_from.category() == Type::Category::StringLiteral)
@@ -886,13 +905,8 @@ string ABIFunctions::abiEncodingFunctionStruct(
solAssert(member.type, "");
if (!member.type->canLiveOutsideStorage())
continue;
- solUnimplementedAssert(
- member.type->mobileType() &&
- member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + member.type->toString() + "\" not yet implemented."
- );
- auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer memberTypeTo = member.type->fullEncodingType(_encodeAsLibraryTypes, true, false);
+ solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented.");
auto memberTypeFrom = _from.memberType(member.name);
solAssert(memberTypeFrom, "");
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
@@ -989,7 +1003,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
)");
templ("functionName", functionName);
- // TODO this can make use of CODECOPY for large strings once we have that in JULIA
+ // TODO this can make use of CODECOPY for large strings once we have that in Yul
size_t words = (value.size() + 31) / 32;
templ("overallSize", to_string(32 + words * 32));
templ("length", to_string(value.size()));
@@ -1188,7 +1202,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
if (!_type.isDynamicallySized())
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
- solAssert(!_type.baseType()->isDynamicallyEncoded(), "");
+ if (_type.baseType()->isDynamicallyEncoded())
+ solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity.");
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
string functionName =
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
index db4d40f5..3caaa1d9 100644
--- a/libsolidity/codegen/ABIFunctions.h
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -17,7 +17,7 @@
/**
* @author Christian <chris@ethereum.org>
* @date 2017
- * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ * Routines that generate Yul code related to ABI encoding, decoding and type conversions.
*/
#pragma once
@@ -203,7 +203,7 @@ private:
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.
+ /// The function reverts for too large lengths.
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
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 0fe66d2d..2b77db8f 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -303,12 +303,17 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
m_context << _sourceType.length();
if (baseSize > 1)
m_context << u256(baseSize) << Instruction::MUL;
- // stack: target source_offset source_len
- m_context << Instruction::DUP1 << Instruction::DUP3 << Instruction::DUP5;
- // stack: target source_offset source_len source_len source_offset target
- m_context << Instruction::CALLDATACOPY;
- m_context << Instruction::DUP3 << Instruction::ADD;
- m_context << Instruction::SWAP2 << Instruction::POP << Instruction::POP;
+
+ string routine = "calldatacopy(target, source, len)\n";
+ if (_padToWordBoundaries)
+ routine += R"(
+ // Set padding suffix to zero
+ mstore(add(target, len), 0)
+ len := and(add(len, 0x1f), not(0x1f))
+ )";
+ routine += "target := add(target, len)\n";
+ m_context.appendInlineAssembly("{" + routine + "}", {"target", "source", "len"});
+ m_context << Instruction::POP << Instruction::POP;
}
else if (_sourceType.location() == DataLocation::Memory)
{
@@ -823,6 +828,85 @@ void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const
})", {"ref"});
}
+void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
+{
+ solAssert(_type.location() == DataLocation::Storage, "");
+ solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+
+ if (_type.isByteArray())
+ {
+ m_context.appendInlineAssembly(R"({
+ let slot_value := sload(ref)
+ switch and(slot_value, 1)
+ case 0 {
+ // short byte array
+ let length := and(div(slot_value, 2), 0x1f)
+ if iszero(length) { invalid() }
+
+ // Zero-out the suffix including the least significant byte.
+ let mask := sub(exp(0x100, sub(33, length)), 1)
+ length := sub(length, 1)
+ slot_value := or(and(not(mask), slot_value), mul(length, 2))
+ sstore(ref, slot_value)
+ }
+ case 1 {
+ // long byte array
+ mstore(0, ref)
+ let length := div(slot_value, 2)
+ let slot := keccak256(0, 0x20)
+ switch length
+ case 32
+ {
+ let data := sload(slot)
+ sstore(slot, 0)
+ data := and(data, not(0xff))
+ sstore(ref, or(data, 62))
+ }
+ default
+ {
+ let offset_inside_slot := and(sub(length, 1), 0x1f)
+ slot := add(slot, div(sub(length, 1), 32))
+ let data := sload(slot)
+
+ // Zero-out the suffix of the byte array by masking it.
+ // ((1<<(8 * (32 - offset))) - 1)
+ let mask := sub(exp(0x100, sub(32, offset_inside_slot)), 1)
+ data := and(not(mask), data)
+ sstore(slot, data)
+
+ // Reduce the length by 1
+ slot_value := sub(slot_value, 2)
+ sstore(ref, slot_value)
+ }
+ }
+ })", {"ref"});
+ m_context << Instruction::POP;
+ }
+ else
+ {
+ // stack: ArrayReference
+ retrieveLength(_type);
+ // stack: ArrayReference oldLength
+ m_context << Instruction::DUP1;
+ // stack: ArrayReference oldLength oldLength
+ m_context << Instruction::ISZERO;
+ m_context.appendConditionalInvalid();
+
+ // Stack: ArrayReference oldLength
+ m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB;
+ // Stack ArrayReference newLength
+ m_context << Instruction::DUP2 << Instruction::DUP2;
+ // Stack ArrayReference newLength ArrayReference newLength;
+ accessIndex(_type, false);
+ // Stack: ArrayReference newLength storage_slot byte_offset
+ StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ // Stack: ArrayReference newLength
+ m_context << Instruction::SWAP1 << Instruction::SSTORE;
+ }
+}
+
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
m_context.callLowLevelFunction(
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index 99786397..daf50bf5 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -73,6 +73,11 @@ public:
/// Stack pre: reference (excludes byte offset)
/// Stack post: new_length
void incrementDynamicArraySize(ArrayType const& _type) const;
+ /// Decrements the size of a dynamic array by one if length is nonzero. Causes an invalid instruction otherwise.
+ /// Clears the removed data element. In case of a byte array, this might move the data.
+ /// Stack pre: reference
+ /// Stack post:
+ void popStorageArrayElement(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp
index d3afada5..55f1d252 100644
--- a/libsolidity/codegen/Compiler.cpp
+++ b/libsolidity/codegen/Compiler.cpp
@@ -46,19 +46,6 @@ void Compiler::compileContract(
m_context.optimise(m_optimize, m_optimizeRuns);
}
-void Compiler::compileClone(
- ContractDefinition const& _contract,
- map<ContractDefinition const*, eth::Assembly const*> const& _contracts
-)
-{
- solAssert(!_contract.isLibrary(), "");
- ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
- ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize);
- m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts);
-
- m_context.optimise(m_optimize, m_optimizeRuns);
-}
-
eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const
{
return m_runtimeContext.functionEntryLabelIfExists(_function);
diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h
index f6865d75..4028ae63 100644
--- a/libsolidity/codegen/Compiler.h
+++ b/libsolidity/codegen/Compiler.h
@@ -50,12 +50,6 @@ public:
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts,
bytes const& _metadata
);
- /// Compiles a contract that uses DELEGATECALL to call into a pre-deployed version of the given
- /// contract at runtime, but contains the full creation-time code.
- void compileClone(
- ContractDefinition const& _contract,
- std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
- );
/// @returns Entire assembly.
eth::Assembly const& assembly() const { return m_context.assembly(); }
/// @returns The entire assembled object (with constructor).
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index a35eea73..71b615b8 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -127,10 +127,14 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, "");
+ unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack();
+ // Variables should not have stack size other than [1, 2],
+ // but that might change when new types are introduced.
+ solAssert(sizeOnStack == 1 || sizeOnStack == 2, "");
m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent);
}
-void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
+void CompilerContext::removeVariable(Declaration const& _declaration)
{
solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), "");
m_localVariables[&_declaration].pop_back();
@@ -138,6 +142,25 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
m_localVariables.erase(&_declaration);
}
+void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight)
+{
+ vector<Declaration const*> toRemove;
+ for (auto _var: m_localVariables)
+ {
+ solAssert(!_var.second.empty(), "");
+ solAssert(_var.second.back() <= stackHeight(), "");
+ if (_var.second.back() >= _stackHeight)
+ toRemove.push_back(_var.first);
+ }
+ for (auto _var: toRemove)
+ removeVariable(*_var);
+}
+
+unsigned CompilerContext::numberOfLocalVariables() const
+{
+ return m_localVariables.size();
+}
+
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const
{
auto ret = m_compiledContracts.find(&_contract);
@@ -388,7 +411,7 @@ FunctionDefinition const& CompilerContext::resolveVirtualFunction(
if (
function->name() == name &&
!function->isConstructor() &&
- FunctionType(*function).hasEqualArgumentTypes(functionType)
+ FunctionType(*function).hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 5776b5d1..3f357821 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -71,7 +71,11 @@ public:
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
- void removeVariable(VariableDeclaration const& _declaration);
+ void removeVariable(Declaration const& _declaration);
+ /// Removes all local variables currently allocated above _stackHeight.
+ void removeVariablesAboveStackHeight(unsigned _stackHeight);
+ /// Returns the number of currently allocated local variables.
+ unsigned numberOfLocalVariables() const;
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index d9f17263..e6ad6d9c 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -181,12 +181,12 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
}
}
-void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory, bool _revertOnOutOfBounds)
+void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory)
{
/// Stack: <source_offset> <length>
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
{
- // Use the new JULIA-based decoding function
+ // Use the new Yul-based decoding function
auto stackHeightBefore = m_context.stackHeight();
abiDecodeV2(_typeParameters, _fromMemory);
solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, "");
@@ -194,14 +194,10 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
}
//@todo this does not yet support nested dynamic arrays
-
- if (_revertOnOutOfBounds)
- {
- size_t encodedSize = 0;
- for (auto const& t: _typeParameters)
- encodedSize += t->decodingType()->calldataEncodedSize(true);
- m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
- }
+ size_t encodedSize = 0;
+ for (auto const& t: _typeParameters)
+ encodedSize += t->decodingType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
@@ -231,26 +227,21 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
{
// compute data pointer
m_context << Instruction::DUP1 << Instruction::MLOAD;
- if (_revertOnOutOfBounds)
- {
- // Check that the data pointer is valid and that length times
- // item size is still inside the range.
- Whiskers templ(R"({
- if gt(ptr, 0x100000000) { revert(0, 0) }
- ptr := add(ptr, base_offset)
- let array_data_start := add(ptr, 0x20)
- if gt(array_data_start, input_end) { revert(0, 0) }
- let array_length := mload(ptr)
- if or(
- gt(array_length, 0x100000000),
- gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
- ) { revert(0, 0) }
- })");
- templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
- m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
- }
- else
- m_context << Instruction::DUP3 << Instruction::ADD;
+ // Check that the data pointer is valid and that length times
+ // item size is still inside the range.
+ Whiskers templ(R"({
+ if gt(ptr, 0x100000000) { revert(0, 0) }
+ ptr := add(ptr, base_offset)
+ let array_data_start := add(ptr, 0x20)
+ if gt(array_data_start, input_end) { revert(0, 0) }
+ let array_length := mload(ptr)
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
+ ) { revert(0, 0) }
+ })");
+ templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
+ m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k)
moveIntoStack(3);
m_context << u256(0x20) << Instruction::ADD;
@@ -273,30 +264,25 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
m_context << Instruction::SWAP1;
// stack: input_end base_offset next_pointer data_offset
- if (_revertOnOutOfBounds)
- m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
+ m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
m_context << Instruction::DUP3 << Instruction::ADD;
// stack: input_end base_offset next_pointer array_head_ptr
- if (_revertOnOutOfBounds)
- m_context.appendInlineAssembly(
- "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
- {"input_end", "base_offset", "next_ptr", "array_head_ptr"}
- );
+ m_context.appendInlineAssembly(
+ "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
+ {"input_end", "base_offset", "next_ptr", "array_head_ptr"}
+ );
// retrieve length
loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
// stack: input_end base_offset next_pointer array_length data_pointer
m_context << Instruction::SWAP2;
// stack: input_end base_offset data_pointer array_length next_pointer
- if (_revertOnOutOfBounds)
- {
- unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
- m_context.appendInlineAssembly(R"({
- if or(
- gt(array_length, 0x100000000),
- gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
- ) { revert(0, 0) }
- })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
- }
+ unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly(R"({
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
+ ) { revert(0, 0) }
+ })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
}
else
{
@@ -347,28 +333,21 @@ void CompilerUtils::encodeToMemory(
)
{
// stack: <v1> <v2> ... <vn> <mem>
+ bool const encoderV2 = m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
solAssert(targetTypes.size() == _givenTypes.size(), "");
for (TypePointer& t: targetTypes)
{
- solUnimplementedAssert(
- t->mobileType() &&
- t->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
- t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
- "Encoding type \"" + t->toString() + "\" not yet implemented."
- );
- t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ TypePointer tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries);
+ solUnimplementedAssert(tEncoding, "Encoding type \"" + t->toString() + "\" not yet implemented.");
+ t = std::move(tEncoding);
}
if (_givenTypes.empty())
return;
- else if (
- _padToWordBoundaries &&
- !_copyDynamicDataInPlace &&
- m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
- )
+ else if (_padToWordBoundaries && !_copyDynamicDataInPlace && encoderV2)
{
- // Use the new JULIA-based encoding function
+ // Use the new Yul-based encoding function
auto stackHeightBefore = m_context.stackHeight();
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes);
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
@@ -377,8 +356,8 @@ void CompilerUtils::encodeToMemory(
// Stack during operation:
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
- // The values dyn_head_i are added during the first loop and they point to the head part
- // of the ith dynamic parameter, which is filled once the dynamic parts are processed.
+ // The values dyn_head_n are added during the first loop and they point to the head part
+ // of the nth dynamic parameter, which is filled once the dynamic parts are processed.
// store memory start pointer
m_context << Instruction::DUP1;
@@ -524,7 +503,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
codecopy(memptr, codesize(), size)
memptr := add(memptr, size)
})");
- templ("element_size", to_string(_type.baseType()->memoryHeadSize()));
+ templ("element_size", to_string(_type.isByteArray() ? 1 : _type.baseType()->memoryHeadSize()));
m_context.appendInlineAssembly(templ.render(), {"length", "memptr"});
}
else
@@ -678,6 +657,11 @@ void CompilerUtils::convertType(
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
+ else if (targetTypeCategory == Type::Category::Address)
+ {
+ solAssert(typeOnStack.numBytes() * 8 == 160, "");
+ rightShiftNumberOnStack(256 - 160);
+ }
else
{
// clear for conversion to longer bytes
@@ -711,23 +695,33 @@ void CompilerUtils::convertType(
break;
case Type::Category::FixedPoint:
solUnimplemented("Not yet implemented - FixedPointType.");
+ case Type::Category::Address:
case Type::Category::Integer:
case Type::Category::Contract:
case Type::Category::RationalNumber:
if (targetTypeCategory == Type::Category::FixedBytes)
{
- solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber,
- "Invalid conversion to FixedBytesType requested.");
+ solAssert(
+ stackTypeCategory == Type::Category::Address ||
+ stackTypeCategory == Type::Category::Integer ||
+ stackTypeCategory == Type::Category::RationalNumber,
+ "Invalid conversion to FixedBytesType requested."
+ );
// conversion from bytes to string. no need to clean the high bit
// only to shift left because of opposite alignment
FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
+ {
if (targetBytesType.numBytes() * 8 > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
+ }
+ else if (stackTypeCategory == Type::Category::Address)
+ solAssert(targetBytesType.numBytes() * 8 == 160, "");
leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8);
}
else if (targetTypeCategory == Type::Category::Enum)
{
+ solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested.");
solAssert(_typeOnStack.mobileType(), "");
// just clean
convertType(_typeOnStack, *_typeOnStack.mobileType(), true);
@@ -754,8 +748,8 @@ void CompilerUtils::convertType(
}
else
{
- solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
- IntegerType addressType(160, IntegerType::Modifier::Address);
+ solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, "");
+ IntegerType addressType(160);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_targetType) : addressType;
if (stackTypeCategory == Type::Category::RationalNumber)
@@ -968,20 +962,12 @@ void CompilerUtils::convertType(
{
TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack);
TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType);
- // fillRight: remove excess values at right side, !fillRight: remove eccess values at left side
- bool fillRight = !targetTuple.components().empty() && (
- !targetTuple.components().back() ||
- targetTuple.components().front()
- );
+ solAssert(targetTuple.components().size() == sourceTuple.components().size(), "");
unsigned depth = sourceTuple.sizeOnStack();
for (size_t i = 0; i < sourceTuple.components().size(); ++i)
{
TypePointer sourceType = sourceTuple.components()[i];
- TypePointer targetType;
- if (fillRight && i < targetTuple.components().size())
- targetType = targetTuple.components()[i];
- else if (!fillRight && targetTuple.components().size() + i >= sourceTuple.components().size())
- targetType = targetTuple.components()[targetTuple.components().size() - (sourceTuple.components().size() - i)];
+ TypePointer targetType = targetTuple.components()[i];
if (!sourceType)
{
solAssert(!targetType, "");
@@ -1025,10 +1011,8 @@ void CompilerUtils::convertType(
m_context << Instruction::ISZERO << Instruction::ISZERO;
break;
default:
- if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Integer)
+ if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address)
{
- IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType);
- solAssert(targetType.isAddress(), "Function type can only be converted to address.");
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
@@ -1184,6 +1168,15 @@ void CompilerUtils::popStackSlots(size_t _amount)
m_context << Instruction::POP;
}
+void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo)
+{
+ solAssert(m_context.stackHeight() >= _toHeight, "");
+ unsigned amount = m_context.stackHeight() - _toHeight;
+ popStackSlots(amount);
+ m_context.appendJumpTo(_jumpTo);
+ m_context.adjustStackOffset(amount);
+}
+
unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes)
{
unsigned size = 0;
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index 8e3a8a5d..ad3d7327 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -97,12 +97,12 @@ public:
/// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data.
- /// Calls revert if @a _revertOnOutOfBounds is true and the supplied size is shorter
- /// than the static data requirements or if dynamic data pointers reach outside of the
- /// area. Also has a hard cap of 0x100000000 for any given length/offset field.
+ /// Calls revert if the supplied size is shorter than the static data requirements
+ /// or if dynamic data pointers reach outside of the area.
+ /// Also has a hard cap of 0x100000000 for any given length/offset field.
/// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen>
- void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false, bool _revertOnOutOfBounds = false);
+ void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false);
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
@@ -241,6 +241,10 @@ public:
void popStackElement(Type const& _type);
/// Removes element from the top of the stack _amount times.
void popStackSlots(size_t _amount);
+ /// Pops slots from the stack such that its height is _toHeight.
+ /// Adds jump to _jumpTo.
+ /// Readjusts the stack offset to the original value.
+ void popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo);
template <class T>
static unsigned sizeOnStack(std::vector<T> const& _variables);
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 0889ac7c..e26bc13a 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -50,7 +50,7 @@ class StackHeightChecker
public:
explicit StackHeightChecker(CompilerContext const& _context):
m_context(_context), stackHeight(m_context.stackHeight()) {}
- void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); }
+ void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)); }
private:
CompilerContext const& m_context;
unsigned stackHeight;
@@ -71,7 +71,11 @@ void ContractCompiler::compileContract(
appendDelegatecallCheck();
initializeContext(_contract, _contracts);
+ // This generates the dispatch function for externally visible functions
+ // and adds the function to the compilation queue. Additionally internal functions,
+ // which are referenced directly or indirectly will be added.
appendFunctionSelector(_contract);
+ // This processes the above populated queue until it is empty.
appendMissingFunctions();
}
@@ -90,27 +94,6 @@ size_t ContractCompiler::compileConstructor(
}
}
-size_t ContractCompiler::compileClone(
- ContractDefinition const& _contract,
- map<ContractDefinition const*, eth::Assembly const*> const& _contracts
-)
-{
- initializeContext(_contract, _contracts);
-
- appendInitAndConstructorCode(_contract);
-
- //@todo determine largest return size of all runtime functions
- auto runtimeSub = m_context.addSubroutine(cloneRuntime());
-
- // stack contains sub size
- m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY;
- m_context << u256(0) << Instruction::RETURN;
-
- appendMissingFunctions();
-
- return size_t(runtimeSub.data());
-}
-
void ContractCompiler::initializeContext(
ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts
@@ -427,7 +410,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context.startFunction(_function);
// stack upon entry: [return address] [arg0] [arg1] ... [argn]
- // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp]
+ // reserve additional slots: [retarg0] ... [retargm]
unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters());
if (!_function.isConstructor())
@@ -441,8 +424,6 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- appendStackVariableInitialisation(*localVariable);
if (_function.isConstructor())
if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope())))
@@ -451,12 +432,11 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
solAssert(m_returnTags.empty(), "");
m_breakTags.clear();
m_continueTags.clear();
- m_stackCleanupForReturn = 0;
m_currentFunction = &_function;
m_modifierDepth = -1;
+ m_scopeStackHeight.clear();
appendModifierOrFunctionCode();
-
solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
@@ -467,14 +447,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters());
unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters());
- unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables());
vector<int> stackLayout;
stackLayout.push_back(c_returnValuesSize); // target of return address
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
for (unsigned i = 0; i < c_returnValuesSize; ++i)
stackLayout.push_back(i);
- stackLayout += vector<int>(c_localVariablesSize, -1);
if (stackLayout.size() > 17)
BOOST_THROW_EXCEPTION(
@@ -493,18 +471,23 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1);
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
- //@todo assert that everything is in place now
+ for (int i = 0; i < int(stackLayout.size()); ++i)
+ if (stackLayout[i] != i)
+ solAssert(false, "Invalid stack layout on cleanup.");
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters())
m_context.removeVariable(*variable);
- for (VariableDeclaration const* localVariable: _function.localVariables())
- m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize);
/// The constructor and the fallback function doesn't to jump out.
- if (!_function.isConstructor() && !_function.isFallback())
- m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ if (!_function.isConstructor())
+ {
+ solAssert(m_context.numberOfLocalVariables() == 0, "");
+ if (!_function.isFallback())
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ }
+
return false;
}
@@ -666,32 +649,36 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement)
{
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _whileStatement);
+
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
- m_continueTags.push_back(loopStart);
- m_breakTags.push_back(loopEnd);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
m_context << loopStart;
- // While loops have the condition prepended
- if (!_whileStatement.isDoWhile())
+ if (_whileStatement.isDoWhile())
{
- compileExpression(_whileStatement.condition());
- m_context << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(loopEnd);
- }
+ eth::AssemblyItem condition = m_context.newTag();
+ m_continueTags.push_back({condition, m_context.stackHeight()});
- _whileStatement.body().accept(*this);
+ _whileStatement.body().accept(*this);
- // Do-while loops have the condition appended
- if (_whileStatement.isDoWhile())
+ m_context << condition;
+ compileExpression(_whileStatement.condition());
+ m_context << Instruction::ISZERO << Instruction::ISZERO;
+ m_context.appendConditionalJumpTo(loopStart);
+ }
+ else
{
+ m_continueTags.push_back({loopStart, m_context.stackHeight()});
compileExpression(_whileStatement.condition());
m_context << Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd);
- }
- m_context.appendJumpTo(loopStart);
+ _whileStatement.body().accept(*this);
+
+ m_context.appendJumpTo(loopStart);
+ }
m_context << loopEnd;
m_continueTags.pop_back();
@@ -708,12 +695,14 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
eth::AssemblyItem loopNext = m_context.newTag();
- m_continueTags.push_back(loopNext);
- m_breakTags.push_back(loopEnd);
+
+ storeStackHeight(&_forStatement);
if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this);
+ m_breakTags.push_back({loopEnd, m_context.stackHeight()});
+ m_continueTags.push_back({loopNext, m_context.stackHeight()});
m_context << loopStart;
// if there is no terminating condition in for, default is to always be true
@@ -733,11 +722,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
_forStatement.loopExpression()->accept(*this);
m_context.appendJumpTo(loopStart);
+
m_context << loopEnd;
m_continueTags.pop_back();
m_breakTags.pop_back();
+ // For the case where no break/return is executed:
+ // loop initialization variables have to be freed
+ popScopedVariables(&_forStatement);
+
checker.check();
return false;
}
@@ -745,16 +739,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement)
bool ContractCompiler::visit(Continue const& _continueStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
- if (!m_continueTags.empty())
- m_context.appendJumpTo(m_continueTags.back());
+ solAssert(!m_continueTags.empty(), "");
+ CompilerUtils(m_context).popAndJump(m_continueTags.back().second, m_continueTags.back().first);
return false;
}
bool ContractCompiler::visit(Break const& _breakStatement)
{
CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
- if (!m_breakTags.empty())
- m_context.appendJumpTo(m_breakTags.back());
+ solAssert(!m_breakTags.empty(), "");
+ CompilerUtils(m_context).popAndJump(m_breakTags.back().second, m_breakTags.back().first);
return false;
}
@@ -780,18 +774,14 @@ bool ContractCompiler::visit(Return const& _return)
for (auto const& retVariable: boost::adaptors::reverse(returnParameters))
CompilerUtils(m_context).moveToStackVariable(*retVariable);
}
- for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
- m_context << Instruction::POP;
- m_context.appendJumpTo(m_returnTags.back());
- m_context.adjustStackOffset(m_stackCleanupForReturn);
+
+ CompilerUtils(m_context).popAndJump(m_returnTags.back().second, m_returnTags.back().first);
return false;
}
-bool ContractCompiler::visit(Throw const& _throw)
+bool ContractCompiler::visit(Throw const&)
{
- CompilerContext::LocationSetter locationSetter(m_context, _throw);
- // Do not send back an error detail.
- m_context.appendRevert();
+ solAssert(false, "Throw statement is disallowed.");
return false;
}
@@ -806,8 +796,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit)
bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
- StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
+
+ // Local variable slots are reserved when their declaration is visited,
+ // and freed in the end of their scope.
+ for (auto _decl: _variableDeclarationStatement.declarations())
+ if (_decl)
+ appendStackVariableInitialisation(*_decl);
+
+ StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue())
{
CompilerUtils utils(m_context);
@@ -817,20 +814,19 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
valueTypes = tupleType->components();
else
valueTypes = TypePointers{expression->annotation().type};
- auto const& assignments = _variableDeclarationStatement.annotation().assignments;
- solAssert(assignments.size() == valueTypes.size(), "");
- for (size_t i = 0; i < assignments.size(); ++i)
+ auto const& declarations = _variableDeclarationStatement.declarations();
+ solAssert(declarations.size() == valueTypes.size(), "");
+ for (size_t i = 0; i < declarations.size(); ++i)
{
- size_t j = assignments.size() - i - 1;
+ size_t j = declarations.size() - i - 1;
solAssert(!!valueTypes[j], "");
- VariableDeclaration const* varDecl = assignments[j];
- if (!varDecl)
- utils.popStackElement(*valueTypes[j]);
- else
+ if (VariableDeclaration const* varDecl = declarations[j].get())
{
utils.convertType(*valueTypes[j], *varDecl->annotation().type);
utils.moveToStackVariable(*varDecl);
}
+ else
+ utils.popStackElement(*valueTypes[j]);
}
}
checker.check();
@@ -857,6 +853,18 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement)
return true;
}
+bool ContractCompiler::visit(Block const& _block)
+{
+ storeStackHeight(&_block);
+ return true;
+}
+
+void ContractCompiler::endVisit(Block const& _block)
+{
+ // Frees local variables declared in the scope of this block.
+ popScopedVariables(&_block);
+}
+
void ContractCompiler::appendMissingFunctions()
{
while (Declaration const* function = m_context.nextFunctionToCompile())
@@ -912,27 +920,19 @@ void ContractCompiler::appendModifierOrFunctionCode()
modifier.parameters()[i]->annotation().type
);
}
- for (VariableDeclaration const* localVariable: modifier.localVariables())
- {
- addedVariables.push_back(localVariable);
- appendStackVariableInitialisation(*localVariable);
- }
- stackSurplus =
- CompilerUtils::sizeOnStack(modifier.parameters()) +
- CompilerUtils::sizeOnStack(modifier.localVariables());
+ stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters());
codeBlock = &modifier.body();
}
}
if (codeBlock)
{
- m_returnTags.push_back(m_context.newTag());
-
+ m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()});
codeBlock->accept(*this);
solAssert(!m_returnTags.empty(), "");
- m_context << m_returnTags.back();
+ m_context << m_returnTags.back().first;
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
@@ -957,25 +957,19 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);
}
-eth::AssemblyPointer ContractCompiler::cloneRuntime() const
-{
- eth::Assembly a;
- a << Instruction::CALLDATASIZE;
- a << u256(0) << Instruction::DUP1 << Instruction::CALLDATACOPY;
- //@todo adjust for larger return values, make this dynamic.
- a << u256(0x20) << u256(0) << Instruction::CALLDATASIZE;
- a << u256(0);
- // this is the address which has to be substituted by the linker.
- //@todo implement as special "marker" AssemblyItem.
- a << u256("0xcafecafecafecafecafecafecafecafecafecafe");
- a << u256(eth::GasCosts::callGas(m_context.evmVersion()) + 10) << Instruction::GAS << Instruction::SUB;
- a << Instruction::DELEGATECALL;
- //Propagate error condition (if DELEGATECALL pushes 0 on stack).
- a << Instruction::ISZERO;
- a << Instruction::ISZERO;
- eth::AssemblyItem afterTag = a.appendJumpI().tag();
- a << Instruction::INVALID << afterTag;
- //@todo adjust for larger return values, make this dynamic.
- a << u256(0x20) << u256(0) << Instruction::RETURN;
- return make_shared<eth::Assembly>(a);
+void ContractCompiler::popScopedVariables(ASTNode const* _node)
+{
+ unsigned blockHeight = m_scopeStackHeight.at(m_modifierDepth).at(_node);
+ m_context.removeVariablesAboveStackHeight(blockHeight);
+ solAssert(m_context.stackHeight() >= blockHeight, "");
+ unsigned stackDiff = m_context.stackHeight() - blockHeight;
+ CompilerUtils(m_context).popStackSlots(stackDiff);
+ m_scopeStackHeight[m_modifierDepth].erase(_node);
+ if (m_scopeStackHeight[m_modifierDepth].size() == 0)
+ m_scopeStackHeight.erase(m_modifierDepth);
+}
+
+void ContractCompiler::storeStackHeight(ASTNode const* _node)
+{
+ m_scopeStackHeight[m_modifierDepth][_node] = m_context.stackHeight();
}
diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h
index 02a3452f..5fa650b1 100644
--- a/libsolidity/codegen/ContractCompiler.h
+++ b/libsolidity/codegen/ContractCompiler.h
@@ -56,13 +56,6 @@ public:
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
);
- /// Compiles a contract that uses DELEGATECALL to call into a pre-deployed version of the given
- /// contract at runtime, but contains the full creation-time code.
- /// @returns the identifier of the runtime sub-assembly.
- size_t compileClone(
- ContractDefinition const& _contract,
- std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
- );
private:
/// Registers the non-function objects inside the contract with the context and stores the basic
@@ -109,6 +102,8 @@ private:
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
virtual bool visit(ExpressionStatement const& _expressionStatement) override;
virtual bool visit(PlaceholderStatement const&) override;
+ virtual bool visit(Block const& _block) override;
+ virtual void endVisit(Block const& _block) override;
/// Repeatedly visits all function which are referenced but which are not compiled yet.
void appendMissingFunctions();
@@ -120,22 +115,31 @@ private:
void appendStackVariableInitialisation(VariableDeclaration const& _variable);
void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer());
- /// @returns the runtime assembly for clone contracts.
- eth::AssemblyPointer cloneRuntime() const;
+ /// Frees the variables of a certain scope (to be used when leaving).
+ void popScopedVariables(ASTNode const* _node);
+
+ /// Sets the stack height for the visited loop.
+ void storeStackHeight(ASTNode const* _node);
bool const m_optimise;
/// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context;
- std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement
- std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
- /// Tag to jump to for a "return" statement, needs to be stacked because of modifiers.
- std::vector<eth::AssemblyItem> m_returnTags;
+ /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_breakTags;
+ /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_continueTags;
+ /// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables.
+ /// Needs to be stacked because of modifiers.
+ std::vector<std::pair<eth::AssemblyItem, unsigned>> m_returnTags;
unsigned m_modifierDepth = 0;
FunctionDefinition const* m_currentFunction = nullptr;
- unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
+
// arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
+
+ /// Stores the variables that were declared inside a specific scope, for each modifier depth.
+ std::map<unsigned, std::map<ASTNode const*, unsigned>> m_scopeStackHeight;
};
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index f38c1e67..bd863e05 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -281,19 +281,19 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
if (_tuple.isInlineArray())
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
-
+
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
m_context << max(u256(32u), arrayType.memorySize());
utils().allocateMemory();
m_context << Instruction::DUP1;
-
+
for (auto const& component: _tuple.components())
{
component->accept(*this);
utils().convertType(*component->annotation().type, *arrayType.baseType(), true);
- utils().storeInMemoryDynamic(*arrayType.baseType(), true);
+ utils().storeInMemoryDynamic(*arrayType.baseType(), true);
}
-
+
m_context << Instruction::POP;
}
else
@@ -349,6 +349,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved.");
+ solUnimplementedAssert(
+ _unaryOperation.annotation().type->category() != Type::Category::FixedPoint,
+ "Not yet implemented - FixedPointType."
+ );
m_currentLValue->retrieveValue(_unaryOperation.location());
if (!_unaryOperation.isPrefixOperation())
{
@@ -562,11 +566,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break;
}
case FunctionType::Kind::External:
- case FunctionType::Kind::CallCode:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
break;
@@ -697,18 +701,26 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.appendRevert();
break;
}
- case FunctionType::Kind::SHA3:
+ case FunctionType::Kind::KECCAK256:
{
- TypePointers argumentTypes;
- for (auto const& arg: arguments)
+ solAssert(arguments.size() == 1, "");
+ solAssert(!function.padArguments(), "");
+ TypePointer const& argType = arguments.front()->annotation().type;
+ solAssert(argType, "");
+ arguments.front()->accept(*this);
+ // Optimization: If type is bytes or string, then do not encode,
+ // but directly compute keccak256 on memory.
+ if (*argType == ArrayType(DataLocation::Memory) || *argType == ArrayType(DataLocation::Memory, true))
{
- arg->accept(*this);
- argumentTypes.push_back(arg->annotation().type);
+ ArrayUtils(m_context).retrieveLength(ArrayType(DataLocation::Memory));
+ m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD;
+ }
+ else
+ {
+ utils().fetchFreeMemoryPointer();
+ utils().packedEncode({argType}, TypePointers());
+ utils().toSizeAfterFreeMemoryPointer();
}
- utils().fetchFreeMemoryPointer();
- solAssert(!function.padArguments(), "");
- utils().packedEncode(argumentTypes, TypePointers());
- utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::KECCAK256;
break;
}
@@ -866,6 +878,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
break;
}
+ case FunctionType::Kind::ArrayPop:
+ {
+ _functionCall.expression().accept(*this);
+ solAssert(function.parameterTypes().empty(), "");
+
+ ArrayType const& arrayType = dynamic_cast<ArrayType const&>(
+ *dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression().annotation().type
+ );
+ solAssert(arrayType.dataStoredIn(DataLocation::Storage), "");
+
+ ArrayUtils(m_context).popStorageArrayElement(arrayType);
+ break;
+ }
case FunctionType::Kind::ObjectCreation:
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
@@ -1045,6 +1070,31 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// stack now: <memory pointer>
break;
}
+ case FunctionType::Kind::ABIDecode:
+ {
+ arguments.front()->accept(*this);
+ TypePointer firstArgType = arguments.front()->annotation().type;
+ TypePointers targetTypes;
+ if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type.get()))
+ targetTypes = targetTupleType->components();
+ else
+ targetTypes = TypePointers{_functionCall.annotation().type};
+ if (
+ *firstArgType == ArrayType(DataLocation::CallData) ||
+ *firstArgType == ArrayType(DataLocation::CallData, true)
+ )
+ utils().abiDecode(targetTypes, false);
+ else
+ {
+ utils().convertType(*firstArgType, ArrayType(DataLocation::Memory));
+ m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
+ m_context << Instruction::SWAP1 << Instruction::MLOAD;
+ // stack now: <mem_pos> <length>
+
+ utils().abiDecode(targetTypes, true);
+ }
+ break;
+ }
case FunctionType::Kind::GasLeft:
m_context << Instruction::GAS;
break;
@@ -1118,18 +1168,18 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "event not found");
// no-op, because the parent node will do the job
break;
+ case FunctionType::Kind::DelegateCall:
+ _memberAccess.expression().accept(*this);
+ m_context << funType->externalIdentifier();
+ break;
case FunctionType::Kind::External:
case FunctionType::Kind::Creation:
- case FunctionType::Kind::DelegateCall:
- case FunctionType::Kind::CallCode:
case FunctionType::Kind::Send:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Transfer:
- _memberAccess.expression().accept(*this);
- m_context << funType->externalIdentifier();
- break;
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
@@ -1189,63 +1239,66 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
switch (_memberAccess.expression().annotation().type->category())
{
case Type::Category::Contract:
- case Type::Category::Integer:
{
- bool alsoSearchInteger = false;
- if (_memberAccess.expression().annotation().type->category() == Type::Category::Contract)
+ ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
+ if (type.isSuper())
{
- ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
- if (type.isSuper())
- {
- solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
- utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
- dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
- type.contractDefinition()
- ));
- }
+ solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
+ utils().pushCombinedFunctionEntryLabel(m_context.superFunction(
+ dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration),
+ type.contractDefinition()
+ ));
+ }
+ // ordinary contract type
+ else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
+ {
+ u256 identifier;
+ if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ identifier = FunctionType(*variable).externalIdentifier();
+ else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
+ identifier = FunctionType(*function).externalIdentifier();
else
- {
- // ordinary contract type
- if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
- {
- u256 identifier;
- if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
- identifier = FunctionType(*variable).externalIdentifier();
- else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
- identifier = FunctionType(*function).externalIdentifier();
- else
- solAssert(false, "Contract member is neither variable nor function.");
- utils().convertType(type, IntegerType(160, IntegerType::Modifier::Address), true);
- m_context << identifier;
- }
- else
- // not found in contract, search in members inherited from address
- alsoSearchInteger = true;
- }
+ solAssert(false, "Contract member is neither variable nor function.");
+ utils().convertType(type, AddressType(type.isPayable() ? StateMutability::Payable : StateMutability::NonPayable), true);
+ m_context << identifier;
}
else
- alsoSearchInteger = true;
-
- if (alsoSearchInteger)
+ solAssert(false, "Invalid member access in contract");
+ break;
+ }
+ case Type::Category::Integer:
+ {
+ solAssert(false, "Invalid member access to integer");
+ break;
+ }
+ case Type::Category::Address:
+ {
+ if (member == "balance")
{
- if (member == "balance")
- {
- utils().convertType(
- *_memberAccess.expression().annotation().type,
- IntegerType(160, IntegerType::Modifier::Address),
- true
- );
- m_context << Instruction::BALANCE;
- }
- else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member))
- utils().convertType(
- *_memberAccess.expression().annotation().type,
- IntegerType(160, IntegerType::Modifier::Address),
- true
- );
- else
- solAssert(false, "Invalid member access to integer");
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::NonPayable),
+ true
+ );
+ m_context << Instruction::BALANCE;
}
+ else if ((set<string>{"send", "transfer"}).count(member))
+ {
+ solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, "");
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::Payable),
+ true
+ );
+ }
+ else if ((set<string>{"call", "callcode", "delegatecall", "staticcall"}).count(member))
+ utils().convertType(
+ *_memberAccess.expression().annotation().type,
+ AddressType(StateMutability::NonPayable),
+ true
+ );
+ else
+ solAssert(false, "Invalid member access to address");
break;
}
case Type::Category::Function:
@@ -1345,11 +1398,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
break;
}
}
- else if (member == "push")
+ else if (member == "push" || member == "pop")
{
solAssert(
- type.isDynamicallySized() && type.location() == DataLocation::Storage,
- "Tried to use .push() on a non-dynamically sized array"
+ type.isDynamicallySized() &&
+ type.location() == DataLocation::Storage &&
+ type.category() == Type::Category::Array,
+ "Tried to use ." + member + "() on a non-dynamically sized array"
);
}
else
@@ -1532,12 +1587,12 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
CompilerContext::LocationSetter locationSetter(m_context, _literal);
TypePointer type = _literal.annotation().type;
-
+
switch (type->category())
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
- case Type::Category::Integer:
+ case Type::Category::Address:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:
@@ -1624,12 +1679,12 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator
void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type)
{
- IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
- bool const c_isSigned = type.isSigned();
-
if (_type.category() == Type::Category::FixedPoint)
solUnimplemented("Not yet implemented - FixedPointType.");
+ IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
+ bool const c_isSigned = type.isSigned();
+
switch (_operator)
{
case Token::Add:
@@ -1722,11 +1777,36 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
m_context << u256(2) << Instruction::EXP << Instruction::MUL;
break;
case Token::SAR:
- // NOTE: SAR rounds differently than SDIV
- if (m_context.evmVersion().hasBitwiseShifting() && !c_valueSigned)
- m_context << Instruction::SHR;
+ if (m_context.evmVersion().hasBitwiseShifting())
+ m_context << (c_valueSigned ? Instruction::SAR : Instruction::SHR);
else
- m_context << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
+ {
+ if (c_valueSigned)
+ // In the following assembly snippet, xor_mask will be zero, if value_to_shift is positive.
+ // Therefore xor'ing with xor_mask is the identity and the computation reduces to
+ // div(value_to_shift, exp(2, shift_amount)), which is correct, since for positive values
+ // arithmetic right shift is dividing by a power of two (which, as a bitwise operation, results
+ // in discarding bits on the right and filling with zeros from the left).
+ // For negative values arithmetic right shift, viewed as a bitwise operation, discards bits to the
+ // right and fills in ones from the left. This is achieved as follows:
+ // If value_to_shift is negative, then xor_mask will have all bits set, so xor'ing with xor_mask
+ // will flip all bits. First all bits in value_to_shift are flipped. As for the positive case,
+ // dividing by a power of two using integer arithmetic results in discarding bits to the right
+ // and filling with zeros from the left. Flipping all bits in the result again, turns all zeros
+ // on the left to ones and restores the non-discarded, shifted bits to their original value (they
+ // have now been flipped twice). In summary we now have discarded bits to the right and filled with
+ // ones from the left, i.e. we have performed an arithmetic right shift.
+ m_context.appendInlineAssembly(R"({
+ let xor_mask := sub(0, slt(value_to_shift, 0))
+ value_to_shift := xor(div(xor(value_to_shift, xor_mask), exp(2, shift_amount)), xor_mask)
+ })", {"value_to_shift", "shift_amount"});
+ else
+ m_context.appendInlineAssembly(R"({
+ value_to_shift := div(value_to_shift, exp(2, shift_amount))
+ })", {"value_to_shift", "shift_amount"});
+ m_context << Instruction::POP;
+
+ }
break;
case Token::SHR:
default:
@@ -1763,71 +1843,46 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
auto funKind = _functionType.kind();
- bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall;
- bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
+
+ solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
+
+ bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
+ bool isCallCode = funKind == FunctionType::Kind::BareCallCode;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
- bool useStaticCall =
- _functionType.stateMutability() <= StateMutability::View &&
- m_context.experimentalFeatureActive(ExperimentalFeature::V050) &&
- m_context.evmVersion().hasStaticCall();
+ bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
- TypePointers returnTypes;
- if (returnSuccessCondition)
- retSize = 0; // return value actually is success condition
- else if (haveReturndatacopy)
- returnTypes = _functionType.returnParameterTypes();
- else
- returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
-
bool dynamicReturnSize = false;
- for (auto const& retType: returnTypes)
- if (retType->isDynamicallyEncoded())
- {
- solAssert(haveReturndatacopy, "");
- dynamicReturnSize = true;
- retSize = 0;
- break;
- }
+ TypePointers returnTypes;
+ if (!returnSuccessConditionAndReturndata)
+ {
+ if (haveReturndatacopy)
+ returnTypes = _functionType.returnParameterTypes();
else
- retSize += retType->calldataEncodedSize();
+ returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
+
+ for (auto const& retType: returnTypes)
+ if (retType->isDynamicallyEncoded())
+ {
+ solAssert(haveReturndatacopy, "");
+ dynamicReturnSize = true;
+ retSize = 0;
+ break;
+ }
+ else
+ retSize += retType->calldataEncodedSize();
+ }
// Evaluate arguments.
TypePointers argumentTypes;
TypePointers parameterTypes = _functionType.parameterTypes();
- bool manualFunctionId = false;
- if (
- (funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) &&
- !_arguments.empty()
- )
- {
- solAssert(_arguments.front()->annotation().type->mobileType(), "");
- manualFunctionId =
- _arguments.front()->annotation().type->mobileType()->calldataEncodedSize(false) ==
- CompilerUtils::dataStartOffset;
- }
- if (manualFunctionId)
- {
- // If we have a Bare* and the first type has exactly 4 bytes, use it as
- // function identifier.
- _arguments.front()->accept(*this);
- utils().convertType(
- *_arguments.front()->annotation().type,
- IntegerType(8 * CompilerUtils::dataStartOffset),
- true
- );
- for (unsigned i = 0; i < gasValueSize; ++i)
- m_context << swapInstruction(gasValueSize - i);
- gasStackPos++;
- valueStackPos++;
- }
if (_functionType.bound())
{
argumentTypes.push_back(_functionType.selfType());
parameterTypes.insert(parameterTypes.begin(), _functionType.selfType());
}
- for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i)
+ for (size_t i = 0; i < _arguments.size(); ++i)
{
_arguments[i]->accept(*this);
argumentTypes.push_back(_arguments[i]->annotation().type);
@@ -1856,26 +1911,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
m_context << u256(0);
utils().fetchFreeMemoryPointer();
- // This touches too much, but that way we save some rounding arithmetics
+ // This touches too much, but that way we save some rounding arithmetic
m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE;
}
}
// Copy function identifier to memory.
utils().fetchFreeMemoryPointer();
- if (!_functionType.isBareCall() || manualFunctionId)
+ if (!_functionType.isBareCall())
{
m_context << dupInstruction(2 + gasValueSize + CompilerUtils::sizeOnStack(argumentTypes));
utils().storeInMemoryDynamic(IntegerType(8 * CompilerUtils::dataStartOffset), false);
}
- // If the function takes arbitrary parameters, copy dynamic length data in place.
+
+ // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
// Move arguments to memory, will not update the free memory pointer (but will update the memory
// pointer on the stack).
utils().encodeToMemory(
argumentTypes,
parameterTypes,
_functionType.padArguments(),
- _functionType.takesArbitraryParameters(),
+ _functionType.takesArbitraryParameters() || _functionType.isBareCall(),
isCallCode || isDelegateCall
);
@@ -1920,7 +1976,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool existenceChecked = false;
// Check the the target contract exists (has code) for non-low-level calls.
- if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
+ if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
@@ -1956,11 +2012,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
unsigned remainsSize =
2 + // contract address, input_memory_end
- _functionType.valueSet() +
- _functionType.gasSet() +
- (!_functionType.isBareCall() || manualFunctionId);
+ (_functionType.valueSet() ? 1 : 0) +
+ (_functionType.gasSet() ? 1 : 0) +
+ (!_functionType.isBareCall() ? 1 : 0);
- if (returnSuccessCondition)
+ if (returnSuccessConditionAndReturndata)
m_context << swapInstruction(remainsSize);
else
{
@@ -1971,9 +2027,31 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().popStackSlots(remainsSize);
- if (returnSuccessCondition)
+ if (returnSuccessConditionAndReturndata)
{
- // already there
+ // success condition is already there
+ // The return parameter types can be empty, when this function is used as
+ // an internal helper function e.g. for ``send`` and ``transfer``. In that
+ // case we're only interested in the success condition, not the return data.
+ if (!_functionType.returnParameterTypes().empty())
+ {
+ if (haveReturndatacopy)
+ {
+ m_context << Instruction::RETURNDATASIZE;
+ m_context.appendInlineAssembly(R"({
+ switch v case 0 {
+ v := 0x60
+ } default {
+ v := mload(0x40)
+ mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
+ mstore(v, returndatasize())
+ returndatacopy(add(v, 0x20), 0, returndatasize())
+ }
+ })", {"v"});
+ }
+ else
+ utils().pushZeroPointer();
+ }
}
else if (funKind == FunctionType::Kind::RIPEMD160)
{
@@ -2025,7 +2103,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
mstore(0x40, newMem)
})", {"start", "size"});
- utils().abiDecode(returnTypes, true, true);
+ utils().abiDecode(returnTypes, true);
}
}
diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp
index dba5823a..6cb91483 100644
--- a/libsolidity/formal/CVC4Interface.cpp
+++ b/libsolidity/formal/CVC4Interface.cpp
@@ -37,6 +37,7 @@ void CVC4Interface::reset()
m_functions.clear();
m_solver.reset();
m_solver.setOption("produce-models", true);
+ m_solver.setTimeLimit(queryTimeout);
}
void CVC4Interface::push()
@@ -49,23 +50,25 @@ void CVC4Interface::pop()
m_solver.pop();
}
-Expression CVC4Interface::newFunction(string _name, Sort _domain, Sort _codomain)
+void CVC4Interface::declareFunction(string _name, Sort _domain, Sort _codomain)
{
- CVC4::Type fType = m_context.mkFunctionType(cvc4Sort(_domain), cvc4Sort(_codomain));
- m_functions.insert({_name, m_context.mkVar(_name.c_str(), fType)});
- return SolverInterface::newFunction(move(_name), _domain, _codomain);
+ if (!m_functions.count(_name))
+ {
+ CVC4::Type fType = m_context.mkFunctionType(cvc4Sort(_domain), cvc4Sort(_codomain));
+ m_functions.insert({_name, m_context.mkVar(_name.c_str(), fType)});
+ }
}
-Expression CVC4Interface::newInteger(string _name)
+void CVC4Interface::declareInteger(string _name)
{
- m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.integerType())});
- return SolverInterface::newInteger(move(_name));
+ if (!m_constants.count(_name))
+ m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.integerType())});
}
-Expression CVC4Interface::newBool(string _name)
+void CVC4Interface::declareBool(string _name)
{
- m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.booleanType())});
- return SolverInterface::newBool(std::move(_name));
+ if (!m_constants.count(_name))
+ m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.booleanType())});
}
void CVC4Interface::addAssertion(Expression const& _expr)
@@ -109,13 +112,13 @@ pair<CheckResult, vector<string>> CVC4Interface::check(vector<Expression> const&
solAssert(false, "");
}
- if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty())
+ if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
{
for (Expression const& e: _expressionsToEvaluate)
values.push_back(toString(m_solver.getValue(toCVC4Expr(e))));
}
}
- catch (CVC4::Exception & e)
+ catch (CVC4::Exception const&)
{
result = CheckResult::ERROR;
values.clear();
diff --git a/libsolidity/formal/CVC4Interface.h b/libsolidity/formal/CVC4Interface.h
index cfaeb412..cd6d761d 100644
--- a/libsolidity/formal/CVC4Interface.h
+++ b/libsolidity/formal/CVC4Interface.h
@@ -21,8 +21,19 @@
#include <boost/noncopyable.hpp>
+#if defined(__GLIBC__)
+// The CVC4 headers includes the deprecated system headers <ext/hash_map>
+// and <ext/hash_set>. These headers cause a warning that will break the
+// build, unless _GLIBCXX_PERMIT_BACKWARD_HASH is set.
+#define _GLIBCXX_PERMIT_BACKWARD_HASH
+#endif
+
#include <cvc4/cvc4.h>
+#if defined(__GLIBC__)
+#undef _GLIBCXX_PERMIT_BACKWARD_HASH
+#endif
+
namespace dev
{
namespace solidity
@@ -40,9 +51,9 @@ public:
void push() override;
void pop() override;
- Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override;
- Expression newInteger(std::string _name) override;
- Expression newBool(std::string _name) override;
+ void declareFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ void declareInteger(std::string _name) override;
+ void declareBool(std::string _name) override;
void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index 425c5c1e..49c90405 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -17,13 +17,7 @@
#include <libsolidity/formal/SMTChecker.h>
-#ifdef HAVE_Z3
-#include <libsolidity/formal/Z3Interface.h>
-#elif HAVE_CVC4
-#include <libsolidity/formal/CVC4Interface.h>
-#else
-#include <libsolidity/formal/SMTLib2Interface.h>
-#endif
+#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SSAVariable.h>
#include <libsolidity/formal/SymbolicIntVariable.h>
@@ -39,16 +33,9 @@ using namespace dev;
using namespace dev::solidity;
SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readFileCallback):
-#ifdef HAVE_Z3
- m_interface(make_shared<smt::Z3Interface>()),
-#elif HAVE_CVC4
- m_interface(make_shared<smt::CVC4Interface>()),
-#else
- m_interface(make_shared<smt::SMTLib2Interface>(_readFileCallback)),
-#endif
+ m_interface(make_shared<smt::SMTPortfolio>(_readFileCallback)),
m_errorReporter(_errorReporter)
{
- (void)_readFileCallback;
}
void SMTChecker::analyze(SourceUnit const& _source)
@@ -68,7 +55,7 @@ bool SMTChecker::visit(ContractDefinition const& _contract)
void SMTChecker::endVisit(ContractDefinition const&)
{
- m_stateVariables.clear();
+ m_variables.clear();
}
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
@@ -86,20 +73,19 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
);
m_currentFunction = &_function;
m_interface->reset();
- m_variables.clear();
- m_variables.insert(m_stateVariables.begin(), m_stateVariables.end());
m_pathConditions.clear();
m_loopExecutionHappened = false;
- initializeLocalVariables(_function);
resetStateVariables();
+ initializeLocalVariables(_function);
return true;
}
void SMTChecker::endVisit(FunctionDefinition const&)
{
- // TOOD we could check for "reachability", i.e. satisfiability here.
+ // TODO we could check for "reachability", i.e. satisfiability here.
// We only handle local variables, so we clear at the beginning of the function.
// If we add storage variables, those should be cleared differently.
+ removeLocalVariables();
m_currentFunction = nullptr;
}
@@ -110,7 +96,7 @@ bool SMTChecker::visit(IfStatement const& _node)
checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE.");
auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition()));
- vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
+ vector<VariableDeclaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
decltype(countersEndTrue) countersEndFalse;
if (_node.falseStatement())
{
@@ -230,10 +216,10 @@ void SMTChecker::endVisit(Assignment const& _assignment)
);
else if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_assignment.leftHandSide()))
{
- Declaration const* decl = identifier->annotation().referencedDeclaration;
- if (knownVariable(*decl))
+ VariableDeclaration const& decl = dynamic_cast<VariableDeclaration const&>(*identifier->annotation().referencedDeclaration);
+ if (knownVariable(decl))
{
- assignment(*decl, _assignment.rightHandSide(), _assignment.location());
+ assignment(decl, _assignment.rightHandSide(), _assignment.location());
defineExpr(_assignment, expr(_assignment.rightHandSide()));
}
else
@@ -266,14 +252,14 @@ void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _
_value < SymbolicIntVariable::minValue(_type),
_location,
"Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")",
- "value",
+ "<result>",
&_value
);
checkCondition(
_value > SymbolicIntVariable::maxValue(_type),
_location,
"Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")",
- "value",
+ "<result>",
&_value
);
}
@@ -296,12 +282,12 @@ void SMTChecker::endVisit(UnaryOperation const& _op)
solAssert(_op.subExpression().annotation().lValueRequested, "");
if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
{
- Declaration const* decl = identifier->annotation().referencedDeclaration;
- if (knownVariable(*decl))
+ VariableDeclaration const& decl = dynamic_cast<VariableDeclaration const&>(*identifier->annotation().referencedDeclaration);
+ if (knownVariable(decl))
{
- auto innerValue = currentValue(*decl);
+ auto innerValue = currentValue(decl);
auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
- assignment(*decl, newValue, _op.location());
+ assignment(decl, newValue, _op.location());
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
}
else
@@ -383,14 +369,21 @@ void SMTChecker::endVisit(FunctionCall const& _funCall)
void SMTChecker::endVisit(Identifier const& _identifier)
{
- Declaration const* decl = _identifier.annotation().referencedDeclaration;
- solAssert(decl, "");
if (_identifier.annotation().lValueRequested)
{
// Will be translated as part of the node that requested the lvalue.
}
else if (SSAVariable::isSupportedType(_identifier.annotation().type->category()))
- defineExpr(_identifier, currentValue(*decl));
+ {
+ if (VariableDeclaration const* decl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
+ defineExpr(_identifier, currentValue(*decl));
+ else
+ // TODO: handle MagicVariableDeclaration here
+ m_errorReporter.warning(
+ _identifier.location(),
+ "Assertion checker does not yet support the type of this variable."
+ );
+ }
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{
if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require)
@@ -401,7 +394,7 @@ void SMTChecker::endVisit(Identifier const& _identifier)
void SMTChecker::endVisit(Literal const& _literal)
{
Type const& type = *_literal.annotation().type;
- if (type.category() == Type::Category::Integer || type.category() == Type::Category::RationalNumber)
+ if (type.category() == Type::Category::Integer || type.category() == Type::Category::Address || type.category() == Type::Category::RationalNumber)
{
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type))
solAssert(!rational->isFractional(), "");
@@ -429,7 +422,14 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
case Token::Div:
{
solAssert(_op.annotation().commonType, "");
- solAssert(_op.annotation().commonType->category() == Type::Category::Integer, "");
+ if (_op.annotation().commonType->category() != Type::Category::Integer)
+ {
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator on non-integer types."
+ );
+ break;
+ }
auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
smt::Expression left(expr(_op.leftExpression()));
smt::Expression right(expr(_op.rightExpression()));
@@ -443,7 +443,7 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
if (_op.getOperator() == Token::Div)
{
- checkCondition(right == 0, _op.location(), "Division by zero", "value", &right);
+ checkCondition(right == 0, _op.location(), "Division by zero", "<result>", &right);
m_interface->addAssertion(right != 0);
}
@@ -485,11 +485,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op)
solUnimplementedAssert(SSAVariable::isBool(_op.annotation().commonType->category()), "Operation not yet supported");
value = make_shared<smt::Expression>(
op == Token::Equal ? (left == right) :
- op == Token::NotEqual ? (left != right) :
- op == Token::LessThan ? (!left && right) :
- op == Token::LessThanOrEqual ? (!left || right) :
- op == Token::GreaterThan ? (left && !right) :
- /*op == Token::GreaterThanOrEqual*/ (left || !right)
+ /*op == Token::NotEqual*/ (left != right)
);
}
// TODO: check that other values for op are not possible.
@@ -534,16 +530,18 @@ smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _rig
return _left / _right;
}
-void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location)
+void SMTChecker::assignment(VariableDeclaration const& _variable, Expression const& _value, SourceLocation const& _location)
{
assignment(_variable, expr(_value), _location);
}
-void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location)
+void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expression const& _value, SourceLocation const& _location)
{
TypePointer type = _variable.type();
if (auto const* intType = dynamic_cast<IntegerType const*>(type.get()))
checkUnderOverflow(_value, *intType, _location);
+ else if (dynamic_cast<AddressType const*>(type.get()))
+ checkUnderOverflow(_value, IntegerType(160), _location);
m_interface->addAssertion(newValue(_variable) == _value);
}
@@ -587,19 +585,7 @@ void SMTChecker::checkCondition(
expressionsToEvaluate.emplace_back(*_additionalValue);
expressionNames.push_back(_additionalValueName);
}
- for (auto const& param: m_currentFunction->parameters())
- if (knownVariable(*param))
- {
- expressionsToEvaluate.emplace_back(currentValue(*param));
- expressionNames.push_back(param->name());
- }
- for (auto const& var: m_currentFunction->localVariables())
- if (knownVariable(*var))
- {
- expressionsToEvaluate.emplace_back(currentValue(*var));
- expressionNames.push_back(var->name());
- }
- for (auto const& var: m_stateVariables)
+ for (auto const& var: m_variables)
if (knownVariable(*var.first))
{
expressionsToEvaluate.emplace_back(currentValue(*var.first));
@@ -623,15 +609,23 @@ void SMTChecker::checkCondition(
message << _description << " happens here";
if (m_currentFunction)
{
- message << " for:\n";
+ std::ostringstream modelMessage;
+ modelMessage << " for:\n";
solAssert(values.size() == expressionNames.size(), "");
+ map<string, string> sortedModel;
for (size_t i = 0; i < values.size(); ++i)
if (expressionsToEvaluate.at(i).name != values.at(i))
- message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
+ sortedModel[expressionNames.at(i)] = values.at(i);
+
+ for (auto const& eval: sortedModel)
+ modelMessage << " " << eval.first << " = " << eval.second << "\n";
+ m_errorReporter.warning(_location, message.str() + loopComment, SecondarySourceLocation().append(modelMessage.str(), SourceLocation()));
}
else
+ {
message << ".";
- m_errorReporter.warning(_location, message.str() + loopComment);
+ m_errorReporter.warning(_location, message.str() + loopComment);
+ }
break;
}
case smt::CheckResult::UNSATISFIABLE:
@@ -639,6 +633,9 @@ void SMTChecker::checkCondition(
case smt::CheckResult::UNKNOWN:
m_errorReporter.warning(_location, _description + " might happen here." + loopComment);
break;
+ case smt::CheckResult::CONFLICTING:
+ m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
+ break;
case smt::CheckResult::ERROR:
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
break;
@@ -666,6 +663,8 @@ void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string co
if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
+ else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING)
+ m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound.");
else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE)
{
// everything fine.
@@ -744,14 +743,17 @@ void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
void SMTChecker::resetStateVariables()
{
- for (auto const& variable: m_stateVariables)
+ for (auto const& variable: m_variables)
{
- newValue(*variable.first);
- setUnknownValue(*variable.first);
+ if (variable.first->isStateVariable())
+ {
+ newValue(*variable.first);
+ setUnknownValue(*variable.first);
+ }
}
}
-void SMTChecker::resetVariables(vector<Declaration const*> _variables)
+void SMTChecker::resetVariables(vector<VariableDeclaration const*> _variables)
{
for (auto const* decl: _variables)
{
@@ -760,11 +762,12 @@ 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)
+void SMTChecker::mergeVariables(vector<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse)
{
- set<Declaration const*> uniqueVars(_variables.begin(), _variables.end());
+ set<VariableDeclaration const*> uniqueVars(_variables.begin(), _variables.end());
for (auto const* decl: uniqueVars)
{
+ solAssert(_countersEndTrue.count(decl) && _countersEndFalse.count(decl), "");
int trueCounter = _countersEndTrue.at(decl).index();
int falseCounter = _countersEndFalse.at(decl).index();
solAssert(trueCounter != falseCounter, "");
@@ -781,14 +784,7 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{
solAssert(m_variables.count(&_varDecl) == 0, "");
- solAssert(m_stateVariables.count(&_varDecl) == 0, "");
- if (_varDecl.isLocalVariable())
- m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
- else
- {
- solAssert(_varDecl.isStateVariable(), "");
- m_stateVariables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
- }
+ m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
return true;
}
else
@@ -806,37 +802,37 @@ string SMTChecker::uniqueSymbol(Expression const& _expr)
return "expr_" + to_string(_expr.id());
}
-bool SMTChecker::knownVariable(Declaration const& _decl)
+bool SMTChecker::knownVariable(VariableDeclaration const& _decl)
{
return m_variables.count(&_decl);
}
-smt::Expression SMTChecker::currentValue(Declaration const& _decl)
+smt::Expression SMTChecker::currentValue(VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
return m_variables.at(&_decl)();
}
-smt::Expression SMTChecker::valueAtSequence(Declaration const& _decl, int _sequence)
+smt::Expression SMTChecker::valueAtSequence(VariableDeclaration const& _decl, int _sequence)
{
solAssert(knownVariable(_decl), "");
return m_variables.at(&_decl)(_sequence);
}
-smt::Expression SMTChecker::newValue(Declaration const& _decl)
+smt::Expression SMTChecker::newValue(VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
++m_variables.at(&_decl);
return m_variables.at(&_decl)();
}
-void SMTChecker::setZeroValue(Declaration const& _decl)
+void SMTChecker::setZeroValue(VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
m_variables.at(&_decl).setZeroValue();
}
-void SMTChecker::setUnknownValue(Declaration const& _decl)
+void SMTChecker::setUnknownValue(VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
m_variables.at(&_decl).setUnknownValue();
@@ -868,6 +864,7 @@ void SMTChecker::createExpr(Expression const& _e)
m_expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e)));
break;
}
+ case Type::Category::Address:
case Type::Category::Integer:
m_expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e)));
break;
@@ -913,3 +910,14 @@ void SMTChecker::addPathImpliedExpression(smt::Expression const& _e)
{
m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e));
}
+
+void SMTChecker::removeLocalVariables()
+{
+ for (auto it = m_variables.begin(); it != m_variables.end(); )
+ {
+ if (it->first->isLocalVariable())
+ it = m_variables.erase(it);
+ else
+ ++it;
+ }
+}
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
index 50d40ab9..6cf4e48a 100644
--- a/libsolidity/formal/SMTChecker.h
+++ b/libsolidity/formal/SMTChecker.h
@@ -76,11 +76,11 @@ private:
/// of rounding for signed division.
smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
- void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location);
- void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location);
+ void assignment(VariableDeclaration const& _variable, Expression const& _value, SourceLocation const& _location);
+ void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, SourceLocation const& _location);
/// Maps a variable to an SSA index.
- using VariableSequenceCounters = std::map<Declaration const*, SSAVariable>;
+ using VariableSequenceCounters = std::map<VariableDeclaration const*, SSAVariable>;
/// 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.
@@ -114,11 +114,11 @@ private:
void initializeLocalVariables(FunctionDefinition const& _function);
void resetStateVariables();
- void resetVariables(std::vector<Declaration const*> _variables);
+ void resetVariables(std::vector<VariableDeclaration 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);
+ void mergeVariables(std::vector<VariableDeclaration 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);
@@ -127,21 +127,21 @@ private:
/// @returns true if _delc is a variable that is known at the current point, i.e.
/// has a valid sequence number
- bool knownVariable(Declaration const& _decl);
+ bool knownVariable(VariableDeclaration const& _decl);
/// @returns an expression denoting the value of the variable declared in @a _decl
/// at the current point.
- smt::Expression currentValue(Declaration const& _decl);
+ smt::Expression currentValue(VariableDeclaration const& _decl);
/// @returns an expression denoting the value of the variable declared in @a _decl
/// at the given sequence point. Does not ensure that this sequence point exists.
- smt::Expression valueAtSequence(Declaration const& _decl, int _sequence);
+ smt::Expression valueAtSequence(VariableDeclaration const& _decl, int _sequence);
/// Allocates a new sequence number for the declaration, updates the current
/// sequence number to this value and returns the expression.
- smt::Expression newValue(Declaration const& _decl);
+ smt::Expression newValue(VariableDeclaration const& _decl);
/// Sets the value of the declaration to zero.
- void setZeroValue(Declaration const& _decl);
+ void setZeroValue(VariableDeclaration const& _decl);
/// Resets the variable to an unknown value (in its range).
- void setUnknownValue(Declaration const& decl);
+ void setUnknownValue(VariableDeclaration const& decl);
/// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e);
@@ -161,12 +161,14 @@ private:
/// Add to the solver: the given expression implied by the current path conditions
void addPathImpliedExpression(smt::Expression const& _e);
+ /// Removes the local variables of a function.
+ void removeLocalVariables();
+
std::shared_ptr<smt::SolverInterface> m_interface;
std::shared_ptr<VariableUsage> m_variableUsage;
bool m_loopExecutionHappened = false;
std::map<Expression const*, smt::Expression> m_expressions;
- std::map<Declaration const*, SSAVariable> m_variables;
- std::map<Declaration const*, SSAVariable> m_stateVariables;
+ std::map<VariableDeclaration const*, SSAVariable> m_variables;
std::vector<smt::Expression> m_pathConditions;
ErrorReporter& m_errorReporter;
diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp
index 0e00665a..a6c1f87c 100644
--- a/libsolidity/formal/SMTLib2Interface.cpp
+++ b/libsolidity/formal/SMTLib2Interface.cpp
@@ -47,6 +47,8 @@ void SMTLib2Interface::reset()
{
m_accumulatedOutput.clear();
m_accumulatedOutput.emplace_back();
+ m_constants.clear();
+ m_functions.clear();
write("(set-option :produce-models true)");
write("(set-logic QF_UFLIA)");
}
@@ -62,30 +64,40 @@ void SMTLib2Interface::pop()
m_accumulatedOutput.pop_back();
}
-Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain)
+void SMTLib2Interface::declareFunction(string _name, Sort _domain, Sort _codomain)
{
- write(
- "(declare-fun |" +
- _name +
- "| (" +
- (_domain == Sort::Int ? "Int" : "Bool") +
- ") " +
- (_codomain == Sort::Int ? "Int" : "Bool") +
- ")"
- );
- return SolverInterface::newFunction(move(_name), _domain, _codomain);
+ // TODO Use domain and codomain as key as well
+ if (!m_functions.count(_name))
+ {
+ m_functions.insert(_name);
+ write(
+ "(declare-fun |" +
+ _name +
+ "| (" +
+ (_domain == Sort::Int ? "Int" : "Bool") +
+ ") " +
+ (_codomain == Sort::Int ? "Int" : "Bool") +
+ ")"
+ );
+ }
}
-Expression SMTLib2Interface::newInteger(string _name)
+void SMTLib2Interface::declareInteger(string _name)
{
- write("(declare-const |" + _name + "| Int)");
- return SolverInterface::newInteger(move(_name));
+ if (!m_constants.count(_name))
+ {
+ m_constants.insert(_name);
+ write("(declare-const |" + _name + "| Int)");
+ }
}
-Expression SMTLib2Interface::newBool(string _name)
+void SMTLib2Interface::declareBool(string _name)
{
- write("(declare-const |" + _name + "| Bool)");
- return SolverInterface::newBool(std::move(_name));
+ if (!m_constants.count(_name))
+ {
+ m_constants.insert(_name);
+ write("(declare-const |" + _name + "| Bool)");
+ }
}
void SMTLib2Interface::addAssertion(Expression const& _expr)
@@ -112,7 +124,7 @@ pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> con
result = CheckResult::ERROR;
vector<string> values;
- if (result != CheckResult::UNSATISFIABLE && result != CheckResult::ERROR)
+ if (result == CheckResult::SATISFIABLE && result != CheckResult::ERROR)
values = parseValues(find(response.cbegin(), response.cend(), '\n'), response.cend());
return make_pair(result, values);
}
@@ -146,7 +158,7 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _
{
auto const& e = _expressionsToEvaluate.at(i);
solAssert(e.sort == Sort::Int || e.sort == Sort::Bool, "Invalid sort for expression to evaluate.");
- command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + "\n";
+ command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + ")\n";
command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n";
}
command += "(check-sat)\n";
diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h
index 63188acd..eb876a7f 100644
--- a/libsolidity/formal/SMTLib2Interface.h
+++ b/libsolidity/formal/SMTLib2Interface.h
@@ -30,6 +30,7 @@
#include <string>
#include <vector>
#include <cstdio>
+#include <set>
namespace dev
{
@@ -48,9 +49,9 @@ public:
void push() override;
void pop() override;
- Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override;
- Expression newInteger(std::string _name) override;
- Expression newBool(std::string _name) override;
+ void declareFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ void declareInteger(std::string _name) override;
+ void declareBool(std::string _name) override;
void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
@@ -68,6 +69,8 @@ private:
ReadCallback::Callback m_queryCallback;
std::vector<std::string> m_accumulatedOutput;
+ std::set<std::string> m_constants;
+ std::set<std::string> m_functions;
};
}
diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp
new file mode 100644
index 00000000..8b9fe9ce
--- /dev/null
+++ b/libsolidity/formal/SMTPortfolio.cpp
@@ -0,0 +1,152 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <libsolidity/formal/SMTPortfolio.h>
+
+#ifdef HAVE_Z3
+#include <libsolidity/formal/Z3Interface.h>
+#endif
+#ifdef HAVE_CVC4
+#include <libsolidity/formal/CVC4Interface.h>
+#endif
+#if !defined (HAVE_Z3) && !defined (HAVE_CVC4)
+#include <libsolidity/formal/SMTLib2Interface.h>
+#endif
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::smt;
+
+SMTPortfolio::SMTPortfolio(ReadCallback::Callback const& _readCallback)
+{
+#ifdef HAVE_Z3
+ m_solvers.emplace_back(make_shared<smt::Z3Interface>());
+#endif
+#ifdef HAVE_CVC4
+ m_solvers.emplace_back(make_shared<smt::CVC4Interface>());
+#endif
+#if !defined (HAVE_Z3) && !defined (HAVE_CVC4)
+ m_solvers.emplace_back(make_shared<smt::SMTLib2Interface>(_readCallback)),
+#endif
+ (void)_readCallback;
+}
+
+void SMTPortfolio::reset()
+{
+ for (auto s : m_solvers)
+ s->reset();
+}
+
+void SMTPortfolio::push()
+{
+ for (auto s : m_solvers)
+ s->push();
+}
+
+void SMTPortfolio::pop()
+{
+ for (auto s : m_solvers)
+ s->pop();
+}
+
+void SMTPortfolio::declareFunction(string _name, Sort _domain, Sort _codomain)
+{
+ for (auto s : m_solvers)
+ s->declareFunction(_name, _domain, _codomain);
+}
+
+void SMTPortfolio::declareInteger(string _name)
+{
+ for (auto s : m_solvers)
+ s->declareInteger(_name);
+}
+
+void SMTPortfolio::declareBool(string _name)
+{
+ for (auto s : m_solvers)
+ s->declareBool(_name);
+}
+
+void SMTPortfolio::addAssertion(Expression const& _expr)
+{
+ for (auto s : m_solvers)
+ s->addAssertion(_expr);
+}
+
+/*
+ * Broadcasts the SMT query to all solvers and returns a single result.
+ * This comment explains how this result is decided.
+ *
+ * When a solver is queried, there are four possible answers:
+ * SATISFIABLE (SAT), UNSATISFIABLE (UNSAT), UNKNOWN, CONFLICTING, ERROR
+ * We say that a solver _answered_ the query if it returns either:
+ * SAT or UNSAT
+ * A solver did not answer the query if it returns either:
+ * UNKNOWN (it tried but couldn't solve it) or ERROR (crash, internal error, API error, etc).
+ *
+ * Ideally all solvers answer the query and agree on what the answer is
+ * (all say SAT or all say UNSAT).
+ *
+ * The actual logic as as follows:
+ * 1) If at least one solver answers the query, all the non-answer results are ignored.
+ * Here SAT/UNSAT is preferred over UNKNOWN since it's an actual answer, and over ERROR
+ * because one buggy solver/integration shouldn't break the portfolio.
+ *
+ * 2) If at least one solver answers SAT and at least one answers UNSAT, at least one of them is buggy
+ * and the result is CONFLICTING.
+ * In the future if we have more than 2 solvers enabled we could go with the majority.
+ *
+ * 3) If NO solver answers the query:
+ * If at least one solver returned UNKNOWN (where the rest returned ERROR), the result is UNKNOWN.
+ * This is preferred over ERROR since the SMTChecker might decide to abstract the query
+ * when it is told that this is a hard query to solve.
+ *
+ * If all solvers return ERROR, the result is ERROR.
+*/
+pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const& _expressionsToEvaluate)
+{
+ CheckResult lastResult = CheckResult::ERROR;
+ vector<string> finalValues;
+ for (auto s : m_solvers)
+ {
+ CheckResult result;
+ vector<string> values;
+ tie(result, values) = s->check(_expressionsToEvaluate);
+ if (solverAnswered(result))
+ {
+ if (!solverAnswered(lastResult))
+ {
+ lastResult = result;
+ finalValues = std::move(values);
+ }
+ else if (lastResult != result)
+ {
+ lastResult = CheckResult::CONFLICTING;
+ break;
+ }
+ }
+ else if (result == CheckResult::UNKNOWN && lastResult == CheckResult::ERROR)
+ lastResult = result;
+ }
+ return make_pair(lastResult, finalValues);
+}
+
+bool SMTPortfolio::solverAnswered(CheckResult result)
+{
+ return result == CheckResult::SATISFIABLE || result == CheckResult::UNSATISFIABLE;
+}
diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h
new file mode 100644
index 00000000..96c7ff57
--- /dev/null
+++ b/libsolidity/formal/SMTPortfolio.h
@@ -0,0 +1,67 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+
+#include <libsolidity/formal/SolverInterface.h>
+
+#include <libsolidity/interface/ReadFile.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+namespace smt
+{
+
+/**
+ * The SMTPortfolio wraps all available solvers within a single interface,
+ * propagating the functionalities to all solvers.
+ * It also checks whether different solvers give conflicting answers
+ * to SMT queries.
+ */
+class SMTPortfolio: public SolverInterface, public boost::noncopyable
+{
+public:
+ SMTPortfolio(ReadCallback::Callback const& _readCallback);
+
+ void reset() override;
+
+ void push() override;
+ void pop() override;
+
+ void declareFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ void declareInteger(std::string _name) override;
+ void declareBool(std::string _name) override;
+
+ void addAssertion(Expression const& _expr) override;
+ std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
+
+private:
+ static bool solverAnswered(CheckResult result);
+
+ std::vector<std::shared_ptr<smt::SolverInterface>> m_solvers;
+};
+
+}
+}
+}
diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp
index f3213e03..4fc2dd45 100644
--- a/libsolidity/formal/SSAVariable.cpp
+++ b/libsolidity/formal/SSAVariable.cpp
@@ -50,7 +50,7 @@ bool SSAVariable::isSupportedType(Type::Category _category)
bool SSAVariable::isInteger(Type::Category _category)
{
- return _category == Type::Category::Integer;
+ return _category == Type::Category::Integer || _category == Type::Category::Address;
}
bool SSAVariable::isBool(Type::Category _category)
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
index 16796684..8bbd0417 100644
--- a/libsolidity/formal/SolverInterface.h
+++ b/libsolidity/formal/SolverInterface.h
@@ -39,7 +39,7 @@ namespace smt
enum class CheckResult
{
- SATISFIABLE, UNSATISFIABLE, UNKNOWN, ERROR
+ SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR
};
enum class Sort
@@ -199,8 +199,10 @@ public:
virtual void push() = 0;
virtual void pop() = 0;
- virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
+ virtual void declareFunction(std::string _name, Sort _domain, Sort _codomain) = 0;
+ Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
{
+ declareFunction(_name, _domain, _codomain);
solAssert(_domain == Sort::Int, "Function sort not supported.");
// Subclasses should do something here
switch (_codomain)
@@ -214,14 +216,18 @@ public:
break;
}
}
- virtual Expression newInteger(std::string _name)
+ virtual void declareInteger(std::string _name) = 0;
+ Expression newInteger(std::string _name)
{
// Subclasses should do something here
+ declareInteger(_name);
return Expression(std::move(_name), {}, Sort::Int);
}
- virtual Expression newBool(std::string _name)
+ virtual void declareBool(std::string _name) = 0;
+ Expression newBool(std::string _name)
{
// Subclasses should do something here
+ declareBool(_name);
return Expression(std::move(_name), {}, Sort::Bool);
}
@@ -231,8 +237,11 @@ public:
/// is available. Throws SMTSolverError on error.
virtual std::pair<CheckResult, std::vector<std::string>>
check(std::vector<Expression> const& _expressionsToEvaluate) = 0;
-};
+protected:
+ // SMT query timeout in milliseconds.
+ static int const queryTimeout = 10000;
+};
}
}
diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp
index 5e71fdcc..4f65b1fd 100644
--- a/libsolidity/formal/SymbolicIntVariable.cpp
+++ b/libsolidity/formal/SymbolicIntVariable.cpp
@@ -29,7 +29,11 @@ SymbolicIntVariable::SymbolicIntVariable(
):
SymbolicVariable(_decl, _interface)
{
- solAssert(m_declaration.type()->category() == Type::Category::Integer, "");
+ solAssert(
+ m_declaration.type()->category() == Type::Category::Integer ||
+ m_declaration.type()->category() == Type::Category::Address,
+ ""
+ );
}
smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const
@@ -44,9 +48,11 @@ void SymbolicIntVariable::setZeroValue(int _seq)
void SymbolicIntVariable::setUnknownValue(int _seq)
{
- auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type());
- m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType));
- m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType));
+ auto intType = dynamic_pointer_cast<IntegerType const>(m_declaration.type());
+ if (!intType)
+ intType = make_shared<IntegerType>(160);
+ m_interface.addAssertion(valueAtSequence(_seq) >= minValue(*intType));
+ m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(*intType));
}
smt::Expression SymbolicIntVariable::minValue(IntegerType const& _t)
diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp
index c2dea844..9282a560 100644
--- a/libsolidity/formal/VariableUsage.cpp
+++ b/libsolidity/formal/VariableUsage.cpp
@@ -50,12 +50,12 @@ VariableUsage::VariableUsage(ASTNode const& _node)
_node.accept(reducer);
}
-vector<Declaration const*> VariableUsage::touchedVariables(ASTNode const& _node) const
+vector<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node) const
{
if (!m_children.count(&_node) && !m_touchedVariable.count(&_node))
return {};
- set<Declaration const*> touched;
+ set<VariableDeclaration const*> touched;
vector<ASTNode const*> toVisit;
toVisit.push_back(&_node);
diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h
index 62561cce..dda13de2 100644
--- a/libsolidity/formal/VariableUsage.h
+++ b/libsolidity/formal/VariableUsage.h
@@ -27,7 +27,7 @@ namespace solidity
{
class ASTNode;
-class Declaration;
+class VariableDeclaration;
/**
* This class collects information about which local variables of value type
@@ -38,11 +38,11 @@ class VariableUsage
public:
explicit VariableUsage(ASTNode const& _node);
- std::vector<Declaration const*> touchedVariables(ASTNode const& _node) const;
+ std::vector<VariableDeclaration const*> touchedVariables(ASTNode const& _node) const;
private:
// Variable touched by a specific AST node.
- std::map<ASTNode const*, Declaration const*> m_touchedVariable;
+ std::map<ASTNode const*, VariableDeclaration const*> m_touchedVariable;
std::map<ASTNode const*, std::vector<ASTNode const*>> m_children;
};
diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp
index 41943c92..747c9172 100644
--- a/libsolidity/formal/Z3Interface.cpp
+++ b/libsolidity/formal/Z3Interface.cpp
@@ -28,7 +28,10 @@ using namespace dev::solidity::smt;
Z3Interface::Z3Interface():
m_solver(m_context)
{
+ // This needs to be set globally.
z3::set_param("rewriter.pull_cheap_ite", true);
+ // This needs to be set in the context.
+ m_context.set("timeout", queryTimeout);
}
void Z3Interface::reset()
@@ -48,22 +51,22 @@ void Z3Interface::pop()
m_solver.pop();
}
-Expression Z3Interface::newFunction(string _name, Sort _domain, Sort _codomain)
+void Z3Interface::declareFunction(string _name, Sort _domain, Sort _codomain)
{
- m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(_domain), z3Sort(_codomain))});
- return SolverInterface::newFunction(move(_name), _domain, _codomain);
+ if (!m_functions.count(_name))
+ m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(_domain), z3Sort(_codomain))});
}
-Expression Z3Interface::newInteger(string _name)
+void Z3Interface::declareInteger(string _name)
{
- m_constants.insert({_name, m_context.int_const(_name.c_str())});
- return SolverInterface::newInteger(move(_name));
+ if (!m_constants.count(_name))
+ m_constants.insert({_name, m_context.int_const(_name.c_str())});
}
-Expression Z3Interface::newBool(string _name)
+void Z3Interface::declareBool(string _name)
{
- m_constants.insert({_name, m_context.bool_const(_name.c_str())});
- return SolverInterface::newBool(std::move(_name));
+ if (!m_constants.count(_name))
+ m_constants.insert({_name, m_context.bool_const(_name.c_str())});
}
void Z3Interface::addAssertion(Expression const& _expr)
@@ -92,7 +95,7 @@ pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _
solAssert(false, "");
}
- if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty())
+ if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
{
z3::model m = m_solver.get_model();
for (Expression const& e: _expressionsToEvaluate)
diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h
index 354ded25..84880ff3 100644
--- a/libsolidity/formal/Z3Interface.h
+++ b/libsolidity/formal/Z3Interface.h
@@ -40,9 +40,9 @@ public:
void push() override;
void pop() override;
- Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override;
- Expression newInteger(std::string _name) override;
- Expression newBool(std::string _name) override;
+ void declareFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ void declareInteger(std::string _name) override;
+ void declareBool(std::string _name) override;
void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index 9f505889..9a0110cf 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -57,7 +57,7 @@ bool AsmAnalyzer::operator()(Label const& _label)
solAssert(!_label.name.empty(), "");
checkLooseFeature(
_label.location,
- "The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead."
+ "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead."
);
m_info.stackHeightInfo[&_label] = m_stackHeight;
warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
@@ -68,7 +68,7 @@ bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
checkLooseFeature(
_instruction.location,
- "The use of non-functional instructions is deprecated. Please use functional notation instead."
+ "The use of non-functional instructions is disallowed. Please use functional notation instead."
);
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
@@ -85,7 +85,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{
m_errorReporter.typeError(
_literal.location,
- "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)"
+ "String literal too long (" + to_string(_literal.value.size()) + " > 32)"
);
return false;
}
@@ -99,7 +99,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
}
else if (_literal.kind == assembly::LiteralKind::Boolean)
{
- solAssert(m_flavour == AsmFlavour::IULIA, "");
+ solAssert(m_flavour == AsmFlavour::Yul, "");
solAssert(_literal.value == "true" || _literal.value == "false", "");
}
m_info.stackHeightInfo[&_literal] = m_stackHeight;
@@ -162,7 +162,7 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
- solAssert(m_flavour != AsmFlavour::IULIA, "");
+ solAssert(m_flavour != AsmFlavour::Yul, "");
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
if (!expectExpression(arg))
@@ -185,7 +185,7 @@ bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement)
Error::Type errorType = m_flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError;
string msg =
"Top-level expressions are not supposed to return values (this expression returns " +
- boost::lexical_cast<string>(m_stackHeight - initialStackHeight) +
+ to_string(m_stackHeight - initialStackHeight) +
" value" +
(m_stackHeight - initialStackHeight == 1 ? "" : "s") +
"). Use ``pop()`` or assign them.";
@@ -201,7 +201,7 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
{
checkLooseFeature(
_assignment.location,
- "The use of stack assignment is deprecated. Please use assignment in functional notation instead."
+ "The use of stack assignment is disallowed. Please use assignment in functional notation instead."
);
bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
@@ -322,8 +322,8 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{
m_errorReporter.typeError(
_funCall.functionName.location,
- "Expected " + boost::lexical_cast<string>(arguments) + " arguments but got " +
- boost::lexical_cast<string>(_funCall.arguments.size()) + "."
+ "Expected " + to_string(arguments) + " arguments but got " +
+ to_string(_funCall.arguments.size()) + "."
);
success = false;
}
@@ -477,7 +477,7 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con
m_errorReporter.typeError(
_location,
"Expected expression to return one item to the stack, but did return " +
- boost::lexical_cast<string>(m_stackHeight - _oldHeight) +
+ to_string(m_stackHeight - _oldHeight) +
" items."
);
return false;
@@ -550,7 +550,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
}
void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
{
- if (m_flavour != AsmFlavour::IULIA)
+ if (m_flavour != AsmFlavour::Yul)
return;
if (!builtinTypes.count(type))
diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h
index 3a9600fe..69cf8f1d 100644
--- a/libsolidity/inlineasm/AsmDataForward.h
+++ b/libsolidity/inlineasm/AsmDataForward.h
@@ -17,7 +17,7 @@
/**
* @author Christian <c@ethdev.com>
* @date 2016
- * Forward declaration of classes for inline assembly / JULIA AST
+ * Forward declaration of classes for inline assembly / Yul AST
*/
#pragma once
@@ -57,7 +57,7 @@ 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
+ Yul // same as Strict mode with types
};
}
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index d3b0808b..f34bbffe 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -150,7 +150,7 @@ assembly::Statement Parser::parseStatement()
expectToken(Token::Comma);
elementary = parseElementaryOperation();
if (elementary.type() != typeid(assembly::Identifier))
- fatalParserError("Variable name expected in multiple assignemnt.");
+ fatalParserError("Variable name expected in multiple assignment.");
assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary));
}
while (currentToken() == Token::Comma);
@@ -173,7 +173,7 @@ assembly::Statement Parser::parseStatement()
if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
{
assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
- if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name))
+ if (m_flavour != AsmFlavour::Yul && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names.");
advance();
assignment.variableNames.emplace_back(identifier);
@@ -279,7 +279,7 @@ assembly::Expression Parser::parseExpression()
"Expected '(' (instruction \"" +
instructionNames().at(instr.instruction) +
"\" expects " +
- boost::lexical_cast<string>(args) +
+ to_string(args) +
" arguments)"
));
}
@@ -318,11 +318,6 @@ std::map<string, dev::solidity::Instruction> const& Parser::instructions()
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
s_instructions[name] = instruction.second;
}
-
- // add alias for suicide
- s_instructions["suicide"] = solidity::Instruction::SELFDESTRUCT;
- // add alis for sha3
- s_instructions["sha3"] = solidity::Instruction::KECCAK256;
}
return s_instructions;
}
@@ -362,7 +357,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
else
literal = currentLiteral();
// first search the set of instructions.
- if (m_flavour != AsmFlavour::IULIA && instructions().count(literal))
+ if (m_flavour != AsmFlavour::Yul && instructions().count(literal))
{
dev::solidity::Instruction const& instr = instructions().at(literal);
ret = Instruction{location(), instr};
@@ -403,7 +398,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
""
};
advance();
- if (m_flavour == AsmFlavour::IULIA)
+ if (m_flavour == AsmFlavour::Yul)
{
expectToken(Token::Colon);
literal.location.end = endPosition();
@@ -416,7 +411,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
}
default:
fatalParserError(
- m_flavour == AsmFlavour::IULIA ?
+ m_flavour == AsmFlavour::Yul ?
"Literal or identifier expected." :
"Literal, identifier or instruction expected."
);
@@ -486,7 +481,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
RecursionGuard recursionGuard(*this);
if (_initialOp.type() == typeid(Instruction))
{
- solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA");
+ solAssert(m_flavour != AsmFlavour::Yul, "Instructions are invalid in Yul");
Instruction& instruction = boost::get<Instruction>(_initialOp);
FunctionalInstruction ret;
ret.instruction = instruction.instruction;
@@ -507,7 +502,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
"Expected expression (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
- boost::lexical_cast<string>(args) +
+ to_string(args) +
" arguments)"
));
@@ -519,7 +514,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
"Expected ',' (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
- boost::lexical_cast<string>(args) +
+ to_string(args) +
" arguments)"
));
else
@@ -532,7 +527,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
"Expected ')' (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
- boost::lexical_cast<string>(args) +
+ to_string(args) +
" arguments)"
));
expectToken(Token::RParen);
@@ -557,7 +552,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
}
else
fatalParserError(
- m_flavour == AsmFlavour::IULIA ?
+ m_flavour == AsmFlavour::Yul ?
"Function name expected." :
"Assembly instruction or function name required in front of \"(\")"
);
@@ -570,7 +565,7 @@ TypedName Parser::parseTypedName()
RecursionGuard recursionGuard(*this);
TypedName typedName = createWithLocation<TypedName>();
typedName.name = expectAsmIdentifier();
- if (m_flavour == AsmFlavour::IULIA)
+ if (m_flavour == AsmFlavour::Yul)
{
expectToken(Token::Colon);
typedName.location.end = endPosition();
@@ -582,7 +577,7 @@ TypedName Parser::parseTypedName()
string Parser::expectAsmIdentifier()
{
string name = currentLiteral();
- if (m_flavour == AsmFlavour::IULIA)
+ if (m_flavour == AsmFlavour::Yul)
{
switch (currentToken())
{
@@ -606,7 +601,9 @@ bool Parser::isValidNumberLiteral(string const& _literal)
{
try
{
- u256(_literal);
+ // Try to convert _literal to u256.
+ auto tmp = u256(_literal);
+ (void) tmp;
}
catch (...)
{
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index bacd7a94..1a15d7eb 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -40,7 +40,7 @@ using namespace dev::solidity::assembly;
string AsmPrinter::operator()(assembly::Instruction const& _instruction)
{
- solAssert(!m_julia, "");
+ solAssert(!m_yul, "");
return boost::to_lower_copy(instructionInfo(_instruction.instruction).name);
}
@@ -92,7 +92,7 @@ string AsmPrinter::operator()(assembly::Identifier const& _identifier)
string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction)
{
- solAssert(!m_julia, "");
+ solAssert(!m_yul, "");
return
boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) +
"(" +
@@ -109,13 +109,13 @@ string AsmPrinter::operator()(ExpressionStatement const& _statement)
string AsmPrinter::operator()(assembly::Label const& _label)
{
- solAssert(!m_julia, "");
+ solAssert(!m_yul, "");
return _label.name + ":";
}
string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
{
- solAssert(!m_julia, "");
+ solAssert(!m_yul, "");
return "=: " + (*this)(_assignment.variableName);
}
@@ -225,7 +225,7 @@ string AsmPrinter::operator()(Block const& _block)
string AsmPrinter::appendTypeName(std::string const& _type) const
{
- if (m_julia)
+ if (m_yul)
return ":" + _type;
return "";
}
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index 5bd87aba..9f2b842a 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -36,7 +36,7 @@ namespace assembly
class AsmPrinter: public boost::static_visitor<std::string>
{
public:
- explicit AsmPrinter(bool _julia = false): m_julia(_julia) {}
+ explicit AsmPrinter(bool _yul = false): m_yul(_yul) {}
std::string operator()(assembly::Instruction const& _instruction);
std::string operator()(assembly::Literal const& _literal);
@@ -57,7 +57,7 @@ public:
private:
std::string appendTypeName(std::string const& _type) const;
- bool m_julia = false;
+ bool m_yul = false;
};
}
diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp
index 64d5bd9a..af81b301 100644
--- a/libsolidity/inlineasm/AsmScope.cpp
+++ b/libsolidity/inlineasm/AsmScope.cpp
@@ -32,7 +32,7 @@ bool Scope::registerLabel(string const& _name)
return true;
}
-bool Scope::registerVariable(string const& _name, JuliaType const& _type)
+bool Scope::registerVariable(string const& _name, YulType const& _type)
{
if (exists(_name))
return false;
@@ -42,7 +42,7 @@ bool Scope::registerVariable(string const& _name, JuliaType const& _type)
return true;
}
-bool Scope::registerFunction(string const& _name, std::vector<JuliaType> const& _arguments, std::vector<JuliaType> const& _returns)
+bool Scope::registerFunction(string const& _name, std::vector<YulType> const& _arguments, std::vector<YulType> const& _returns)
{
if (exists(_name))
return false;
diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h
index 447d6490..c8c63f8f 100644
--- a/libsolidity/inlineasm/AsmScope.h
+++ b/libsolidity/inlineasm/AsmScope.h
@@ -62,27 +62,27 @@ struct GenericVisitor<>: public boost::static_visitor<> {
struct Scope
{
- using JuliaType = std::string;
+ using YulType = std::string;
using LabelID = size_t;
- struct Variable { JuliaType type; };
+ struct Variable { YulType type; };
struct Label { };
struct Function
{
- std::vector<JuliaType> arguments;
- std::vector<JuliaType> returns;
+ std::vector<YulType> arguments;
+ std::vector<YulType> returns;
};
using Identifier = boost::variant<Variable, Label, Function>;
using Visitor = GenericVisitor<Variable const, Label const, Function const>;
using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
- bool registerVariable(std::string const& _name, JuliaType const& _type);
+ bool registerVariable(std::string const& _name, YulType const& _type);
bool registerLabel(std::string const& _name);
bool registerFunction(
std::string const& _name,
- std::vector<JuliaType> const& _arguments,
- std::vector<JuliaType> const& _returns
+ std::vector<YulType> const& _arguments,
+ std::vector<YulType> const& _returns
);
/// Looks up the identifier in this or super scopes and returns a valid pointer if found
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
index 86f3809c..2d15c820 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.cpp
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -75,10 +75,10 @@ bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
- vector<Scope::JuliaType> arguments;
+ vector<Scope::YulType> arguments;
for (auto const& _argument: _funDef.parameters)
arguments.push_back(_argument.type);
- vector<Scope::JuliaType> returns;
+ vector<Scope::YulType> returns;
for (auto const& _return: _funDef.returnVariables)
returns.push_back(_return.type);
if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp
index 7f97336b..46fa1d6b 100644
--- a/libsolidity/interface/AssemblyStack.cpp
+++ b/libsolidity/interface/AssemblyStack.cpp
@@ -15,7 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
- * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and
* eWasm as output.
*/
@@ -48,11 +48,11 @@ assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language)
return assembly::AsmFlavour::Loose;
case AssemblyStack::Language::StrictAssembly:
return assembly::AsmFlavour::Strict;
- case AssemblyStack::Language::JULIA:
- return assembly::AsmFlavour::IULIA;
+ case AssemblyStack::Language::Yul:
+ return assembly::AsmFlavour::Yul;
}
solAssert(false, "");
- return assembly::AsmFlavour::IULIA;
+ return assembly::AsmFlavour::Yul;
}
}
@@ -117,9 +117,9 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
{
MachineAssemblyObject object;
julia::EVMAssembly assembly(true);
- julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::JULIA, true)(*m_parserResult);
+ julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::Yul, true)(*m_parserResult);
object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize());
- /// TOOD: fill out text representation
+ /// TODO: fill out text representation
return object;
}
case Machine::eWasm:
@@ -132,5 +132,5 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
string AssemblyStack::print() const
{
solAssert(m_parserResult, "");
- return assembly::AsmPrinter(m_language == Language::JULIA)(*m_parserResult);
+ return assembly::AsmPrinter(m_language == Language::Yul)(*m_parserResult);
}
diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h
index 720220ab..8132ce63 100644
--- a/libsolidity/interface/AssemblyStack.h
+++ b/libsolidity/interface/AssemblyStack.h
@@ -15,7 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
- * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and
* eWasm as output.
*/
@@ -47,13 +47,13 @@ struct MachineAssemblyObject
};
/*
- * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and
* eWasm as output.
*/
class AssemblyStack
{
public:
- enum class Language { JULIA, Assembly, StrictAssembly };
+ enum class Language { Yul, Assembly, StrictAssembly };
enum class Machine { EVM, EVM15, eWasm };
explicit AssemblyStack(EVMVersion _evmVersion = EVMVersion(), Language _language = Language::Assembly):
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 47dc30cf..e800b278 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -58,22 +58,31 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-void CompilerStack::setRemappings(vector<string> const& _remappings)
+boost::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping)
+{
+ auto eq = find(_remapping.begin(), _remapping.end(), '=');
+ if (eq == _remapping.end())
+ return {};
+
+ auto colon = find(_remapping.begin(), eq, ':');
+
+ Remapping r;
+
+ r.context = colon == eq ? string() : string(_remapping.begin(), colon);
+ r.prefix = colon == eq ? string(_remapping.begin(), eq) : string(colon + 1, eq);
+ r.target = string(eq + 1, _remapping.end());
+
+ if (r.prefix.empty())
+ return {};
+
+ return r;
+}
+
+void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
{
- vector<Remapping> remappings;
for (auto const& remapping: _remappings)
- {
- auto eq = find(remapping.begin(), remapping.end(), '=');
- if (eq == remapping.end())
- continue; // ignore
- auto colon = find(remapping.begin(), eq, ':');
- Remapping r;
- r.context = colon == eq ? string() : string(remapping.begin(), colon);
- r.prefix = colon == eq ? string(remapping.begin(), eq) : string(colon + 1, eq);
- r.target = string(eq + 1, remapping.end());
- remappings.push_back(r);
- }
- swap(m_remappings, remappings);
+ solAssert(!remapping.prefix.empty(), "");
+ m_remappings = _remappings;
}
void CompilerStack::setEVMVersion(EVMVersion _version)
@@ -191,6 +200,8 @@ bool CompilerStack::analyze()
if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false;
+ // This is the main name and type resolution loop. Needs to be run for every contract, because
+ // the special variables "this" and "super" must be set appropriately.
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@@ -204,11 +215,15 @@ bool CompilerStack::analyze()
// thus contracts can only conflict if declared in the same source file. This
// already causes a double-declaration error elsewhere, so we do not report
// an error here and instead silently drop any additional contracts we find.
-
if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
m_contracts[contract->fullyQualifiedName()].contract = contract;
}
+ // This cannot be done in the above loop, because cross-contract types couldn't be resolved.
+ // A good example is `LibraryName.TypeName x;`.
+ //
+ // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed,
+ // which is only done one step later.
TypeChecker typeChecker(m_evmVersion, m_errorReporter);
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
@@ -218,6 +233,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ // Checks that can only be done when all types of all AST nodes are known.
PostTypeChecker postTypeChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!postTypeChecker.check(*source->ast))
@@ -226,6 +242,8 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ // Control flow graph generator and analyzer. It can check for issues such as
+ // variable is used before it is assigned to.
CFG cfg(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!cfg.constructFlow(*source->ast))
@@ -242,6 +260,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ // Checks for common mistakes. Only generates warnings.
StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!staticAnalyzer.analyze(*source->ast))
@@ -250,6 +269,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ // Check for state mutability in every function.
vector<ASTPointer<ASTNode>> ast;
for (Source const* source: m_sourceOrder)
ast.push_back(source->ast);
@@ -300,6 +320,7 @@ bool CompilerStack::compile()
if (!parseAndAnalyze())
return false;
+ // Only compile contracts individually which have been requested.
map<ContractDefinition const*, eth::Assembly const*> compiledContracts;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
@@ -317,7 +338,6 @@ void CompilerStack::link()
{
contract.second.object.link(m_libraries);
contract.second.runtimeObject.link(m_libraries);
- contract.second.cloneObject.link(m_libraries);
}
}
@@ -396,11 +416,6 @@ eth::LinkerObject const& CompilerStack::runtimeObject(string const& _contractNam
return contract(_contractName).runtimeObject;
}
-eth::LinkerObject const& CompilerStack::cloneObject(string const& _contractName) const
-{
- return contract(_contractName).cloneObject;
-}
-
/// FIXME: cache this string
string CompilerStack::assemblyString(string const& _contractName, StringMap _sourceCodes) const
{
@@ -571,7 +586,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
for (auto const& node: _ast.nodes())
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
- string importPath = absolutePath(import->path(), _sourcePath);
+ string importPath = dev::absolutePath(import->path(), _sourcePath);
// The current value of `path` is the absolute path as seen from this source file.
// We first have to apply remappings before we can store the actual absolute path
// as seen globally.
@@ -614,8 +629,8 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context
for (auto const& redir: m_remappings)
{
- string context = sanitizePath(redir.context);
- string prefix = sanitizePath(redir.prefix);
+ string context = dev::sanitizePath(redir.context);
+ string prefix = dev::sanitizePath(redir.prefix);
// Skip if current context is closer
if (context.length() < longestContext)
@@ -632,7 +647,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context
longestContext = context.length();
longestPrefix = prefix.length();
- bestMatchTarget = sanitizePath(redir.target);
+ bestMatchTarget = dev::sanitizePath(redir.target);
}
string path = bestMatchTarget;
path.append(_path.begin() + longestPrefix, _path.end());
@@ -669,23 +684,6 @@ void CompilerStack::resolveImports()
swap(m_sourceOrder, sourceOrder);
}
-string CompilerStack::absolutePath(string const& _path, string const& _reference) const
-{
- using path = boost::filesystem::path;
- path p(_path);
- // Anything that does not start with `.` is an absolute path.
- if (p.begin() == p.end() || (*p.begin() != "." && *p.begin() != ".."))
- return _path;
- path result(_reference);
- result.remove_filename();
- for (path::iterator it = p.begin(); it != p.end(); ++it)
- if (*it == "..")
- result = result.parent_path();
- else if (*it != ".")
- result /= *it;
- return result.generic_string();
-}
-
namespace
{
bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& features)
@@ -711,39 +709,33 @@ void CompilerStack::compileContract(
for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts);
- shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns);
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
- string metadata = createMetadata(compiledContract);
- bytes cborEncodedHash =
- // CBOR-encoding of the key "bzzr0"
- bytes{0x65, 'b', 'z', 'z', 'r', '0'}+
- // CBOR-encoding of the hash
- bytes{0x58, 0x20} + dev::swarmHash(metadata).asBytes();
- bytes cborEncodedMetadata;
- if (onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures))
- cborEncodedMetadata =
- // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)}
- bytes{0xa1} +
- cborEncodedHash;
- else
- cborEncodedMetadata =
- // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true}
- bytes{0xa2} +
- cborEncodedHash +
- bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5};
- solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large");
- // 16-bit big endian length
- cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
- compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
+
+ shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns);
compiledContract.compiler = compiler;
+ string metadata = createMetadata(compiledContract);
+ compiledContract.metadata = metadata;
+
+ bytes cborEncodedMetadata = createCBORMetadata(
+ metadata,
+ !onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures)
+ );
+
try
{
- compiledContract.object = compiler->assembledObject();
+ // Run optimiser and compile the contract.
+ compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
}
catch(eth::OptimizerException const&)
{
- solAssert(false, "Assembly optimizer exception for bytecode");
+ solAssert(false, "Optimizer exception during compilation");
+ }
+
+ try
+ {
+ // Assemble deployment (incl. runtime) object.
+ compiledContract.object = compiler->assembledObject();
}
catch(eth::AssemblyException const&)
{
@@ -752,36 +744,15 @@ void CompilerStack::compileContract(
try
{
+ // Assemble runtime object.
compiledContract.runtimeObject = compiler->runtimeObject();
}
- catch(eth::OptimizerException const&)
- {
- solAssert(false, "Assembly optimizer exception for deployed bytecode");
- }
catch(eth::AssemblyException const&)
{
solAssert(false, "Assembly exception for deployed bytecode");
}
- compiledContract.metadata = metadata;
_compiledContracts[compiledContract.contract] = &compiler->assembly();
-
- try
- {
- if (!_contract.isLibrary())
- {
- Compiler cloneCompiler(m_evmVersion, m_optimize, m_optimizeRuns);
- cloneCompiler.compileClone(_contract, _compiledContracts);
- compiledContract.cloneObject = cloneCompiler.assembledObject();
- }
- }
- catch (eth::AssemblyException const&)
- {
- // In some cases (if the constructor requests a runtime function), it is not
- // possible to compile the clone.
-
- // TODO: Report error / warning
- }
}
string const CompilerStack::lastContractName() const
@@ -894,6 +865,31 @@ string CompilerStack::createMetadata(Contract const& _contract) const
return jsonCompactPrint(meta);
}
+bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode)
+{
+ bytes cborEncodedHash =
+ // CBOR-encoding of the key "bzzr0"
+ bytes{0x65, 'b', 'z', 'z', 'r', '0'}+
+ // CBOR-encoding of the hash
+ bytes{0x58, 0x20} + dev::swarmHash(_metadata).asBytes();
+ bytes cborEncodedMetadata;
+ if (_experimentalMode)
+ cborEncodedMetadata =
+ // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true}
+ bytes{0xa2} +
+ cborEncodedHash +
+ bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5};
+ else
+ cborEncodedMetadata =
+ // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)}
+ bytes{0xa1} +
+ cborEncodedHash;
+ solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large");
+ // 16-bit big endian length
+ cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
+ return cborEncodedMetadata;
+}
+
string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const
{
string ret;
@@ -938,17 +934,17 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con
if (components-- > 0)
{
if (location.start != prevStart)
- ret += std::to_string(location.start);
+ ret += to_string(location.start);
if (components-- > 0)
{
ret += ':';
if (length != prevLength)
- ret += std::to_string(length);
+ ret += to_string(length);
if (components-- > 0)
{
ret += ':';
if (sourceIndex != prevSourceIndex)
- ret += std::to_string(sourceIndex);
+ ret += to_string(sourceIndex);
if (components-- > 0)
{
ret += ':';
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index 13c9cc7a..9a15fbf0 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -36,7 +36,6 @@
#include <json/json.h>
#include <boost/noncopyable.hpp>
-#include <boost/filesystem.hpp>
#include <ostream>
#include <string>
@@ -85,6 +84,13 @@ public:
CompilationSuccessful
};
+ struct Remapping
+ {
+ std::string context;
+ std::string prefix;
+ std::string target;
+ };
+
/// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions.
@@ -93,7 +99,7 @@ public:
m_errorList(),
m_errorReporter(m_errorList) {}
- /// @returns the list of errors that occured during parsing and type checking.
+ /// @returns the list of errors that occurred during parsing and type checking.
ErrorList const& errors() const { return m_errorReporter.errors(); }
/// @returns the current state.
@@ -104,8 +110,11 @@ public:
/// All settings, with the exception of remappings, are reset.
void reset(bool _keepSources = false);
- /// Sets path remappings in the format "context:prefix=target"
- void setRemappings(std::vector<std::string> const& _remappings);
+ // Parses a remapping of the format "context:prefix=target".
+ static boost::optional<Remapping> parseRemapping(std::string const& _remapping);
+
+ /// Sets path remappings.
+ void setRemappings(std::vector<Remapping> const& _remappings);
/// Sets library addresses. Addresses are cleared iff @a _libraries is missing.
/// Will not take effect before running compile.
@@ -122,6 +131,8 @@ public:
m_optimizeRuns = _runs;
}
+ /// Set the EVM version used before running compile.
+ /// When called without an argument it will revert to the default version.
void setEVMVersion(EVMVersion _version = EVMVersion{});
/// Sets the list of requested contract names. If empty, no filtering is performed and every contract
@@ -188,12 +199,6 @@ public:
/// @returns the runtime object for the contract.
eth::LinkerObject const& runtimeObject(std::string const& _contractName) const;
- /// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL.
- /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to
- /// substituted by the actual address. Note that this sequence starts end ends in three X
- /// characters but can contain anything in between.
- eth::LinkerObject const& cloneObject(std::string const& _contractName) const;
-
/// @returns normal contract assembly items
eth::AssemblyItems const* assemblyItems(std::string const& _contractName) const;
@@ -240,9 +245,7 @@ public:
Json::Value gasEstimates(std::string const& _contractName) const;
private:
- /**
- * Information pertaining to one source unit, filled gradually during parsing and compilation.
- */
+ /// The state per source unit. Filled gradually during parsing.
struct Source
{
std::shared_ptr<Scanner> scanner;
@@ -251,13 +254,13 @@ private:
void reset() { scanner.reset(); ast.reset(); }
};
+ /// The state per contract. Filled gradually during compilation.
struct Contract
{
ContractDefinition const* contract = nullptr;
std::shared_ptr<Compiler> compiler;
- eth::LinkerObject object;
- eth::LinkerObject runtimeObject;
- eth::LinkerObject cloneObject;
+ eth::LinkerObject object; ///< Deployment object (includes the runtime sub-object).
+ eth::LinkerObject runtimeObject; ///< Runtime object.
std::string metadata; ///< The metadata json that will be hashed into the chain.
mutable std::unique_ptr<Json::Value const> abi;
mutable std::unique_ptr<Json::Value const> userDocumentation;
@@ -272,10 +275,6 @@ private:
StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path);
std::string applyRemapping(std::string const& _path, std::string const& _context);
void resolveImports();
- /// @returns the absolute path corresponding to @a _path relative to @a _reference.
- std::string absolutePath(std::string const& _path, std::string const& _reference) const;
- /// Helper function to return path converted strings.
- std::string sanitizePath(std::string const& _path) const { return boost::filesystem::path(_path).generic_string(); }
/// @returns true if the contract is requested to be compiled.
bool isRequestedContract(ContractDefinition const& _contract) const;
@@ -285,19 +284,42 @@ private:
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
);
+
+ /// Links all the known library addresses in the available objects. Any unknown
+ /// library will still be kept as an unlinked placeholder in the objects.
void link();
+ /// @returns the contract object for the given @a _contractName.
+ /// Can only be called after state is CompilationSuccessful.
Contract const& contract(std::string const& _contractName) const;
+
+ /// @returns the source object for the given @a _sourceName.
+ /// Can only be called after state is SourcesSet.
Source const& source(std::string const& _sourceName) const;
/// @returns the parsed contract with the supplied name. Throws an exception if the contract
/// does not exist.
ContractDefinition const& contractDefinition(std::string const& _contractName) const;
+ /// @returns the metadata JSON as a compact string for the given contract.
std::string createMetadata(Contract const& _contract) const;
+
+ /// @returns the metadata CBOR for the given serialised metadata JSON.
+ static bytes createCBORMetadata(std::string _metadata, bool _experimentalMode);
+
+ /// @returns the computer source mapping string.
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
+
+ /// @returns the contract ABI as a JSON object.
+ /// This will generate the JSON object and store it in the Contract object if it is not present yet.
Json::Value const& contractABI(Contract const&) const;
+
+ /// @returns the Natspec User documentation as a JSON object.
+ /// This will generate the JSON object and store it in the Contract object if it is not present yet.
Json::Value const& natspecUser(Contract const&) const;
+
+ /// @returns the Natspec Developer documentation as a JSON object.
+ /// This will generate the JSON object and store it in the Contract object if it is not present yet.
Json::Value const& natspecDev(Contract const&) const;
/// @returns the offset of the entry point of the given function into the list of assembly items
@@ -307,13 +329,6 @@ private:
FunctionDefinition const& _function
) const;
- struct Remapping
- {
- std::string context;
- std::string prefix;
- std::string target;
- };
-
ReadCallback::Callback m_readFile;
ReadCallback::Callback m_smtQuery;
bool m_optimize = false;
@@ -326,8 +341,9 @@ private:
std::vector<Remapping> m_remappings;
std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext;
- std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
std::vector<Source const*> m_sourceOrder;
+ /// This is updated during compilation.
+ std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
std::map<std::string const, Contract> m_contracts;
ErrorList m_errorList;
ErrorReporter m_errorReporter;
diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h
index d1a0030f..fd53587a 100644
--- a/libsolidity/interface/ErrorReporter.h
+++ b/libsolidity/interface/ErrorReporter.h
@@ -92,6 +92,12 @@ public:
void clear();
+ /// @returns true iff there is any error (ignores warnings).
+ bool hasErrors() const
+ {
+ return m_errorCount > 0;
+ }
+
private:
void error(Error::Type _type,
SourceLocation const& _location,
diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h
index 7c66d572..629b8f3f 100644
--- a/libsolidity/interface/Exceptions.h
+++ b/libsolidity/interface/Exceptions.h
@@ -117,7 +117,7 @@ public:
if (occurrences > 32)
{
infos.resize(32);
- _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences.";
+ _message += " Truncated from " + std::to_string(occurrences) + " to the first 32 occurrences.";
}
}
diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp
index 7f7084ef..a8716862 100644
--- a/libsolidity/interface/Natspec.cpp
+++ b/libsolidity/interface/Natspec.cpp
@@ -36,6 +36,19 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
Json::Value doc;
Json::Value methods(Json::objectValue);
+ auto constructorDefinition(_contractDef.constructor());
+ if (constructorDefinition)
+ {
+ string value = extractDoc(constructorDefinition->annotation().docTags, "notice");
+ if (!value.empty())
+ // add the constructor, only if we have any documentation to add
+ methods["constructor"] = Json::Value(value);
+ }
+
+ string notice = extractDoc(_contractDef.annotation().docTags, "notice");
+ if (!notice.empty())
+ doc["notice"] = Json::Value(notice);
+
for (auto const& it: _contractDef.interfaceFunctions())
if (it.second->hasDeclaration())
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
@@ -65,34 +78,25 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
auto title = extractDoc(_contractDef.annotation().docTags, "title");
if (!title.empty())
doc["title"] = title;
+ auto dev = extractDoc(_contractDef.annotation().docTags, "dev");
+ if (!dev.empty())
+ doc["details"] = Json::Value(dev);
+
+ auto constructorDefinition(_contractDef.constructor());
+ if (constructorDefinition) {
+ Json::Value constructor(devDocumentation(constructorDefinition->annotation().docTags));
+ if (!constructor.empty())
+ // add the constructor, only if we have any documentation to add
+ methods["constructor"] = constructor;
+ }
for (auto const& it: _contractDef.interfaceFunctions())
{
if (!it.second->hasDeclaration())
continue;
- Json::Value method;
if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
{
- auto dev = extractDoc(fun->annotation().docTags, "dev");
- if (!dev.empty())
- method["details"] = Json::Value(dev);
-
- auto author = extractDoc(fun->annotation().docTags, "author");
- if (!author.empty())
- method["author"] = author;
-
- auto ret = extractDoc(fun->annotation().docTags, "return");
- if (!ret.empty())
- method["return"] = ret;
-
- Json::Value params(Json::objectValue);
- auto paramRange = fun->annotation().docTags.equal_range("param");
- for (auto i = paramRange.first; i != paramRange.second; ++i)
- params[i->second.paramName] = Json::Value(i->second.content);
-
- if (!params.empty())
- method["params"] = params;
-
+ Json::Value method(devDocumentation(fun->annotation().docTags));
if (!method.empty())
// add the function, only if we have any documentation to add
methods[it.second->externalSignature()] = method;
@@ -111,3 +115,31 @@ string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const&
value += i->second.content;
return value;
}
+
+Json::Value Natspec::devDocumentation(std::multimap<std::string, DocTag> const &_tags)
+{
+ Json::Value json(Json::objectValue);
+ auto dev = extractDoc(_tags, "dev");
+ if (!dev.empty())
+ json["details"] = Json::Value(dev);
+
+ auto author = extractDoc(_tags, "author");
+ if (!author.empty())
+ json["author"] = author;
+
+ // for constructors, the "return" node will never exist. invalid tags
+ // will already generate an error within dev::solidity::DocStringAnalyzer.
+ auto ret = extractDoc(_tags, "return");
+ if (!ret.empty())
+ json["return"] = ret;
+
+ Json::Value params(Json::objectValue);
+ auto paramRange = _tags.equal_range("param");
+ for (auto i = paramRange.first; i != paramRange.second; ++i)
+ params[i->second.paramName] = Json::Value(i->second.content);
+
+ if (!params.empty())
+ json["params"] = params;
+
+ return json;
+}
diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h
index 0701f821..0be4dda2 100644
--- a/libsolidity/interface/Natspec.h
+++ b/libsolidity/interface/Natspec.h
@@ -45,7 +45,7 @@ public:
/// @param _contractDef The contract definition
/// @return A JSON representation of the contract's user documentation
static Json::Value userDocumentation(ContractDefinition const& _contractDef);
- /// Genereates the Developer's documentation of the contract
+ /// Generates the Developer's documentation of the contract
/// @param _contractDef The contract definition
/// @return A JSON representation
/// of the contract's developer documentation
@@ -54,6 +54,12 @@ public:
private:
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
+
+ /// Helper-function that will create a json object with dev specific annotations, if present.
+ /// @param _tags docTags that are used.
+ /// @return A JSON representation
+ /// of the contract's developer documentation
+ static Json::Value devDocumentation(std::multimap<std::string, DocTag> const &_tags);
};
} //solidity NS
diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp
index 0f014372..4724fc7f 100644
--- a/libsolidity/interface/SourceReferenceFormatter.cpp
+++ b/libsolidity/interface/SourceReferenceFormatter.cpp
@@ -55,8 +55,14 @@ void SourceReferenceFormatter::printSourceLocation(SourceLocation const* _locati
}
if (line.length() > 150)
{
- line = " ... " + line.substr(startColumn, locationLength) + " ... ";
- startColumn = 5;
+ int len = line.length();
+ line = line.substr(max(0, startColumn - 35), min(startColumn, 35) + min(locationLength + 35, len - startColumn));
+ if (startColumn + locationLength + 35 < len)
+ line += " ...";
+ if (startColumn > 35) {
+ line = " ... " + line;
+ startColumn = 40;
+ }
endColumn = startColumn + locationLength;
}
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index ee9b1440..c1996777 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -117,7 +117,7 @@ bool hashMatchesContent(string const& _hash, string const& _content)
{
return dev::h256(_hash) == dev::keccak256(_content);
}
- catch (dev::BadHexCharacter)
+ catch (dev::BadHexCharacter const&)
{
return false;
}
@@ -326,9 +326,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
m_compilerStack.setEVMVersion(*version);
}
- vector<string> remappings;
+ vector<CompilerStack::Remapping> remappings;
for (auto const& remapping: settings.get("remappings", Json::Value()))
- remappings.push_back(remapping.asString());
+ {
+ if (auto r = CompilerStack::parseRemapping(remapping.asString()))
+ remappings.emplace_back(std::move(*r));
+ else
+ return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\"");
+ }
m_compilerStack.setRemappings(remappings);
Json::Value optimizerSettings = settings.get("optimizer", Json::Value());
@@ -366,7 +371,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
// @TODO use libraries only for the given source
libraries[library] = h160(address);
}
- catch (dev::BadHexCharacter)
+ catch (dev::BadHexCharacter const&)
{
return formatFatalError(
"JSONError",
@@ -567,7 +572,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
return output;
}
-Json::Value StandardCompiler::compile(Json::Value const& _input)
+Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
{
try
{
@@ -591,7 +596,7 @@ Json::Value StandardCompiler::compile(Json::Value const& _input)
}
}
-string StandardCompiler::compile(string const& _input)
+string StandardCompiler::compile(string const& _input) noexcept
{
Json::Value input;
string errors;
@@ -600,7 +605,7 @@ string StandardCompiler::compile(string const& _input)
if (!jsonParseStrict(_input, input, &errors))
return jsonCompactPrint(formatFatalError("JSONError", errors));
}
- catch(...)
+ catch (...)
{
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}";
}
@@ -613,7 +618,7 @@ string StandardCompiler::compile(string const& _input)
{
return jsonCompactPrint(output);
}
- catch(...)
+ catch (...)
{
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}";
}
diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h
index 11a0b4c2..fc9c3a59 100644
--- a/libsolidity/interface/StandardCompiler.h
+++ b/libsolidity/interface/StandardCompiler.h
@@ -31,7 +31,7 @@ namespace solidity
{
/**
- * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput.
+ * Standard JSON compiler interface, which expects a JSON input and returns a JSON output.
* See docs/using-the-compiler#compiler-input-and-output-json-description.
*/
class StandardCompiler: boost::noncopyable
@@ -47,10 +47,10 @@ public:
/// Sets all input parameters according to @a _input which conforms to the standardized input
/// format, performs compilation and returns a standardized output.
- Json::Value compile(Json::Value const& _input);
+ Json::Value compile(Json::Value const& _input) noexcept;
/// Parses input as JSON and peforms the above processing steps, returning a serialized JSON
/// output. Parsing errors are returned as regular errors.
- std::string compile(std::string const& _input);
+ std::string compile(std::string const& _input) noexcept;
private:
Json::Value compileInternal(Json::Value const& _input);
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index e2e1eebc..1228b833 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -57,7 +57,7 @@ public:
solAssert(m_location.sourceName, "");
if (m_location.end < 0)
markEndPosition();
- return make_shared<NodeType>(m_location, forward<Args>(_args)...);
+ return make_shared<NodeType>(m_location, std::forward<Args>(_args)...);
}
private:
@@ -75,7 +75,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
vector<ASTPointer<ASTNode>> nodes;
while (m_scanner->currentToken() != Token::EOS)
{
- switch (auto token = m_scanner->currentToken())
+ switch (m_scanner->currentToken())
{
case Token::Pragma:
nodes.push_back(parsePragmaDirective());
@@ -86,7 +86,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
case Token::Interface:
case Token::Contract:
case Token::Library:
- nodes.push_back(parseContractDefinition(token));
+ nodes.push_back(parseContractDefinition());
break;
default:
fatalParserError(string("Expected pragma, import directive or contract/interface/library definition."));
@@ -198,31 +198,35 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases));
}
-ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token)
+ContractDefinition::ContractKind Parser::parseContractKind()
{
- switch(_token)
+ ContractDefinition::ContractKind kind;
+ switch(m_scanner->currentToken())
{
case Token::Interface:
- return ContractDefinition::ContractKind::Interface;
+ kind = ContractDefinition::ContractKind::Interface;
+ break;
case Token::Contract:
- return ContractDefinition::ContractKind::Contract;
+ kind = ContractDefinition::ContractKind::Contract;
+ break;
case Token::Library:
- return ContractDefinition::ContractKind::Library;
+ kind = ContractDefinition::ContractKind::Library;
+ break;
default:
- fatalParserError("Unsupported contract type.");
+ solAssert(false, "Invalid contract kind.");
}
- // FIXME: fatalParserError is not considered as throwing here
- return ContractDefinition::ContractKind::Contract;
+ m_scanner->next();
+ return kind;
}
-ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind)
+ASTPointer<ContractDefinition> Parser::parseContractDefinition()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
- expectToken(_expectedKind);
+ ContractDefinition::ContractKind contractKind = parseContractKind();
ASTPointer<ASTString> name = expectIdentifierToken();
vector<ASTPointer<InheritanceSpecifier>> baseContracts;
if (m_scanner->currentToken() == Token::Is)
@@ -239,13 +243,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp
Token::Value currentTokenValue = m_scanner->currentToken();
if (currentTokenValue == Token::RBrace)
break;
- else if (
- currentTokenValue == Token::Function ||
- (currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor")
- )
+ else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor)
// This can be a function or a state variable of function type (especially
// complicated to distinguish fallback function from function type state variable)
- subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get()));
+ subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable());
else if (currentTokenValue == Token::Struct)
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
@@ -278,7 +279,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp
docString,
baseContracts,
subNodes,
- tokenToContractKind(_expectedKind)
+ contractKind
);
}
@@ -300,63 +301,85 @@ ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
return nodeFactory.createNode<InheritanceSpecifier>(name, std::move(arguments));
}
-Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
+Declaration::Visibility Parser::parseVisibilitySpecifier()
{
Declaration::Visibility visibility(Declaration::Visibility::Default);
- if (_token == Token::Public)
- visibility = Declaration::Visibility::Public;
- else if (_token == Token::Internal)
- visibility = Declaration::Visibility::Internal;
- else if (_token == Token::Private)
- visibility = Declaration::Visibility::Private;
- else if (_token == Token::External)
- visibility = Declaration::Visibility::External;
- else
- solAssert(false, "Invalid visibility specifier.");
+ Token::Value token = m_scanner->currentToken();
+ switch (token)
+ {
+ case Token::Public:
+ visibility = Declaration::Visibility::Public;
+ break;
+ case Token::Internal:
+ visibility = Declaration::Visibility::Internal;
+ break;
+ case Token::Private:
+ visibility = Declaration::Visibility::Private;
+ break;
+ case Token::External:
+ visibility = Declaration::Visibility::External;
+ break;
+ default:
+ solAssert(false, "Invalid visibility specifier.");
+ }
m_scanner->next();
return visibility;
}
-StateMutability Parser::parseStateMutability(Token::Value _token)
+StateMutability Parser::parseStateMutability()
{
StateMutability stateMutability(StateMutability::NonPayable);
- if (_token == Token::Payable)
- stateMutability = StateMutability::Payable;
- // FIXME: constant should be removed at the next breaking release
- else if (_token == Token::View || _token == Token::Constant)
- stateMutability = StateMutability::View;
- else if (_token == Token::Pure)
- stateMutability = StateMutability::Pure;
- else
- solAssert(false, "Invalid state mutability specifier.");
+ Token::Value token = m_scanner->currentToken();
+ switch(token)
+ {
+ case Token::Payable:
+ stateMutability = StateMutability::Payable;
+ break;
+ case Token::View:
+ stateMutability = StateMutability::View;
+ break;
+ case Token::Pure:
+ stateMutability = StateMutability::Pure;
+ break;
+ case Token::Constant:
+ stateMutability = StateMutability::View;
+ parserError(
+ "The state mutability modifier \"constant\" was removed in version 0.5.0. "
+ "Use \"view\" or \"pure\" instead."
+ );
+ break;
+ default:
+ solAssert(false, "Invalid state mutability specifier.");
+ }
m_scanner->next();
return stateMutability;
}
-Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
- bool _forceEmptyName,
- bool _allowModifiers,
- ASTString const* _contractName
-)
+Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers)
{
RecursionGuard recursionGuard(*this);
FunctionHeaderParserResult result;
result.isConstructor = false;
- if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor")
+ if (m_scanner->currentToken() == Token::Constructor)
result.isConstructor = true;
else if (m_scanner->currentToken() != Token::Function)
solAssert(false, "Function or constructor expected.");
m_scanner->next();
- if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen)
+ if (result.isConstructor)
result.name = make_shared<ASTString>();
+ else if (_forceEmptyName || m_scanner->currentToken() == Token::LParen)
+ result.name = make_shared<ASTString>();
+ else if (m_scanner->currentToken() == Token::Constructor)
+ fatalParserError(string(
+ "This function is named \"constructor\" but is not the constructor of the contract. "
+ "If you intend this to be a constructor, use \"constructor(...) { ... }\" without the \"function\" keyword to define it."
+ ));
else
result.name = expectIdentifierToken();
- if (!result.name->empty() && _contractName && *result.name == *_contractName)
- result.isConstructor = true;
VarDeclParserOptions options;
options.allowLocationSpecifier = true;
@@ -398,7 +421,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
m_scanner->next();
}
else
- result.visibility = parseVisibilitySpecifier(token);
+ result.visibility = parseVisibilitySpecifier();
}
else if (Token::isStateMutabilitySpecifier(token))
{
@@ -412,7 +435,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
m_scanner->next();
}
else
- result.stateMutability = parseStateMutability(token);
+ result.stateMutability = parseStateMutability();
}
else
break;
@@ -428,7 +451,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
return result;
}
-ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName)
+ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
@@ -436,7 +459,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
- FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName);
+ FunctionHeaderParserResult header = parseFunctionHeader(false, true);
if (
header.isConstructor ||
@@ -559,7 +582,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
bool isIndexed = false;
bool isDeclaredConst = false;
Declaration::Visibility visibility(Declaration::Visibility::Default);
- VariableDeclaration::Location location = VariableDeclaration::Location::Default;
+ VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
ASTPointer<ASTString> identifier;
while (true)
@@ -567,6 +590,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
Token::Value token = m_scanner->currentToken();
if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token))
{
+ nodeFactory.markEndPosition();
if (visibility != Declaration::Visibility::Default)
{
parserError(string(
@@ -577,7 +601,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
m_scanner->next();
}
else
- visibility = parseVisibilitySpecifier(token);
+ visibility = parseVisibilitySpecifier();
}
else
{
@@ -587,34 +611,45 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
isDeclaredConst = true;
else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token))
{
- if (location != VariableDeclaration::Location::Default)
+ if (location != VariableDeclaration::Location::Unspecified)
parserError(string("Location already specified."));
else if (!type)
parserError(string("Location specifier needs explicit type name."));
else
- location = (
- token == Token::Memory ?
- VariableDeclaration::Location::Memory :
- VariableDeclaration::Location::Storage
- );
+ {
+ switch (token)
+ {
+ case Token::Storage:
+ location = VariableDeclaration::Location::Storage;
+ break;
+ case Token::Memory:
+ location = VariableDeclaration::Location::Memory;
+ break;
+ case Token::CallData:
+ location = VariableDeclaration::Location::CallData;
+ break;
+ default:
+ solAssert(false, "Unknown data location.");
+ }
+ }
}
else
break;
+ nodeFactory.markEndPosition();
m_scanner->next();
}
}
- nodeFactory.markEndPosition();
if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier)
{
identifier = make_shared<ASTString>("");
solAssert(!_options.allowVar, ""); // allowEmptyName && allowVar makes no sense
- if (type)
- nodeFactory.setEndPositionFromNode(type);
- // if type is null this has already caused an error
}
else
+ {
+ nodeFactory.markEndPosition();
identifier = expectIdentifierToken();
+ }
ASTPointer<Expression> value;
if (_options.allowInitialValue)
{
@@ -778,8 +813,24 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
unsigned secondSize;
tie(firstSize, secondSize) = m_scanner->currentTokenInfo();
ElementaryTypeNameToken elemTypeName(token, firstSize, secondSize);
- type = ASTNodeFactory(*this).createNode<ElementaryTypeName>(elemTypeName);
+ ASTNodeFactory nodeFactory(*this);
+ nodeFactory.markEndPosition();
m_scanner->next();
+ auto stateMutability = boost::make_optional(elemTypeName.token() == Token::Address, StateMutability::NonPayable);
+ if (Token::isStateMutabilitySpecifier(m_scanner->currentToken(), false))
+ {
+ if (elemTypeName.token() == Token::Address)
+ {
+ nodeFactory.markEndPosition();
+ stateMutability = parseStateMutability();
+ }
+ else
+ {
+ parserError("State mutability can only be specified for address types.");
+ m_scanner->next();
+ }
+ }
+ type = nodeFactory.createNode<ElementaryTypeName>(elemTypeName, stateMutability);
}
else if (token == Token::Var)
{
@@ -928,10 +979,11 @@ ASTPointer<Statement> Parser::parseStatement()
}
case Token::Assembly:
return parseInlineAssembly(docString);
+ case Token::Emit:
+ statement = parseEmitStatement(docString);
+ break;
case Token::Identifier:
- if (m_scanner->currentLiteral() == "emit")
- statement = parseEmitStatement(docString);
- else if (m_insideModifier && m_scanner->currentLiteral() == "_")
+ if (m_insideModifier && m_scanner->currentLiteral() == "_")
{
statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(docString);
m_scanner->next();
@@ -1051,6 +1103,8 @@ ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const&
ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const& _docString)
{
+ expectToken(Token::Emit, false);
+
ASTNodeFactory nodeFactory(*this);
m_scanner->next();
ASTNodeFactory eventCallNodeFactory(*this);
@@ -1577,8 +1631,8 @@ Parser::LookAheadInfo Parser::peekStatementType() const
// Distinguish between variable declaration (and potentially assignment) and expression statement
// (which include assignments to other expressions and pre-declared variables).
// We have a variable declaration if we get a keyword that specifies a type name.
- // If it is an identifier or an elementary type name followed by an identifier, we also have
- // a variable declaration.
+ // If it is an identifier or an elementary type name followed by an identifier
+ // or a mutability specifier, we also have a variable declaration.
// If we get an identifier followed by a "[" or ".", it can be both ("lib.type[9] a;" or "variable.el[9] = 7;").
// In all other cases, we have an expression statement.
Token::Value token(m_scanner->currentToken());
@@ -1589,6 +1643,12 @@ Parser::LookAheadInfo Parser::peekStatementType() const
if (mightBeTypeName)
{
Token::Value next = m_scanner->peekNextToken();
+ // So far we only allow ``address payable`` in variable declaration statements and in no other
+ // kind of statement. This means, for example, that we do not allow type expressions of the form
+ // ``address payable;``.
+ // If we want to change this in the future, we need to consider another scanner token here.
+ if (Token::isElementaryTypeName(token) && Token::isStateMutabilitySpecifier(next, false))
+ return LookAheadInfo::VariableDeclaration;
if (next == Token::Identifier || Token::isLocationSpecifier(next))
return LookAheadInfo::VariableDeclaration;
if (next == Token::LBrack || next == Token::Period)
diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h
index 08653364..fa974171 100644
--- a/libsolidity/parsing/Parser.h
+++ b/libsolidity/parsing/Parser.h
@@ -69,17 +69,13 @@ private:
///@name Parsing functions for the AST nodes
ASTPointer<PragmaDirective> parsePragmaDirective();
ASTPointer<ImportDirective> parseImportDirective();
- ContractDefinition::ContractKind tokenToContractKind(Token::Value _token);
- ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind);
+ ContractDefinition::ContractKind parseContractKind();
+ ASTPointer<ContractDefinition> parseContractDefinition();
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
- Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
- StateMutability parseStateMutability(Token::Value _token);
- FunctionHeaderParserResult parseFunctionHeader(
- bool _forceEmptyName,
- bool _allowModifiers,
- ASTString const* _contractName = nullptr
- );
- ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName);
+ Declaration::Visibility parseVisibilitySpecifier();
+ StateMutability parseStateMutability();
+ FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);
+ ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable();
ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName);
ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition();
diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp
index dbe1f389..c9d5b969 100644
--- a/libsolidity/parsing/Scanner.cpp
+++ b/libsolidity/parsing/Scanner.cpp
@@ -746,10 +746,18 @@ Token::Value Scanner::scanHexString()
return Token::StringLiteral;
}
+// Parse for regex [:digit:]+(_[:digit:]+)*
void Scanner::scanDecimalDigits()
{
- while (isDecimalDigit(m_char))
- addLiteralCharAndAdvance();
+ // MUST begin with a decimal digit.
+ if (!isDecimalDigit(m_char))
+ return;
+
+ // May continue with decimal digit or underscore for grouping.
+ do addLiteralCharAndAdvance();
+ while (!m_source.isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_'));
+
+ // Defer further validation of underscore to SyntaxChecker.
}
Token::Value Scanner::scanNumber(char _charSeen)
@@ -760,6 +768,8 @@ Token::Value Scanner::scanNumber(char _charSeen)
{
// we have already seen a decimal point of the float
addLiteralChar('.');
+ if (m_char == '_')
+ return Token::Illegal;
scanDecimalDigits(); // we know we have at least one digit
}
else
@@ -777,7 +787,8 @@ Token::Value Scanner::scanNumber(char _charSeen)
addLiteralCharAndAdvance();
if (!isHexDigit(m_char))
return Token::Illegal; // we must have at least one hex digit after 'x'/'X'
- while (isHexDigit(m_char))
+
+ while (isHexDigit(m_char) || m_char == '_') // We keep the underscores for later validation
addLiteralCharAndAdvance();
}
else if (isDecimalDigit(m_char))
@@ -790,8 +801,22 @@ Token::Value Scanner::scanNumber(char _charSeen)
scanDecimalDigits(); // optional
if (m_char == '.')
{
+ if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_')
+ {
+ // Assume the input may be a floating point number with leading '_' in fraction part.
+ // Recover by consuming it all but returning `Illegal` right away.
+ addLiteralCharAndAdvance(); // '.'
+ addLiteralCharAndAdvance(); // '_'
+ scanDecimalDigits();
+ }
+ if (m_source.isPastEndOfInput() || !isDecimalDigit(m_source.get(1)))
+ {
+ // A '.' has to be followed by a number.
+ literal.complete();
+ return Token::Number;
+ }
addLiteralCharAndAdvance();
- scanDecimalDigits(); // optional
+ scanDecimalDigits();
}
}
}
@@ -801,8 +826,18 @@ Token::Value Scanner::scanNumber(char _charSeen)
solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number");
if (kind != DECIMAL)
return Token::Illegal;
+ else if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_')
+ {
+ // Recover from wrongly placed underscore as delimiter in literal with scientific
+ // notation by consuming until the end.
+ addLiteralCharAndAdvance(); // 'e'
+ addLiteralCharAndAdvance(); // '_'
+ scanDecimalDigits();
+ literal.complete();
+ return Token::Number;
+ }
// scan exponent
- addLiteralCharAndAdvance();
+ addLiteralCharAndAdvance(); // 'e' | 'E'
if (m_char == '+' || m_char == '-')
addLiteralCharAndAdvance();
if (!isDecimalDigit(m_char))
diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h
index 602532e4..7564c788 100644
--- a/libsolidity/parsing/Scanner.h
+++ b/libsolidity/parsing/Scanner.h
@@ -226,7 +226,7 @@ private:
bool isSourcePastEndOfInput() const { return m_source.isPastEndOfInput(); }
TokenDesc m_skippedComment; // desc for current skipped comment
- TokenDesc m_nextSkippedComment; // desc for next skiped comment
+ TokenDesc m_nextSkippedComment; // desc for next skipped comment
TokenDesc m_currentToken; // desc for current token (as returned by Next())
TokenDesc m_nextToken; // desc for next token (one token look-ahead)
diff --git a/libsolidity/parsing/Token.cpp b/libsolidity/parsing/Token.cpp
index 5ce74316..27acb7d4 100644
--- a/libsolidity/parsing/Token.cpp
+++ b/libsolidity/parsing/Token.cpp
@@ -63,7 +63,7 @@ void ElementaryTypeNameToken::assertDetails(Token::Value _baseType, unsigned con
{
solAssert(_second == 0, "There should not be a second size argument to type " + string(Token::toString(_baseType)) + ".");
solAssert(
- _first <= 256 && _first % 8 == 0,
+ _first <= 256 && _first % 8 == 0,
"No elementary type " + string(Token::toString(_baseType)) + to_string(_first) + "."
);
}
@@ -165,7 +165,7 @@ tuple<Token::Value, unsigned int, unsigned int> Token::fromIdentifierOrKeyword(s
else
return make_tuple(Token::FixedMxN, m, n);
}
- }
+ }
}
return make_tuple(Token::Identifier, 0, 0);
}
diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h
index 4d7a7bc6..73c85482 100644
--- a/libsolidity/parsing/Token.h
+++ b/libsolidity/parsing/Token.h
@@ -144,11 +144,13 @@ namespace solidity
K(Assembly, "assembly", 0) \
K(Break, "break", 0) \
K(Constant, "constant", 0) \
+ K(Constructor, "constructor", 0) \
K(Continue, "continue", 0) \
K(Contract, "contract", 0) \
K(Do, "do", 0) \
K(Else, "else", 0) \
K(Enum, "enum", 0) \
+ K(Emit, "emit", 0) \
K(Event, "event", 0) \
K(External, "external", 0) \
K(For, "for", 0) \
@@ -173,6 +175,7 @@ namespace solidity
K(Return, "return", 0) \
K(Returns, "returns", 0) \
K(Storage, "storage", 0) \
+ K(CallData, "calldata", 0) \
K(Struct, "struct", 0) \
K(Throw, "throw", 0) \
K(Using, "using", 0) \
@@ -221,22 +224,41 @@ namespace solidity
/* Keywords reserved for future use. */ \
K(Abstract, "abstract", 0) \
K(After, "after", 0) \
+ K(Alias, "alias", 0) \
+ K(Apply, "apply", 0) \
+ K(Auto, "auto", 0) \
K(Case, "case", 0) \
K(Catch, "catch", 0) \
+ K(CopyOf, "copyof", 0) \
K(Default, "default", 0) \
+ K(Define, "define", 0) \
K(Final, "final", 0) \
+ K(Immutable, "immutable", 0) \
+ K(Implements, "implements", 0) \
K(In, "in", 0) \
K(Inline, "inline", 0) \
K(Let, "let", 0) \
+ K(Macro, "macro", 0) \
K(Match, "match", 0) \
+ K(Mutable, "mutable", 0) \
K(NullLiteral, "null", 0) \
K(Of, "of", 0) \
+ K(Override, "override", 0) \
+ K(Partial, "partial", 0) \
+ K(Promise, "promise", 0) \
+ K(Reference, "reference", 0) \
K(Relocatable, "relocatable", 0) \
+ K(Sealed, "sealed", 0) \
+ K(Sizeof, "sizeof", 0) \
K(Static, "static", 0) \
+ K(Supports, "supports", 0) \
K(Switch, "switch", 0) \
K(Try, "try", 0) \
K(Type, "type", 0) \
+ K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \
+ K(Unchecked, "unchecked", 0) \
+ \
/* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \
\
@@ -289,11 +311,16 @@ public:
static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); }
static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; }
static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; }
- static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage; }
- static bool isStateMutabilitySpecifier(Value op) { return op == Pure || op == Constant || op == View || op == Payable; }
+ static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage || op == CallData; }
+ static bool isStateMutabilitySpecifier(Value op, bool _allowConstant = true)
+ {
+ if (op == Constant && _allowConstant)
+ return true;
+ return op == Pure || op == View || op == Payable;
+ }
static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; }
static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; }
- static bool isReservedKeyword(Value op) { return (Abstract <= op && op <= TypeOf); }
+ static bool isReservedKeyword(Value op) { return (Abstract <= op && op <= Unchecked); }
// @returns a string corresponding to the JS token string
// (.e., "<" for the token LT) or NULL if the token doesn't
@@ -348,7 +375,7 @@ public:
unsigned int secondNumber() const { return m_secondNumber; }
Token::Value token() const { return m_token; }
///if tokValue is set to true, then returns the actual token type name, otherwise, returns full type
- std::string toString(bool const& tokenValue = false) const
+ std::string toString(bool const& tokenValue = false) const
{
std::string name = Token::toString(m_token);
if (tokenValue || (firstNumber() == 0 && secondNumber() == 0))