Merge pull request #3476 from ethereum/scoping

C99/C++ scoping rules
This commit is contained in:
chriseth 2018-02-27 17:06:10 +01:00 committed by GitHub
commit 908b46e9a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 598 additions and 89 deletions

View File

@ -1,6 +1,7 @@
### 0.4.21 (unreleased)
Features:
* C99/C++-style scoping rules (instead of JavaScript function scoping) take effect as experimental v0.5.0 feature.
* Code Generator: Assert that ``k != 0`` for ``molmod(a, b, k)`` and ``addmod(a, b, k)`` as experimental 0.5.0 feature.
* Standard JSON: Reject badly formatted invalid JSON inputs.
* Type Checker: Disallow uninitialized storage pointers as experimental 0.5.0 feature.

View File

@ -324,7 +324,8 @@ is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For
element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string.
A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared.
A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared
(this will change soon, see below).
This happens because Solidity inherits its scoping rules from JavaScript.
This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block.
As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``::
@ -366,7 +367,9 @@ As a result, the following code is illegal and cause the compiler to throw an er
}
In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value.
As a result, the following code is legal, despite being poorly written::
As a result, the following code is legal, despite being poorly written:
::
pragma solidity ^0.4.0;
@ -383,6 +386,60 @@ As a result, the following code is legal, despite being poorly written::
}
}
Scoping starting from Version 0.5.0
-----------------------------------
Starting from version 0.5.0, Solidity will change to the more widespread scoping rules of C99
(and many other languages): Variables are visible from the point right after their declaration
until the end of a ``{ }``-block. As an exception to this rule, variables declared in the
initialization part of a for-loop are only visible until the end of the for-loop.
Variables and other items declared outside of a code block, for example functions, contracts,
user-defined types, etc., do not change their scoping behaviour. This means you can
use state variables before they are declared and call functions recursively.
These rules are already introduced now as an experimental feature.
As a consequence, the following examples will compile without warnings, since
the two variables have the same name but disjoint scopes. In non-0.5.0-mode,
they have the same scope (the function ``minimalScoping``) and thus it does
not compile there.
::
pragma solidity ^0.4.0;
pragma experimental "v0.5.0";
contract C {
function minimalScoping() pure public {
{
uint same2 = 0;
}
{
uint same2 = 0;
}
}
}
As a special example of the C99 scoping rules, note that in the following,
the first assignment to ``x`` will actually assign the outer and not the inner variable.
In any case, you will get a warning about the outer variable being shadowed.
::
pragma solidity ^0.4.0;
pragma experimental "v0.5.0";
contract C {
function f() pure public returns (uint) {
uint x = 1;
{
x = 2; // this will assign to the outer variable
uint x;
}
return x; // x has value 2
}
}
.. index:: ! exception, ! throw, ! assert, ! require, ! revert
Error handling: Assert, Require, Revert and Exceptions

View File

@ -79,6 +79,17 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
return nullptr;
}
void DeclarationContainer::activateVariable(ASTString const& _name)
{
solAssert(
m_invisibleDeclarations.count(_name) && m_invisibleDeclarations.at(_name).size() == 1,
"Tried to activate a non-inactive variable or multiple inactive variables with the same name."
);
solAssert(m_declarations.count(_name) == 0 || m_declarations.at(_name).empty(), "");
m_declarations[_name].emplace_back(m_invisibleDeclarations.at(_name).front());
m_invisibleDeclarations.erase(_name);
}
bool DeclarationContainer::registerDeclaration(
Declaration const& _declaration,
ASTString const* _name,
@ -106,15 +117,17 @@ bool DeclarationContainer::registerDeclaration(
return true;
}
vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const
vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive, bool _alsoInvisible) const
{
solAssert(!_name.empty(), "Attempt to resolve empty name.");
auto result = m_declarations.find(_name);
if (result != m_declarations.end())
return result->second;
if (_recursive && m_enclosingContainer)
return m_enclosingContainer->resolveName(_name, true);
return vector<Declaration const*>({});
vector<Declaration const*> result;
if (m_declarations.count(_name))
result = m_declarations.at(_name);
if (_alsoInvisible && m_invisibleDeclarations.count(_name))
result += m_invisibleDeclarations.at(_name);
if (result.empty() && _recursive && m_enclosingContainer)
result = m_enclosingContainer->resolveName(_name, true, _alsoInvisible);
return result;
}
vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const
@ -129,6 +142,12 @@ vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) con
if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
similar.push_back(declarationName);
}
for (auto const& declaration: m_invisibleDeclarations)
{
string const& declarationName = declaration.first;
if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
similar.push_back(declarationName);
}
if (m_enclosingContainer)
similar += m_enclosingContainer->similarNames(_name);

