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

This commit is contained in:
chriseth
2020-10-08 20:18:04 +02:00
91 changed files with 924 additions and 171 deletions
@@ -276,11 +276,17 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
if (_variable.annotation().type)
return;
if (_variable.isConstant() && !_variable.isStateVariable())
if (_variable.isFileLevelVariable() && !_variable.isConstant())
m_errorReporter.declarationError(
8342_error,
_variable.location(),
"Only constant variables are allowed at file level."
);
if (_variable.isConstant() && (!_variable.isStateVariable() && !_variable.isFileLevelVariable()))
m_errorReporter.declarationError(
1788_error,
_variable.location(),
"The \"constant\" keyword can only be used for state variables."
"The \"constant\" keyword can only be used for state variables or variables at file level."
);
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(
@@ -344,6 +350,11 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
}
else if (_variable.isFileLevelVariable())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
}
else if (_variable.isStateVariable())
{
solAssert(varLoc == Location::Unspecified, "");
+1 -1
View File
@@ -87,7 +87,7 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
{
if (!_variable.isStateVariable())
if (!_variable.isStateVariable() && !_variable.isFileLevelVariable())
return false;
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
+4 -4
View File
@@ -61,13 +61,13 @@ bool DocStringTagParser::visit(VariableDeclaration const& _variable)
{
if (_variable.isStateVariable())
{
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "inheritdoc"};
static set<string> const validNonPublicTags = set<string>{"dev", "inheritdoc"};
if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
parseDocStrings(_variable, _variable.annotation(), {"dev", "notice", "return", "inheritdoc"}, "public state variables");
else
parseDocStrings(_variable, _variable.annotation(), validNonPublicTags, "non-public state variables");
parseDocStrings(_variable, _variable.annotation(), {"dev", "inheritdoc"}, "non-public state variables");
}
else if (_variable.isFileLevelVariable())
parseDocStrings(_variable, _variable.annotation(), {"dev"}, "file-level variables");
return false;
}
@@ -130,8 +130,11 @@ bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source)
try
{
for (shared_ptr<ASTNode> const& node: _source.nodes())
{
setScope(&_source);
if (!resolveNamesAndTypesInternal(*node, true))
return false;
}
}
catch (langutil::FatalError const&)
{
+27 -10
View File
@@ -38,6 +38,13 @@ bool PostTypeChecker::check(ASTNode const& _astRoot)
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool PostTypeChecker::finalize()
{
for (auto& checker: m_checkers)
checker->finalize();
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition)
{
return callVisit(_contractDefinition);
@@ -83,6 +90,11 @@ bool PostTypeChecker::visit(Identifier const& _identifier)
return callVisit(_identifier);
}
bool PostTypeChecker::visit(MemberAccess const& _memberAccess)
{
return callVisit(_memberAccess);
}
bool PostTypeChecker::visit(StructDefinition const& _struct)
{
return callVisit(_struct);
@@ -110,14 +122,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
ConstStateVarCircularReferenceChecker(ErrorReporter& _errorReporter):
Checker(_errorReporter) {}
bool visit(ContractDefinition const&) override
{
solAssert(!m_currentConstVariable, "");
solAssert(m_constVariableDependencies.empty(), "");
return true;
}
void endVisit(ContractDefinition const&) override
void finalize() override
{
solAssert(!m_currentConstVariable, "");
for (auto declaration: m_constVariables)
@@ -128,9 +133,12 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
"The value of the constant " + declaration->name() +
" has a cyclic dependency via " + identifier->name() + "."
);
}
m_constVariables.clear();
m_constVariableDependencies.clear();
bool visit(ContractDefinition const&) override
{
solAssert(!m_currentConstVariable, "");
return true;
}
bool visit(VariableDeclaration const& _variable) override
@@ -162,6 +170,15 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
return true;
}
bool visit(MemberAccess const& _memberAccess) override
{
if (m_currentConstVariable)
if (auto var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
if (var->isConstant())
m_constVariableDependencies[m_currentConstVariable].insert(var);
return true;
}
VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom)
{
auto visitor = [&](VariableDeclaration const& _variable, util::CycleDetector<VariableDeclaration>& _cycleDetector, size_t _depth)
+8 -1
View File
@@ -35,7 +35,7 @@ namespace solidity::frontend
/**
* This module performs analyses on the AST that are done after type checking and assignments of types:
* - whether there are circular references in constant state variables
* - whether there are circular references in constant variables
* - whether override specifiers are actually contracts
* - whether a modifier is in a function header
* - whether an event is used outside of an emit statement
@@ -54,6 +54,9 @@ public:
{
Checker(langutil::ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter) {}
/// Called after all source units have been visited.
virtual void finalize() {}
protected:
langutil::ErrorReporter& m_errorReporter;
};
@@ -63,6 +66,9 @@ public:
bool check(ASTNode const& _astRoot);
/// Called after all source units have been visited.
bool finalize();
private:
bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;
@@ -77,6 +83,7 @@ private:
bool visit(FunctionCall const& _functionCall) override;
bool visit(Identifier const& _identifier) override;
bool visit(MemberAccess const& _identifier) override;
bool visit(StructDefinition const& _struct) override;
void endVisit(StructDefinition const& _struct) override;
+7 -2
View File
@@ -599,8 +599,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (auto referenceType = dynamic_cast<ReferenceType const*>(varType))
{
auto result = referenceType->validForLocation(referenceType->location());
if (result && (_variable.isConstructorParameter() || _variable.isPublicCallableParameter()))
result = referenceType->validForLocation(DataLocation::CallData);
if (result)
{
bool isLibraryStorageParameter = (_variable.isLibraryFunctionParameter() && referenceType->location() == DataLocation::Storage);
bool callDataCheckRequired = ((_variable.isConstructorParameter() || _variable.isPublicCallableParameter()) && !isLibraryStorageParameter);
if (callDataCheckRequired)
result = referenceType->validForLocation(DataLocation::CallData);
}
if (!result)
{
solAssert(!result.message().empty(), "Expected detailed error message");
+5
View File
@@ -647,6 +647,11 @@ bool VariableDeclaration::isStateVariable() const
return dynamic_cast<ContractDefinition const*>(scope());
}
bool VariableDeclaration::isFileLevelVariable() const
{
return dynamic_cast<SourceUnit const*>(scope());
}
set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
{
using Location = VariableDeclaration::Location;
+1
View File
@@ -967,6 +967,7 @@ public:
/// Can only be called after reference resolution.
bool hasReferenceOrMappingType() const;
bool isStateVariable() const;
bool isFileLevelVariable() const;
bool isIndexed() const { return m_isIndexed; }
Mutability mutability() const { return m_mutability; }
bool isConstant() const { return m_mutability == Mutability::Constant; }
+4 -1
View File
@@ -126,7 +126,6 @@ public:
/// stack slot, it takes exactly that number of values.
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
private:
struct EncodingOptions
{
/// Pad/signextend value types and bytes/string to multiples of 32 bytes.
@@ -146,6 +145,7 @@ private:
std::string toFunctionNameSuffix() const;
};
/// Internal encoding function that is also used by some copying routines.
/// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage
@@ -155,6 +155,7 @@ private:
Type const& _targetType,
EncodingOptions const& _options
);
/// Internal encoding function that is also used by some copying routines.
/// @returns the name of a function that internally calls `abiEncodingFunction`
/// but always returns the updated encoding position, even if the type is
/// statically encoded.
@@ -163,6 +164,8 @@ private:
Type const& _targetType,
EncodingOptions const& _options
);
private:
/// Part of @a abiEncodingFunction for array target type and given calldata array.
/// Uses calldatacopy and does not perform cleanup or validation and can therefore only
/// be used for byte arrays and arrays with the base type uint256 or bytes32.
+12 -6
View File
@@ -1753,18 +1753,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{
Type::Category category = _memberAccess.annotation().type->category();
solAssert(
dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) ||
dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) ||
category == Type::Category::TypeType ||
category == Type::Category::Module ||
category == Type::Category::Function,
category == Type::Category::Module,
""
);
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
{
auto const* funDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
solAssert(funDef && funDef->isFree(), "");
solAssert(variable->isConstant(), "");
appendVariable(*variable, static_cast<Expression const&>(_memberAccess));
}
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
{
auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
solAssert(function && function->isFree(), "");
solAssert(funType->kind() == FunctionType::Kind::Internal, "");
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
utils().pushCombinedFunctionEntryLabel(*funDef);
utils().pushCombinedFunctionEntryLabel(*function);
}
break;
}
@@ -33,8 +33,11 @@ string MultiUseYulFunctionCollector::requestedFunctions()
{
string result;
for (auto const& f: m_requestedFunctions)
{
solAssert(f.second != "<<STUB<<", "");
// std::map guarantees ascending order when iterating through its keys.
result += f.second;
}
m_requestedFunctions.clear();
return result;
}
@@ -43,9 +46,10 @@ string MultiUseYulFunctionCollector::createFunction(string const& _name, functio
{
if (!m_requestedFunctions.count(_name))
{
m_requestedFunctions[_name] = "<<STUB<<";
string fun = _creator();
solAssert(!fun.empty(), "");
solAssert(fun.find("function " + _name) != string::npos, "Function not properly named.");
solAssert(fun.find("function " + _name + "(") != string::npos, "Function not properly named.");
m_requestedFunctions[_name] = std::move(fun);
}
return _name;
+147 -23
View File
@@ -851,7 +851,6 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
@@ -1147,6 +1146,45 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
string functionName = "clear_struct_storage_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
MemberList::MemberMap structMembers = _type.nativeMembers(nullptr);
vector<map<string, string>> memberSetValues;
for (auto const& member: structMembers)
{
auto const& [memberSlotDiff, memberStorageOffset] = _type.storageOffsetsOfMember(member.name);
memberSetValues.emplace_back().emplace("clearMember", Whiskers(R"(
<setZero>(add(slot, <memberSlotDiff>), <memberStorageOffset>)
)")
("setZero", storageSetToZeroFunction(*member.type))
("memberSlotDiff", memberSlotDiff.str())
("memberStorageOffset", to_string(memberStorageOffset))
.render()
);
}
return Whiskers(R"(
function <functionName>(slot) {
<#member>
<clearMember>
</member>
}
)")
("functionName", functionName)
("allocStruct", allocateMemoryStructFunction(_type))
("storageSize", _type.storageSize().str())
("member", memberSetValues)
.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
@@ -1446,6 +1484,70 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to)
{
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
solAssert(_to.dataStoredIn(DataLocation::Memory), "");
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
if (!_from.isDynamicallySized())
solAssert(_from.length() == _to.length(), "");
string functionName = "copy_array_from_storage_to_memory_" + _from.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
if (_from.baseType()->isValueType())
{
solAssert(_from.baseType() == _to.baseType(), "");
ABIFunctions abi(m_evmVersion, m_revertStrings, m_functionCollector);
return Whiskers(R"(
function <functionName>(slot) -> memptr {
memptr := <allocateTemp>()
let end := <encode>(slot, memptr)
mstore(<freeMemoryPointer>, end)
}
)")
("functionName", functionName)
("allocateTemp", allocationTemporaryMemoryFunction())
(
"encode",
abi.abiEncodeAndReturnUpdatedPosFunction(_from, _to, ABIFunctions::EncodingOptions{})
)
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
.render();
}
else
{
solAssert(_to.memoryStride() == 32, "");
solAssert(_to.baseType()->dataStoredIn(DataLocation::Memory), "");
solAssert(_from.baseType()->dataStoredIn(DataLocation::Storage), "");
solAssert(!_from.isByteArray(), "");
solAssert(*_to.withLocation(DataLocation::Storage, _from.isPointer()) == _from, "");
return Whiskers(R"(
function <functionName>(slot) -> memptr {
let length := <lengthFunction>(slot)
memptr := <allocateArray>(length)
let mpos := memptr
<?dynamic>mpos := add(mpos, 0x20)</dynamic>
let spos := <arrayDataArea>(slot)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
mstore(mpos, <convert>(spos))
mpos := add(mpos, 0x20)
spos := add(spos, <baseStorageSize>)
}
}
)")
("functionName", functionName)
("lengthFunction", arrayLengthFunction(_from))
("allocateArray", allocateMemoryArrayFunction(_to))
("arrayDataArea", arrayDataAreaFunction(_from))
("dynamic", _to.isDynamicallySized())
("convert", conversionFunction(*_from.baseType(), *_to.baseType()))
("baseStorageSize", _from.baseType()->storageSize().str())
.render();
}
});
}
string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType)
{
solAssert(_keyType.sizeOnStack() <= 1, "");
@@ -2261,8 +2363,12 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
break;
case DataLocation::Memory:
// Copy the array to a free position in memory, unless it is already in memory.
solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet.");
body = "converted := value";
if (from.location() == DataLocation::Memory)
body = "converted := value";
else if (from.location() == DataLocation::CallData)
solUnimplemented("Conversion of calldata types not yet implemented.");
else
body = "converted := " + copyArrayFromStorageToMemoryFunction(from, to) + "(value)";
break;
case DataLocation::CallData:
solUnimplemented("Conversion of calldata types not yet implemented.");
@@ -2278,27 +2384,32 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
auto const& toStructType = dynamic_cast<StructType const &>(_to);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), "");
solUnimplementedAssert(toStructType.location() == DataLocation::Memory, "");
solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, "");
if (fromStructType.location() == DataLocation::CallData)
{
body = Whiskers(R"(
converted := <abiDecode>(value, calldatasize())
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
{&toStructType}
)).render();
}
if (fromStructType.location() == toStructType.location() && toStructType.isPointer())
body = "converted := value";
else
{
solAssert(fromStructType.location() == DataLocation::Storage, "");
solUnimplementedAssert(toStructType.location() == DataLocation::Memory, "");
solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, "");
body = Whiskers(R"(
converted := <readFromStorage>(value)
)")
("readFromStorage", readFromStorage(toStructType, 0, true))
.render();
if (fromStructType.location() == DataLocation::CallData)
{
solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), "");
body = Whiskers(R"(
converted := <abiDecode>(value, calldatasize())
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
{&toStructType}
)).render();
}
else
{
solAssert(fromStructType.location() == DataLocation::Storage, "");
body = Whiskers(R"(
converted := <readFromStorage>(value)
)")
("readFromStorage", readFromStorage(toStructType, 0, true))
.render();
}
}
break;
@@ -2441,7 +2552,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
case Type::Category::Enum:
{
// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
templ("body", "cleaned := value " + validatorFunction(_type) + "(value)");
templ("body", "cleaned := value " + validatorFunction(_type, false) + "(value)");
break;
}
case Type::Category::InaccessibleDynamic:
@@ -2741,11 +2852,24 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
else if (_type.category() == Type::Category::Array)
return Whiskers(R"(
function <functionName>(slot, offset) {
if iszero(eq(offset, 0)) { <panic>() }
<clearArray>(slot)
}
)")
("functionName", functionName)
("clearArray", clearStorageArrayFunction(dynamic_cast<ArrayType const&>(_type)))
("panic", panicFunction())
.render();
else if (_type.category() == Type::Category::Struct)
return Whiskers(R"(
function <functionName>(slot, offset) {
if iszero(eq(offset, 0)) { <panic>() }
<clearStruct>(slot)
}
)")
("functionName", functionName)
("clearStruct", clearStorageStructFunction(dynamic_cast<StructType const&>(_type)))
("panic", panicFunction())
.render();
else
solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!");
@@ -2933,7 +3057,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
)")
("functionName", functionName)
("fromCalldata", _fromCalldata)
("validate", validatorFunction(_type))
("validate", validatorFunction(_type, true))
// Byte array elements generally need cleanup.
// Other types are cleaned as well to account for dirty memory e.g. due to inline assembly.
("cleanup", cleanupFunction(_type))
+9 -1
View File
@@ -220,6 +220,10 @@ public:
/// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that allocates a memory array and copies the contents
/// of the storage array into it.
std::string copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to);
/// @returns the name of a function that performs index access for mappings.
/// @param _mappingType the type of the mapping
/// @param _keyType the type of the value provided
@@ -343,7 +347,7 @@ public:
/// otherwise an assertion failure.
///
/// This is used for data decoded from external sources.
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false);
std::string validatorFunction(Type const& _type, bool _revertOnFailure);
std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes);
@@ -427,6 +431,10 @@ private:
/// signature: (slot, offset)
std::string partialClearStorageSlotFunction();
/// @returns the name of a function that will clear the given storage struct
/// signature: (slot) ->
std::string clearStorageStructFunction(StructType const& _type);
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
MultiUseYulFunctionCollector& m_functionCollector;
@@ -1871,6 +1871,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(false, "");
break;
}
case Type::Category::Module:
{
Type::Category category = _memberAccess.annotation().type->category();
solAssert(
dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) ||
dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) ||
category == Type::Category::TypeType ||
category == Type::Category::Module,
""
);
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
{
solAssert(variable->isConstant(), "");
handleVariableReference(*variable, static_cast<Expression const&>(_memberAccess));
}
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
{
auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
solAssert(function && function->isFree(), "");
solAssert(function->functionType(true), "");
solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal, "");
solAssert(funType->kind() == FunctionType::Kind::Internal, "");
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
define(_memberAccess) << to_string(function->id()) << "\n";
m_context.internalFunctionAccessed(_memberAccess, *function);
}
break;
}
default:
solAssert(false, "Member access to unknown type.");
}
@@ -2130,6 +2159,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
// no-op
}
else if (dynamic_cast<ImportDirective const*>(declaration))
{
// no-op
}
else
{
solAssert(false, "Identifier type not expected in expression context.");
@@ -2162,7 +2195,7 @@ void IRGeneratorForStatements::handleVariableReference(
)
{
setLocation(_referencingExpression);
if (_variable.isStateVariable() && _variable.isConstant())
if ((_variable.isStateVariable() || _variable.isFileLevelVariable()) && _variable.isConstant())
define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
else if (_variable.isStateVariable() && _variable.immutable())
setLValue(_referencingExpression, IRLValue{
+2
View File
@@ -387,6 +387,8 @@ bool CompilerStack::analyze()
for (Source const* source: m_sourceOrder)
if (source->ast && !postTypeChecker.check(*source->ast))
noErrors = false;
if (!postTypeChecker.finalize())
noErrors = false;
}
// Check that immutable variables are never read in c'tors and assigned
+28 -13
View File
@@ -111,7 +111,17 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
nodes.push_back(parseFunctionDefinition(true));
break;
default:
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/function definition.");
// Constant variable.
if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS)
{
VarDeclParserOptions options;
options.kind = VarDeclKind::FileLevel;
options.allowInitialValue = true;
nodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon);
}
else
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition.");
}
}
solAssert(m_recursionDepth == 0, "");
@@ -332,15 +342,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition());
else if (
currentTokenValue == Token::Identifier ||
currentTokenValue == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentTokenValue) ||
(currentTokenValue == Token::Function && m_scanner->peekNextToken() == Token::LParen)
)
else if (variableDeclarationStart())
{
VarDeclParserOptions options;
options.isStateVariable = true;
options.kind = VarDeclKind::State;
options.allowInitialValue = true;
subNodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon);
@@ -687,10 +692,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName();
nodeFactory.setEndPositionFromNode(type);
if (!_options.isStateVariable && documentation != nullptr)
parserError(2837_error, "Only state variables can have a docstring.");
if (_options.kind == VarDeclKind::Other && documentation != nullptr)
parserError(2837_error, "Only state variables or file-level variables can have a docstring.");
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace)
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.kind == VarDeclKind::State && m_scanner->currentToken() == Token::LBrace)
fatalParserError(
2915_error,
"Expected a state variable declaration. If you intended this as a fallback function "
@@ -708,7 +713,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
while (true)
{
Token token = m_scanner->currentToken();
if (_options.isStateVariable && TokenTraits::isVariableVisibilitySpecifier(token))
if (_options.kind == VarDeclKind::State && TokenTraits::isVariableVisibilitySpecifier(token))
{
nodeFactory.markEndPosition();
if (visibility != Visibility::Default)
@@ -724,7 +729,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
else
visibility = parseVisibilitySpecifier();
}
else if (_options.isStateVariable && token == Token::Override)
else if (_options.kind == VarDeclKind::State && token == Token::Override)
{
if (overrides)
parserError(9125_error, "Override already specified.");
@@ -1928,6 +1933,16 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
return ret;
}
bool Parser::variableDeclarationStart()
{
Token currentToken = m_scanner->currentToken();
return
currentToken == Token::Identifier ||
currentToken == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentToken) ||
(currentToken == Token::Function && m_scanner->peekNextToken() == Token::LParen);
}
optional<string> Parser::findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes)
{
// We circumvent the scanner here, because it skips non-docstring comments.
+5 -2
View File
@@ -52,13 +52,13 @@ public:
private:
class ASTNodeFactory;
enum class VarDeclKind { FileLevel, State, Other };
struct VarDeclParserOptions
{
// This is actually not needed, but due to a defect in the C++ standard, we have to.
// https://stackoverflow.com/questions/17430377
VarDeclParserOptions() {}
bool isStateVariable = false;
VarDeclKind kind = VarDeclKind::Other;
bool allowIndexed = false;
bool allowEmptyName = false;
bool allowInitialValue = false;
@@ -155,6 +155,9 @@ private:
///@{
///@name Helper functions
/// @return true if we are at the start of a variable declaration.
bool variableDeclarationStart();
/// Used as return value of @see peekStatementType.
enum class LookAheadInfo
{