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

This commit is contained in:
chriseth
2020-09-29 09:53:50 +02:00
509 changed files with 1813 additions and 932 deletions
+50 -7
View File
@@ -38,15 +38,45 @@ namespace
{
template <class T, class B>
bool hasEqualNameAndParameters(T const& _a, B const& _b)
bool hasEqualParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
return FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
}
template<typename T>
map<ASTString, vector<T const*>> filterDeclarations(
map<ASTString, vector<Declaration const*>> const& _declarations)
{
map<ASTString, vector<T const*>> filteredDeclarations;
for (auto const& [name, overloads]: _declarations)
for (auto const* declaration: overloads)
if (auto typedDeclaration = dynamic_cast<T const*>(declaration))
filteredDeclarations[name].push_back(typedDeclaration);
return filteredDeclarations;
}
}
bool ContractLevelChecker::check(SourceUnit const& _sourceUnit)
{
bool noErrors = true;
findDuplicateDefinitions(
filterDeclarations<FunctionDefinition>(*_sourceUnit.annotation().exportedSymbols)
);
// This check flags duplicate free events when free events become
// a Solidity feature
findDuplicateDefinitions(
filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols)
);
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
noErrors = false;
for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!check(*contract))
noErrors = false;
return noErrors;
}
bool ContractLevelChecker::check(ContractDefinition const& _contract)
@@ -143,8 +173,21 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j)
if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
if (hasEqualParameters(*overloads[i], *overloads[j]))
{
solAssert(
(
dynamic_cast<ContractDefinition const*>(overloads[i]->scope()) &&
dynamic_cast<ContractDefinition const*>(overloads[j]->scope()) &&
overloads[i]->name() == overloads[j]->name()
) ||
(
dynamic_cast<SourceUnit const*>(overloads[i]->scope()) &&
dynamic_cast<SourceUnit const*>(overloads[j]->scope())
),
"Override is neither a namesake function/event in contract scope nor "
"a free function/event (alias)."
);
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
}
+6 -3
View File
@@ -39,7 +39,7 @@ namespace solidity::frontend
/**
* Component that verifies overloads, abstract contracts, function clashes and others
* checks at contract or function level.
* checks at file, contract, or function level.
*/
class ContractLevelChecker
{
@@ -51,11 +51,14 @@ public:
m_errorReporter(_errorReporter)
{}
/// Performs checks on the given source ast.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
bool check(SourceUnit const& _sourceUnit);
private:
/// Performs checks on the given contract.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
bool check(ContractDefinition const& _contract);
private:
/// Checks that two functions defined in this contract with the same name have different
/// arguments and that there is at most one constructor.
void checkDuplicateFunctions(ContractDefinition const& _contract);
+10 -17
View File
@@ -109,7 +109,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
else
for (Declaration const* declaration: declarations)
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, alias.alias.get(), &alias.location, true, false, m_errorReporter
target, *declaration, alias.alias.get(), &alias.location, false, m_errorReporter
))
error = true;
}
@@ -117,10 +117,11 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& nameAndDeclaration: scope->second->declarations())
for (auto const& declaration: nameAndDeclaration.second)
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, &nameAndDeclaration.first, &imp->location(), true, false, m_errorReporter
target, *declaration, &nameAndDeclaration.first, &imp->location(), false, m_errorReporter
))
error = true;
}
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
return !error;
}
@@ -446,7 +447,6 @@ bool DeclarationRegistrationHelper::registerDeclaration(
Declaration const& _declaration,
string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
bool _inactive,
ErrorReporter& _errorReporter
)
@@ -456,7 +456,11 @@ bool DeclarationRegistrationHelper::registerDeclaration(
string name = _name ? *_name : _declaration.name();
Declaration const* shadowedDeclaration = nullptr;
if (_warnOnShadow && !name.empty() && _container.enclosingContainer())
// Do not warn about shadowing for structs and enums because their members are
// not accessible without prefixes. Also do not warn about event parameters
// because they do not participate in any proper scope.
bool warnOnShadow = !_declaration.isStructMember() && !_declaration.isEnumValue() && !_declaration.isEventParameter();
if (warnOnShadow && !name.empty() && _container.enclosingContainer())
for (auto const* decl: _container.enclosingContainer()->resolveName(name, true, true))
shadowedDeclaration = decl;
@@ -533,7 +537,6 @@ bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit)
{
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
ASTVisitor::endVisit(_sourceUnit);
}
@@ -623,23 +626,13 @@ void DeclarationRegistrationHelper::closeCurrentScope()
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration)
{
solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope.");
bool warnAboutShadowing = true;
// Do not warn about shadowing for structs and enums because their members are
// not accessible without prefixes. Also do not warn about event parameters
// because they don't participate in any proper scope.
if (
dynamic_cast<StructDefinition const*>(m_currentScope) ||
dynamic_cast<EnumDefinition const*>(m_currentScope) ||
dynamic_cast<EventDefinition const*>(m_currentScope)
)
warnAboutShadowing = false;
solAssert(m_currentScope == _declaration.scope(), "Unexpected current scope.");
// Register declaration as inactive if we are in block scope.
bool inactive =
(dynamic_cast<Block const*>(m_currentScope) || dynamic_cast<ForStatement const*>(m_currentScope));
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, inactive, m_errorReporter);
solAssert(_declaration.annotation().scope == m_currentScope, "");
solAssert(_declaration.annotation().contract == m_currentContract, "");
@@ -152,7 +152,6 @@ public:
Declaration const& _declaration,
std::string const* _name,
langutil::SourceLocation const* _errorLocation,
bool _warnOnShadow,
bool _inactive,
langutil::ErrorReporter& _errorReporter
);
+3 -3
View File
@@ -2922,6 +2922,9 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
isPure = false;
}
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
TypePointer exprType = type(_access.baseExpression());
if (exprType->category() == Type::Category::TypeType)
{
@@ -2936,14 +2939,11 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
else if (arrayType->baseType()->isDynamicallyEncoded())
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
return false;
}
+23 -5
View File
@@ -495,6 +495,24 @@ string Scopable::sourceUnitName() const
return *sourceUnit().annotation().path;
}
bool Declaration::isEnumValue() const
{
solAssert(scope(), "");
return dynamic_cast<EnumDefinition const*>(scope());
}
bool Declaration::isStructMember() const
{
solAssert(scope(), "");
return dynamic_cast<StructDefinition const*>(scope());
}
bool Declaration::isEventParameter() const
{
solAssert(scope(), "");
return dynamic_cast<EventDefinition const*>(scope());
}
DeclarationAnnotation& Declaration::annotation() const
{
return initAnnotation<DeclarationAnnotation>();
@@ -617,11 +635,6 @@ bool VariableDeclaration::isLibraryFunctionParameter() const
return false;
}
bool VariableDeclaration::isEventParameter() const
{
return dynamic_cast<EventDefinition const*>(scope()) != nullptr;
}
bool VariableDeclaration::hasReferenceOrMappingType() const
{
solAssert(typeName().annotation().type, "Can only be called after reference resolution");
@@ -629,6 +642,11 @@ bool VariableDeclaration::hasReferenceOrMappingType() const
return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type);
}
bool VariableDeclaration::isStateVariable() const
{
return dynamic_cast<ContractDefinition const*>(scope());
}
set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
{
using Location = VariableDeclaration::Location;
+8 -9
View File
@@ -258,10 +258,16 @@ public:
bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
virtual bool isVisibleViaContractTypeAccess() const { return false; }
virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
/// @returns true if this is a declaration of an enum member.
bool isEnumValue() const;
/// @returns true if this is a declaration of a struct member.
bool isStructMember() const;
/// @returns true if this is a declaration of a parameter of an event.
bool isEventParameter() const;
/// @returns the type of expressions referencing this declaration.
/// This can only be called once types of variable declarations have already been resolved.
virtual TypePointer type() const = 0;
@@ -899,7 +905,6 @@ public:
ASTPointer<Expression> _value,
Visibility _visibility,
ASTPointer<StructuredDocumentation> const _documentation = nullptr,
bool _isStateVar = false,
bool _isIndexed = false,
Mutability _mutability = Mutability::Mutable,
ASTPointer<OverrideSpecifier> _overrides = nullptr,
@@ -909,7 +914,6 @@ public:
StructurallyDocumented(std::move(_documentation)),
m_typeName(std::move(_type)),
m_value(std::move(_value)),
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_mutability(_mutability),
m_overrides(std::move(_overrides)),
@@ -952,15 +956,11 @@ public:
bool isConstructorParameter() const;
/// @returns true iff this variable is a parameter(or return parameter of a library function
bool isLibraryFunctionParameter() const;
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
/// in the body of a function or modifier.
/// @returns true if this variable is a parameter of an event.
bool isEventParameter() const;
/// @returns true if the type of the variable is a reference or mapping type, i.e.
/// array, struct or mapping. These types can take a data location (and often require it).
/// Can only be called after reference resolution.
bool hasReferenceOrMappingType() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isStateVariable() const;
bool isIndexed() const { return m_isIndexed; }
Mutability mutability() const { return m_mutability; }
bool isConstant() const { return m_mutability == Mutability::Constant; }
@@ -989,7 +989,6 @@ private:
/// Initially assigned value, can be missing. For local variables, this is stored inside
/// VariableDeclarationStatement and not here.
ASTPointer<Expression> m_value;
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
/// Whether the variable is "constant", "immutable" or non-marked (mutable).
Mutability m_mutability = Mutability::Mutable;
-1
View File
@@ -454,7 +454,6 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
nullOrCast<Expression>(member(_node, "value")),
visibility(_node),
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
mutability,
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
+6 -6
View File
@@ -836,7 +836,7 @@ void BMC::checkCondition(
case smtutil::CheckResult::SATISFIABLE:
{
std::ostringstream message;
message << _description << " happens here.";
message << "BMC: " << _description << " happens here.";
if (_callStack.size())
{
std::ostringstream modelMessage;
@@ -865,13 +865,13 @@ void BMC::checkCondition(
case smtutil::CheckResult::UNSATISFIABLE:
break;
case smtutil::CheckResult::UNKNOWN:
m_errorReporter.warning(_errorMightHappen, _location, _description + " might happen here.", secondaryLocation);
m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + _description + " might happen here.", secondaryLocation);
break;
case smtutil::CheckResult::CONFLICTING:
m_errorReporter.warning(1584_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
m_errorReporter.warning(1584_error, _location, "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
break;
case smtutil::CheckResult::ERROR:
m_errorReporter.warning(1823_error, _location, "Error trying to invoke SMT solver.");
m_errorReporter.warning(1823_error, _location, "BMC: Error trying to invoke SMT solver.");
break;
}
@@ -919,13 +919,13 @@ void BMC::checkBooleanNotConstant(
if (positiveResult == smtutil::CheckResult::SATISFIABLE)
{
solAssert(negatedResult == smtutil::CheckResult::UNSATISFIABLE, "");
description = "Condition is always true.";
description = "BMC: Condition is always true.";
}
else
{
solAssert(positiveResult == smtutil::CheckResult::UNSATISFIABLE, "");
solAssert(negatedResult == smtutil::CheckResult::SATISFIABLE, "");
description = "Condition is always false.";
description = "BMC: Condition is always false.";
}
m_errorReporter.warning(
6838_error,
+5 -5
View File
@@ -1103,10 +1103,10 @@ pair<CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression c
case CheckResult::UNKNOWN:
break;
case CheckResult::CONFLICTING:
m_outerErrorReporter.warning(1988_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
m_outerErrorReporter.warning(1988_error, _location, "CHC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
break;
case CheckResult::ERROR:
m_outerErrorReporter.warning(1218_error, _location, "Error trying to invoke SMT solver.");
m_outerErrorReporter.warning(1218_error, _location, "CHC: Error trying to invoke SMT solver.");
break;
}
return {result, cex};
@@ -1264,21 +1264,21 @@ void CHC::checkAndReportTarget(
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
_satMsg,
"CHC: " + _satMsg,
SecondarySourceLocation().append("\nCounterexample:\n" + *cex, SourceLocation{})
);
else
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
_satMsg
"CHC: " + _satMsg
);
}
else if (!_unknownMsg.empty())
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
_unknownMsg
"CHC: " + _unknownMsg
);
}
+65 -9
View File
@@ -663,6 +663,9 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
case FunctionType::Kind::Event:
// These can be safely ignored.
break;
case FunctionType::Kind::ObjectCreation:
visitObjectCreation(_funCall);
return;
default:
m_errorReporter.warning(
4588_error,
@@ -735,6 +738,25 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1));
}
void SMTEncoder::visitObjectCreation(FunctionCall const& _funCall)
{
auto const& args = _funCall.arguments();
solAssert(args.size() >= 1, "");
auto argType = args.front()->annotation().type->category();
solAssert(argType == Type::Category::Integer || argType == Type::Category::RationalNumber, "");
smtutil::Expression arraySize = expr(*args.front());
setSymbolicUnknownValue(arraySize, TypeProvider::uint256(), m_context);
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_funCall));
solAssert(symbArray, "");
smt::setSymbolicZeroValue(*symbArray, m_context);
auto zeroElements = symbArray->elements();
symbArray->increaseIndex();
m_context.addAssertion(symbArray->length() == arraySize);
m_context.addAssertion(symbArray->elements() == zeroElements);
}
void SMTEncoder::endVisit(Identifier const& _identifier)
{
if (auto decl = identifierToVariable(_identifier))
@@ -834,7 +856,20 @@ void SMTEncoder::endVisit(Literal const& _literal)
else if (smt::isBool(type))
defineExpr(_literal, smtutil::Expression(_literal.token() == Token::TrueLiteral ? true : false));
else if (smt::isStringLiteral(type))
{
createExpr(_literal);
// Add constraints for the length and values as it is known.
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_literal));
solAssert(symbArray, "");
auto value = _literal.value();
m_context.addAssertion(symbArray->length() == value.length());
for (size_t i = 0; i < value.length(); i++)
m_context.addAssertion(
smtutil::Expression::select(symbArray->elements(), i) == size_t(value[i])
);
}
else
{
m_errorReporter.warning(
@@ -977,13 +1012,36 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
if (_indexAccess.annotation().type->category() == Type::Category::TypeType)
return;
if (_indexAccess.baseExpression().annotation().type->category() == Type::Category::FixedBytes)
if (auto const* type = dynamic_cast<FixedBytesType const*>(_indexAccess.baseExpression().annotation().type))
{
m_errorReporter.warning(
7989_error,
_indexAccess.location(),
"Assertion checker does not yet support index accessing fixed bytes."
);
smtutil::Expression base = expr(_indexAccess.baseExpression());
if (type->numBytes() == 1)
defineExpr(_indexAccess, base);
else
{
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_indexAccess.baseExpression().annotation().type);
solAssert(!isSigned, "");
solAssert(bvSize >= 16, "");
solAssert(bvSize % 8 == 0, "");
smtutil::Expression idx = expr(*_indexAccess.indexExpression());
auto bvBase = smtutil::Expression::int2bv(base, bvSize);
auto bvShl = smtutil::Expression::int2bv(idx * 8, bvSize);
auto bvShr = smtutil::Expression::int2bv(bvSize - 8, bvSize);
auto result = (bvBase << bvShl) >> bvShr;
auto anyValue = expr(_indexAccess);
m_context.expression(_indexAccess)->increaseIndex();
unsigned numBytes = bvSize / 8;
auto withBound = smtutil::Expression::ite(
idx < numBytes,
smtutil::Expression::bv2int(result, false),
anyValue
);
defineExpr(_indexAccess, withBound);
}
return;
}
@@ -1654,9 +1712,7 @@ void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression con
// This is a special case where the SMT sorts are different.
// For now we are unaware of other cases where this happens, but if they do appear
// we should extract this into an `implicitConversion` function.
if (_variable.type()->category() != Type::Category::Array || _value.annotation().type->category() != Type::Category::StringLiteral)
assignment(_variable, expr(_value, _variable.type()));
// TODO else { store each string literal byte into the array }
assignment(_variable, expr(_value, _variable.type()));
}
void SMTEncoder::assignment(VariableDeclaration const& _variable, smtutil::Expression const& _value)
+1
View File
@@ -137,6 +137,7 @@ protected:
void visitAssert(FunctionCall const& _funCall);
void visitRequire(FunctionCall const& _funCall);
void visitGasLeft(FunctionCall const& _funCall);
void visitObjectCreation(FunctionCall const& _funCall);
void visitTypeConversion(FunctionCall const& _funCall);
void visitFunctionIdentifier(Identifier const& _identifier);
+3 -5
View File
@@ -366,12 +366,10 @@ bool CompilerStack::analyze()
// This also calculates whether a contract is abstract, which is needed by the
// type checker.
ContractLevelChecker contractLevelChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!contractLevelChecker.check(*contract))
noErrors = false;
if (auto sourceAst = source->ast)
noErrors = contractLevelChecker.check(*sourceAst);
// Requires ContractLevelChecker
DocStringAnalyser docStringAnalyser(m_errorReporter);
-1
View File
@@ -800,7 +800,6 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
value,
visibility,
documentation,
_options.isStateVariable,
isIndexed,
mutability,
overrides,