View File

@ -51,13 +51,17 @@ public:
/// @param _update if true, replaces a potential declaration that is already present
/// @returns false if the name was already declared.
bool registerDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr, bool _invisible = false, bool _update = false);
std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const;
std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false, bool _alsoInvisible = false) const;
ASTNode const* enclosingNode() const { return m_enclosingNode; }
DeclarationContainer const* enclosingContainer() const { return m_enclosingContainer; }
std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; }
/// @returns whether declaration is valid, and if not also returns previous declaration.
Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const;
/// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
/// VariableDeclarationStatements.
void activateVariable(ASTString const& _name);
/// @returns existing declaration names similar to @a _name.
/// Searches this and all parent containers.
std::vector<ASTString> similarNames(ASTString const& _name) const;

View File

@ -50,12 +50,13 @@ NameAndTypeResolver::NameAndTypeResolver(
m_scopes[nullptr]->registerDeclaration(*declaration);
}
bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope)
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
{
bool useC99Scoping = _sourceUnit.annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, useC99Scoping, m_errorReporter, _currentScope);
}
catch (FatalError const&)
{
@ -106,7 +107,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
else
for (Declaration const* declaration: declarations)
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, alias.second.get(), &imp->location(), true, m_errorReporter
target, *declaration, alias.second.get(), &imp->location(), true, false, m_errorReporter
))
error = true;
}
@ -114,7 +115,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& nameAndDeclaration: scope->second->declarations())
for (auto const& declaration: nameAndDeclaration.second)
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, &nameAndDeclaration.first, &imp->location(), true, m_errorReporter
target, *declaration, &nameAndDeclaration.first, &imp->location(), true, false, m_errorReporter
))
error = true;
}
@ -151,6 +152,12 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
return true;
}
void NameAndTypeResolver::activateVariable(string const& _name)
{
solAssert(m_currentScope, "");
m_currentScope->activateVariable(_name);
}
vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const
{
auto iterator = m_scopes.find(_scope);
@ -159,15 +166,15 @@ vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _na
return iterator->second->resolveName(_name, false);
}
vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _recursive) const
vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles) const
{
return m_currentScope->resolveName(_name, _recursive);
return m_currentScope->resolveName(_name, true, _includeInvisibles);
}
Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path, bool _recursive) const
Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path) const
{
solAssert(!_path.empty(), "");
vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), _recursive);
vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), true);
for (size_t i = 1; i < _path.size() && candidates.size() == 1; i++)
{
if (!m_scopes.count(candidates.front()))
@ -229,7 +236,7 @@ void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
for (auto const& instruction: c_instructions)
{
string const instructionName{boost::algorithm::to_lower_copy(instruction.first)};
auto declarations = nameFromCurrentScope(instructionName);
auto declarations = nameFromCurrentScope(instructionName, true);
for (Declaration const* const declaration: declarations)
{
solAssert(!!declaration, "");
@ -244,19 +251,24 @@ void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
}
}
void NameAndTypeResolver::setScope(ASTNode const* _node)
{
m_currentScope = m_scopes[_node].get();
}
bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode)
{
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
{
bool success = true;
m_currentScope = m_scopes[contract->scope()].get();
setScope(contract->scope());
solAssert(!!m_currentScope, "");
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
if (!resolveNamesAndTypes(*baseContract, true))
success = false;
m_currentScope = m_scopes[contract].get();
setScope(contract);
if (success)
{
@ -273,7 +285,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
// these can contain code, only resolve parameters for now
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
setScope(contract);
if (!resolveNamesAndTypes(*node, false))
{
success = false;
@ -287,12 +299,12 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
if (!_resolveInsideCode)
return success;
m_currentScope = m_scopes[contract].get();
setScope(contract);
// now resolve references inside the code
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
setScope(contract);
if (!resolveNamesAndTypes(*node, true))
success = false;
}
@ -301,7 +313,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
else
{
if (m_scopes.count(&_node))
m_currentScope = m_scopes[&_node].get();
setScope(&_node);
return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node);
}
}
@ -434,9 +446,11 @@ string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope
):
m_useC99Scoping(_useC99Scoping),
m_scopes(_scopes),
m_currentScope(_currentScope),
m_errorReporter(_errorReporter)
@ -451,6 +465,7 @@ bool DeclarationRegistrationHelper::registerDeclaration(
string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
bool _inactive,
ErrorReporter& _errorReporter
)
{
@ -460,10 +475,13 @@ bool DeclarationRegistrationHelper::registerDeclaration(
string name = _name ? *_name : _declaration.name();
Declaration const* shadowedDeclaration = nullptr;
if (_warnOnShadow && !name.empty() && _container.enclosingContainer())
for (auto const* decl: _container.enclosingContainer()->resolveName(name, true))
for (auto const* decl: _container.enclosingContainer()->resolveName(name, true, true))
shadowedDeclaration = decl;
if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract()))
// We use "invisible" for both inactive variables in blocks and for members invisible in contracts.
// They cannot both be true at the same time.
solAssert(!(_inactive && !_declaration.isVisibleInContract()), "");
if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract() || _inactive))
{
SourceLocation firstDeclarationLocation;
SourceLocation secondDeclarationLocation;
@ -605,6 +623,34 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&)
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(Block& _block)
{
_block.setScope(m_currentScope);
if (m_useC99Scoping)
enterNewSubScope(_block);
return true;
}
void DeclarationRegistrationHelper::endVisit(Block&)
{
if (m_useC99Scoping)
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ForStatement& _for)
{
_for.setScope(m_currentScope);
if (m_useC99Scoping)
enterNewSubScope(_for);
return true;
}
void DeclarationRegistrationHelper::endVisit(ForStatement&)
{
if (m_useC99Scoping)
closeCurrentScope();
}
void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement)
{
// Register the local variables with the function
@ -632,14 +678,14 @@ void DeclarationRegistrationHelper::endVisit(EventDefinition&)
closeCurrentScope();
}
void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration)
void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope)
{
map<ASTNode const*, shared_ptr<DeclarationContainer>>::iterator iter;
bool newlyAdded;
shared_ptr<DeclarationContainer> container(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get()));
tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, move(container));
tie(iter, newlyAdded) = m_scopes.emplace(&_subScope, move(container));
solAssert(newlyAdded, "Unable to add new scope.");
m_currentScope = &_declaration;
m_currentScope = &_subScope;
}
void DeclarationRegistrationHelper::closeCurrentScope()
@ -667,7 +713,12 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
if (fun->isConstructor())
warnAboutShadowing = false;
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, m_errorReporter);
// Register declaration as inactive if we are in block scope and C99 mode.
bool inactive =
m_useC99Scoping &&
(dynamic_cast<Block const*>(m_currentScope) || dynamic_cast<ForStatement const*>(m_currentScope));
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
_declaration.setScope(m_currentScope);
if (_opensScope)

