diff options
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/analysis/ControlFlowAnalyzer.cpp | 5 | ||||
-rw-r--r-- | libsolidity/analysis/DeclarationContainer.cpp | 7 | ||||
-rw-r--r-- | libsolidity/analysis/DocStringAnalyser.cpp | 34 | ||||
-rw-r--r-- | libsolidity/analysis/DocStringAnalyser.h | 11 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.cpp | 11 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.h | 2 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.cpp | 192 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 164 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.h | 8 | ||||
-rw-r--r-- | libsolidity/analysis/ViewPureChecker.cpp | 2 | ||||
-rw-r--r-- | libsolidity/ast/AST.cpp | 123 | ||||
-rw-r--r-- | libsolidity/ast/AST.h | 16 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 187 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 32 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 42 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 37 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 21 | ||||
-rw-r--r-- | libsolidity/interface/Natspec.cpp | 67 | ||||
-rw-r--r-- | libsolidity/interface/Natspec.h | 6 | ||||
-rw-r--r-- | libsolidity/interface/StandardCompiler.cpp | 9 |
20 files changed, 689 insertions, 287 deletions
diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index 483d08c8..ab6569be 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -75,7 +75,10 @@ void ControlFlowAnalyzer::checkUnassignedStorageReturnValues( { auto& unassignedAtFunctionEntry = unassigned[_functionEntry]; for (auto const& returnParameter: _function.returnParameterList()->parameters()) - if (returnParameter->type()->dataStoredIn(DataLocation::Storage)) + if ( + returnParameter->type()->dataStoredIn(DataLocation::Storage) || + returnParameter->type()->category() == Type::Category::Mapping + ) unassignedAtFunctionEntry.insert(returnParameter.get()); } diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 347daaf8..5f980788 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -138,19 +138,22 @@ vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _n vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const { static size_t const MAXIMUM_EDIT_DISTANCE = 2; + // because the function below has quadratic runtime - it will not magically improve once a better algorithm is discovered ;) + // since 80 is the suggested line length limit, we use 80^2 as length threshold + static size_t const MAXIMUM_LENGTH_THRESHOLD = 80 * 80; vector<ASTString> similar; for (auto const& declaration: m_declarations) { string const& declarationName = declaration.first; - if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE)) + if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD)) similar.push_back(declarationName); } for (auto const& declaration: m_invisibleDeclarations) { string const& declarationName = declaration.first; - if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE)) + if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD)) similar.push_back(declarationName); } diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index b3fb5258..c1b97def 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -48,7 +48,10 @@ bool DocStringAnalyser::visit(ContractDefinition const& _contract) bool DocStringAnalyser::visit(FunctionDefinition const& _function) { - handleCallable(_function, _function, _function.annotation()); + if (_function.isConstructor()) + handleConstructor(_function, _function, _function.annotation()); + else + handleCallable(_function, _function, _function.annotation()); return true; } @@ -66,15 +69,11 @@ bool DocStringAnalyser::visit(EventDefinition const& _event) return true; } -void DocStringAnalyser::handleCallable( +void DocStringAnalyser::checkParameters( CallableDeclaration const& _callable, - Documented const& _node, DocumentedAnnotation& _annotation ) { - static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"}; - parseDocStrings(_node, _annotation, validTags, "functions"); - set<string> validParams; for (auto const& p: _callable.parameters()) validParams.insert(p->name()); @@ -89,6 +88,29 @@ void DocStringAnalyser::handleCallable( i->second.paramName + "\" not found in the parameter list of the function." ); + +} + +void DocStringAnalyser::handleConstructor( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation +) +{ + static const set<string> validTags = set<string>{"author", "dev", "notice", "param"}; + parseDocStrings(_node, _annotation, validTags, "constructor"); + checkParameters(_callable, _annotation); +} + +void DocStringAnalyser::handleCallable( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation +) +{ + static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"}; + parseDocStrings(_node, _annotation, validTags, "functions"); + checkParameters(_callable, _annotation); } void DocStringAnalyser::parseDocStrings( diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index 158b4060..5d339428 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -48,6 +48,17 @@ private: virtual bool visit(ModifierDefinition const& _modifier) override; virtual bool visit(EventDefinition const& _event) override; + void checkParameters( + CallableDeclaration const& _callable, + DocumentedAnnotation& _annotation + ); + + void handleConstructor( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation + ); + void handleCallable( CallableDeclaration const& _callable, Documented const& _node, diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index c5c429ce..b452a49a 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -626,6 +626,17 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(FunctionTypeName& _funTypeName) +{ + enterNewSubScope(_funTypeName); + return true; +} + +void DeclarationRegistrationHelper::endVisit(FunctionTypeName&) +{ + closeCurrentScope(); +} + bool DeclarationRegistrationHelper::visit(Block& _block) { _block.setScope(m_currentScope); diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 8178ee17..a72c21e3 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -171,6 +171,8 @@ private: void endVisit(FunctionDefinition& _function) override; bool visit(ModifierDefinition& _modifier) override; void endVisit(ModifierDefinition& _modifier) override; + bool visit(FunctionTypeName& _funTypeName) override; + void endVisit(FunctionTypeName& _funTypeName) override; bool visit(Block& _block) override; void endVisit(Block& _block) override; bool visit(ForStatement& _forLoop) override; diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index fa0888dd..f33de7b7 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -30,7 +30,10 @@ #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/interface/ErrorReporter.h> +#include <libdevcore/StringUtils.h> + #include <boost/algorithm/string.hpp> +#include <boost/range/adaptor/transformed.hpp> using namespace std; using namespace dev; @@ -155,7 +158,10 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration)) _typeName.annotation().type = make_shared<ContractType>(*contract); else + { + _typeName.annotation().type = make_shared<TupleType>(); typeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); + } } void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) @@ -166,13 +172,13 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) case VariableDeclaration::Visibility::External: break; default: - typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); return; } if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) { - typeError(_typeName.location(), "Only external function types can be payable."); + fatalTypeError(_typeName.location(), "Only external function types can be payable."); return; } @@ -182,7 +188,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) solAssert(t->annotation().type, "Type not set for parameter."); if (!t->annotation().type->canBeUsedExternally(false)) { - typeError(t->location(), "Internal type cannot be used for external function type."); + fatalTypeError(t->location(), "Internal type cannot be used for external function type."); return; } } @@ -300,6 +306,9 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) if (_variable.annotation().type) return; + if (_variable.isConstant() && !_variable.isStateVariable()) + m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables."); + if (!_variable.typeName()) { // This can still happen in very unusual cases where a developer uses constructs, such as @@ -309,127 +318,92 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) // after this step. return; } - - TypePointer type; - type = _variable.typeName()->annotation().type; using Location = VariableDeclaration::Location; Location varLoc = _variable.referenceLocation(); DataLocation typeLoc = DataLocation::Memory; - // References are forced to calldata for external function parameters (not return) - // and memory for parameters (also return) of publicly visible functions. - // They default to memory for function parameters and storage for local variables. - // As an exception, "storage" is allowed for library functions. - if (auto ref = dynamic_cast<ReferenceType const*>(type.get())) + + set<Location> allowedDataLocations = _variable.allowedDataLocations(); + if (!allowedDataLocations.count(varLoc)) { - bool isPointer = true; - if (_variable.isExternalCallableParameter()) + auto locationToString = [](VariableDeclaration::Location _location) -> string { - auto const& contract = dynamic_cast<ContractDefinition const&>( - *dynamic_cast<Declaration const&>(*_variable.scope()).scope() - ); - if (contract.isLibrary()) - { - if (varLoc == Location::Memory) - fatalTypeError(_variable.location(), - "Location has to be calldata or storage for external " - "library functions (remove the \"memory\" keyword)." - ); - } - else + switch (_location) { - // force location of external function parameters (not return) to calldata - if (varLoc != Location::CallData && varLoc != Location::Default) - fatalTypeError(_variable.location(), - "Location has to be calldata for external functions " - "(remove the \"memory\" or \"storage\" keyword)." - ); + case Location::Memory: return "\"memory\""; + case Location::Storage: return "\"storage\""; + case Location::CallData: return "\"calldata\""; + case Location::Default: return "none"; } - if (varLoc == Location::Default || varLoc == Location::CallData) - typeLoc = DataLocation::CallData; - else - typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage; - } - else if (_variable.isCallableParameter() && dynamic_cast<Declaration const&>(*_variable.scope()).isPublic()) + return {}; + }; + + string errorString; + if (!_variable.hasReferenceOrMappingType()) + errorString = "Data location can only be specified for array, struct or mapping types"; + else { - auto const& contract = dynamic_cast<ContractDefinition const&>( - *dynamic_cast<Declaration const&>(*_variable.scope()).scope() + errorString = "Data location must be " + + joinHumanReadable( + allowedDataLocations | boost::adaptors::transformed(locationToString), + ", ", + " or " ); - // force locations of public or external function (return) parameters to memory - if (varLoc != Location::Memory && varLoc != Location::Default && !contract.isLibrary()) - fatalTypeError(_variable.location(), - "Location has to be memory for publicly visible functions " - "(remove the \"storage\" or \"calldata\" keyword)." - ); - if (varLoc == Location::Default || !contract.isLibrary()) - typeLoc = DataLocation::Memory; + if (_variable.isCallableParameter()) + errorString += + " for " + + string(_variable.isReturnParameter() ? "return " : "") + + "parameter in" + + string(_variable.isExternalCallableParameter() ? " external" : "") + + " function"; else - { - if (varLoc == Location::CallData) - fatalTypeError(_variable.location(), - "Location cannot be calldata for non-external functions " - "(remove the \"calldata\" keyword)." - ); - typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage; - } + errorString += " for variable"; } - else + errorString += ", but " + locationToString(varLoc) + " was given."; + typeError(_variable.location(), errorString); + + solAssert(!allowedDataLocations.empty(), ""); + varLoc = *allowedDataLocations.begin(); + } + + // Find correct data location. + if (_variable.isEventParameter()) + { + solAssert(varLoc == Location::Default, ""); + typeLoc = DataLocation::Memory; + } + else if (_variable.isStateVariable()) + { + solAssert(varLoc == Location::Default, ""); + typeLoc = _variable.isConstant() ? DataLocation::Memory : DataLocation::Storage; + } + else if ( + dynamic_cast<StructDefinition const*>(_variable.scope()) || + dynamic_cast<EnumDefinition const*>(_variable.scope()) + ) + // The actual location will later be changed depending on how the type is used. + typeLoc = DataLocation::Storage; + else + switch (varLoc) { - if (_variable.isConstant()) - { - if (varLoc != Location::Default && varLoc != Location::Memory) - fatalTypeError( - _variable.location(), - "Data location has to be \"memory\" (or unspecified) for constants." - ); - typeLoc = DataLocation::Memory; - } - else if (varLoc == Location::Default) - { - if (_variable.isCallableParameter()) - typeLoc = DataLocation::Memory; - else - { - typeLoc = DataLocation::Storage; - if (_variable.isLocalVariable()) - typeError( - _variable.location(), - "Data location must be specified as either \"memory\" or \"storage\"." - ); - } - } - else - { - switch (varLoc) - { - case Location::Memory: - typeLoc = DataLocation::Memory; - break; - case Location::Storage: - typeLoc = DataLocation::Storage; - break; - case Location::CallData: - fatalTypeError(_variable.location(), - "Variable cannot be declared as \"calldata\" (remove the \"calldata\" keyword)." - ); - break; - default: - solAssert(false, "Unknown data location"); - } - } - isPointer = !_variable.isStateVariable(); + case Location::Memory: + typeLoc = DataLocation::Memory; + break; + case Location::Storage: + typeLoc = DataLocation::Storage; + break; + case Location::CallData: + typeLoc = DataLocation::CallData; + break; + case Location::Default: + solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set."); } - type = ref->copyForLocation(typeLoc, isPointer); - } - else if (dynamic_cast<MappingType const*>(type.get())) + + TypePointer type = _variable.typeName()->annotation().type; + if (auto ref = dynamic_cast<ReferenceType const*>(type.get())) { - if (_variable.isLocalVariable() && varLoc != Location::Storage) - typeError( - _variable.location(), - "Data location for mappings must be specified as \"storage\"." - ); + bool isPointer = !_variable.isStateVariable(); + type = ref->copyForLocation(typeLoc, isPointer); } - else if (varLoc != Location::Default && !ref) - typeError(_variable.location(), "Data location can only be given for array or struct types."); _variable.annotation().type = type; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index bcc3757a..fca64f6a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -525,6 +525,75 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) ); } +TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2) +{ + vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); + if (arguments.size() != 2) + m_errorReporter.typeError( + _functionCall.location(), + "This function takes two arguments, but " + + toString(arguments.size()) + + " were provided." + ); + if (arguments.size() >= 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory))) + m_errorReporter.typeError( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*arguments.front())->toString() + + " to bytes memory requested." + ); + + TypePointer returnType = make_shared<TupleType>(); + + if (arguments.size() < 2) + return returnType; + + // The following is a rather syntactic restriction, but we check it here anyway: + // The second argument has to be a tuple expression containing type names. + TupleExpression const* tupleExpression = dynamic_cast<TupleExpression const*>(arguments[1].get()); + if (!tupleExpression) + { + m_errorReporter.typeError( + arguments[1]->location(), + "The second argument to \"abi.decode\" has to be a tuple of types." + ); + return returnType; + } + + vector<TypePointer> components; + for (auto const& typeArgument: tupleExpression->components()) + { + solAssert(typeArgument, ""); + if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument).get())) + { + TypePointer actualType = argTypeType->actualType(); + solAssert(actualType, ""); + // We force memory because the parser currently cannot handle + // data locations. Furthermore, storage can be a little dangerous and + // calldata is not really implemented anyway. + actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType); + solAssert( + !actualType->dataStoredIn(DataLocation::CallData) && + !actualType->dataStoredIn(DataLocation::Storage), + "" + ); + if (!actualType->fullEncodingType(false, _abiEncoderV2, false)) + m_errorReporter.typeError( + typeArgument->location(), + "Decoding type " + actualType->toString(false) + " not supported." + ); + components.push_back(actualType); + } + else + { + m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name."); + components.push_back(make_shared<TupleType>()); + } + } + return make_shared<TupleType>(components); +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); @@ -580,9 +649,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) bool TypeChecker::visit(StructDefinition const& _struct) { - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) - m_errorReporter.typeError(_struct.location(), "Structs cannot be defined in interfaces."); - for (ASTPointer<VariableDeclaration> const& member: _struct.members()) if (!type(*member)->canBeStored()) m_errorReporter.typeError(member->location(), "Type cannot be used in struct."); @@ -610,7 +676,10 @@ bool TypeChecker::visit(StructDefinition const& _struct) if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr) m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition."); + bool insideStruct = true; + swap(insideStruct, m_insideStruct); ASTNode::listAccept(_struct.members(), *this); + m_insideStruct = insideStruct; return false; } @@ -629,7 +698,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function) } for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters()) { - if (!type(*var)->canLiveOutsideStorage()) + if ( + type(*var)->category() == Type::Category::Mapping && + !type(*var)->dataStoredIn(DataLocation::Storage) + ) + m_errorReporter.typeError(var->location(), "Mapping types can only have a data location of \"storage\"."); + else if ( + !type(*var)->canLiveOutsideStorage() && + _function.visibility() > FunctionDefinition::Visibility::Internal + ) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); @@ -690,10 +767,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { // Forbid any variable declarations inside interfaces unless they are part of - // a function's input/output parameters. + // * a function's input/output parameters, + // * or inside of a struct definition. if ( m_scope->contractKind() == ContractDefinition::ContractKind::Interface && !_variable.isCallableParameter() + && !m_insideStruct ) m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); @@ -711,8 +790,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) expectType(*_variable.value(), *varType); if (_variable.isConstant()) { - if (!_variable.isStateVariable()) - m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier."); if (!_variable.type()->isValueType()) { bool allowed = false; @@ -742,7 +819,9 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) ) m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables."); - if (varType->category() == Type::Category::Array) + switch (varType->category()) + { + case Type::Category::Array: if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get())) if ( ((arrayType->location() == DataLocation::Memory) || @@ -750,6 +829,18 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) !arrayType->validForCalldata() ) m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded."); + break; + case Type::Category::Mapping: + if (auto mappingType = dynamic_cast<MappingType const*>(varType.get())) + if ( + mappingType->keyType()->isDynamicallySized() && + _variable.visibility() == Declaration::Visibility::Public + ) + m_errorReporter.typeError(_variable.location(), "Dynamically-sized keys for public mappings are not supported."); + break; + default: + break; + } return false; } @@ -818,7 +909,17 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters()) { if (var->isIndexed()) + { numIndexed++; + if ( + _eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && + dynamic_cast<ReferenceType const*>(type(*var).get()) + ) + m_errorReporter.typeError( + var->location(), + "Indexed reference types cannot yet be used with ABIEncoderV2." + ); + } if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (!type(*var)->interfaceType(false)) @@ -1282,7 +1383,8 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) if ( kind == FunctionType::Kind::BareCall || kind == FunctionType::Kind::BareCallCode || - kind == FunctionType::Kind::BareDelegateCall + kind == FunctionType::Kind::BareDelegateCall || + kind == FunctionType::Kind::BareStaticCall ) m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used."); else if (kind == FunctionType::Kind::Send) @@ -1626,10 +1728,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else { TypePointer const& argType = type(*arguments.front()); + // Resulting data location is memory unless we are converting from a reference + // type with a different data location. + // (data location cannot yet be specified for type conversions) + DataLocation dataLoc = DataLocation::Memory; if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get())) - // do not change the data location when converting - // (data location cannot yet be specified for type conversions) - resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType); + dataLoc = argRefType->location(); + resultType = ReferenceType::copyForLocationIfReference(dataLoc, resultType); if (!argType->isExplicitlyConvertibleTo(*resultType)) m_errorReporter.typeError( _functionCall.location(), @@ -1674,6 +1779,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) return false; } + if (functionType->kind() == FunctionType::Kind::BareStaticCall && !m_evmVersion.hasStaticCall()) + m_errorReporter.typeError(_functionCall.location(), "\"staticcall\" is not supported by the VM version."); + auto returnTypes = allowDynamicTypes ? functionType->returnParameterTypes() : @@ -1716,7 +1824,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } } - if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size()) + bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2); + + if (functionType->kind() == FunctionType::Kind::ABIDecode) + _functionCall.annotation().type = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2); + else if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size()) { solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, ""); m_errorReporter.typeError( @@ -1750,7 +1862,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else if ( functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCallCode || - functionType->kind() == FunctionType::Kind::BareDelegateCall + functionType->kind() == FunctionType::Kind::BareDelegateCall || + functionType->kind() == FunctionType::Kind::BareStaticCall ) { if (arguments.empty()) @@ -1771,8 +1884,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } else if (isPositionalCall) { - bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2); - for (size_t i = 0; i < arguments.size(); ++i) { auto const& argType = type(*arguments[i]); @@ -1785,7 +1896,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); errored = true; } - if (!errored && !argType->fullEncodingType(false, abiEncodeV2, !functionType->padArguments())) + if (!errored && !argType->fullEncodingType(false, abiEncoderV2, !functionType->padArguments())) m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded."); } else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) @@ -1800,7 +1911,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if ( functionType->kind() == FunctionType::Kind::BareCall || functionType->kind() == FunctionType::Kind::BareCallCode || - functionType->kind() == FunctionType::Kind::BareDelegateCall + functionType->kind() == FunctionType::Kind::BareDelegateCall || + functionType->kind() == FunctionType::Kind::BareStaticCall ) msg += " This function requires a single bytes argument. If all your arguments are value types, you can use abi.encode(...) to properly generate it."; else if ( @@ -2348,22 +2460,6 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte "." ); } - - if ( - type(_expression)->category() == Type::Category::RationalNumber && - _expectedType.category() == Type::Category::FixedBytes - ) - { - auto literal = dynamic_cast<Literal const*>(&_expression); - - if (literal && !literal->isHexNumber()) - m_errorReporter.warning( - _expression.location(), - "Decimal literal assigned to bytesXX variable will be left-aligned. " - "Use an explicit conversion to silence this warning." - ); - } - } void TypeChecker::requireLValue(Expression const& _expression) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b696de85..4be0d1e4 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -91,6 +91,11 @@ private: // and reports an error, if not. void checkExpressionAssignment(Type const& _type, Expression const& _expression); + /// Performs type checks for ``abi.decode(bytes memory, (...))`` and returns the + /// return type (which is basically the second argument) if successful. It returns + /// the empty tuple type or error. + TypePointer typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2); + virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; virtual bool visit(StructDefinition const& _struct) override; @@ -149,6 +154,9 @@ private: /// Flag indicating whether we are currently inside an EmitStatement. bool m_insideEmitStatement = false; + /// Flag indicating whether we are currently inside a StructDefinition. + bool m_insideStruct = false; + ErrorReporter& m_errorReporter; }; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index d936ada0..e92ad2fa 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -295,7 +295,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) { // we can ignore the kind of magic and only look at the name of the member set<string> static const pureMembers{ - "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash" + "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode", "data", "sig", "blockhash" }; if (!pureMembers.count(member)) mutability = StateMutability::View; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 23797d52..635ab024 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -397,7 +397,7 @@ SourceUnit const& Scopable::sourceUnit() const { ASTNode const* s = scope(); solAssert(s, ""); - // will not always be a declaratoion + // will not always be a declaration while (dynamic_cast<Scopable const*>(s) && dynamic_cast<Scopable const*>(s)->scope()) s = dynamic_cast<Scopable const*>(s)->scope(); return dynamic_cast<SourceUnit const&>(*s); @@ -418,6 +418,7 @@ bool VariableDeclaration::isLocalVariable() const { auto s = scope(); return + dynamic_cast<FunctionTypeName const*>(s) || dynamic_cast<CallableDeclaration const*>(s) || dynamic_cast<Block const*>(s) || dynamic_cast<ForStatement const*>(s); @@ -425,14 +426,18 @@ bool VariableDeclaration::isLocalVariable() const bool VariableDeclaration::isCallableParameter() const { - auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()); - if (!callable) - return false; - for (auto const& variable: callable->parameters()) - if (variable.get() == this) - return true; - if (callable->returnParameterList()) - for (auto const& variable: callable->returnParameterList()->parameters()) + if (isReturnParameter()) + return true; + + vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr; + + if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope())) + parameters = &funTypeName->parameterTypes(); + else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope())) + parameters = &callable->parameters(); + + if (parameters) + for (auto const& variable: *parameters) if (variable.get() == this) return true; return false; @@ -445,11 +450,16 @@ bool VariableDeclaration::isLocalOrReturn() const bool VariableDeclaration::isReturnParameter() const { - auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()); - if (!callable) - return false; - if (callable->returnParameterList()) - for (auto const& variable: callable->returnParameterList()->parameters()) + vector<ASTPointer<VariableDeclaration>> const* returnParameters = nullptr; + + if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope())) + returnParameters = &funTypeName->returnParameterTypes(); + else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope())) + if (callable->returnParameterList()) + returnParameters = &callable->returnParameterList()->parameters(); + + if (returnParameters) + for (auto const& variable: *returnParameters) if (variable.get() == this) return true; return false; @@ -457,15 +467,88 @@ bool VariableDeclaration::isReturnParameter() const bool VariableDeclaration::isExternalCallableParameter() const { - auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()); - if (!callable || callable->visibility() != Declaration::Visibility::External) + if (!isCallableParameter()) return false; - for (auto const& variable: callable->parameters()) - if (variable.get() == this) - return true; + + if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope())) + if (callable->visibility() == Declaration::Visibility::External) + return !isReturnParameter(); + return false; } +bool VariableDeclaration::isInternalCallableParameter() const +{ + if (!isCallableParameter()) + return false; + + if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope())) + return funTypeName->visibility() == Declaration::Visibility::Internal; + else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope())) + return callable->visibility() <= Declaration::Visibility::Internal; + return false; +} + +bool VariableDeclaration::isLibraryFunctionParameter() const +{ + if (!isCallableParameter()) + return false; + if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope())) + return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary(); + else + return false; +} + +bool VariableDeclaration::isEventParameter() const +{ + return dynamic_cast<EventDefinition const*>(scope()) != nullptr; +} + +bool VariableDeclaration::hasReferenceOrMappingType() const +{ + solAssert(typeName(), ""); + solAssert(typeName()->annotation().type, "Can only be called after reference resolution"); + TypePointer const& type = typeName()->annotation().type; + return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type.get()); +} + +set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const +{ + using Location = VariableDeclaration::Location; + + if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter()) + return set<Location>{ Location::Default }; + else if (isStateVariable() && isConstant()) + return set<Location>{ Location::Memory }; + else if (isExternalCallableParameter()) + { + set<Location> locations{ Location::CallData }; + if (isLibraryFunctionParameter()) + locations.insert(Location::Storage); + return locations; + } + else if (isCallableParameter()) + { + set<Location> locations{ Location::Memory }; + if (isInternalCallableParameter() || isLibraryFunctionParameter()) + locations.insert(Location::Storage); + return locations; + } + else if (isLocalVariable()) + { + solAssert(typeName(), ""); + solAssert(typeName()->annotation().type, "Can only be called after reference resolution"); + if (typeName()->annotation().type->category() == Type::Category::Mapping) + return set<Location>{ Location::Storage }; + else + // TODO: add Location::Calldata once implemented for local variables. + return set<Location>{ Location::Memory, Location::Storage }; + } + else + // Struct members etc. + return set<Location>{ Location::Default }; +} + TypePointer VariableDeclaration::type() const { return annotation().type; @@ -580,7 +663,7 @@ bool Literal::passesAddressChecksum() const return dev::passesAddressChecksum(value(), true); } -std::string Literal::getChecksummedAddress() const +string Literal::getChecksummedAddress() const { solAssert(isHexNumber(), "Expected hex number"); /// Pad literal to be a proper hex address. diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 69c6fa05..b953211d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -685,6 +685,8 @@ public: virtual bool isLValue() const override; virtual bool isPartOfExternalInterface() const override { return isPublic(); } + /// @returns true iff this variable is the parameter (or return parameter) of a function + /// (or function type name or event) or declared inside a function body. bool isLocalVariable() const; /// @returns true if this variable is a parameter or return parameter of a function. bool isCallableParameter() const; @@ -693,13 +695,27 @@ public: /// @returns true if this variable is a local variable or return parameter. bool isLocalOrReturn() const; /// @returns true if this variable is a parameter (not return parameter) of an external function. + /// This excludes parameters of external function type names. bool isExternalCallableParameter() const; + /// @returns true if this variable is a parameter or return parameter of an internal function + /// or a function type of internal visibility. + bool isInternalCallableParameter() const; + /// @returns true iff this variable is a parameter(or return parameter of a library function + bool isLibraryFunctionParameter() const; /// @returns true if the type of the variable does not need to be specified, i.e. it is declared /// in the body of a function or modifier. + /// @returns true if this variable is a parameter of an event. + bool isEventParameter() const; + /// @returns true if the type of the variable is a reference or mapping type, i.e. + /// array, struct or mapping. These types can take a data location (and often require it). + /// Can only be called after reference resolution. + bool hasReferenceOrMappingType() const; bool isStateVariable() const { return m_isStateVariable; } bool isIndexed() const { return m_isIndexed; } bool isConstant() const { return m_isConstant; } Location referenceLocation() const { return m_location; } + /// @returns a set of allowed storage locations for the variable. + std::set<Location> allowedDataLocations() const; virtual TypePointer type() const override; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d9eb034d..a725618e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -355,13 +355,7 @@ TypePointer Type::forLiteral(Literal const& _literal) case Token::FalseLiteral: return make_shared<BoolType>(); case Token::Number: - { - tuple<bool, rational> validLiteral = RationalNumberType::isValidLiteral(_literal); - if (get<0>(validLiteral) == true) - return make_shared<RationalNumberType>(get<1>(validLiteral)); - else - return TypePointer(); - } + return RationalNumberType::forLiteral(_literal); case Token::StringLiteral: return make_shared<StringLiteralType>(_literal); default: @@ -400,17 +394,17 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p encodingType = encodingType->interfaceType(_inLibraryCall); if (encodingType) encodingType = encodingType->encodingType(); - if (auto structType = dynamic_cast<StructType const*>(encodingType.get())) - { - // Structs are fine in the following circumstances: - // - ABIv2 without packed encoding or, - // - storage struct for a library - if (!( - (_encoderV2 && !_packed) || - (structType->location() == DataLocation::Storage && _inLibraryCall) - )) + // Structs are fine in the following circumstances: + // - ABIv2 without packed encoding or, + // - storage struct for a library + if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage)) + return encodingType; + TypePointer baseType = encodingType; + while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get())) + baseType = arrayType->baseType(); + if (dynamic_cast<StructType const*>(baseType.get())) + if (!_encoderV2 || _packed) return TypePointer(); - } return encodingType; } @@ -627,6 +621,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons {"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, {"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareDelegateCall, false)}, {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, + {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)}, {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} }; else @@ -779,6 +774,30 @@ tuple<bool, rational> RationalNumberType::parseRational(string const& _value) } } +TypePointer RationalNumberType::forLiteral(Literal const& _literal) +{ + solAssert(_literal.token() == Token::Number, ""); + tuple<bool, rational> validLiteral = isValidLiteral(_literal); + if (get<0>(validLiteral)) + { + TypePointer compatibleBytesType; + if (_literal.isHexNumber()) + { + size_t digitCount = count_if( + _literal.value().begin() + 2, // skip "0x" + _literal.value().end(), + [](unsigned char _c) -> bool { return isxdigit(_c); } + ); + // require even number of digits + if (!(digitCount & 1)) + compatibleBytesType = make_shared<FixedBytesType>(digitCount / 2); + } + + return make_shared<RationalNumberType>(get<1>(validLiteral), compatibleBytesType); + } + return TypePointer(); +} + tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal) { rational value; @@ -918,14 +937,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } case Category::FixedBytes: - { - FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo); - if (isFractional()) - return false; - if (integerType()) - return fixedBytes.numBytes() * 8 >= integerType()->numBits(); - return false; - } + return (m_value == rational(0)) || (m_compatibleBytesType && *m_compatibleBytesType == _convertTo); default: return false; } @@ -933,11 +945,15 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - TypePointer mobType = mobileType(); - return - (mobType && mobType->isExplicitlyConvertibleTo(_convertTo)) || - (!isFractional() && _convertTo.category() == Category::FixedBytes) - ; + if (isImplicitlyConvertibleTo(_convertTo)) + return true; + else if (_convertTo.category() != Category::FixedBytes) + { + TypePointer mobType = mobileType(); + return (mobType && mobType->isExplicitlyConvertibleTo(_convertTo)); + } + else + return false; } TypePointer RationalNumberType::unaryOperatorResult(Token::Value _operator) const @@ -1263,7 +1279,8 @@ shared_ptr<FixedPointType const> RationalNumberType::fixedPointType() const return shared_ptr<FixedPointType const>(); // This means we round towards zero for positive and negative values. bigint v = value.numerator() / value.denominator(); - if (negative) + + if (negative && v != 0) // modify value to satisfy bit requirements for negative numbers: // add one bit for sign and decrement because negative numbers can be larger v = (v - 1) << 1; @@ -2502,6 +2519,7 @@ string FunctionType::richIdentifier() const case Kind::BareCall: id += "barecall"; break; case Kind::BareCallCode: id += "barecallcode"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break; + case Kind::BareStaticCall: id += "barestaticcall"; break; case Kind::Creation: id += "creation"; break; case Kind::Send: id += "send"; break; case Kind::Transfer: id += "transfer"; break; @@ -2533,6 +2551,7 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodePacked: id += "abiencodepacked"; break; case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; + case Kind::ABIDecode: id += "abidecode"; break; } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); @@ -2549,28 +2568,10 @@ bool FunctionType::operator==(Type const& _other) const { if (_other.category() != category()) return false; - FunctionType const& other = dynamic_cast<FunctionType const&>(_other); - if ( - m_kind != other.m_kind || - m_stateMutability != other.stateMutability() || - m_parameterTypes.size() != other.m_parameterTypes.size() || - m_returnParameterTypes.size() != other.m_returnParameterTypes.size() - ) + if (!equalExcludingStateMutability(other)) return false; - - auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; - if ( - !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) || - !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare) - ) - return false; - //@todo this is ugly, but cannot be prevented right now - if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet) - return false; - if (bound() != other.bound()) - return false; - if (bound() && *selfType() != *other.selfType()) + if (m_stateMutability != other.stateMutability()) return false; return true; } @@ -2586,6 +2587,31 @@ bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const return _convertTo.category() == category(); } +bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.category() != category()) + return false; + + FunctionType const& convertTo = dynamic_cast<FunctionType const&>(_convertTo); + + if (!equalExcludingStateMutability(convertTo)) + return false; + + // non-payable should not be convertible to payable + if (m_stateMutability != StateMutability::Payable && convertTo.stateMutability() == StateMutability::Payable) + return false; + + // payable should be convertible to non-payable, because you are free to pay 0 ether + if (m_stateMutability == StateMutability::Payable && convertTo.stateMutability() == StateMutability::NonPayable) + return true; + + // e.g. pure should be convertible to view, but not the other way around. + if (m_stateMutability > convertTo.stateMutability()) + return false; + + return true; +} + TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const { if (_operator == Token::Value::Delete) @@ -2676,6 +2702,7 @@ unsigned FunctionType::sizeOnStack() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::Internal: case Kind::ArrayPush: case Kind::ArrayPop: @@ -2743,6 +2770,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: { MemberList::MemberMap members; if (m_kind == Kind::External) @@ -2843,6 +2871,38 @@ bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const ); } +bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const +{ + if (m_returnParameterTypes.size() != _other.m_returnParameterTypes.size()) + return false; + return equal( + m_returnParameterTypes.cbegin(), + m_returnParameterTypes.cend(), + _other.m_returnParameterTypes.cbegin(), + [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; } + ); +} + +bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) const +{ + if (m_kind != _other.m_kind) + return false; + + if (!hasEqualParameterTypes(_other) || !hasEqualReturnTypes(_other)) + return false; + + //@todo this is ugly, but cannot be prevented right now + if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet) + return false; + + if (bound() != _other.bound()) + return false; + + solAssert(!bound() || *selfType() == *_other.selfType(), ""); + + return true; +} + bool FunctionType::isBareCall() const { switch (m_kind) @@ -2850,6 +2910,7 @@ bool FunctionType::isBareCall() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::ECRecover: case Kind::SHA256: case Kind::RIPEMD160: @@ -2863,6 +2924,17 @@ string FunctionType::externalSignature() const { solAssert(m_declaration != nullptr, "External signature of function needs declaration"); solAssert(!m_declaration->name().empty(), "Fallback function has no signature."); + switch (kind()) + { + case Kind::Internal: + case Kind::External: + case Kind::CallCode: + case Kind::DelegateCall: + case Kind::Event: + break; + default: + solAssert(false, "Invalid function type for requesting external signature."); + } bool const inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary(); FunctionTypePointer external = interfaceFunctionType(); @@ -2899,7 +2971,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIEncode || m_kind == Kind::ABIEncodePacked || m_kind == Kind::ABIEncodeWithSelector || - m_kind == Kind::ABIEncodeWithSignature; + m_kind == Kind::ABIEncodeWithSignature || + m_kind == Kind::ABIDecode; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -2992,6 +3065,7 @@ bool FunctionType::padArguments() const case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: + case Kind::BareStaticCall: case Kind::SHA256: case Kind::RIPEMD160: case Kind::KECCAK256: @@ -3253,6 +3327,15 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const FunctionType::Kind::ABIEncodeWithSignature, true, StateMutability::Pure + )}, + {"decode", make_shared<FunctionType>( + TypePointers(), + TypePointers(), + strings{}, + strings{}, + FunctionType::Kind::ABIDecode, + true, + StateMutability::Pure )} }); default: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3b57109d..0b1b5d6d 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -423,12 +423,12 @@ public: virtual Category category() const override { return Category::RationalNumber; } - /// @returns true if the literal is a valid integer. - static std::tuple<bool, rational> isValidLiteral(Literal const& _literal); + static TypePointer forLiteral(Literal const& _literal); - explicit RationalNumberType(rational const& _value): - m_value(_value) + explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()): + m_value(_value), m_compatibleBytesType(_compatibleBytesType) {} + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -446,7 +446,8 @@ public: /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. std::shared_ptr<IntegerType const> integerType() const; - /// @returns the smallest fixed type that can hold the value or incurs the least precision loss. + /// @returns the smallest fixed type that can hold the value or incurs the least precision loss, + /// unless the value was truncated, then a suitable type will be chosen to indicate such event. /// If the integer part does not fit, returns an empty pointer. std::shared_ptr<FixedPointType const> fixedPointType() const; @@ -462,6 +463,13 @@ public: private: rational m_value; + /// Bytes type to which the rational can be explicitly converted. + /// Empty for all rationals that are not directly parsed from hex literals. + TypePointer m_compatibleBytesType; + + /// @returns true if the literal is a valid integer. + static std::tuple<bool, rational> isValidLiteral(Literal const& _literal); + /// @returns true if the literal is a valid rational number. static std::tuple<bool, rational> parseRational(std::string const& _value); @@ -896,6 +904,7 @@ public: BareCall, ///< CALL without function hash BareCallCode, ///< CALLCODE without function hash BareDelegateCall, ///< DELEGATECALL without function hash + BareStaticCall, ///< STATICCALL without function hash Creation, ///< external call using CREATE Send, ///< CALL, but without data and gas Transfer, ///< CALL, but without data and throws on error @@ -926,7 +935,8 @@ public: ABIEncodePacked, ABIEncodeWithSelector, ABIEncodeWithSignature, - GasLeft ///< gasleft() + ABIDecode, + GasLeft, ///< gasleft() }; virtual Category category() const override { return Category::Function; } @@ -1005,6 +1015,7 @@ public: virtual std::string richIdentifier() const override; virtual bool operator==(Type const& _other) const override; + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; @@ -1034,10 +1045,14 @@ public: /// @param _selfType if the function is bound, this has to be supplied and is the type of the /// expression the function is called on. bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const; - /// @returns true if the types of parameters are equal (doesn't check return parameter types) + /// @returns true if the types of parameters are equal (does not check return parameter types) bool hasEqualParameterTypes(FunctionType const& _other) const; + /// @returns true iff the return types are equal (does not check parameter types) + bool hasEqualReturnTypes(FunctionType const& _other) const; + /// @returns true iff the function type is equal to the given type, ignoring state mutability differences. + bool equalExcludingStateMutability(FunctionType const& _other) const; - /// @returns true if the ABI is used for this call (only meaningful for external calls) + /// @returns true if the ABI is NOT used for this call (only meaningful for external calls) bool isBareCall() const; Kind const& kind() const { return m_kind; } StateMutability stateMutability() const { return m_stateMutability; } @@ -1076,6 +1091,7 @@ public: case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: return true; default: return false; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 412a7255..53a06090 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -571,6 +571,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; @@ -1070,6 +1071,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack now: <memory pointer> break; } + case FunctionType::Kind::ABIDecode: + { + arguments.front()->accept(*this); + TypePointer firstArgType = arguments.front()->annotation().type; + TypePointers const& targetTypes = dynamic_cast<TupleType const&>(*_functionCall.annotation().type).components(); + if ( + *firstArgType == ArrayType(DataLocation::CallData) || + *firstArgType == ArrayType(DataLocation::CallData, true) + ) + utils().abiDecode(targetTypes, false); + else + { + utils().convertType(*firstArgType, ArrayType(DataLocation::Memory)); + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + m_context << Instruction::SWAP1 << Instruction::MLOAD; + // stack now: <mem_pos> <length> + + utils().abiDecode(targetTypes, true); + } + break; + } case FunctionType::Kind::GasLeft: m_context << Instruction::GAS; break; @@ -1143,18 +1165,19 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "event not found"); // no-op, because the parent node will do the job break; - case FunctionType::Kind::External: - case FunctionType::Kind::Creation: case FunctionType::Kind::DelegateCall: + _memberAccess.expression().accept(*this); + m_context << funType->externalIdentifier(); + break; case FunctionType::Kind::CallCode: + case FunctionType::Kind::External: + case FunctionType::Kind::Creation: case FunctionType::Kind::Send: case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: case FunctionType::Kind::Transfer: - _memberAccess.expression().accept(*this); - m_context << funType->externalIdentifier(); - break; case FunctionType::Kind::Log0: case FunctionType::Kind::Log1: case FunctionType::Kind::Log2: @@ -1252,7 +1275,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); m_context << Instruction::BALANCE; } - else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) + else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall", "staticcall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, IntegerType(160, IntegerType::Modifier::Address), @@ -1804,10 +1827,13 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); auto funKind = _functionType.kind(); - bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; + + solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); + + bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; - bool useStaticCall = _functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall(); + bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); unsigned retSize = 0; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 836e30d2..e800b278 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -58,22 +58,31 @@ using namespace std; using namespace dev; using namespace dev::solidity; -void CompilerStack::setRemappings(vector<string> const& _remappings) +boost::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping) +{ + auto eq = find(_remapping.begin(), _remapping.end(), '='); + if (eq == _remapping.end()) + return {}; + + auto colon = find(_remapping.begin(), eq, ':'); + + Remapping r; + + r.context = colon == eq ? string() : string(_remapping.begin(), colon); + r.prefix = colon == eq ? string(_remapping.begin(), eq) : string(colon + 1, eq); + r.target = string(eq + 1, _remapping.end()); + + if (r.prefix.empty()) + return {}; + + return r; +} + +void CompilerStack::setRemappings(vector<Remapping> const& _remappings) { - vector<Remapping> remappings; for (auto const& remapping: _remappings) - { - auto eq = find(remapping.begin(), remapping.end(), '='); - if (eq == remapping.end()) - continue; // ignore - auto colon = find(remapping.begin(), eq, ':'); - Remapping r; - r.context = colon == eq ? string() : string(remapping.begin(), colon); - r.prefix = colon == eq ? string(remapping.begin(), eq) : string(colon + 1, eq); - r.target = string(eq + 1, remapping.end()); - remappings.push_back(r); - } - swap(m_remappings, remappings); + solAssert(!remapping.prefix.empty(), ""); + m_remappings = _remappings; } void CompilerStack::setEVMVersion(EVMVersion _version) diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 2234a8c9..9a15fbf0 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -84,6 +84,13 @@ public: CompilationSuccessful }; + struct Remapping + { + std::string context; + std::string prefix; + std::string target; + }; + /// Creates a new compiler stack. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. @@ -103,8 +110,11 @@ public: /// All settings, with the exception of remappings, are reset. void reset(bool _keepSources = false); - /// Sets path remappings in the format "context:prefix=target" - void setRemappings(std::vector<std::string> const& _remappings); + // Parses a remapping of the format "context:prefix=target". + static boost::optional<Remapping> parseRemapping(std::string const& _remapping); + + /// Sets path remappings. + void setRemappings(std::vector<Remapping> const& _remappings); /// Sets library addresses. Addresses are cleared iff @a _libraries is missing. /// Will not take effect before running compile. @@ -319,13 +329,6 @@ private: FunctionDefinition const& _function ) const; - struct Remapping - { - std::string context; - std::string prefix; - std::string target; - }; - ReadCallback::Callback m_readFile; ReadCallback::Callback m_smtQuery; bool m_optimize = false; diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 29a5b798..a8716862 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -36,6 +36,15 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) Json::Value doc; Json::Value methods(Json::objectValue); + auto constructorDefinition(_contractDef.constructor()); + if (constructorDefinition) + { + string value = extractDoc(constructorDefinition->annotation().docTags, "notice"); + if (!value.empty()) + // add the constructor, only if we have any documentation to add + methods["constructor"] = Json::Value(value); + } + string notice = extractDoc(_contractDef.annotation().docTags, "notice"); if (!notice.empty()) doc["notice"] = Json::Value(notice); @@ -73,33 +82,21 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) if (!dev.empty()) doc["details"] = Json::Value(dev); + auto constructorDefinition(_contractDef.constructor()); + if (constructorDefinition) { + Json::Value constructor(devDocumentation(constructorDefinition->annotation().docTags)); + if (!constructor.empty()) + // add the constructor, only if we have any documentation to add + methods["constructor"] = constructor; + } + for (auto const& it: _contractDef.interfaceFunctions()) { if (!it.second->hasDeclaration()) continue; - Json::Value method; if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) { - auto dev = extractDoc(fun->annotation().docTags, "dev"); - if (!dev.empty()) - method["details"] = Json::Value(dev); - - auto author = extractDoc(fun->annotation().docTags, "author"); - if (!author.empty()) - method["author"] = author; - - auto ret = extractDoc(fun->annotation().docTags, "return"); - if (!ret.empty()) - method["return"] = ret; - - Json::Value params(Json::objectValue); - auto paramRange = fun->annotation().docTags.equal_range("param"); - for (auto i = paramRange.first; i != paramRange.second; ++i) - params[i->second.paramName] = Json::Value(i->second.content); - - if (!params.empty()) - method["params"] = params; - + Json::Value method(devDocumentation(fun->annotation().docTags)); if (!method.empty()) // add the function, only if we have any documentation to add methods[it.second->externalSignature()] = method; @@ -118,3 +115,31 @@ string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const& value += i->second.content; return value; } + +Json::Value Natspec::devDocumentation(std::multimap<std::string, DocTag> const &_tags) +{ + Json::Value json(Json::objectValue); + auto dev = extractDoc(_tags, "dev"); + if (!dev.empty()) + json["details"] = Json::Value(dev); + + auto author = extractDoc(_tags, "author"); + if (!author.empty()) + json["author"] = author; + + // for constructors, the "return" node will never exist. invalid tags + // will already generate an error within dev::solidity::DocStringAnalyzer. + auto ret = extractDoc(_tags, "return"); + if (!ret.empty()) + json["return"] = ret; + + Json::Value params(Json::objectValue); + auto paramRange = _tags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + params[i->second.paramName] = Json::Value(i->second.content); + + if (!params.empty()) + json["params"] = params; + + return json; +} diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 6a827d3b..0be4dda2 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -54,6 +54,12 @@ public: private: /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name); + + /// Helper-function that will create a json object with dev specific annotations, if present. + /// @param _tags docTags that are used. + /// @return A JSON representation + /// of the contract's developer documentation + static Json::Value devDocumentation(std::multimap<std::string, DocTag> const &_tags); }; } //solidity NS diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 58b84163..c1996777 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -326,9 +326,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setEVMVersion(*version); } - vector<string> remappings; + vector<CompilerStack::Remapping> remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) - remappings.push_back(remapping.asString()); + { + if (auto r = CompilerStack::parseRemapping(remapping.asString())) + remappings.emplace_back(std::move(*r)); + else + return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); + } m_compilerStack.setRemappings(remappings); Json::Value optimizerSettings = settings.get("optimizer", Json::Value()); |