Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth
2020-05-12 17:48:53 +02:00
342 changed files with 4644 additions and 1159 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
endif()
add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS})
target_link_libraries(solidity PUBLIC yul evmasm langutil solutil Boost::boost Boost::filesystem Boost::system)
target_link_libraries(solidity PUBLIC yul evmasm langutil solutil Boost::boost)
if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC z3::libz3)
+3 -1
View File
@@ -29,6 +29,7 @@
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
using namespace solidity::langutil;
void ConstantEvaluator::endVisit(UnaryOperation const& _operation)
{
@@ -46,6 +47,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
TypePointer commonType = left->binaryOperatorResult(_operation.getOperator(), right);
if (!commonType)
m_errorReporter.fatalTypeError(
6020_error,
_operation.location(),
"Operator " +
string(TokenTraits::toString(_operation.getOperator())) +
@@ -82,7 +84,7 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier)
else if (!m_types->count(value.get()))
{
if (m_depth > 32)
m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted).");
m_errorReporter.fatalTypeError(5210_error, _identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted).");
ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value);
}
+39 -14
View File
@@ -26,7 +26,6 @@
#include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
using namespace std;
@@ -78,6 +77,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
{
if (constructor)
m_errorReporter.declarationError(
7997_error,
function->location(),
SecondarySourceLocation().append("Another declaration is here:", constructor->location()),
"More than one constructor defined."
@@ -88,6 +88,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
{
if (fallback)
m_errorReporter.declarationError(
7301_error,
function->location(),
SecondarySourceLocation().append("Another declaration is here:", fallback->location()),
"Only one fallback function is allowed."
@@ -98,6 +99,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
{
if (receive)
m_errorReporter.declarationError(
4046_error,
function->location(),
SecondarySourceLocation().append("Another declaration is here:", receive->location()),
"Only one receive function is allowed."
@@ -110,7 +112,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
functions[function->name()].push_back(function);
}
findDuplicateDefinitions(functions, "Function with same name and arguments defined twice.");
findDuplicateDefinitions(functions);
}
void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contract)
@@ -121,11 +123,11 @@ void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contr
for (EventDefinition const* event: _contract.events())
events[event->name()].push_back(event);
findDuplicateDefinitions(events, "Event with same name and arguments defined twice.");
findDuplicateDefinitions(events);
}
template <class T>
void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const& _definitions, string _message)
void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const& _definitions)
{
for (auto const& it: _definitions)
{
@@ -144,12 +146,27 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
if (ssl.infos.size() > 0)
{
ssl.limitSize(_message);
ErrorId error;
string message;
if constexpr (is_same_v<T, FunctionDefinition const*>)
{
error = 1686_error;
message = "Function with same name and arguments defined twice.";
}
else
{
static_assert(is_same_v<T, EventDefinition const*>, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\"");
error = 5883_error;
message = "Event with same name and arguments defined twice.";
}
ssl.limitSize(message);
m_errorReporter.declarationError(
error,
overloads[i]->location(),
ssl,
_message
message
);
}
}
@@ -197,9 +214,9 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
if (_contract.abstract())
{
if (_contract.contractKind() == ContractKind::Interface)
m_errorReporter.typeError(_contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly.");
m_errorReporter.typeError(9348_error, _contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly.");
else if (_contract.contractKind() == ContractKind::Library)
m_errorReporter.typeError(_contract.location(), "Libraries cannot be abstract.");
m_errorReporter.typeError(9571_error, _contract.location(), "Libraries cannot be abstract.");
else
solAssert(_contract.contractKind() == ContractKind::Contract, "");
}
@@ -215,10 +232,12 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
SecondarySourceLocation ssl;
for (auto declaration: _contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(_contract.location(), ssl,
"Contract \"" + _contract.annotation().canonicalName
+ "\" should be marked as abstract.");
m_errorReporter.typeError(
3656_error,
_contract.location(),
ssl,
"Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."
);
}
}
@@ -243,6 +262,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
}
else
m_errorReporter.declarationError(
1563_error,
modifier->location(),
"Modifier-style base constructor call without arguments."
);
@@ -304,6 +324,7 @@ void ContractLevelChecker::annotateBaseConstructorArguments(
}
m_errorReporter.declarationError(
3364_error,
*mainLocation,
ssl,
"Base constructor arguments given twice."
@@ -343,6 +364,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
for (size_t j = i + 1; j < it.second.size(); ++j)
if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second))
m_errorReporter.typeError(
9914_error,
it.second[j].first->location(),
"Function overload clash during conversion to external types for arguments."
);
@@ -356,6 +378,7 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra
util::FixedHash<4> const& hash = it.first;
if (hashes.count(hash))
m_errorReporter.typeError(
1860_error,
_contract.location(),
string("Function signature hash collision for ") + it.second->externalSignature()
);
@@ -369,11 +392,11 @@ void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _c
return;
if (!_contract.baseContracts().empty())
m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit.");
m_errorReporter.typeError(9469_error, _contract.location(), "Library is not allowed to inherit.");
for (auto const& var: _contract.stateVariables())
if (!var->isConstant())
m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables");
m_errorReporter.typeError(9957_error, var->location(), "Library cannot have non-constant state variables");
}
void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract)
@@ -412,6 +435,7 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
if (!errors.infos.empty())
m_errorReporter.fatalTypeError(
6594_error,
_contract.location(),
errors,
std::string("Contract \"") +
@@ -428,6 +452,7 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition
if (auto const* fallback = _contract.fallbackFunction())
if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction())
m_errorReporter.warning(
3628_error,
_contract.location(),
"This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.",
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
+1 -1
View File
@@ -60,7 +60,7 @@ private:
void checkDuplicateFunctions(ContractDefinition const& _contract);
void checkDuplicateEvents(ContractDefinition const& _contract);
template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions);
/// Checks for unimplemented functions and modifiers.
void checkAbstractDefinitions(ContractDefinition const& _contract);
/// Checks that the base constructor arguments are properly provided.
+2 -1
View File
@@ -136,6 +136,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
ssl.append("The variable was declared here.", variableOccurrence->declaration().location());
m_errorReporter.typeError(
3464_error,
variableOccurrence->occurrence() ?
*variableOccurrence->occurrence() :
variableOccurrence->declaration().location(),
@@ -176,6 +177,6 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
// Extend the location, as long as the next location overlaps (unreachable is sorted).
for (; it != unreachable.end() && it->start <= location.end; ++it)
location.end = std::max(location.end, it->end);
m_errorReporter.warning(location, "Unreachable code.");
m_errorReporter.warning(5740_error, location, "Unreachable code.");
}
}
@@ -18,7 +18,6 @@
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/analysis/ControlFlowBuilder.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
using namespace std;
@@ -188,12 +188,14 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
{
if (contractType->contractDefinition().isLibrary())
m_errorReporter.fatalTypeError(
1665_error,
typeName->location(),
"Library types cannot be used as mapping keys."
);
}
else if (typeName->annotation().type->category() != Type::Category::Enum)
m_errorReporter.fatalTypeError(
7804_error,
typeName->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
);
@@ -253,9 +255,9 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
return;
if (_variable.isConstant() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
m_errorReporter.declarationError(1788_error, _variable.location(), "The \"constant\" keyword can only be used for state variables.");
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
m_errorReporter.declarationError(8297_error, _variable.location(), "The \"immutable\" keyword can only be used for state variables.");
if (!_variable.typeName())
{
@@ -360,19 +362,19 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
void DeclarationTypeChecker::typeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.typeError(_location, _description);
m_errorReporter.typeError(2311_error, _location, _description);
}
void DeclarationTypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalTypeError(_location, _description);
m_errorReporter.fatalTypeError(5651_error, _location, _description);
}
void DeclarationTypeChecker::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalDeclarationError(_location, _description);
m_errorReporter.fatalDeclarationError(2046_error, _location, _description);
}
bool DeclarationTypeChecker::check(ASTNode const& _node)
+1 -1
View File
@@ -173,5 +173,5 @@ void DocStringAnalyser::parseDocStrings(
void DocStringAnalyser::appendError(SourceLocation const& _location, string const& _description)
{
m_errorOccured = true;
m_errorReporter.docstringParsingError(_location, _description);
m_errorReporter.docstringParsingError(7816_error, _location, _description);
}
+6 -4
View File
@@ -100,11 +100,13 @@ inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariable
magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)),
// Accepts a MagicType that can be any contract type or an Integer type and returns a
// MagicType. The TypeChecker handles the correctness of the input and output types.
magicVarDecl("type", TypeProvider::function(
strings{"address"} /* accepts any contract type, handled by the type checker */,
strings{} /* returns a MagicType, handled by the type checker */,
strings{},
strings{},
FunctionType::Kind::MetaType,
false,
true,
StateMutability::Pure
)),
};
@@ -123,7 +125,7 @@ vector<Declaration const*> GlobalContext::declarations() const
{
vector<Declaration const*> declarations;
declarations.reserve(m_magicVariables.size());
for (ASTPointer<Declaration const> const& variable: m_magicVariables)
for (ASTPointer<MagicVariableDeclaration const> const& variable: m_magicVariables)
declarations.push_back(variable.get());
return declarations;
}
+9 -1
View File
@@ -22,6 +22,7 @@
#include <boost/range/adaptor/reversed.hpp>
using namespace solidity::frontend;
using namespace solidity::langutil;
void ImmutableValidator::analyze()
{
@@ -42,7 +43,7 @@ void ImmutableValidator::analyze()
visitCallableIfNew(*contract->constructor());
for (ContractDefinition const* contract: linearizedContracts)
for (std::shared_ptr<InheritanceSpecifier> const inheritSpec: contract->baseContracts())
for (std::shared_ptr<InheritanceSpecifier> const& inheritSpec: contract->baseContracts())
if (auto args = inheritSpec->arguments())
ASTNode::listAccept(*args, *this);
@@ -165,33 +166,39 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
{
if (!m_currentConstructor)
m_errorReporter.typeError(
1581_error,
_expression.location(),
"Immutable variables can only be initialized inline or assigned directly in the constructor."
);
else if (m_currentConstructor->annotation().contract->id() != _variableReference.annotation().contract->id())
m_errorReporter.typeError(
7484_error,
_expression.location(),
"Immutable variables must be initialized in the constructor of the contract they are defined in."
);
else if (m_inLoop)
m_errorReporter.typeError(
6672_error,
_expression.location(),
"Immutable variables can only be initialized once, not in a while statement."
);
else if (m_inBranch)
m_errorReporter.typeError(
4599_error,
_expression.location(),
"Immutable variables must be initialized unconditionally, not in an if statement."
);
if (!m_initializedStateVariables.emplace(&_variableReference).second)
m_errorReporter.typeError(
1574_error,
_expression.location(),
"Immutable state variable already initialized."
);
}
else if (m_inConstructionContext)
m_errorReporter.typeError(
7733_error,
_expression.location(),
"Immutable variables cannot be read during contract creation time, which means "
"they cannot be read in the constructor or any function or modifier called from it."
@@ -205,6 +212,7 @@ void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::Source
if (varDecl->immutable())
if (!util::contains(m_initializedStateVariables, varDecl))
m_errorReporter.typeError(
2658_error,
_location,
solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()),
"Construction control flow ends without initializing all immutable state variables."
+10 -3
View File
@@ -80,6 +80,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
if (!_sourceUnits.count(path))
{
m_errorReporter.declarationError(
5073_error,
imp->location(),
"Import \"" + path + "\" (referenced as \"" + imp->path() + "\") not found."
);
@@ -95,6 +96,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
if (declarations.empty())
{
m_errorReporter.declarationError(
2904_error,
imp->location(),
"Declaration \"" +
alias.symbol->name() +
@@ -208,6 +210,7 @@ void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
// Don't warn the user for what the user did not.
continue;
m_errorReporter.warning(
8261_error,
declaration->location(),
"Variable is shadowed in inline assembly by an instruction of the same name"
);
@@ -326,6 +329,7 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
}
m_errorReporter.declarationError(
9097_error,
secondDeclarationLocation,
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
@@ -343,19 +347,19 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
UserDefinedTypeName const& baseName = baseSpecifier->name();
auto base = dynamic_cast<ContractDefinition const*>(baseName.annotation().referencedDeclaration);
if (!base)
m_errorReporter.fatalTypeError(baseName.location(), "Contract expected.");
m_errorReporter.fatalTypeError(8758_error, baseName.location(), "Contract expected.");
// "push_front" has the effect that bases mentioned later can overwrite members of bases
// mentioned earlier
input.back().push_front(base);
vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts;
if (basesBases.empty())
m_errorReporter.fatalTypeError(baseName.location(), "Definition of base has to precede definition of derived contract");
m_errorReporter.fatalTypeError(2449_error, baseName.location(), "Definition of base has to precede definition of derived contract");
input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end()));
}
input.back().push_front(&_contract);
vector<ContractDefinition const*> result = cThreeMerge(input);
if (result.empty())
m_errorReporter.fatalTypeError(_contract.location(), "Linearization of inheritance graph impossible");
m_errorReporter.fatalTypeError(5005_error, _contract.location(), "Linearization of inheritance graph impossible");
_contract.annotation().linearizedBaseContracts = result;
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
}
@@ -476,6 +480,7 @@ bool DeclarationRegistrationHelper::registerDeclaration(
}
_errorReporter.declarationError(
2333_error,
secondDeclarationLocation,
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
@@ -486,6 +491,7 @@ bool DeclarationRegistrationHelper::registerDeclaration(
{
if (dynamic_cast<MagicVariableDeclaration const*>(shadowedDeclaration))
_errorReporter.warning(
2319_error,
*_errorLocation,
"This declaration shadows a builtin symbol."
);
@@ -493,6 +499,7 @@ bool DeclarationRegistrationHelper::registerDeclaration(
{
auto shadowedLocation = shadowedDeclaration->location();
_errorReporter.warning(
2519_error,
_declaration.location(),
"This declaration shadows an existing declaration.",
SecondarySourceLocation().append("The shadowed declaration is here:", shadowedLocation)
+28 -15
View File
@@ -27,7 +27,6 @@
#include <liblangutil/ErrorReporter.h>
#include <libsolutil/Visitor.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
@@ -461,6 +460,7 @@ void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract)
{
if (contains_if(inheritedFuncs, MatchByName{modifier->name()}))
m_errorReporter.typeError(
5631_error,
modifier->location(),
"Override changes function or public state variable to modifier."
);
@@ -474,7 +474,7 @@ void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract)
continue;
if (contains_if(inheritedMods, MatchByName{function->name()}))
m_errorReporter.typeError(function->location(), "Override changes modifier to function.");
m_errorReporter.typeError(1469_error, function->location(), "Override changes modifier to function.");
checkOverrideList(OverrideProxy{function}, inheritedFuncs);
}
@@ -484,7 +484,7 @@ void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract)
continue;
if (contains_if(inheritedMods, MatchByName{stateVar->name()}))
m_errorReporter.typeError(stateVar->location(), "Override changes modifier to public state variable.");
m_errorReporter.typeError(1456_error, stateVar->location(), "Override changes modifier to public state variable.");
checkOverrideList(OverrideProxy{stateVar}, inheritedFuncs);
}
@@ -500,17 +500,19 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
if (_overriding.isModifier() && *_overriding.modifierType() != *_super.modifierType())
m_errorReporter.typeError(
1078_error,
_overriding.location(),
"Override changes modifier signature."
);
if (!_overriding.overrides())
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier.");
overrideError(_overriding, _super, 9456_error, "Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier.");
if (_super.isVariable())
overrideError(
_super,
_overriding,
1452_error,
"Cannot override public state variable.",
"Overriding " + _overriding.astNodeName() + " is here:"
);
@@ -518,6 +520,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
overrideError(
_super,
_overriding,
4334_error,
"Trying to override non-virtual " + _super.astNodeName() + ". Did you forget to add \"virtual\"?",
"Overriding " + _overriding.astNodeName() + " is here:"
);
@@ -525,7 +528,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
if (_overriding.isVariable())
{
if (_super.visibility() != Visibility::External)
overrideError(_overriding, _super, "Public state variables can only override functions with external visibility.");
overrideError(_overriding, _super, 5225_error, "Public state variables can only override functions with external visibility.");
solAssert(_overriding.visibility() == Visibility::External, "");
}
else if (_overriding.visibility() != _super.visibility())
@@ -536,7 +539,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
_super.visibility() == Visibility::External &&
_overriding.visibility() == Visibility::Public
))
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " visibility differs.");
overrideError(_overriding, _super, 9098_error, "Overriding " + _overriding.astNodeName() + " visibility differs.");
}
if (_super.isFunction())
@@ -547,7 +550,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " return types differ.");
overrideError(_overriding, _super, 4822_error, "Overriding " + _overriding.astNodeName() + " return types differ.");
// This is only relevant for a function overriding a function.
if (_overriding.isFunction())
@@ -556,6 +559,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
overrideError(
_overriding,
_super,
2837_error,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
@@ -567,6 +571,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
overrideError(
_overriding,
_super,
4593_error,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
@@ -576,6 +581,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
void OverrideChecker::overrideListError(
OverrideProxy const& _item,
set<ContractDefinition const*, CompareByID> _secondary,
ErrorId _error,
string const& _message1,
string const& _message2
)
@@ -593,6 +599,7 @@ void OverrideChecker::overrideListError(
contractSingularPlural = "contracts ";
m_errorReporter.typeError(
_error,
_item.overrides() ? _item.overrides()->location() : _item.location(),
ssl,
_message1 +
@@ -603,9 +610,10 @@ void OverrideChecker::overrideListError(
);
}
void OverrideChecker::overrideError(Declaration const& _overriding, Declaration const& _super, string const& _message, string const& _secondaryMsg)
void OverrideChecker::overrideError(Declaration const& _overriding, Declaration const& _super, ErrorId _error, string const& _message, string const& _secondaryMsg)
{
m_errorReporter.typeError(
_error,
_overriding.location(),
SecondarySourceLocation().append(_secondaryMsg, _super.location()),
_message
@@ -613,9 +621,10 @@ void OverrideChecker::overrideError(Declaration const& _overriding, Declaration
}
void OverrideChecker::overrideError(OverrideProxy const& _overriding, OverrideProxy const& _super, string const& _message, string const& _secondaryMsg)
void OverrideChecker::overrideError(OverrideProxy const& _overriding, OverrideProxy const& _super, ErrorId _error, string const& _message, string const& _secondaryMsg)
{
m_errorReporter.typeError(
_error,
_overriding.location(),
SecondarySourceLocation().append(_secondaryMsg, _super.location()),
_message
@@ -718,7 +727,7 @@ void OverrideChecker::checkAmbiguousOverridesInternal(set<OverrideProxy> _baseCa
" Since one of the bases defines a public state variable which cannot be overridden, "
"you have to change the inheritance layout or the names of the functions.";
m_errorReporter.typeError(_location, ssl, message);
m_errorReporter.typeError(6480_error, _location, ssl, message);
}
set<ContractDefinition const*, OverrideChecker::CompareByID> OverrideChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const
@@ -766,13 +775,14 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign
SecondarySourceLocation ssl;
ssl.append("First occurrence here: ", list[i-1]->location());
m_errorReporter.typeError(
4520_error,
list[i]->location(),
ssl,
"Duplicate contract \"" +
joinHumanReadable(list[i]->namePath(), ".") +
"\" found in override list of \"" +
_item.name() +
"\"."
"Duplicate contract \"" +
joinHumanReadable(list[i]->namePath(), ".") +
"\" found in override list of \"" +
_item.name() +
"\"."
);
}
}
@@ -791,6 +801,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign
if (_item.overrides() && expectedContracts.empty())
m_errorReporter.typeError(
7792_error,
_item.overrides()->location(),
_item.astNodeNameCapitalized() + " has override specified but does not override anything."
);
@@ -804,6 +815,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign
overrideListError(
_item,
missingContracts,
4327_error,
_item.astNodeNameCapitalized() + " needs to specify overridden ",
""
);
@@ -813,6 +825,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign
overrideListError(
_item,
surplusContracts,
2353_error,
"Invalid ",
"specified in override list: "
);
+4
View File
@@ -32,6 +32,7 @@
namespace solidity::langutil
{
class ErrorReporter;
struct ErrorId;
struct SourceLocation;
}
@@ -160,18 +161,21 @@ private:
void overrideListError(
OverrideProxy const& _item,
std::set<ContractDefinition const*, CompareByID> _secondary,
langutil::ErrorId _error,
std::string const& _message1,
std::string const& _message2
);
void overrideError(
Declaration const& _overriding,
Declaration const& _super,
langutil::ErrorId _error,
std::string const& _message,
std::string const& _secondaryMsg = "Overridden function is here:"
);
void overrideError(
OverrideProxy const& _overriding,
OverrideProxy const& _super,
langutil::ErrorId _error,
std::string const& _message,
std::string const& _secondaryMsg = "Overridden function is here:"
);
+6 -2
View File
@@ -122,6 +122,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
for (auto declaration: m_constVariables)
if (auto identifier = findCycle(*declaration))
m_errorReporter.typeError(
6161_error,
declaration->location(),
"The value of the constant " + declaration->name() +
" has a cyclic dependency via " + identifier->name() + "."
@@ -165,7 +166,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
auto visitor = [&](VariableDeclaration const& _variable, util::CycleDetector<VariableDeclaration>& _cycleDetector, size_t _depth)
{
if (_depth >= 256)
m_errorReporter.fatalDeclarationError(_variable.location(), "Variable definition exhausting cyclic dependency validator.");
m_errorReporter.fatalDeclarationError(7380_error, _variable.location(), "Variable definition exhausting cyclic dependency validator.");
// Iterating through the dependencies needs to be deterministic and thus cannot
// depend on the memory layout.
@@ -209,6 +210,7 @@ struct OverrideSpecifierChecker: public PostTypeChecker::Checker
TypeType const* actualTypeType = dynamic_cast<TypeType const*>(decl->type());
m_errorReporter.typeError(
9301_error,
override->location(),
"Expected contract but got " +
actualTypeType->actualType()->toString(true) +
@@ -243,6 +245,7 @@ struct ModifierContextChecker: public PostTypeChecker::Checker
if (ModifierType const* type = dynamic_cast<decltype(type)>(_identifier.annotation().type))
{
m_errorReporter.typeError(
3112_error,
_identifier.location(),
"Modifier can only be referenced in function headers."
);
@@ -280,6 +283,7 @@ struct EventOutsideEmitChecker: public PostTypeChecker::Checker
// Check for event outside of emit statement
if (!m_insideEmitStatement && functionType->kind() == FunctionType::Kind::Event)
m_errorReporter.typeError(
3132_error,
_functionCall.location(),
"Event invocations have to be prefixed by \"emit\"."
);
@@ -308,7 +312,7 @@ struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
&& !_variable.isCallableOrCatchParameter()
&& !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
m_errorReporter.typeError(8274_error, _variable.location(), "Variables cannot be declared in interfaces.");
return true;
}
+3 -3
View File
@@ -268,19 +268,19 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(_location, _description);
m_errorReporter.declarationError(8532_error, _location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _ssl, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(_location, _ssl, _description);
m_errorReporter.declarationError(3881_error, _location, _ssl, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalDeclarationError(_location, _description);
m_errorReporter.fatalDeclarationError(6546_error, _location, _description);
}
}
+12 -1
View File
@@ -120,13 +120,14 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
{
if (var.first.second->isCallableOrCatchParameter())
m_errorReporter.warning(
5667_error,
var.first.second->location(),
"Unused " +
string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") +
" parameter. Remove or comment out the variable name to silence this warning."
);
else
m_errorReporter.warning(var.first.second->location(), "Unused local variable.");
m_errorReporter.warning(2072_error, var.first.second->location(), "Unused local variable.");
}
m_localVarUseCount.clear();
m_constructor = false;
@@ -159,6 +160,7 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
set<StructDefinition const*> structsSeen;
if (structureSizeEstimate(*_variable.type(), structsSeen) >= bigint(1) << 64)
m_errorReporter.warning(
3408_error,
_variable.location(),
"Variable covers a large part of storage and thus makes collisions likely. "
"Either use mappings or dynamic arrays and allow their size to be increased only "
@@ -183,6 +185,7 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
m_errorReporter.warning(
6133_error,
_statement.location(),
"Statement has no effect."
);
@@ -196,11 +199,13 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
{
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
m_errorReporter.typeError(
1400_error,
_memberAccess.location(),
"\"msg.gas\" has been deprecated in favor of \"gasleft()\""
);
else if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash")
m_errorReporter.typeError(
8113_error,
_memberAccess.location(),
"\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
);
@@ -211,6 +216,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
ContractType const& contract = dynamic_cast<ContractType const&>(*type->typeArgument());
if (m_constructorUsesAssembly->check(contract.contractDefinition()))
m_errorReporter.warning(
6417_error,
_memberAccess.location(),
"The constructor of the contract (or its base) uses inline assembly. "
"Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode."
@@ -222,6 +228,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
if (type->kind() == FunctionType::Kind::BareCallCode)
m_errorReporter.typeError(
2256_error,
_memberAccess.location(),
"\"callcode\" has been deprecated in favour of \"delegatecall\"."
);
@@ -235,6 +242,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
{
if (id->name() == "this")
m_errorReporter.warning(
5805_error,
id->location(),
"\"this\" used in constructor. "
"Note that external functions of a contract "
@@ -285,6 +293,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation)
))
if (rhs->isZero())
m_errorReporter.typeError(
1211_error,
_operation.location(),
(_operation.getOperator() == Token::Div) ? "Division by zero." : "Modulo zero."
);
@@ -307,6 +316,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
))
if (lastArg->isZero())
m_errorReporter.typeError(
4195_error,
_functionCall.location(),
"Arithmetic modulo zero."
);
@@ -317,6 +327,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
functionType->declaration().scope() == m_currentContract
)
m_errorReporter.typeError(
6700_error,
_functionCall.location(),
SecondarySourceLocation().append(
"The function declaration is here:",
+39 -27
View File
@@ -27,7 +27,6 @@
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h>
#include <boost/algorithm/cxx11/all_of.hpp>
#include <boost/algorithm/string.hpp>
#include <memory>
@@ -69,7 +68,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
string(";\"");
// when reporting the warning, print the source name only
m_errorReporter.warning({-1, -1, _sourceUnit.location().source}, errorString);
m_errorReporter.warning(3420_error, {-1, -1, _sourceUnit.location().source}, errorString);
}
m_sourceUnit = nullptr;
}
@@ -79,18 +78,20 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
solAssert(!_pragma.tokens().empty(), "");
solAssert(_pragma.tokens().size() == _pragma.literals().size(), "");
if (_pragma.tokens()[0] != Token::Identifier)
m_errorReporter.syntaxError(_pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\"");
m_errorReporter.syntaxError(5226_error, _pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\"");
else if (_pragma.literals()[0] == "experimental")
{
solAssert(m_sourceUnit, "");
vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
if (literals.empty())
m_errorReporter.syntaxError(
9679_error,
_pragma.location(),
"Experimental feature name is missing."
);
else if (literals.size() > 1)
m_errorReporter.syntaxError(
6022_error,
_pragma.location(),
"Stray arguments."
);
@@ -98,17 +99,17 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
{
string const literal = literals[0];
if (literal.empty())
m_errorReporter.syntaxError(_pragma.location(), "Empty experimental feature name is invalid.");
m_errorReporter.syntaxError(3250_error, _pragma.location(), "Empty experimental feature name is invalid.");
else if (!ExperimentalFeatureNames.count(literal))
m_errorReporter.syntaxError(_pragma.location(), "Unsupported experimental feature name.");
m_errorReporter.syntaxError(8491_error, _pragma.location(), "Unsupported experimental feature name.");
else if (m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeatureNames.at(literal)))
m_errorReporter.syntaxError(_pragma.location(), "Duplicate experimental feature name.");
m_errorReporter.syntaxError(1231_error, _pragma.location(), "Duplicate experimental feature name.");
else
{
auto feature = ExperimentalFeatureNames.at(literal);
m_sourceUnit->annotation().experimentalFeatures.insert(feature);
if (!ExperimentalFeatureWithoutWarning.count(feature))
m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
m_errorReporter.warning(2264_error, _pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
}
}
}
@@ -121,15 +122,16 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
static SemVerVersion const currentVersion{string(VersionString)};
if (!matchExpression.matches(currentVersion))
m_errorReporter.syntaxError(
3997_error,
_pragma.location(),
"Source file requires different compiler version (current compiler is " +
string(VersionString) + " - note that nightly builds are considered to be "
string(VersionString) + ") - note that nightly builds are considered to be "
"strictly less than the released version"
);
m_versionPragmaFound = true;
}
else
m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
return true;
}
@@ -142,7 +144,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
{
if (_modifier.isImplemented() && !m_placeholderFound)
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_errorReporter.syntaxError(2883_error, _modifier.body().location(), "Modifier body does not contain '_'.");
m_placeholderFound = false;
}
@@ -150,7 +152,7 @@ void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _stat
{
auto varDecl = dynamic_cast<VariableDeclarationStatement const*>(&_statement);
if (varDecl)
m_errorReporter.syntaxError(_statement.location(), "Variable declarations can only be used inside blocks.");
m_errorReporter.syntaxError(9079_error, _statement.location(), "Variable declarations can only be used inside blocks.");
}
bool SyntaxChecker::visit(IfStatement const& _ifStatement)
@@ -189,7 +191,7 @@ bool SyntaxChecker::visit(Continue const& _continueStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
m_errorReporter.syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
m_errorReporter.syntaxError(4123_error, _continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
return true;
}
@@ -197,13 +199,14 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
m_errorReporter.syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
m_errorReporter.syntaxError(6102_error, _breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
return true;
}
bool SyntaxChecker::visit(Throw const& _throwStatement)
{
m_errorReporter.syntaxError(
4538_error,
_throwStatement.location(),
"\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
);
@@ -222,29 +225,29 @@ bool SyntaxChecker::visit(Literal const& _literal)
// Generic checks no matter what base this number literal is of:
if (value.back() == '_')
{
m_errorReporter.syntaxError(_literal.location(), "Invalid use of underscores in number literal. No trailing underscores allowed.");
m_errorReporter.syntaxError(2090_error, _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.");
m_errorReporter.syntaxError(2990_error, _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.");
m_errorReporter.syntaxError(3891_error, _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.");
m_errorReporter.syntaxError(1023_error, _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.");
m_errorReporter.syntaxError(6415_error, _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.");
m_errorReporter.syntaxError(6165_error, _literal.location(), "Invalid use of underscores in number literal. No underscore in front of exponent allowed.");
}
return true;
@@ -253,7 +256,7 @@ bool SyntaxChecker::visit(Literal const& _literal)
bool SyntaxChecker::visit(UnaryOperation const& _operation)
{
if (_operation.getOperator() == Token::Add)
m_errorReporter.syntaxError(_operation.location(), "Use of unary + is disallowed.");
m_errorReporter.syntaxError(9636_error, _operation.location(), "Use of unary + is disallowed.");
return true;
}
@@ -265,6 +268,7 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations()))
m_errorReporter.syntaxError(
6553_error,
_inlineAssembly.location(),
"The msize instruction cannot be used when the Yul optimizer is activated because "
"it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
@@ -285,7 +289,9 @@ bool SyntaxChecker::visit(ContractDefinition const& _contract)
ASTString const& contractName = _contract.name();
for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->name() == contractName)
m_errorReporter.syntaxError(function->location(),
m_errorReporter.syntaxError(
5796_error,
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."
);
@@ -298,15 +304,16 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError(
4937_error,
_function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"
);
}
if (m_isInterface && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions in interfaces cannot have modifiers.");
m_errorReporter.syntaxError(5842_error, _function.location(), "Functions in interfaces cannot have modifiers.");
else if (!_function.isImplemented() && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
m_errorReporter.syntaxError(2668_error, _function.location(), "Functions without implementation cannot have modifiers.");
return true;
}
@@ -315,11 +322,11 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node)
{
for (auto const& decl: _node.parameterTypeList()->parameters())
if (!decl->name().empty())
m_errorReporter.warning(decl->location(), "Naming function type parameters is deprecated.");
m_errorReporter.warning(6162_error, decl->location(), "Naming function type parameters is deprecated.");
for (auto const& decl: _node.returnParameterTypeList()->parameters())
if (!decl->name().empty())
m_errorReporter.syntaxError(decl->location(), "Return parameters in function types may not be named.");
m_errorReporter.syntaxError(7304_error, decl->location(), "Return parameters in function types may not be named.");
return true;
}
@@ -327,8 +334,13 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node)
bool SyntaxChecker::visit(VariableDeclarationStatement const& _statement)
{
// 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))
if (std::all_of(
_statement.declarations().begin(),
_statement.declarations().end(),
[](ASTPointer<VariableDeclaration> const& declaration) { return declaration == nullptr; }
))
m_errorReporter.syntaxError(
3299_error,
_statement.location(),
"The use of the \"var\" keyword is disallowed. The declaration part of the statement can be removed, since it is empty."
);
@@ -339,7 +351,7 @@ bool SyntaxChecker::visit(VariableDeclarationStatement const& _statement)
bool SyntaxChecker::visit(StructDefinition const& _struct)
{
if (_struct.members().empty())
m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed.");
m_errorReporter.syntaxError(5306_error, _struct.location(), "Defining empty structs is disallowed.");
return true;
}
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -171,6 +171,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
!_funDef.overrides()
)
m_errorReporter.warning(
2018_error,
_funDef.location(),
"Function state mutability can be restricted to " + stateMutabilityToString(m_bestMutabilityAndLocation.mutability)
);
@@ -229,7 +230,7 @@ void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
{
AssemblyViewPureChecker{
_inlineAssembly.dialect(),
[=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
[&](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
}(_inlineAssembly.operations());
}
@@ -252,6 +253,7 @@ void ViewPureChecker::reportMutability(
))
{
m_errorReporter.typeError(
2527_error,
_location,
"Function declared as pure, but this expression (potentially) reads from the "
"environment or state and thus requires \"view\"."
@@ -261,6 +263,7 @@ void ViewPureChecker::reportMutability(
else if (_mutability == StateMutability::NonPayable)
{
m_errorReporter.typeError(
8961_error,
_location,
"Function declared as " +
stateMutabilityToString(m_currentFunction->stateMutability()) +
@@ -277,12 +280,14 @@ void ViewPureChecker::reportMutability(
{
if (_nestedLocation)
m_errorReporter.typeError(
4006_error,
_location,
SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
"This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
);
else
m_errorReporter.typeError(
5887_error,
_location,
"\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function "
"\"payable\" or use an internal function to avoid this error."
@@ -357,6 +362,8 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::MetaType, "runtimeCode"},
{MagicType::Kind::MetaType, "name"},
{MagicType::Kind::MetaType, "interfaceId"},
{MagicType::Kind::MetaType, "min"},
{MagicType::Kind::MetaType, "max"},
};
set<MagicMember> static const payableMembers{
{MagicType::Kind::Message, "value"}
+7 -1
View File
@@ -556,7 +556,13 @@ MagicType const* TypeProvider::magic(MagicType::Kind _kind)
MagicType const* TypeProvider::meta(Type const* _type)
{
solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now.");
solAssert(
_type && (
_type->category() == Type::Category::Contract ||
_type->category() == Type::Category::Integer
),
"Only contracts or integer types supported for now."
);
return createAndGet<MagicType>(_type);
}
+42 -11
View File
@@ -556,6 +556,22 @@ string IntegerType::toString(bool) const
return prefix + util::toString(m_bits);
}
u256 IntegerType::min() const
{
if (isSigned())
return s2u(s256(minValue()));
else
return u256(minValue());
}
u256 IntegerType::max() const
{
if (isSigned())
return s2u(s256(maxValue()));
else
return u256(maxValue());
}
bigint IntegerType::minValue() const
{
if (isSigned())
@@ -3763,20 +3779,35 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
case Kind::MetaType:
{
solAssert(
m_typeArgument && m_typeArgument->category() == Type::Category::Contract,
"Only contracts supported for now"
m_typeArgument && (
m_typeArgument->category() == Type::Category::Contract ||
m_typeArgument->category() == Type::Category::Integer
),
"Only contracts or integer types supported for now"
);
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition();
if (contract.canBeDeployed())
if (m_typeArgument->category() == Type::Category::Contract)
{
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition();
if (contract.canBeDeployed())
return MemberList::MemberMap({
{"creationCode", TypeProvider::array(DataLocation::Memory)},
{"runtimeCode", TypeProvider::array(DataLocation::Memory)},
{"name", TypeProvider::stringMemory()},
});
else
return MemberList::MemberMap({
{"interfaceId", TypeProvider::fixedBytes(4)},
});
}
else if (m_typeArgument->category() == Type::Category::Integer)
{
IntegerType const* integerTypePointer = dynamic_cast<IntegerType const*>(m_typeArgument);
return MemberList::MemberMap({
{"creationCode", TypeProvider::array(DataLocation::Memory)},
{"runtimeCode", TypeProvider::array(DataLocation::Memory)},
{"name", TypeProvider::stringMemory()},
});
else
return MemberList::MemberMap({
{"interfaceId", TypeProvider::fixedBytes(4)},
{"min", integerTypePointer},
{"max", integerTypePointer},
});
}
}
}
solAssert(false, "Unknown kind of magic.");
+3
View File
@@ -452,6 +452,9 @@ public:
unsigned numBits() const { return m_bits; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
u256 min() const;
u256 max() const;
bigint minValue() const;
bigint maxValue() const;
+16 -5
View File
@@ -28,7 +28,6 @@
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace solidity;
@@ -38,7 +37,8 @@ using namespace solidity::frontend;
string ABIFunctions::tupleEncoder(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes
bool _encodeAsLibraryTypes,
bool _reversed
)
{
EncodingOptions options;
@@ -54,6 +54,8 @@ string ABIFunctions::tupleEncoder(
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
if (_reversed)
functionName += "_reversed";
return createFunction(functionName, [&]() {
// Note that the values are in reverse due to the difference in calling semantics.
@@ -94,7 +96,10 @@ string ABIFunctions::tupleEncoder(
stackPos += sizeOnStack;
}
solAssert(headPos == headSize_, "");
string valueParams = suffixedVariableNameList("value", stackPos, 0);
string valueParams =
_reversed ?
suffixedVariableNameList("value", stackPos, 0) :
suffixedVariableNameList("value", 0, stackPos);
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
templ("encodeElements", encodeElements);
@@ -104,7 +109,8 @@ string ABIFunctions::tupleEncoder(
string ABIFunctions::tupleEncoderPacked(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes
TypePointers const& _targetTypes,
bool _reversed
)
{
EncodingOptions options;
@@ -120,6 +126,8 @@ string ABIFunctions::tupleEncoderPacked(
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
if (_reversed)
functionName += "_reversed";
return createFunction(functionName, [&]() {
solAssert(!_givenTypes.empty(), "");
@@ -158,7 +166,10 @@ string ABIFunctions::tupleEncoderPacked(
encodeElements += elementTempl.render();
stackPos += sizeOnStack;
}
string valueParams = suffixedVariableNameList("value", stackPos, 0);
string valueParams =
_reversed ?
suffixedVariableNameList("value", stackPos, 0) :
suffixedVariableNameList("value", 0, stackPos);
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
templ("encodeElements", encodeElements);
+28 -6
View File
@@ -67,31 +67,53 @@ public:
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
/// into memory, converting the types to @a _targetTypes on the fly.
/// Parameters are: <headStart> <value_n> ... <value_1>, i.e.
/// the layout on the stack is <value_1> ... <value_n> <headStart> with
/// Parameters are: <headStart> <value_1> ... <value_n>, i.e.
/// the layout on the stack is <value_n> ... <value_1> <headStart> with
/// the top of the stack on the right.
/// The values represent stack slots. If a type occupies more or less than one
/// stack slot, it takes exactly that number of values.
/// Returns a pointer to the end of the area written in memory.
/// Does not allocate memory (does not change the free memory pointer), but writes
/// to memory starting at $headStart and an unrestricted amount after that.
/// If @reversed is true, the order of the variables after <headStart> is reversed.
std::string tupleEncoder(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes = false
bool _encodeAsLibraryTypes = false,
bool _reversed = false
);
/// Specialization of tupleEncoder to _reversed = true
std::string tupleEncoderReversed(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes = false
) {
return tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes, true);
}
/// @returns name of an assembly function to encode values of @a _givenTypes
/// with packed encoding into memory, converting the types to @a _targetTypes on the fly.
/// Parameters are: <memPos> <value_n> ... <value_1>, i.e.
/// the layout on the stack is <value_1> ... <value_n> <memPos> with
/// Parameters are: <memPos> <value_1> ... <value_n>, i.e.
/// the layout on the stack is <value_n> ... <value_1> <memPos> with
/// the top of the stack on the right.
/// The values represent stack slots. If a type occupies more or less than one
/// stack slot, it takes exactly that number of values.
/// Returns a pointer to the end of the area written in memory.
/// Does not allocate memory (does not change the free memory pointer), but writes
/// to memory starting at memPos and an unrestricted amount after that.
std::string tupleEncoderPacked(TypePointers const& _givenTypes, TypePointers const& _targetTypes);
/// If @reversed is true, the order of the variables after <headStart> is reversed.
std::string tupleEncoderPacked(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _reversed = false
);
/// Specialization of tupleEncoderPacked to _reversed = true
std::string tupleEncoderPackedReversed(TypePointers const& _givenTypes, TypePointers const& _targetTypes)
{
return tupleEncoderPacked(_givenTypes, _targetTypes, true);
}
/// @returns name of an assembly function to ABI-decode values of @a _types
/// into memory. If @a _fromMemory is true, decodes from memory instead of
+3
View File
@@ -49,6 +49,9 @@ void Compiler::compileContract(
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimiserSettings);
solAssert(m_context.requestedYulFunctionsRan(), "requestedYulFunctions() was not called.");
solAssert(m_runtimeContext.requestedYulFunctionsRan(), "requestedYulFunctions() was not called.");
}
std::shared_ptr<evmasm::Assembly> Compiler::runtimeAssemblyPtr() const
+3
View File
@@ -192,6 +192,9 @@ void CompilerContext::appendMissingLowLevelFunctions()
pair<string, set<string>> CompilerContext::requestedYulFunctions()
{
solAssert(!m_requestedYulFunctionsRan, "requestedYulFunctions called more than once.");
m_requestedYulFunctionsRan = true;
set<string> empty;
swap(empty, m_externallyUsedYulFunctions);
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
+3
View File
@@ -167,6 +167,7 @@ public:
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
bool requestedYulFunctionsRan() const { return m_requestedYulFunctionsRan; }
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
@@ -389,6 +390,8 @@ private:
YulUtilFunctions m_yulUtilFunctions;
/// The queue of low-level functions to generate.
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
/// Flag to check that requestedYulFunctions() was called exactly once
bool m_requestedYulFunctionsRan = false;
};
}
+2 -2
View File
@@ -559,8 +559,8 @@ void CompilerUtils::abiEncodeV2(
string encoderName =
_padToWordBoundaries ?
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
m_context.abiFunctions().tupleEncoderReversed(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
m_context.abiFunctions().tupleEncoderPackedReversed(_givenTypes, _targetTypes);
m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
}
+11 -7
View File
@@ -103,8 +103,6 @@ void ContractCompiler::compileContract(
// 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();
}
size_t ContractCompiler::compileConstructor(
@@ -159,10 +157,13 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
if (FunctionDefinition const* constructor = _contract.constructor())
appendConstructor(*constructor);
else if (auto c = _contract.nextConstructor(m_context.mostDerivedContract()))
appendBaseConstructor(*c);
else
{
// Implicit constructors are always non-payable.
appendCallValueCheck();
if (auto c = _contract.nextConstructor(m_context.mostDerivedContract()))
appendBaseConstructor(*c);
}
}
size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract)
@@ -215,6 +216,9 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
solAssert(!!m_runtimeCompiler, "");
solAssert(_contract.isLibrary(), "Tried to deploy contract as library.");
appendMissingFunctions();
m_runtimeCompiler->appendMissingFunctions();
CompilerContext::LocationSetter locationSetter(m_context, _contract);
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
@@ -586,13 +590,13 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (!_function.isConstructor())
// adding 1 for return address.
m_context.adjustStackOffset(parametersSize + 1);
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters())
for (ASTPointer<VariableDeclaration> const& variable: _function.parameters())
{
m_context.addVariable(*variable, parametersSize);
parametersSize -= variable->annotation().type->sizeOnStack();
}
for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters())
for (ASTPointer<VariableDeclaration> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable);
if (_function.isConstructor())
@@ -649,7 +653,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (stackLayout[i] != i)
solAssert(false, "Invalid stack layout on cleanup.");
for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters())
for (ASTPointer<VariableDeclaration> const& variable: _function.parameters() + _function.returnParameters())
m_context.removeVariable(*variable);
m_context.adjustStackOffset(-(int)c_returnValuesSize);
+10 -1
View File
@@ -36,7 +36,6 @@
#include <libsolutil/Whiskers.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <numeric>
#include <utility>
@@ -1591,6 +1590,16 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
result ^= fromBigEndian<uint64_t>(function.first.ref());
m_context << (u256{result} << (256 - 32));
}
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);
IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument());
if (member == "min")
m_context << integerType->min();
else
m_context << integerType->max();
}
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
{
// no-op
@@ -23,8 +23,6 @@
#include <liblangutil/Exceptions.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace solidity;
+142 -34
View File
@@ -28,9 +28,6 @@
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@@ -299,9 +296,6 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
string YulUtilFunctions::shiftRightFunctionDynamic()
{
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string const functionName = "shift_right_unsigned_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return
@@ -321,6 +315,86 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
});
}
string YulUtilFunctions::shiftRightSignedFunctionDynamic()
{
string const functionName = "shift_right_signed_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(bits, value) -> result {
<?hasShifts>
result := sar(bits, value)
<!hasShifts>
let divisor := exp(2, bits)
let xor_mask := sub(0, slt(value, 0))
result := xor(div(xor(value, xor_mask), divisor), xor_mask)
// combined version of
// switch slt(value, 0)
// case 0 { result := div(value, divisor) }
// default { result := not(div(not(value), divisor)) }
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _amountType)
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
<?amountSigned>
if slt(bits, 0) { invalid() }
</amountSigned>
result := <cleanup>(<shift>(bits, value))
}
)")
("functionName", functionName)
("amountSigned", dynamic_cast<IntegerType const&>(_amountType).isSigned())
("cleanAmount", cleanupFunction(_amountType))
("shift", shiftLeftFunctionDynamic())
("cleanup", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const& _amountType)
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
IntegerType const* integerType = dynamic_cast<IntegerType const*>(&_type);
bool valueSigned = integerType && integerType->isSigned();
string const functionName = "shift_right_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
<?amountSigned>
if slt(bits, 0) { invalid() }
</amountSigned>
result := <cleanup>(<shift>(bits, <cleanup>(value)))
}
)")
("functionName", functionName)
("amountSigned", dynamic_cast<IntegerType const&>(_amountType).isSigned())
("cleanAmount", cleanupFunction(_amountType))
("shift", valueSigned ? shiftRightSignedFunctionDynamic() : shiftRightFunctionDynamic())
("cleanup", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{
solAssert(_numBytes <= 32, "");
@@ -393,6 +467,8 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// overflow, if x >= 0 and y > (maxValue - x)
if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { revert(0, 0) }
@@ -409,6 +485,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -421,6 +498,8 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
// Multiplication by zero could be treated separately and directly return zero.
Whiskers(R"(
function <functionName>(x, y) -> product {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { revert(0, 0) }
@@ -441,6 +520,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -452,6 +532,8 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { revert(0, 0) }
<?signed>
// overflow for minVal / -1
@@ -466,6 +548,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
("functionName", functionName)
("signed", _type.isSigned())
("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -477,12 +560,15 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
return
Whiskers(R"(
function <functionName>(x, y) -> r {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
if iszero(y) { revert(0, 0) }
r := <?signed>s</signed>mod(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -494,6 +580,8 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
x := <cleanupFunction>(x)
y := <cleanupFunction>(y)
<?signed>
// underflow, if y >= 0 and x < (minValue + y)
if and(iszero(slt(y, 0)), slt(x, add(<minValue>, y))) { revert(0, 0) }
@@ -509,6 +597,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -1033,13 +1122,12 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
return m_functionCollector.createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function <functionName>(slot <comma> <key>) -> dataSlot {
dataSlot := <hash>(slot <comma> <key>)
function <functionName>(slot <?+key>,</+key> <key>) -> dataSlot {
dataSlot := <hash>(<key> <?+key>,</+key> slot)
}
)")
("functionName", functionName)
("key", _keyType.sizeOnStack() > 0 ? "key" : "")
("comma", _keyType.sizeOnStack() > 0 ? "," : "")
("hash", packedHashFunction(
{&_keyType, TypeProvider::uint256()},
{_mappingType.keyType(), TypeProvider::uint256()}
@@ -1764,8 +1852,18 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Function:
solAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External, "");
templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)");
switch (dynamic_cast<FunctionType const&>(_type).kind())
{
case FunctionType::Kind::External:
templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)");
break;
case FunctionType::Kind::Internal:
templ("body", "cleaned := value");
break;
default:
solAssert(false, "");
break;
}
break;
case Type::Category::Array:
case Type::Category::Struct:
@@ -1938,14 +2036,16 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if <lt>(value, <minval>) { revert(0,0) }
ret := sub(value, 1)
}
)")
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
("lt", type.isSigned() ? "slt" : "lt")
.render();
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
("lt", type.isSigned() ? "slt" : "lt")
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -1966,14 +2066,16 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if <gt>(value, <maxval>) { revert(0,0) }
ret := add(value, 1)
}
)")
("functionName", functionName)
("maxval", toCompactHexWithPrefix(maxintval))
("gt", type.isSigned() ? "sgt" : "gt")
.render();
("functionName", functionName)
("maxval", toCompactHexWithPrefix(maxintval))
("gt", type.isSigned() ? "sgt" : "gt")
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
@@ -1988,15 +2090,17 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(_value) -> ret {
if slt(_value, <minval>) { revert(0,0) }
ret := sub(0, _value)
function <functionName>(value) -> ret {
value := <cleanupFunction>(value)
if slt(value, <minval>) { revert(0,0) }
ret := sub(0, value)
}
)")
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
.render();
});
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
("cleanupFunction", cleanupFunction(_type))
.render();
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
@@ -2214,7 +2318,6 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
}
solAssert(_type.isValueType(), "");
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
if (funType->kind() == FunctionType::Kind::External)
return Whiskers(R"(
@@ -2228,18 +2331,23 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
("splitFunction", splitExternalFunctionIdFunction())
.render();
return Whiskers(R"(
function <functionName>(memPtr) -> value {
value := <load>(memPtr)
<?needsValidation>
function <functionName>(ptr) -> value {
<?fromCalldata>
value := calldataload(ptr)
<validate>(value)
</needsValidation>
<!fromCalldata>
value := <cleanup>(mload(ptr))
</fromCalldata>
}
)")
("functionName", functionName)
("load", _fromCalldata ? "calldataload" : "mload")
("needsValidation", _fromCalldata)
("validate", _fromCalldata ? validatorFunction(_type) : "")
("fromCalldata", _fromCalldata)
("validate", validatorFunction(_type))
// Byte array elements generally need cleanup.
// Other types are cleaned as well to account for dirty memory e.g. due to inline assembly.
("cleanup", cleanupFunction(_type))
.render();
});
}
+9 -1
View File
@@ -81,6 +81,14 @@ public:
std::string shiftLeftFunctionDynamic();
std::string shiftRightFunction(size_t _numBits);
std::string shiftRightFunctionDynamic();
std::string shiftRightSignedFunctionDynamic();
/// @returns the name of a function that performs a left shift and subsequent cleanup
/// and, if needed, prior cleanup.
/// If the amount to shift by is signed, a check for negativeness is performed.
/// signature: (value, amountToShift) -> result
std::string typedShiftLeftFunction(Type const& _type, Type const& _amountType);
std::string typedShiftRightFunction(Type const& _type, Type const& _amountType);
/// @returns the name of a function which replaces the
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
@@ -204,7 +212,7 @@ public:
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes);
/// @returns a function that reads a value type from memory.
/// @returns a function that reads a value type from memory. Performs cleanup.
/// signature: (addr) -> value
std::string readFromMemory(Type const& _type);
/// @returns a function that reads a value type from calldata.
+56 -48
View File
@@ -38,9 +38,6 @@
#include <liblangutil/SourceReferenceFormatter.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <sstream>
using namespace std;
@@ -172,24 +169,24 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
string functionName = m_context.functionName(_function);
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> {
<initReturnVariables>
<body>
}
)");
t("functionName", functionName);
string params;
vector<string> params;
for (auto const& varDecl: _function.parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
params += m_context.addLocalVariable(*varDecl).stackSlots();
t("params", joinHumanReadable(params));
vector<string> retParams;
string retInit;
for (auto const& varDecl: _function.returnParameters())
{
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
retParams += m_context.addLocalVariable(*varDecl).stackSlots();
retInit += generateInitialAssignment(*varDecl);
}
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("retParams", joinHumanReadable(retParams));
t("initReturnVariables", retInit);
t("body", generate(_function.body()));
return t.render();
@@ -202,11 +199,11 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
Type const* type = _varDecl.annotation().type;
solAssert(!_varDecl.isConstant(), "");
solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
return m_context.functionCollector().createFunction(functionName, [&]() {
solAssert(!_varDecl.isConstant() && !_varDecl.immutable(), "");
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
solAssert(slot_offset.second == 0, "");
FunctionType funType(_varDecl);
@@ -268,6 +265,16 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
("id", to_string(_varDecl.id()))
.render();
}
else if (_varDecl.isConstant())
return Whiskers(R"(
function <functionName>() -> <ret> {
<ret> := <constantValueFunction>()
}
)")
("functionName", functionName)
("constantValueFunction", IRGeneratorForStatements(m_context, m_utils).constantValueFunction(_varDecl))
("ret", suffixedVariableNameList("ret_", 0, _varDecl.type()->sizeOnStack()))
.render();
else
{
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
@@ -293,31 +300,35 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec
return generator.code();
}
pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstructorArguments(
pair<string, map<ContractDefinition const*, vector<string>>> IRGenerator::evaluateConstructorArguments(
ContractDefinition const& _contract
)
{
map<ContractDefinition const*, string> constructorParams;
map<ContractDefinition const*, vector<string>> constructorParams;
vector<pair<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *>> baseConstructorArguments;
for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
base->name().annotation().referencedDeclaration
)->constructor(); baseConstructor && base->arguments())
base->name().annotation().referencedDeclaration
)->constructor(); baseConstructor && base->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
base->arguments()
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
base->arguments()
);
if (FunctionDefinition const* constructor = _contract.constructor())
for (auto const& modifier: constructor->modifiers())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
for (ASTPointer<ModifierInvocation> const& modifier: constructor->modifiers())
if (auto const* baseContract = dynamic_cast<ContractDefinition const*>(
modifier->name()->annotation().referencedDeclaration
)->constructor(); baseConstructor && modifier->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
modifier->arguments()
);
))
if (
FunctionDefinition const* baseConstructor = baseContract->constructor();
baseConstructor && modifier->arguments()
)
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
modifier->arguments()
);
IRGeneratorForStatements generator{m_context, m_utils};
for (auto&& [baseContract, arguments]: baseConstructorArguments)
@@ -327,11 +338,11 @@ pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstr
{
vector<string> params;
for (size_t i = 0; i < arguments->size(); ++i)
params.emplace_back(generator.evaluateExpression(
*(arguments->at(i)),
*(baseContract->constructor()->parameters()[i]->type())
).commaSeparatedList());
constructorParams[baseContract] = joinHumanReadable(params);
params += generator.evaluateExpression(
*(arguments->at(i)),
*(baseContract->constructor()->parameters()[i]->type())
).stackSlots();
constructorParams[baseContract] = std::move(params);
}
}
@@ -352,16 +363,16 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract)
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
{
auto listAllParams = [&](
map<ContractDefinition const*, string> const& baseParams) -> string
map<ContractDefinition const*, vector<string>> const& baseParams) -> vector<string>
{
vector<string> params;
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
if (baseParams.count(contract))
params.emplace_back(baseParams.at(contract));
return joinHumanReadable(params);
params += baseParams.at(contract);
return params;
};
map<ContractDefinition const*, string> baseConstructorParams;
map<ContractDefinition const*, vector<string>> baseConstructorParams;
for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
{
ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
@@ -376,16 +387,16 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra
<userDefinedConstructorBody>
}
)");
string params;
vector<string> params;
if (contract->constructor())
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string baseParamsString = listAllParams(baseConstructorParams);
t("baseParams", baseParamsString);
t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
params += m_context.addLocalVariable(*varDecl).stackSlots();
t("params", joinHumanReadable(params));
vector<string> baseParams = listAllParams(baseConstructorParams);
t("baseParams", joinHumanReadable(baseParams));
t("comma", !params.empty() && !baseParams.empty() ? ", " : "");
t("functionName", implicitConstructorName(*contract));
pair<string, map<ContractDefinition const*, string>> evaluatedArgs = evaluateConstructorArguments(*contract);
pair<string, map<ContractDefinition const*, vector<string>>> evaluatedArgs = evaluateConstructorArguments(*contract);
baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
t("evalBaseArguments", evaluatedArgs.first);
if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
@@ -393,7 +404,7 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra
t("hasNextConstructor", true);
ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
t("nextConstructor", implicitConstructorName(*nextContract));
t("nextParams", listAllParams(baseConstructorParams));
t("nextParams", joinHumanReadable(listAllParams(baseConstructorParams)));
}
else
t("hasNextConstructor", false);
@@ -468,10 +479,10 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
{
// <functionName>
<callValueCheck>
<assignToParams> <abiDecode>(4, calldatasize())
<assignToRetParams> <function>(<params>)
<?+params>let <params> := </+params> <abiDecode>(4, calldatasize())
<?+retParams>let <retParams> := </+retParams> <function>(<params>)
let memPos := <allocate>(0)
let memEnd := <abiEncode>(memPos <comma> <retParams>)
let memEnd := <abiEncode>(memPos <?+retParams>,</+retParams> <retParams>)
return(memPos, sub(memEnd, memPos))
}
</cases>
@@ -493,13 +504,11 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
unsigned paramVars = make_shared<TupleType>(type->parameterTypes())->sizeOnStack();
unsigned retVars = make_shared<TupleType>(type->returnParameterTypes())->sizeOnStack();
templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ";
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := ";
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
templ["params"] = suffixedVariableNameList("param_", 0, paramVars);
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
templ["retParams"] = suffixedVariableNameList("ret_", 0, retVars);
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
templ["function"] = m_context.enqueueFunctionForCodeGeneration(*funDef);
@@ -510,7 +519,6 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
templ["allocate"] = m_utils.allocationFunction();
templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false);
templ["comma"] = retVars == 0 ? "" : ", ";
}
t("cases", functions);
if (FunctionDefinition const* fallback = _contract.fallbackFunction())
+2 -3
View File
@@ -81,9 +81,8 @@ private:
/// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of
/// @a _contract
/// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params
std::pair<std::string, std::map<ContractDefinition const*, std::string>> evaluateConstructorArguments(
ContractDefinition const& _contract
);
std::pair<std::string, std::map<ContractDefinition const*, std::vector<std::string>>>
evaluateConstructorArguments(ContractDefinition const& _contract);
/// Initializes state variables of
/// @a _contract
@@ -42,7 +42,6 @@
#include <libsolutil/Keccak256.h>
#include <libsolutil/Visitor.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/transformed.hpp>
using namespace std;
@@ -117,10 +116,12 @@ struct CopyTranslate: public yul::ASTCopier
reference.isOffset == false && reference.isSlot == false,
"Should not be called for offset/slot"
);
auto const& var = m_context.localVariable(*varDecl);
solAssert(var.type().sizeOnStack() == 1, "");
return yul::Identifier{
_identifier.location,
yul::YulString{m_context.localVariable(*varDecl).name()}
yul::YulString{var.commaSeparatedList()}
};
}
@@ -178,6 +179,28 @@ IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expre
return variable;
}
string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant)
{
string functionName = "constant_" + _constant.name() + "_" + to_string(_constant.id());
return m_context.functionCollector().createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>() -> <ret> {
<code>
<ret> := <value>
}
)");
templ("functionName", functionName);
IRGeneratorForStatements generator(m_context, m_utils);
solAssert(_constant.value(), "");
Type const& constantType = *_constant.type();
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
templ("code", generator.code());
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
return templ.render();
});
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@@ -230,29 +253,53 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional)
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
_assignment.rightHandSide().accept(*this);
Type const* intermediateType = type(_assignment.rightHandSide()).closestTemporaryType(
&type(_assignment.leftHandSide())
);
IRVariable value = convert(_assignment.rightHandSide(), *intermediateType);
Token assignmentOperator = _assignment.assignmentOperator();
Token binaryOperator =
assignmentOperator == Token::Assign ?
assignmentOperator :
TokenTraits::AssignmentToBinaryOp(assignmentOperator);
Type const* rightIntermediateType =
TokenTraits::isShiftOp(binaryOperator) ?
type(_assignment.rightHandSide()).mobileType() :
type(_assignment.rightHandSide()).closestTemporaryType(
&type(_assignment.leftHandSide())
);
solAssert(rightIntermediateType, "");
IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
_assignment.leftHandSide().accept(*this);
solAssert(!!m_currentLValue, "LValue not retrieved.");
if (_assignment.assignmentOperator() != Token::Assign)
if (assignmentOperator != Token::Assign)
{
solAssert(type(_assignment.leftHandSide()) == *intermediateType, "");
solAssert(intermediateType->isValueType(), "Compound operators only available for value types.");
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
m_code << value.name() << " := " << binaryOperation(
TokenTraits::AssignmentToBinaryOp(_assignment.assignmentOperator()),
*intermediateType,
leftIntermediate.name(),
value.name()
);
if (TokenTraits::isShiftOp(binaryOperator))
{
solAssert(type(_assignment) == leftIntermediate.type(), "");
solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
define(_assignment) << shiftOperation(binaryOperator, leftIntermediate, value);
writeToLValue(*m_currentLValue, IRVariable(_assignment));
m_currentLValue.reset();
return false;
}
else
{
solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, "");
m_code << value.name() << " := " << binaryOperation(
binaryOperator,
*rightIntermediateType,
leftIntermediate.name(),
value.name()
);
}
}
writeToLValue(*m_currentLValue, value);
m_currentLValue.reset();
if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
define(_assignment, value);
@@ -477,12 +524,16 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
return false;
}
if (commonType->category() == Type::Category::RationalNumber)
{
define(_binOp) << toCompactHexWithPrefix(commonType->literalValue(nullptr)) << "\n";
return false; // skip sub-expressions
}
_binOp.leftExpression().accept(*this);
_binOp.rightExpression().accept(*this);
if (commonType->category() == Type::Category::RationalNumber)
define(_binOp) << toCompactHexWithPrefix(commonType->literalValue(nullptr)) << "\n";
else if (TokenTraits::isCompareOp(op))
if (TokenTraits::isCompareOp(op))
{
if (auto type = dynamic_cast<FunctionType const*>(commonType))
{
@@ -496,9 +547,9 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
isSigned = type->isSigned();
string args =
expressionAsType(_binOp.leftExpression(), *commonType) +
expressionAsType(_binOp.leftExpression(), *commonType, true) +
", " +
expressionAsType(_binOp.rightExpression(), *commonType);
expressionAsType(_binOp.rightExpression(), *commonType, true);
string expr;
if (op == Token::Equal)
@@ -517,6 +568,12 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
solAssert(false, "Unknown comparison operator.");
define(_binOp) << expr << "\n";
}
else if (TokenTraits::isShiftOp(op))
{
IRVariable left = convert(_binOp.leftExpression(), *commonType);
IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
define(_binOp) << shiftOperation(_binOp.getOperator(), left, right) << "\n";
}
else
{
string left = expressionAsType(_binOp.leftExpression(), *commonType);
@@ -584,13 +641,6 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break;
case FunctionType::Kind::Internal:
{
vector<string> args;
for (size_t i = 0; i < arguments.size(); ++i)
if (functionType->takesArbitraryParameters())
args.emplace_back(IRVariable(*arguments[i]).commaSeparatedList());
else
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
optional<FunctionDefinition const*> functionDef;
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
{
@@ -628,6 +678,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
solAssert(functionDef.has_value(), "");
solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), "");
vector<string> args;
for (size_t i = 0; i < arguments.size(); ++i)
if (functionType->takesArbitraryParameters())
args += IRVariable(*arguments[i]).stackSlots();
else
args += convert(*arguments[i], *parameterTypes[i]).stackSlots();
if (functionDef.value() != nullptr)
define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) <<
@@ -649,10 +706,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
appendExternalFunctionCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
appendExternalFunctionCall(_functionCall, arguments);
appendBareCall(_functionCall, arguments);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
@@ -663,7 +722,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
vector<IRVariable> indexedArgs;
string nonIndexedArgs;
vector<string> nonIndexedArgs;
TypePointers nonIndexedArgTypes;
TypePointers nonIndexedParamTypes;
if (!event.isAnonymous())
@@ -686,10 +745,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
else
{
string vars = IRVariable(arg).commaSeparatedList();
if (!vars.empty())
// In reverse because abi_encode expects it like that.
nonIndexedArgs = ", " + move(vars) + nonIndexedArgs;
nonIndexedArgs += IRVariable(arg).stackSlots();
nonIndexedArgTypes.push_back(arg.annotation().type);
nonIndexedParamTypes.push_back(paramTypes[i]);
}
@@ -704,7 +760,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
templ("end", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
templ("nonIndexedArgs", nonIndexedArgs);
templ("nonIndexedArgs", joinHumanReadablePrefixed(nonIndexedArgs));
templ("log", "log" + to_string(indexedArgs.size()));
templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs | boost::adaptors::transformed([&](auto const& _arg) {
return _arg.commaSeparatedList();
@@ -731,13 +787,134 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break;
}
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeWithSignature:
{
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(functionType->padArguments() != isPacked, "");
bool const hasSelectorOrSignature =
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes;
TypePointers targetTypes;
vector<string> argumentVars;
for (size_t i = 0; i < arguments.size(); ++i)
{
// ignore selector
if (hasSelectorOrSignature && i == 0)
continue;
argumentTypes.emplace_back(&type(*arguments[i]));
targetTypes.emplace_back(type(*arguments[i]).fullEncodingType(false, true, isPacked));
argumentVars += IRVariable(*arguments[i]).stackSlots();
}
string selector;
if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
{
// hash the signature
Type const& selectorType = type(*arguments.front());
if (auto const* stringType = dynamic_cast<StringLiteralType const*>(&selectorType))
{
FixedHash<4> hash(keccak256(stringType->value()));
selector = formatNumber(u256(FixedHash<4>::Arith(hash)) << (256 - 32));
}
else
{
// Used to reset the free memory pointer later.
string freeMemoryPre = m_context.newYulVariable();
m_code << "let " << freeMemoryPre << " := " << freeMemory() << "\n";
IRVariable array = convert(*arguments[0], *TypeProvider::bytesMemory());
IRVariable hashVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(32));
define(hashVariable) <<
"keccak256(" <<
m_utils.arrayDataAreaFunction(*TypeProvider::bytesMemory()) <<
"(" <<
array.commaSeparatedList() <<
"), " <<
m_utils.arrayLengthFunction(*TypeProvider::bytesMemory()) <<
"(" <<
array.commaSeparatedList() <<
"))\n";
IRVariable selectorVariable(m_context.newYulVariable(), *TypeProvider::fixedBytes(4));
define(selectorVariable, hashVariable);
m_code << "mstore(" << to_string(CompilerUtils::freeMemoryPointer) << ", " << freeMemoryPre << ")\n";
}
}
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector)
selector = convert(*arguments.front(), *TypeProvider::fixedBytes(4)).name();
Whiskers templ(R"(
let <data> := <allocateTemporary>()
let <mpos> := add(<data>, 0x20)
<?+selector>
mstore(<mpos>, <selector>)
<mpos> := add(<mpos>, 4)
</+selector>
let <mend> := <encode>(<mpos><arguments>)
mstore(<data>, sub(<mend>, add(<data>, 0x20)))
mstore(<freeMemPtr>, <roundUp>(<mend>))
)");
templ("data", IRVariable(_functionCall).part("mpos").name());
templ("allocateTemporary", m_utils.allocationTemporaryMemoryFunction());
templ("mpos", m_context.newYulVariable());
templ("mend", m_context.newYulVariable());
templ("selector", selector);
templ("encode",
isPacked ?
m_context.abiFunctions().tupleEncoderPacked(argumentTypes, targetTypes) :
m_context.abiFunctions().tupleEncoder(argumentTypes, targetTypes, false)
);
templ("arguments", joinHumanReadablePrefixed(argumentVars));
templ("freeMemPtr", to_string(CompilerUtils::freeMemoryPointer));
templ("roundUp", m_utils.roundUpFunction());
m_code << templ.render();
break;
}
case FunctionType::Kind::Revert:
{
solAssert(arguments.size() == parameterTypes.size(), "");
if (arguments.empty())
m_code << "revert(0, 0)\n";
else
solUnimplementedAssert(false, "");
{
solAssert(arguments.size() == 1, "");
if (m_context.revertStrings() == RevertStrings::Strip)
m_code << "revert(0, 0)\n";
else
{
solAssert(type(*arguments.front()).isImplicitlyConvertibleTo(*TypeProvider::stringMemory()),"");
Whiskers templ(R"({
let <pos> := <allocateTemporary>()
mstore(<pos>, <hash>)
let <end> := <encode>(add(<pos>, 4) <argumentVars>)
revert(<pos>, sub(<end>, <pos>))
})");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ(
"hash",
(u256(util::FixedHash<4>::Arith(util::FixedHash<4>(util::keccak256("Error(string)")))) << (256 - 32)).str()
);
templ("allocateTemporary", m_utils.allocationTemporaryMemoryFunction());
templ(
"argumentVars",
joinHumanReadablePrefixed(IRVariable{*arguments.front()}.stackSlots())
);
templ("encode", m_context.abiFunctions().tupleEncoder(
{&type(*arguments.front())},
{TypeProvider::stringMemory()}
));
m_code << templ.render();
}
}
break;
}
@@ -760,6 +937,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
solAssert(arguments.size() == 1, "");
ArrayType const* arrayType = TypeProvider::bytesMemory();
auto array = convert(*arguments[0], *arrayType);
define(_functionCall) <<
@@ -905,11 +1083,11 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
);
TypePointers argumentTypes;
string constructorParams;
vector<string> constructorParams;
for (ASTPointer<Expression const> const& arg: arguments)
{
argumentTypes.push_back(arg->annotation().type);
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
constructorParams += IRVariable{*arg}.stackSlots();
}
ContractDefinition const* contract =
@@ -935,9 +1113,9 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
t("object", m_context.creationObjectName(*contract));
t("abiEncode",
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(), false)
);
t("constructorParams", constructorParams);
t("constructorParams", joinHumanReadablePrefixed(constructorParams));
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
t("saltSet", functionType->saltSet());
if (functionType->saltSet())
@@ -1126,7 +1304,20 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(false, "Blockhash has been removed.");
else if (member == "creationCode" || member == "runtimeCode")
{
solUnimplementedAssert(false, "");
solUnimplementedAssert(member != "runtimeCode", "");
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
m_context.subObjectsCreated().insert(&contract);
m_code << Whiskers(R"(
let <size> := datasize("<objectName>")
let <result> := <allocationFunction>(add(<size>, 32))
mstore(<result>, <size>)
datacopy(add(<result>, 32), dataoffset("<objectName>"), <size>)
)")
("allocationFunction", m_utils.allocationFunction())
("size", m_context.newYulVariable())
("objectName", m_context.creationObjectName(contract))
("result", IRVariable(_memberAccess).commaSeparatedList()).render();
}
else if (member == "name")
{
@@ -1141,6 +1332,16 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
result ^= fromBigEndian<uint64_t>(function.first.ref());
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
}
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);
IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument());
if (member == "min")
define(_memberAccess) << formatNumber(integerType->min()) << "\n";
else
define(_memberAccess) << formatNumber(integerType->max()) << "\n";
}
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
{
// no-op
@@ -1312,14 +1513,11 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
solAssert(keyType.sizeOnStack() <= 1, "");
string slot = m_context.newYulVariable();
Whiskers templ("let <slot> := <indexAccess>(<base> <key>)\n");
Whiskers templ("let <slot> := <indexAccess>(<base><?+key>,<key></+key>)\n");
templ("slot", slot);
templ("indexAccess", m_utils.mappingIndexAccessFunction(mappingType, keyType));
templ("base", IRVariable(_indexAccess.baseExpression()).commaSeparatedList());
if (keyType.sizeOnStack() == 0)
templ("key", "");
else
templ("key", ", " + IRVariable(*_indexAccess.indexExpression()).commaSeparatedList());
templ("key", IRVariable(*_indexAccess.indexExpression()).commaSeparatedList());
m_code << templ.render();
setLValue(_indexAccess, IRLValue{
*_indexAccess.annotation().type,
@@ -1542,11 +1740,9 @@ void IRGeneratorForStatements::handleVariableReference(
Expression const& _referencingExpression
)
{
// TODO for the constant case, we have to be careful:
// If the value is visited twice, `defineExpression` is called twice on
// the same expression.
solUnimplementedAssert(!_variable.isConstant(), "");
if (_variable.isStateVariable() && _variable.immutable())
if (_variable.isStateVariable() && _variable.isConstant())
define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
else if (_variable.isStateVariable() && _variable.immutable())
setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type,
IRLValue::Immutable{&_variable}
@@ -1574,18 +1770,20 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(
funType.takesArbitraryParameters() ||
_arguments.size() == funType.parameterTypes().size(), ""
);
solUnimplementedAssert(!funType.bound(), "");
solAssert(!funType.takesArbitraryParameters(), "");
solAssert(_arguments.size() == funType.parameterTypes().size(), "");
solAssert(!funType.isBareCall(), "");
FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
solAssert(
funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall,
"Can only be used for regular external calls."
);
bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
solUnimplementedAssert(!funType.bound(), "");
bool const isDelegateCall = funKind == FunctionType::Kind::DelegateCall;
bool const useStaticCall = funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall();
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
@@ -1594,12 +1792,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
for (auto const& arg: _arguments)
{
argumentTypes.emplace_back(&type(*arg));
if (IRVariable(*arg).type().sizeOnStack() > 0)
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
argumentStrings += IRVariable(*arg).stackSlots();
}
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (!m_context.evmVersion().canOverchargeGasForCall())
{
@@ -1611,33 +1806,19 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
}
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
</checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
// storage for arguments and returned data
let <pos> := <freeMemory>
<?bareCall>
<!bareCall>
mstore(<pos>, <shl28>(<funId>))
</bareCall>
let <end> := <encodeArgs>(
<?bareCall>
<pos>
<!bareCall>
add(<pos>, 4)
</bareCall>
<argumentString>
)
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
<?noTryCall>
if iszero(<success>) { <forwardingRevert>() }
</noTryCall>
<?hasRetVars> let <retVars> </hasRetVars>
<?+retVars> let <retVars> </+retVars>
if <success> {
<?dynamicReturnSize>
// copy dynamic return data out
@@ -1648,12 +1829,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
// decode return parameters from external try-call into retVars
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
<?+retVars> <retVars> := </+retVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
}
)");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("bareCall", funType.isBareCall());
if (_functionCall.annotation().tryCall)
templ("success", m_context.trySuccessConditionVariable(_functionCall));
else
@@ -1661,17 +1841,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("freeMemory", freeMemory());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
if (!funType.isBareCall())
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
if (funKind == FunctionType::Kind::ECRecover)
templ("address", "1");
else if (funKind == FunctionType::Kind::SHA256)
templ("address", "2");
else if (funKind == FunctionType::Kind::RIPEMD160)
templ("address", "3");
else
templ("address", IRVariable(_functionCall.expression()).part("address").name());
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
templ("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
@@ -1684,39 +1855,20 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
string const retVars = IRVariable(_functionCall).commaSeparatedList();
templ("retVars", retVars);
templ("hasRetVars", !retVars.empty());
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), "");
templ("roundUp", m_utils.roundUpFunction());
templ("abiDecode", abi.tupleDecoder(returnInfo.returnTypes, true));
templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true));
templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("noTryCall", !_functionCall.annotation().tryCall);
// 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).
bool encodeInPlace = funType.takesArbitraryParameters() || funType.isBareCall();
if (funType.kind() == FunctionType::Kind::ECRecover)
// This would be the only combination of padding and in-place encoding,
// but all parameters of ecrecover are value types anyway.
encodeInPlace = false;
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
if (encodeInPlace)
{
solUnimplementedAssert(!encodeForLibraryCall, "");
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
}
else
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", argumentString);
// Output data will replace input data, unless we have ECRecover (then, output
// area will be 32 bytes just before input area).
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
solAssert(funType.padArguments(), "");
templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", joinHumanReadablePrefixed(argumentStrings));
solAssert(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
@@ -1724,10 +1876,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("hasValue", !isDelegateCall && !useStaticCall);
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
// Check that the target contract exists (has code) for non-low-level calls.
bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
templ("checkExistence", checkExistence);
if (funType.gasSet())
templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
else if (m_context.evmVersion().canOverchargeGasForCall())
@@ -1740,8 +1888,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet())
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
if (!checkExistence)
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
// Order is important here, STATICCALL might overlap with DELEGATECALL.
@@ -1754,8 +1900,103 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("forwardingRevert", m_utils.forwardingRevertFunction());
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
m_code << templ.render();
}
void IRGeneratorForStatements::appendBareCall(
FunctionCall const& _functionCall,
vector<ASTPointer<Expression const>> const& _arguments
)
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(
!funType.bound() &&
!funType.takesArbitraryParameters() &&
_arguments.size() == 1 &&
funType.parameterTypes().size() == 1, ""
);
FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
solAssert(
funKind == FunctionType::Kind::BareCall ||
funKind == FunctionType::Kind::BareDelegateCall ||
funKind == FunctionType::Kind::BareStaticCall, ""
);
solAssert(!_functionCall.annotation().tryCall, "");
Whiskers templ(R"(
<?needsEncoding>
let <pos> := mload(<freeMemoryPointer>)
let <length> := sub(<encode>(<pos> <?+arg>,</+arg> <arg>), <pos>)
<!needsEncoding>
let <pos> := add(<arg>, 0x20)
let <length> := mload(<arg>)
</needsEncoding>
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
<?+returndataVar>
let <returndataVar> := <extractReturndataFunction>()
</+returndataVar>
)");
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("pos", m_context.newYulVariable());
templ("length", m_context.newYulVariable());
templ("arg", IRVariable(*_arguments.front()).commaSeparatedList());
Type const& argType = type(*_arguments.front());
if (argType == *TypeProvider::bytesMemory() || argType == *TypeProvider::stringMemory())
templ("needsEncoding", false);
else
{
templ("needsEncoding", true);
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
templ("encode", abi.tupleEncoderPacked({&argType}, {TypeProvider::bytesMemory()}));
}
templ("success", IRVariable(_functionCall).tupleComponent(0).name());
if (IRVariable(_functionCall).tupleComponent(1).type().category() == Type::Category::InaccessibleDynamic)
templ("returndataVar", "");
else
{
templ("returndataVar", IRVariable(_functionCall).tupleComponent(1).part("mpos").name());
templ("extractReturndataFunction", m_utils.extractReturndataFunction());
}
templ("address", IRVariable(_functionCall.expression()).part("address").name());
if (funKind == FunctionType::Kind::BareCall)
{
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
templ("call", "call");
}
else
{
solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall.");
templ("value", "");
if (funKind == FunctionType::Kind::BareStaticCall)
templ("call", "staticcall");
else
templ("call", "delegatecall");
}
if (funType.gasSet())
templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
else if (m_context.evmVersion().canOverchargeGasForCall())
// Send all gas (requires tangerine whistle EVM)
templ("gas", "gas()");
else
{
// send all gas except the amount needed to execute "SUB" and "CALL"
// @todo this retains too much gas for now, needs to be fine-tuned.
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
if (funType.valueSet())
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
m_code << templ.render();
}
@@ -1777,11 +2018,16 @@ IRVariable IRGeneratorForStatements::convert(IRVariable const& _from, Type const
}
}
std::string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to)
std::string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to, bool _forceCleanup)
{
IRVariable from(_expression);
if (from.type() == _to)
return from.commaSeparatedList();
{
if (_forceCleanup)
return m_utils.cleanupFunction(_to) + "(" + from.commaSeparatedList() + ")";
else
return from.commaSeparatedList();
}
else
return m_utils.conversionFunction(from.type(), _to) + "(" + from.commaSeparatedList() + ")";
}
@@ -1860,6 +2106,10 @@ string IRGeneratorForStatements::binaryOperation(
string const& _right
)
{
solAssert(
!TokenTraits::isShiftOp(_operator),
"Have to use specific shift operation function for shifts."
);
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
{
string fun;
@@ -1903,6 +2153,31 @@ string IRGeneratorForStatements::binaryOperation(
return {};
}
std::string IRGeneratorForStatements::shiftOperation(
langutil::Token _operator,
IRVariable const& _value,
IRVariable const& _amountToShift
)
{
IntegerType const* amountType = dynamic_cast<IntegerType const*>(&_amountToShift.type());
solAssert(amountType, "");
solAssert(_operator == Token::SHL || _operator == Token::SAR, "");
return
Whiskers(R"(
<shift>(<value>, <amount>)
)")
("shift",
_operator == Token::SHL ?
m_utils.typedShiftLeftFunction(_value.type(), *amountType) :
m_utils.typedShiftRightFunction(_value.type(), *amountType)
)
("value", _value.name())
("amount", _amountToShift.name())
.render();
}
void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp)
{
langutil::Token const op = _binOp.getOperator();
@@ -2021,13 +2296,7 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
")\n";
},
[&](IRLValue::Memory const& _memory) {
if (_memory.byteArrayElement)
define(result) <<
m_utils.cleanupFunction(_lvalue.type) <<
"(mload(" <<
_memory.address <<
"))\n";
else if (_lvalue.type.isValueType())
if (_lvalue.type.isValueType())
define(result) <<
m_utils.readFromMemory(_lvalue.type) <<
"(" <<
@@ -54,6 +54,10 @@ public:
/// Calculates expression's value and returns variable where it was stored
IRVariable evaluateExpression(Expression const& _expression, Type const& _to);
/// @returns the name of a function that computes the value of the given constant
/// and also generates the function.
std::string constantValueFunction(VariableDeclaration const& _constant);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override;
@@ -102,6 +106,13 @@ private:
std::vector<ASTPointer<Expression const>> const& _arguments
);
/// Appends code for .call / .delegatecall / .staticcall.
/// All involved expressions have already been visited.
void appendBareCall(
FunctionCall const& _functionCall,
std::vector<ASTPointer<Expression const>> const& _arguments
);
/// @returns code that evaluates to the first unused memory slot (which does not have to
/// be empty).
static std::string freeMemory();
@@ -112,7 +123,8 @@ private:
/// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to);
/// If @a _forceCleanup is set to true, it also cleans the value, in case it already has type @a _to.
std::string expressionAsType(Expression const& _expression, Type const& _to, bool _forceCleanup = false);
/// @returns an output stream that can be used to define @a _var using a function call or
/// single stack slot expression.
@@ -142,6 +154,11 @@ private:
std::string const& _right
);
/// @returns code to perform the given shift operation.
/// The operation itself will be performed in the type of the value,
/// while the amount to shift can have its own type.
std::string shiftOperation(langutil::Token _op, IRVariable const& _value, IRVariable const& _shiftAmount);
/// Assigns the value of @a _value to the lvalue @a _lvalue.
void writeToLValue(IRLValue const& _lvalue, IRVariable const& _value);
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
+2 -2
View File
@@ -54,7 +54,7 @@ IRVariable IRVariable::part(string const& _name) const
solAssert(itemName.empty() || itemType, "");
return IRVariable{suffixedName(itemName), itemType ? *itemType : m_type};
}
solAssert(false, "Invalid stack item name.");
solAssert(false, "Invalid stack item name: " + _name);
}
vector<string> IRVariable::stackSlots() const
@@ -89,7 +89,7 @@ string IRVariable::name() const
{
solAssert(m_type.sizeOnStack() == 1, "");
auto const& [itemName, type] = m_type.stackItems().front();
solAssert(!type, "");
solAssert(!type, "Expected null type for name " + itemName);
return suffixedName(itemName);
}
+2 -1
View File
@@ -69,10 +69,11 @@ public:
/// The returned IRVariable is itself typed with the type of the stack slot as defined
/// in ``m_type.stackItems()`` and may again occupy multiple stack slots.
IRVariable part(std::string const& _slot) const;
private:
/// @returns a vector containing the names of the stack slots of the variable.
std::vector<std::string> stackSlots() const;
private:
/// @returns a name consisting of the base name appended with an underscore and @æ _suffix,
/// unless @a _suffix is empty, in which case the base name itself is returned.
std::string suffixedName(std::string const& _suffix) const;
+35 -22
View File
@@ -21,8 +21,6 @@
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>
#include <boost/algorithm/string/replace.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@@ -44,6 +42,7 @@ BMC::BMC(
if (_enabledSolvers.some())
if (!_smtlib2Responses.empty())
m_errorReporter.warning(
5622_error,
"SMT-LIB2 query responses were given in the auxiliary input, "
"but this Solidity binary uses an SMT solver (Z3/CVC4) directly."
"These responses will be ignored."
@@ -74,6 +73,7 @@ void BMC::analyze(SourceUnit const& _source, set<Expression const*> _safeAsserti
{
m_noSolverWarning = true;
m_outerErrorReporter.warning(
8084_error,
SourceLocation(),
"BMC analysis was not possible since no integrated SMT solver (Z3 or CVC4) was found."
);
@@ -467,6 +467,7 @@ void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall)
inlineFunctionCall(_funCall);
else if (funType.kind() == FunctionType::Kind::Internal)
m_errorReporter.warning(
5729_error,
_funCall.location(),
"Assertion checker does not yet implement this type of function call."
);
@@ -591,8 +592,7 @@ void BMC::checkConstantCondition(BMCVerificationTarget& _target)
*_target.expression,
_target.constraints,
_target.value,
_target.callStack,
"Condition is always $VALUE."
_target.callStack
);
}
@@ -610,6 +610,8 @@ void BMC::checkUnderflow(BMCVerificationTarget& _target, smt::Expression const&
_target.callStack,
_target.modelExpressions,
_target.expression->location(),
4144_error,
8312_error,
"Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")",
"<result>",
&_target.value
@@ -630,6 +632,8 @@ void BMC::checkOverflow(BMCVerificationTarget& _target, smt::Expression const& _
_target.callStack,
_target.modelExpressions,
_target.expression->location(),
2661_error,
8065_error,
"Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")",
"<result>",
&_target.value
@@ -644,6 +648,8 @@ void BMC::checkDivByZero(BMCVerificationTarget& _target)
_target.callStack,
_target.modelExpressions,
_target.expression->location(),
3046_error,
5272_error,
"Division by zero",
"<result>",
&_target.value
@@ -658,6 +664,8 @@ void BMC::checkBalance(BMCVerificationTarget& _target)
_target.callStack,
_target.modelExpressions,
_target.expression->location(),
1236_error,
4010_error,
"Insufficient funds",
"address(this).balance"
);
@@ -672,6 +680,8 @@ void BMC::checkAssert(BMCVerificationTarget& _target)
_target.callStack,
_target.modelExpressions,
_target.expression->location(),
4661_error,
7812_error,
"Assertion violation"
);
}
@@ -702,9 +712,11 @@ void BMC::addVerificationTarget(
void BMC::checkCondition(
smt::Expression _condition,
vector<SMTEncoder::CallStackEntry> const& callStack,
vector<SMTEncoder::CallStackEntry> const& _callStack,
pair<vector<smt::Expression>, vector<string>> const& _modelExpressions,
SourceLocation const& _location,
ErrorId _errorHappens,
ErrorId _errorMightHappen,
string const& _description,
string const& _additionalValueName,
smt::Expression const* _additionalValue
@@ -716,7 +728,7 @@ void BMC::checkCondition(
vector<smt::Expression> expressionsToEvaluate;
vector<string> expressionNames;
tie(expressionsToEvaluate, expressionNames) = _modelExpressions;
if (callStack.size())
if (_callStack.size())
if (_additionalValue)
{
expressionsToEvaluate.emplace_back(*_additionalValue);
@@ -747,7 +759,7 @@ void BMC::checkCondition(
{
std::ostringstream message;
message << _description << " happens here";
if (callStack.size())
if (_callStack.size())
{
std::ostringstream modelMessage;
modelMessage << " for:\n";
@@ -760,30 +772,31 @@ void BMC::checkCondition(
for (auto const& eval: sortedModel)
modelMessage << " " << eval.first << " = " << eval.second << "\n";
m_errorReporter.warning(
_errorHappens,
_location,
message.str(),
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
.append(SMTEncoder::callStackMessage(callStack))
.append(SMTEncoder::callStackMessage(_callStack))
.append(move(secondaryLocation))
);
}
else
{
message << ".";
m_errorReporter.warning(_location, message.str(), secondaryLocation);
m_errorReporter.warning(6084_error, _location, message.str(), secondaryLocation);
}
break;
}
case smt::CheckResult::UNSATISFIABLE:
break;
case smt::CheckResult::UNKNOWN:
m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation);
m_errorReporter.warning(_errorMightHappen, _location, _description + " might happen here.", secondaryLocation);
break;
case smt::CheckResult::CONFLICTING:
m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
m_errorReporter.warning(1584_error, _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.");
m_errorReporter.warning(1823_error, _location, "Error trying to invoke SMT solver.");
break;
}
@@ -794,8 +807,7 @@ void BMC::checkBooleanNotConstant(
Expression const& _condition,
smt::Expression const& _constraints,
smt::Expression const& _value,
vector<SMTEncoder::CallStackEntry> const& _callStack,
string const& _description
vector<SMTEncoder::CallStackEntry> const& _callStack
)
{
// Do not check for const-ness if this is a constant.
@@ -813,9 +825,9 @@ void BMC::checkBooleanNotConstant(
m_interface->pop();
if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
m_errorReporter.warning(8592_error, _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.");
m_errorReporter.warning(3356_error, _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.
@@ -825,24 +837,25 @@ void BMC::checkBooleanNotConstant(
// can't do anything.
}
else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
m_errorReporter.warning(_condition.location(), "Condition unreachable.", SMTEncoder::callStackMessage(_callStack));
m_errorReporter.warning(2512_error, _condition.location(), "Condition unreachable.", SMTEncoder::callStackMessage(_callStack));
else
{
string value;
string description;
if (positiveResult == smt::CheckResult::SATISFIABLE)
{
solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, "");
value = "true";
description = "Condition is always true.";
}
else
{
solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, "");
solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
value = "false";
description = "Condition is always false.";
}
m_errorReporter.warning(
6838_error,
_condition.location(),
boost::algorithm::replace_all_copy(_description, "$VALUE", value),
description,
SMTEncoder::callStackMessage(_callStack)
);
}
@@ -862,7 +875,7 @@ BMC::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expression
string description("Error querying SMT solver");
if (_e.comment())
description += ": " + *_e.comment();
m_errorReporter.warning(description);
m_errorReporter.warning(8140_error, description);
result = smt::CheckResult::ERROR;
}
+5 -4
View File
@@ -44,6 +44,7 @@ using solidity::util::h256;
namespace solidity::langutil
{
class ErrorReporter;
struct ErrorId;
struct SourceLocation;
}
@@ -144,22 +145,22 @@ private:
/// Check that a condition can be satisfied.
void checkCondition(
smt::Expression _condition,
std::vector<CallStackEntry> const& callStack,
std::vector<CallStackEntry> const& _callStack,
std::pair<std::vector<smt::Expression>, std::vector<std::string>> const& _modelExpressions,
langutil::SourceLocation const& _location,
langutil::ErrorId _errorHappens,
langutil::ErrorId _errorMightHappen,
std::string const& _description,
std::string const& _additionalValueName = "",
smt::Expression const* _additionalValue = nullptr
);
/// Checks that a boolean condition is not constant. Do not warn if the expression
/// is a literal constant.
/// @param _description the warning string, $VALUE will be replaced by the constant value.
void checkBooleanNotConstant(
Expression const& _condition,
smt::Expression const& _constraints,
smt::Expression const& _value,
std::vector<CallStackEntry> const& _callStack,
std::string const& _description
std::vector<CallStackEntry> const& _callStack
);
std::pair<smt::CheckResult, std::vector<std::string>>
checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
+2 -2
View File
@@ -965,10 +965,10 @@ pair<smt::CheckResult, vector<string>> CHC::query(smt::Expression const& _query,
case smt::CheckResult::UNKNOWN:
break;
case smt::CheckResult::CONFLICTING:
m_outerErrorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
m_outerErrorReporter.warning(1988_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
break;
case smt::CheckResult::ERROR:
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
m_outerErrorReporter.warning(1218_error, _location, "Error trying to invoke SMT solver.");
break;
}
return {result, values};
@@ -21,7 +21,6 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <array>
#include <fstream>
+28 -1
View File
@@ -266,6 +266,7 @@ void SMTEncoder::endVisit(FunctionDefinition const&)
bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{
m_errorReporter.warning(
7737_error,
_inlineAsm.location(),
"Assertion checker does not support inline assembly."
);
@@ -325,6 +326,7 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
}
else
m_errorReporter.warning(
7186_error,
_varDecl.location(),
"Assertion checker does not yet implement such variable declarations."
);
@@ -350,6 +352,7 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
m_context.newValue(*varDecl);
m_errorReporter.warning(
9149_error,
_assignment.location(),
"Assertion checker does not yet implement this assignment operator."
);
@@ -409,6 +412,7 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
if (_tuple.isInlineArray())
m_errorReporter.warning(
2177_error,
_tuple.location(),
"Assertion checker does not yet implement inline arrays."
);
@@ -493,6 +497,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
}
else
m_errorReporter.warning(
1950_error,
_op.location(),
"Assertion checker does not yet implement such increments / decrements."
);
@@ -522,6 +527,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
arrayIndexAssignment(_op.subExpression(), symbVar->currentValue());
else
m_errorReporter.warning(
2683_error,
_op.location(),
"Assertion checker does not yet implement \"delete\" for this expression."
);
@@ -530,6 +536,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
}
default:
m_errorReporter.warning(
3682_error,
_op.location(),
"Assertion checker does not yet implement this operator."
);
@@ -568,6 +575,7 @@ void SMTEncoder::endVisit(BinaryOperation const& _op)
compareOperation(_op);
else
m_errorReporter.warning(
3876_error,
_op.location(),
"Assertion checker does not yet implement this operator."
);
@@ -580,6 +588,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
if (_funCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
m_errorReporter.warning(
4639_error,
_funCall.location(),
"Assertion checker does not yet implement this expression."
);
@@ -639,6 +648,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
}
default:
m_errorReporter.warning(
4588_error,
_funCall.location(),
"Assertion checker does not yet implement this type of function call."
);
@@ -771,6 +781,7 @@ void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
}
m_errorReporter.warning(
5084_error,
_funCall.location(),
"Type conversion is not yet fully supported and might yield false positives."
);
@@ -800,6 +811,7 @@ void SMTEncoder::endVisit(Literal const& _literal)
else
{
m_errorReporter.warning(
7885_error,
_literal.location(),
"Assertion checker does not yet support the type of this literal (" +
_literal.annotation().type->toString() +
@@ -850,6 +862,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
accessedName = identifier->name();
else
m_errorReporter.warning(
9551_error,
_memberAccess.location(),
"Assertion checker does not yet support this expression."
);
@@ -879,6 +892,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
}
else
m_errorReporter.warning(
7650_error,
_memberAccess.location(),
"Assertion checker does not yet support this expression."
);
@@ -903,6 +917,7 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
if (varDecl->type()->category() == Type::Category::FixedBytes)
{
m_errorReporter.warning(
7989_error,
_indexAccess.location(),
"Assertion checker does not yet support index accessing fixed bytes."
);
@@ -917,6 +932,7 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
else
{
m_errorReporter.warning(
9118_error,
_indexAccess.location(),
"Assertion checker does not yet implement this expression."
);
@@ -940,6 +956,7 @@ void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
createExpr(_indexRangeAccess);
m_errorReporter.warning(
2923_error,
_indexRangeAccess.location(),
"Assertion checker does not yet implement this expression."
);
@@ -1019,6 +1036,7 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression c
else
{
m_errorReporter.warning(
9056_error,
_expr.location(),
"Assertion checker does not yet implement this expression."
);
@@ -1034,6 +1052,7 @@ void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _ex
bool abstract = m_context.createGlobalSymbol(_name, _expr);
if (abstract)
m_errorReporter.warning(
1695_error,
_expr.location(),
"Assertion checker does not yet support this global variable."
);
@@ -1087,6 +1106,7 @@ void SMTEncoder::arithmeticOperation(BinaryOperation const& _op)
}
default:
m_errorReporter.warning(
5188_error,
_op.location(),
"Assertion checker does not yet implement this operator."
);
@@ -1094,6 +1114,7 @@ void SMTEncoder::arithmeticOperation(BinaryOperation const& _op)
}
else
m_errorReporter.warning(
9011_error,
_op.location(),
"Assertion checker does not yet implement this operator for type " + type->richIdentifier() + "."
);
@@ -1185,6 +1206,7 @@ void SMTEncoder::compareOperation(BinaryOperation const& _op)
}
else
m_errorReporter.warning(
7229_error,
_op.location(),
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons"
);
@@ -1213,6 +1235,7 @@ void SMTEncoder::booleanOperation(BinaryOperation const& _op)
}
else
m_errorReporter.warning(
3263_error,
_op.location(),
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations"
);
@@ -1245,6 +1268,7 @@ void SMTEncoder::assignment(
m_context.newValue(*varDecl);
m_errorReporter.warning(
6191_error,
_location,
"Assertion checker does not yet implement type " + _type->toString()
);
@@ -1272,6 +1296,7 @@ void SMTEncoder::assignment(
}
else
m_errorReporter.warning(
8182_error,
_location,
"Assertion checker does not yet implement such assignments."
);
@@ -1482,6 +1507,7 @@ bool SMTEncoder::createVariable(VariableDeclaration const& _varDecl)
if (abstract)
{
m_errorReporter.warning(
8115_error,
_varDecl.location(),
"Assertion checker does not yet support the type of this variable."
);
@@ -1494,7 +1520,7 @@ smt::Expression SMTEncoder::expr(Expression const& _e, TypePointer _targetType)
{
if (!m_context.knownExpression(_e))
{
m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." );
m_errorReporter.warning(6031_error, _e.location(), "Internal error: Expression undefined for SMT solver." );
createExpr(_e);
}
@@ -1506,6 +1532,7 @@ void SMTEncoder::createExpr(Expression const& _e)
bool abstract = m_context.createExpression(_e);
if (abstract)
m_errorReporter.warning(
8364_error,
_e.location(),
"Assertion checker does not yet implement type " + _e.annotation().type->toString()
);
-1
View File
@@ -21,7 +21,6 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <array>
#include <fstream>
+3 -2
View File
@@ -73,9 +73,10 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
);
abi.emplace(std::move(method));
}
if (_contractDef.constructor())
FunctionDefinition const* constructor = _contractDef.constructor();
if (constructor && constructor->visibility() >= Visibility::Public)
{
FunctionType constrType(*_contractDef.constructor());
FunctionType constrType(*constructor);
FunctionType const* externalFunctionType = constrType.interfaceFunctionType();
solAssert(!!externalFunctionType, "");
Json::Value method;
+4 -2
View File
@@ -67,7 +67,7 @@
#include <json/json.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <utility>
using namespace std;
@@ -239,7 +239,7 @@ bool CompilerStack::parse()
m_errorReporter.clear();
if (SemVerVersion{string(VersionString)}.isPrerelease())
m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production.");
m_errorReporter.warning(3805_error, "This is a pre-release compiler version, please do not use it in production.");
Parser parser{m_errorReporter, m_evmVersion, m_parserErrorRecovery};
@@ -949,6 +949,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
else
{
m_errorReporter.parserError(
6275_error,
import->location(),
string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage)
);
@@ -1110,6 +1111,7 @@ void CompilerStack::compileContract(
compiledContract.runtimeObject.bytecode.size() > 0x6000
)
m_errorReporter.warning(
5574_error,
_contract.location(),
"Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). "
"This contract may not be deployable on mainnet. "
+6 -8
View File
@@ -103,16 +103,14 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
{
Json::Value method(devDocumentation(fun->annotation().docTags));
// add the function, only if we have any documentation to add
Json::Value jsonReturn = extractReturnParameterDocs(fun->annotation().docTags, *fun);
if (!jsonReturn.empty())
method["returns"] = jsonReturn;
if (!method.empty())
{
// add the function, only if we have any documentation to add
Json::Value jsonReturn = extractReturnParameterDocs(fun->annotation().docTags, *fun);
if (!jsonReturn.empty())
method["returns"] = jsonReturn;
methods[it.second->externalSignature()] = method;
}
}
}
+9 -10
View File
@@ -31,8 +31,7 @@
#include <libsolutil/JSON.h>
#include <libsolutil/Keccak256.h>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <algorithm>
#include <optional>
@@ -491,7 +490,7 @@ std::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSelect
}
/// Validates the optimizer settings and returns them in a parsed object.
/// On error returns the json-formatted error message.
boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput)
std::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput)
{
if (auto result = checkOptimizerKeys(_jsonInput))
return *result;
@@ -552,7 +551,7 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
}
boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler::parseInput(Json::Value const& _input)
std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler::parseInput(Json::Value const& _input)
{
InputsAndSettings ret;
@@ -740,10 +739,10 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
if (settings.isMember("optimizer"))
{
auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]);
if (optimiserSettings.type() == typeid(Json::Value))
return boost::get<Json::Value>(std::move(optimiserSettings)); // was an error
if (std::holds_alternative<Json::Value>(optimiserSettings))
return std::get<Json::Value>(std::move(optimiserSettings)); // was an error
else
ret.optimiserSettings = boost::get<OptimiserSettings>(std::move(optimiserSettings));
ret.optimiserSettings = std::get<OptimiserSettings>(std::move(optimiserSettings));
}
Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue));
@@ -1155,9 +1154,9 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
try
{
auto parsed = parseInput(_input);
if (parsed.type() == typeid(Json::Value))
return boost::get<Json::Value>(std::move(parsed));
InputsAndSettings settings = boost::get<InputsAndSettings>(std::move(parsed));
if (std::holds_alternative<Json::Value>(parsed))
return std::get<Json::Value>(std::move(parsed));
InputsAndSettings settings = std::get<InputsAndSettings>(std::move(parsed));
if (settings.language == "Solidity")
return compileSolidity(std::move(settings));
else if (settings.language == "Yul")
+2 -2
View File
@@ -24,9 +24,9 @@
#include <libsolidity/interface/CompilerStack.h>
#include <boost/variant.hpp>
#include <optional>
#include <utility>
#include <variant>
namespace solidity::frontend
{
@@ -73,7 +73,7 @@ private:
/// Parses the input json (and potentially invokes the read callback) and either returns
/// it in condensed form or an error as a json object.
boost::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
std::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
Json::Value compileSolidity(InputsAndSettings _inputsAndSettings);
Json::Value compileYul(InputsAndSettings _inputsAndSettings);
+1 -1
View File
@@ -195,5 +195,5 @@ void DocStringParser::newTag(string const& _tagName)
void DocStringParser::appendError(string const& _description)
{
m_errorsOccurred = true;
m_errorReporter->docstringParsingError(_description);
m_errorReporter->docstringParsingError(9440_error, _description);
}
+46 -39
View File
@@ -103,7 +103,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
nodes.push_back(parseEnumDefinition());
break;
default:
fatalParserError(string("Expected pragma, import directive or contract/interface/library/struct/enum definition."));
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum definition.");
}
}
solAssert(m_recursionDepth == 0, "");
@@ -128,9 +128,10 @@ void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> c
// so we don't need to report anything here.
if (!m_parserErrorRecovery)
m_errorReporter.fatalParserError(
5333_error,
_location,
"Source file requires different compiler version (current compiler is " +
string(VersionString) + " - note that nightly builds are considered to be "
string(VersionString) + ") - note that nightly builds are considered to be "
"strictly less than the released version"
);
}
@@ -162,7 +163,7 @@ ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
{
Token token = m_scanner->currentToken();
if (token == Token::Illegal)
parserError("Token incompatible with Solidity parser as part of pragma directive.");
parserError(6281_error, "Token incompatible with Solidity parser as part of pragma directive.");
else
{
string literal = m_scanner->currentLiteral();
@@ -240,18 +241,18 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
unitAlias = expectIdentifierToken();
}
else
fatalParserError("Expected string literal (path), \"*\" or alias list.");
fatalParserError(9478_error, "Expected string literal (path), \"*\" or alias list.");
// "from" is not a keyword but parsed as an identifier because of backwards
// compatibility and because it is a really common word.
if (m_scanner->currentToken() != Token::Identifier || m_scanner->currentLiteral() != "from")
fatalParserError("Expected \"from\".");
fatalParserError(8208_error, "Expected \"from\".");
m_scanner->next();
if (m_scanner->currentToken() != Token::StringLiteral)
fatalParserError("Expected import path.");
fatalParserError(6845_error, "Expected import path.");
path = getLiteralAndAdvance();
}
if (path->empty())
fatalParserError("Import path cannot be empty.");
fatalParserError(6326_error, "Import path cannot be empty.");
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases));
@@ -278,7 +279,7 @@ std::pair<ContractKind, bool> Parser::parseContractKind()
kind = ContractKind::Library;
break;
default:
parserError("Expected keyword \"contract\", \"interface\" or \"library\".");
parserError(3515_error, "Expected keyword \"contract\", \"interface\" or \"library\".");
return std::make_pair(ContractKind::Contract, abstract);
}
m_scanner->next();
@@ -343,7 +344,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
else if (currentTokenValue == Token::Using)
subNodes.push_back(parseUsingDirective());
else
fatalParserError(string("Function, variable, struct or modifier declaration expected."));
fatalParserError(9182_error, "Function, variable, struct or modifier declaration expected.");
}
}
catch (FatalError const&)
@@ -462,6 +463,7 @@ StateMutability Parser::parseStateMutability()
case Token::Constant:
stateMutability = StateMutability::View;
parserError(
7698_error,
"The state mutability modifier \"constant\" was removed in version 0.5.0. "
"Use \"view\" or \"pure\" instead."
);
@@ -494,11 +496,12 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVari
// Detect this and return early.
if (_isStateVariable && (result.visibility == Visibility::External || result.visibility == Visibility::Internal))
break;
parserError(string(
parserError(
9439_error,
"Visibility already specified as \"" +
Declaration::visibilityToString(result.visibility) +
"\"."
));
);
m_scanner->next();
}
else
@@ -508,11 +511,12 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVari
{
if (result.stateMutability != StateMutability::NonPayable)
{
parserError(string(
parserError(
9680_error,
"State mutability already specified as \"" +
stateMutabilityToString(result.stateMutability) +
"\"."
));
);
m_scanner->next();
}
else
@@ -521,14 +525,14 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVari
else if (!_isStateVariable && token == Token::Override)
{
if (result.overrides)
parserError("Override already specified.");
parserError(1827_error, "Override already specified.");
result.overrides = parseOverrideSpecifier();
}
else if (!_isStateVariable && token == Token::Virtual)
{
if (result.isVirtual)
parserError("Virtual already specified.");
parserError(6879_error, "Virtual already specified.");
result.isVirtual = true;
m_scanner->next();
@@ -576,9 +580,9 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
"the \"function\" keyword to define it."
};
if (m_scanner->currentToken() == Token::Constructor)
parserError(message);
parserError(3323_error, message);
else
parserWarning(message);
parserWarning(3445_error, message);
m_scanner->next();
}
else
@@ -659,10 +663,10 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
break;
expectToken(Token::Comma);
if (m_scanner->currentToken() != Token::Identifier)
fatalParserError(string("Expected identifier after ','"));
fatalParserError(1612_error, "Expected identifier after ','");
}
if (members.empty())
parserError({"enum with no members is not allowed."});
parserError(3147_error, "enum with no members is not allowed.");
nodeFactory.markEndPosition();
expectToken(Token::RBrace);
@@ -689,6 +693,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace)
fatalParserError(
2915_error,
"Expected a state variable declaration. If you intended this as a fallback function "
"or a function to handle plain ether transactions, use the \"fallback\" keyword "
"or the \"receive\" keyword instead."
@@ -709,11 +714,12 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
nodeFactory.markEndPosition();
if (visibility != Visibility::Default)
{
parserError(string(
parserError(
4110_error,
"Visibility already specified as \"" +
Declaration::visibilityToString(visibility) +
"\"."
));
);
m_scanner->next();
}
else
@@ -722,7 +728,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
else if (_options.isStateVariable && token == Token::Override)
{
if (overrides)
parserError("Override already specified.");
parserError(9125_error, "Override already specified.");
overrides = parseOverrideSpecifier();
}
@@ -734,6 +740,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
{
if (mutability != VariableDeclaration::Mutability::Mutable)
parserError(
3109_error,
string("Mutability already set to ") +
(mutability == VariableDeclaration::Mutability::Constant ? "\"constant\"" : "\"immutable\"")
);
@@ -745,9 +752,9 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
{
if (location != VariableDeclaration::Location::Unspecified)
parserError(string("Location already specified."));
parserError(3548_error, "Location already specified.");
else if (!type)
parserError(string("Location specifier needs explicit type name."));
parserError(7439_error, "Location specifier needs explicit type name.");
else
{
switch (token)
@@ -836,13 +843,13 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
if (m_scanner->currentToken() == Token::Override)
{
if (overrides)
parserError("Override already specified.");
parserError(9102_error, "Override already specified.");
overrides = parseOverrideSpecifier();
}
else if (m_scanner->currentToken() == Token::Virtual)
{
if (isVirtual)
parserError("Virtual already specified.");
parserError(2662_error, "Virtual already specified.");
isVirtual = true;
m_scanner->next();
@@ -990,7 +997,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
}
else
{
parserError("State mutability can only be specified for address types.");
parserError(9106_error, "State mutability can only be specified for address types.");
m_scanner->next();
}
}
@@ -999,7 +1006,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
else if (token == Token::Var)
{
if (!_allowVar)
parserError(string("Expected explicit type name."));
parserError(7059_error, "Expected explicit type name.");
m_scanner->next();
}
else if (token == Token::Function)
@@ -1009,7 +1016,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
else if (token == Token::Identifier)
type = parseUserDefinedTypeName();
else
fatalParserError(string("Expected type name"));
fatalParserError(3546_error, "Expected type name");
if (type)
// Parse "[...]" postfixes for arrays.
@@ -1052,7 +1059,7 @@ ASTPointer<Mapping> Parser::parseMapping()
m_scanner->next();
}
else
fatalParserError(string("Expected elementary type name or identifier for mapping key type"));
fatalParserError(1005_error, "Expected elementary type name or identifier for mapping key type");
expectToken(Token::Arrow);
bool const allowVar = false;
ASTPointer<TypeName> valueType = parseTypeName(allowVar);
@@ -1078,7 +1085,7 @@ ASTPointer<ParameterList> Parser::parseParameterList(
while (m_scanner->currentToken() != Token::RParen)
{
if (m_scanner->currentToken() == Token::Comma && m_scanner->peekNextToken() == Token::RParen)
fatalParserError("Unexpected trailing comma in parameter list.");
fatalParserError(7591_error, "Unexpected trailing comma in parameter list.");
expectToken(Token::Comma);
parameters.push_back(parseVariableDeclaration(options));
}
@@ -1213,7 +1220,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
if (m_scanner->currentToken() == Token::StringLiteral)
{
if (m_scanner->currentLiteral() != "evmasm")
fatalParserError("Only \"evmasm\" supported.");
fatalParserError(4531_error, "Only \"evmasm\" supported.");
// This can be used in the future to set the dialect.
m_scanner->next();
}
@@ -1376,7 +1383,7 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const
ASTNodeFactory eventCallNodeFactory(*this);
if (m_scanner->currentToken() != Token::Identifier)
fatalParserError("Expected event name or path.");
fatalParserError(5620_error, "Expected event name or path.");
IndexAccessedPath iap;
while (true)
@@ -1844,7 +1851,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
nodeFactory.markEndPosition();
m_scanner->next();
if (m_scanner->currentToken() == Token::Illegal)
fatalParserError(to_string(m_scanner->currentError()));
fatalParserError(5428_error, to_string(m_scanner->currentError()));
expression = nodeFactory.createNode<Literal>(token, make_shared<ASTString>(literal));
break;
}
@@ -1875,7 +1882,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
if (m_scanner->currentToken() != Token::Comma && m_scanner->currentToken() != oppositeToken)
components.push_back(parseExpression());
else if (isArray)
parserError("Expected expression (inline array elements cannot be omitted).");
parserError(4799_error, "Expected expression (inline array elements cannot be omitted).");
else
components.push_back(ASTPointer<Expression>());
@@ -1890,7 +1897,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
break;
}
case Token::Illegal:
fatalParserError(to_string(m_scanner->currentError()));
fatalParserError(8936_error, to_string(m_scanner->currentError()));
break;
default:
if (TokenTraits::isElementaryTypeName(token))
@@ -1906,7 +1913,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
m_scanner->next();
}
else
fatalParserError(string("Expected primary expression."));
fatalParserError(6933_error, "Expected primary expression.");
break;
}
return expression;
@@ -1964,7 +1971,7 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
m_scanner->peekNextToken() == Token::RBrace
)
{
parserError("Unexpected trailing comma.");
parserError(2074_error, "Unexpected trailing comma.");
m_scanner->next();
}
@@ -2083,7 +2090,7 @@ ASTPointer<TypeName> Parser::typeNameFromIndexAccessStructure(Parser::IndexAcces
for (auto const& lengthExpression: _iap.indices)
{
if (lengthExpression.end)
parserError(lengthExpression.location, "Expected array length expression.");
parserError(5464_error, lengthExpression.location, "Expected array length expression.");
nodeFactory.setLocation(lengthExpression.location);
type = nodeFactory.createNode<ArrayTypeName>(type, lengthExpression.start);
}