View File

@ -56,7 +56,7 @@ public:
/// @returns false in case of error.
/// @param _currentScope should be nullptr but can be used to inject new declarations into
/// existing scopes, used by the snippets feature.
bool registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope = nullptr);
bool registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope = nullptr);
/// Applies the effect of import directives.
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
/// Resolves all names and types referenced from the given AST Node.
@ -69,20 +69,24 @@ public:
/// that create their own scope.
/// @returns false in case of error.
bool updateDeclaration(Declaration const& _declaration);
/// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for
/// VariableDeclarationStatements.
void activateVariable(std::string const& _name);
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
/// the global scope is used (i.e. the one containing only the pre-defined global variables).
/// @returns a pointer to the declaration on success or nullptr on failure.
/// SHOULD only be used for testing.
std::vector<Declaration const*> resolveName(ASTString const& _name, ASTNode const* _scope = nullptr) const;
/// Resolves a name in the "current" scope. Should only be called during the initial
/// resolving phase.
std::vector<Declaration const*> nameFromCurrentScope(ASTString const& _name, bool _recursive = true) const;
/// Resolves a name in the "current" scope, but also searches parent scopes.
/// Should only be called during the initial resolving phase.
std::vector<Declaration const*> nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles = false) const;
/// Resolves a path starting from the "current" scope. Should only be called during the initial
/// resolving phase.
/// Resolves a path starting from the "current" scope, but also searches parent scopes.
/// Should only be called during the initial resolving phase.
/// @note Returns a null pointer if any component in the path was not unique or not found.
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path, bool _recursive = true) const;
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const;
/// returns the vector of declarations without repetitions
std::vector<Declaration const*> cleanedDeclarations(
@ -96,6 +100,9 @@ public:
/// @returns a list of similar identifiers in the current and enclosing scopes. May return empty string if no suggestions.
std::string similarNameSuggestions(ASTString const& _name) const;
/// Sets the current scope.
void setScope(ASTNode const* _node);
private:
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
@ -135,6 +142,7 @@ public:
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
bool _useC99Scoping,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope = nullptr
);
@ -145,6 +153,7 @@ public:
std::string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
bool _inactive,
ErrorReporter& _errorReporter
);
@ -163,12 +172,16 @@ private:
void endVisit(FunctionDefinition& _function) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
bool visit(Block& _block) override;
void endVisit(Block& _block) override;
bool visit(ForStatement& _forLoop) override;
void endVisit(ForStatement& _forLoop) override;
void endVisit(VariableDeclarationStatement& _variableDeclarationStatement) override;
bool visit(VariableDeclaration& _declaration) override;
bool visit(EventDefinition& _event) override;
void endVisit(EventDefinition& _event) override;
void enterNewSubScope(Declaration const& _declaration);
void enterNewSubScope(ASTNode& _subScope);
void closeCurrentScope();
void registerDeclaration(Declaration& _declaration, bool _opensScope);
@ -177,6 +190,7 @@ private:
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
bool m_useC99Scoping = false;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;

