mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge branch 'develop' into anurag_issue_3667
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
switch (_location)
|
||||
{
|
||||
if (varLoc == Location::Memory)
|
||||
fatalTypeError(_variable.location(),
|
||||
"Location has to be calldata or storage for external "
|
||||
"library functions (remove the \"memory\" keyword)."
|
||||
);
|
||||
case Location::Memory: return "\"memory\"";
|
||||
case Location::Storage: return "\"storage\"";
|
||||
case Location::CallData: return "\"calldata\"";
|
||||
case Location::Default: return "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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)."
|
||||
);
|
||||
}
|
||||
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())
|
||||
{
|
||||
auto const& contract = dynamic_cast<ContractDefinition const&>(
|
||||
*dynamic_cast<Declaration const&>(*_variable.scope()).scope()
|
||||
);
|
||||
// force locations of public or external function (return) parameters to memory
|
||||
if (varLoc != Location::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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
string errorString;
|
||||
if (!_variable.hasReferenceOrMappingType())
|
||||
errorString = "Data location can only be specified for array, struct or mapping types";
|
||||
else
|
||||
{
|
||||
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\"."
|
||||
);
|
||||
}
|
||||
}
|
||||
errorString = "Data location must be " +
|
||||
joinHumanReadable(
|
||||
allowedDataLocations | boost::adaptors::transformed(locationToString),
|
||||
", ",
|
||||
" or "
|
||||
);
|
||||
if (_variable.isCallableParameter())
|
||||
errorString +=
|
||||
" for " +
|
||||
string(_variable.isReturnParameter() ? "return " : "") +
|
||||
"parameter in" +
|
||||
string(_variable.isExternalCallableParameter() ? " external" : "") +
|
||||
" function";
|
||||
else
|
||||
{
|
||||
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();
|
||||
errorString += " for variable";
|
||||
}
|
||||
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)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
|
||||
TypePointer type = _variable.typeName()->annotation().type;
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
|
||||
{
|
||||
bool isPointer = !_variable.isStateVariable();
|
||||
type = ref->copyForLocation(typeLoc, isPointer);
|
||||
}
|
||||
else if (dynamic_cast<MappingType const*>(type.get()))
|
||||
{
|
||||
if (_variable.isLocalVariable() && varLoc != Location::Storage)
|
||||
typeError(
|
||||
_variable.location(),
|
||||
"Data location for mappings must be specified as \"storage\"."
|
||||
);
|
||||
}
|
||||
else if (varLoc != Location::Default && !ref)
|
||||
typeError(_variable.location(), "Data location can only be given for array or struct types.");
|
||||
|
||||
_variable.annotation().type = type;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+103
-20
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+135
-52
@@ -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:
|
||||
|
||||
+24
-8
@@ -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;
|
||||
|
||||
@@ -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::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::DelegateCall:
|
||||
case FunctionType::Kind::CallCode:
|
||||
case FunctionType::Kind::Send:
|
||||
case FunctionType::Kind::BareCall:
|
||||
case FunctionType::Kind::BareCallCode:
|
||||
case FunctionType::Kind::BareDelegateCall:
|
||||
case FunctionType::Kind::BareStaticCall:
|
||||
case FunctionType::Kind::Transfer:
|
||||
_memberAccess.expression().accept(*this);
|
||||
m_context << funType->externalIdentifier();
|
||||
break;
|
||||
case FunctionType::Kind::Log0:
|
||||
case FunctionType::Kind::Log1:
|
||||
case FunctionType::Kind::Log2:
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user