View File

@ -43,6 +43,56 @@ bool ReferencesResolver::resolve(ASTNode const& _root)
return !m_errorOccurred;
}
bool ReferencesResolver::visit(Block const& _block)
{
if (!m_resolveInsideCode)
return false;
m_experimental050Mode = _block.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(&_block);
return true;
}
void ReferencesResolver::endVisit(Block const& _block)
{
if (!m_resolveInsideCode)
return;
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(_block.scope());
}
bool ReferencesResolver::visit(ForStatement const& _for)
{
if (!m_resolveInsideCode)
return false;
m_experimental050Mode = _for.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// C99-scoped variables
if (m_experimental050Mode)
m_resolver.setScope(&_for);
return true;
}
void ReferencesResolver::endVisit(ForStatement const& _for)
{
if (!m_resolveInsideCode)
return;
if (m_experimental050Mode)
m_resolver.setScope(_for.scope());
}
void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (!m_resolveInsideCode)
return;
if (m_experimental050Mode)
for (auto const& var: _varDeclStatement.declarations())
if (var)
m_resolver.activateVariable(var->name());
}
bool ReferencesResolver::visit(Identifier const& _identifier)
{
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name());

View File

@ -57,7 +57,11 @@ public:
bool resolve(ASTNode const& _root);
private:
virtual bool visit(Block const&) override { return m_resolveInsideCode; }
virtual bool visit(Block const& _block) override;
virtual void endVisit(Block const& _block) override;
virtual bool visit(ForStatement const& _for) override;
virtual void endVisit(ForStatement const& _for) override;
virtual void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
virtual bool visit(Identifier const& _identifier) override;
virtual bool visit(ElementaryTypeName const& _typeName) override;
virtual bool visit(FunctionDefinition const& _functionDefinition) override;
@ -90,6 +94,7 @@ private:
std::vector<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode;
bool m_errorOccurred = false;
bool m_experimental050Mode = false;
};
}

View File

@ -96,20 +96,6 @@ set<SourceUnit const*> SourceUnit::referencedSourceUnits(bool _recurse, set<Sour
return sourceUnits;
}
SourceUnit const& Declaration::sourceUnit() const
{
solAssert(!!m_scope, "");
ASTNode const* scope = m_scope;
while (dynamic_cast<Declaration const*>(scope) && dynamic_cast<Declaration const*>(scope)->m_scope)
scope = dynamic_cast<Declaration const*>(scope)->m_scope;
return dynamic_cast<SourceUnit const&>(*scope);
}
string Declaration::sourceUnitName() const
{
return sourceUnit().annotation().path;
}
ImportAnnotation& ImportDirective::annotation() const
{
if (!m_annotation)
@ -408,12 +394,36 @@ UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const
return dynamic_cast<UserDefinedTypeNameAnnotation&>(*m_annotation);
}
SourceUnit const& Scopable::sourceUnit() const
{
ASTNode const* s = scope();
solAssert(s, "");
// will not always be a declaratoion
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);
}
string Scopable::sourceUnitName() const
{
return sourceUnit().annotation().path;
}
bool VariableDeclaration::isLValue() const
{
// External function parameters and constant declared variables are Read-Only
return !isExternalCallableParameter() && !m_isConstant;
}
bool VariableDeclaration::isLocalVariable() const
{
auto s = scope();
return
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<ForStatement const*>(s);
}
bool VariableDeclaration::isCallableParameter() const
{
auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
@ -459,8 +469,7 @@ bool VariableDeclaration::isExternalCallableParameter() const
bool VariableDeclaration::canHaveAutoType() const
{
auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
return (!!callable && !isCallableParameter());
return isLocalVariable() && !isCallableParameter();
}
TypePointer VariableDeclaration::type() const

View File

@ -139,10 +139,33 @@ private:
std::vector<ASTPointer<ASTNode>> m_nodes;
};
/**
* Abstract class that is added to each AST node that is stored inside a scope
* (including scopes).
*/
class Scopable
{
public:
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope() const { return m_scope; }
void setScope(ASTNode const* _scope) { m_scope = _scope; }
/// @returns the source unit this scopable is present in.
SourceUnit const& sourceUnit() const;
/// @returns the source name this scopable is present in.
/// Can be combined with annotation().canonicalName (if present) to form a globally unique name.
std::string sourceUnitName() const;
protected:
ASTNode const* m_scope = nullptr;
};
/**
* Abstract AST class for a declaration (contract, function, struct, variable, import directive).
*/
class Declaration: public ASTNode
class Declaration: public ASTNode, public Scopable
{
public:
/// Visibility ordered from restricted to unrestricted.
@ -171,7 +194,7 @@ public:
ASTPointer<ASTString> const& _name,
Visibility _visibility = Visibility::Default
):
ASTNode(_location), m_name(_name), m_visibility(_visibility), m_scope(nullptr) {}
ASTNode(_location), m_name(_name), m_visibility(_visibility) {}
/// @returns the declared name.
ASTString const& name() const { return *m_name; }
@ -181,17 +204,6 @@ public:
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope() const { return m_scope; }
void setScope(ASTNode const* _scope) { m_scope = _scope; }
/// @returns the source unit this declaration is present in.
SourceUnit const& sourceUnit() const;
/// @returns the source name this declaration is present in.
/// Can be combined with annotation().canonicalName to form a globally unique name.
std::string sourceUnitName() const;
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
virtual bool isLValue() const { return false; }
@ -213,7 +225,6 @@ protected:
private:
ASTPointer<ASTString> m_name;
Visibility m_visibility;
ASTNode const* m_scope;
};
/**
@ -289,6 +300,8 @@ private:
/**
* Abstract class that is added to each AST node that can store local variables.
* Local variables in functions are always added to functions, even though they are not
* in scope for the whole function.
*/
class VariableScope
{
@ -662,7 +675,7 @@ public:
virtual bool isLValue() const override;
virtual bool isPartOfExternalInterface() const override { return isPublic(); }
bool isLocalVariable() const { return !!dynamic_cast<CallableDeclaration const*>(scope()); }
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
/// @returns true if this variable is a return parameter of a function.
@ -1004,7 +1017,7 @@ private:
/**
* Brace-enclosed block containing zero or more statements.
*/
class Block: public Statement
class Block: public Statement, public Scopable
{
public:
Block(
@ -1111,7 +1124,7 @@ private:
/**
* For loop statement
*/
class ForStatement: public BreakableStatement
class ForStatement: public BreakableStatement, public Scopable
{
public:
ForStatement(

View File

@ -466,7 +466,8 @@ BOOST_AUTO_TEST_CASE(for_loop)
text = R"(
contract C {
function f(uint x) public pure {
for (uint y = 2; x < 10; ) {
uint y;
for (y = 2; x < 10; ) {
y = 3;
}
assert(y == 3);
@ -477,7 +478,8 @@ BOOST_AUTO_TEST_CASE(for_loop)
text = R"(
contract C {
function f(uint x) public pure {
for (uint y = 2; x < 10; ) {
uint y;
for (y = 2; x < 10; ) {
y = 3;
}
assert(y == 2);

View File

@ -284,6 +284,54 @@ BOOST_AUTO_TEST_CASE(conditional_expression_functions)
ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(u256(2)));
}
BOOST_AUTO_TEST_CASE(C99_scoping_activation)
{
char const* sourceCode = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public returns (uint) {
uint x = 7;
{
x = 3; // This should still assign to the outer variable
uint x;
x = 4; // This should assign to the new one
}
return x;
}
function g() pure public returns (uint x) {
x = 7;
{
x = 3;
uint x;
return x; // This returns the new variable, i.e. 0
}
}
function h() pure public returns (uint x, uint a, uint b) {
x = 7;
{
x = 3;
a = x; // This should read from the outer
uint x = 4;
b = x;
}
}
function i() pure public returns (uint x, uint a) {
x = 7;
{
x = 3;
uint x = x; // This should read from the outer and assign to the inner
a = x;
}
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("f()"), encodeArgs(3));
ABI_CHECK(callContractFunction("g()"), encodeArgs(0));
ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4));
ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3));
}
BOOST_AUTO_TEST_CASE(recursive_calls)
{
char const* sourceCode = R"(

View File

@ -322,10 +322,10 @@ BOOST_AUTO_TEST_CASE(arithmetics)
{
char const* sourceCode = R"(
contract test {
function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }
function f(uint y) { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
bytes expectation({byte(Instruction::PUSH1), 0x1,
byte(Instruction::PUSH1), 0x2,
byte(Instruction::PUSH1), 0x3,
@ -334,7 +334,7 @@ BOOST_AUTO_TEST_CASE(arithmetics)
byte(Instruction::PUSH1), 0x6,
byte(Instruction::PUSH1), 0x7,
byte(Instruction::PUSH1), 0x8,
byte(Instruction::DUP10),
byte(Instruction::DUP9),
byte(Instruction::XOR),
byte(Instruction::AND),
byte(Instruction::OR),
@ -364,13 +364,13 @@ BOOST_AUTO_TEST_CASE(unary_operators)
{
char const* sourceCode = R"(
contract test {
function f(int y) { var x = !(~+- y == 2); }
function f(int y) { !(~+- y == 2); }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
bytes expectation({byte(Instruction::PUSH1), 0x2,
byte(Instruction::DUP3),
byte(Instruction::DUP2),
byte(Instruction::PUSH1), 0x0,
byte(Instruction::SUB),
byte(Instruction::NOT),
@ -383,7 +383,7 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec)
{
char const* sourceCode = R"(
contract test {
function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); }
function f(uint a) returns (uint x) { x = --a ^ (a-- ^ (++a ^ a++)); }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}});
@ -426,7 +426,10 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec)
byte(Instruction::POP), // second ++
// Stack here: a x a^(a+2)^(a+2)
byte(Instruction::DUP3), // will change
byte(Instruction::XOR)});
byte(Instruction::XOR),
byte(Instruction::SWAP1),
byte(Instruction::POP),
byte(Instruction::DUP1)});
// Stack here: a x a^(a+2)^(a+2)^a
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
}

View File

@ -76,15 +76,236 @@ BOOST_AUTO_TEST_CASE(double_function_declaration)
BOOST_AUTO_TEST_CASE(double_variable_declaration)
{
char const* text = R"(
string text = R"(
contract test {
function f() public {
function f() pure public {
uint256 x;
if (true) { uint256 x; }
}
}
)";
CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
CHECK_ERROR(text, DeclarationError, "Identifier already declared");
}
BOOST_AUTO_TEST_CASE(double_variable_declaration_050)
{
string text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
uint256 x;
if (true) { uint256 x; }
}
}
)";
CHECK_WARNING_ALLOW_MULTI(text, (vector<string>{
"This declaration shadows an existing declaration.",
"Experimental features",
"Unused local variable",
"Unused local variable"
}));
}
BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope)
{
string text = R"(
contract test {
function f() pure public {
{ uint x; }
{ uint x; }
}
}
)";
CHECK_ERROR(text, DeclarationError, "Identifier already declared");
}
BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_050)
{
string text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
{ uint x; }
{ uint x; }
}
}
)";
CHECK_WARNING_ALLOW_MULTI(text, (vector<string>{
"Experimental features",
"Unused local variable",
"Unused local variable"
}));
}
BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_activation)
{
string text = R"(
contract test {
function f() pure public {
{ uint x; }
uint x;
}
}
)";
CHECK_ERROR(text, DeclarationError, "Identifier already declared");
}
BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_activation_050)
{
string text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
{ uint x; }
uint x;
}
}
)";
CHECK_WARNING_ALLOW_MULTI(text, (vector<string>{
"Experimental features",
"Unused local variable",
"Unused local variable"
}));
}
BOOST_AUTO_TEST_CASE(scoping_old)
{
char const* text = R"(
contract test {
function f() pure public {
x = 4;
uint256 x = 2;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(scoping)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() public {
{
uint256 x;
}
x = 2;
}
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier");
}
BOOST_AUTO_TEST_CASE(scoping_activation_old)
{
char const* text = R"(
contract test {
function f() pure public {
x = 3;
uint x;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(scoping_activation)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
x = 3;
uint x;
}
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier");
}
BOOST_AUTO_TEST_CASE(scoping_self_use)
{
char const* text = R"(
contract test {
function f() pure public {
uint a = a;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(scoping_self_use_050)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
uint a = a;
}
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier");
}
BOOST_AUTO_TEST_CASE(scoping_for)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++){
x = 2;
}
}
}
)";
CHECK_WARNING(text, "Experimental features");
}
BOOST_AUTO_TEST_CASE(scoping_for2)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++)
x = 2;
}
}
)";
CHECK_WARNING(text, "Experimental features");
}
BOOST_AUTO_TEST_CASE(scoping_for3)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (uint x = 0; x < 10; x ++){
x = 2;
}
x = 4;
}
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier");
}
BOOST_AUTO_TEST_CASE(scoping_for_decl_in_body)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract test {
function f() pure public {
for (;; y++){
uint y = 3;
}
}
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier");
}
BOOST_AUTO_TEST_CASE(name_shadowing)
@ -1004,7 +1225,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation)
{
char const* text = R"(
contract B {
function f() mod1(2, true) mod2("0123456") public { }
function f() mod1(2, true) mod2("0123456") pure public { }
modifier mod1(uint a, bool b) { if (b) _; }
modifier mod2(bytes7 a) { while (a == "1234567") _; }
}
@ -1039,11 +1260,23 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables)
{
char const* text = R"(
contract B {
function f() mod(x) public { uint x = 7; }
function f() mod(x) pure public { uint x = 7; }
modifier mod(uint a) { if (a > 0) _; }
}
)";
CHECK_SUCCESS(text);
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables050)
{
char const* text = R"(
pragma experimental "v0.5.0";
contract B {
function f() mod(x) pure public { uint x = 7; }
modifier mod(uint a) { if (a > 0) _; }
}
)";
CHECK_ERROR(text, DeclarationError, "Undeclared identifier.");
}
BOOST_AUTO_TEST_CASE(function_modifier_double_invocation)