Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn if shadowing built-ins #1637

Merged
merged 1 commit into from
Jul 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Features:
* Type Checker: Include types in explicit conversion error message.
* Type Checker: Raise proper error for arrays too large for ABI encoding.
* Type checker: Warn if using ``this`` in a constructor.
* Type checker: Warn when existing symbols, including builtins, are overwritten.

Bugfixes:
* Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs.
Expand Down
129 changes: 89 additions & 40 deletions libsolidity/analysis/NameAndTypeResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,29 +104,18 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
}
else
for (Declaration const* declaration: declarations)
{
ASTString const* name = alias.second ? alias.second.get() : &declaration->name();
if (!target.registerDeclaration(*declaration, name))
{
m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + *name + "\" already declared."
);
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, alias.second.get(), &imp->location(), true, m_errorReporter
))
error = true;
}
}
}
else if (imp->name().empty())
for (auto const& nameAndDeclaration: scope->second->declarations())
for (auto const& declaration: nameAndDeclaration.second)
if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first))
{
m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + nameAndDeclaration.first + "\" already declared."
);
error = true;
}
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, &nameAndDeclaration.first, &imp->location(), true, m_errorReporter
))
error = true;
}
return !error;
}
Expand Down Expand Up @@ -450,6 +439,75 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper(
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
}

bool DeclarationRegistrationHelper::registerDeclaration(
DeclarationContainer& _container,
Declaration const& _declaration,
string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
ErrorReporter& _errorReporter
)
{
if (!_errorLocation)
_errorLocation = &_declaration.location();

Declaration const* shadowedDeclaration = nullptr;
if (_warnOnShadow && !_declaration.name().empty())
for (auto const* decl: _container.resolveName(_declaration.name(), true))
if (decl != &_declaration)
{
shadowedDeclaration = decl;
break;
}

if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract()))
{
SourceLocation firstDeclarationLocation;
SourceLocation secondDeclarationLocation;
Declaration const* conflictingDeclaration = _container.conflictingDeclaration(_declaration, _name);
solAssert(conflictingDeclaration, "");
bool const comparable =
_errorLocation->sourceName &&
conflictingDeclaration->location().sourceName &&
*_errorLocation->sourceName == *conflictingDeclaration->location().sourceName;
if (comparable && _errorLocation->start < conflictingDeclaration->location().start)
{
firstDeclarationLocation = *_errorLocation;
secondDeclarationLocation = conflictingDeclaration->location();
}
else
{
firstDeclarationLocation = conflictingDeclaration->location();
secondDeclarationLocation = *_errorLocation;
}

_errorReporter.declarationError(
secondDeclarationLocation,
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
);
return false;
}
else if (shadowedDeclaration)
{
if (dynamic_cast<MagicVariableDeclaration const*>(shadowedDeclaration))
_errorReporter.warning(
_declaration.location(),
"This declaration shadows a builtin symbol."
);
else
{
auto shadowedLocation = shadowedDeclaration->location();
_errorReporter.warning(
_declaration.location(),
"This declaration shadows an existing declaration.",
SecondarySourceLocation().append("The shadowed declaration is here:", shadowedLocation)
);
}
}
return true;
}

bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
{
if (!m_scopes[&_sourceUnit])
Expand Down Expand Up @@ -590,30 +648,21 @@ void DeclarationRegistrationHelper::closeCurrentScope()
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
{
solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope.");
if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract()))
{
SourceLocation firstDeclarationLocation;
SourceLocation secondDeclarationLocation;
Declaration const* conflictingDeclaration = m_scopes[m_currentScope]->conflictingDeclaration(_declaration);
solAssert(conflictingDeclaration, "");

if (_declaration.location().start < conflictingDeclaration->location().start)
{
firstDeclarationLocation = _declaration.location();
secondDeclarationLocation = conflictingDeclaration->location();
}
else
{
firstDeclarationLocation = conflictingDeclaration->location();
secondDeclarationLocation = _declaration.location();
}
bool warnAboutShadowing = true;
// Do not warn about shadowing for structs and enums because their members are
// not accessible without prefixes.
if (
dynamic_cast<StructDefinition const*>(m_currentScope) ||
dynamic_cast<EnumDefinition const*>(m_currentScope)
)
warnAboutShadowing = false;
// Do not warn about the constructor shadowing the contract.
if (auto fun = dynamic_cast<FunctionDefinition const*>(&_declaration))
if (fun->isConstructor())
warnAboutShadowing = false;

m_errorReporter.declarationError(
secondDeclarationLocation,
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
);
}
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, m_errorReporter);

_declaration.setScope(m_currentScope);
if (_opensScope)
Expand Down
9 changes: 9 additions & 0 deletions libsolidity/analysis/NameAndTypeResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ class DeclarationRegistrationHelper: private ASTVisitor
ASTNode const* _currentScope = nullptr
);

static bool registerDeclaration(
DeclarationContainer& _container,
Declaration const& _declaration,
std::string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
ErrorReporter& _errorReporter
);

private:
bool visit(SourceUnit& _sourceUnit) override;
void endVisit(SourceUnit& _sourceUnit) override;
Expand Down
14 changes: 13 additions & 1 deletion libsolidity/interface/ErrorReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,23 @@ void ErrorReporter::warning(string const& _description)
error(Error::Type::Warning, SourceLocation(), _description);
}

void ErrorReporter::warning(SourceLocation const& _location, string const& _description)
void ErrorReporter::warning(
SourceLocation const& _location,
string const& _description
)
{
error(Error::Type::Warning, _location, _description);
}

void ErrorReporter::warning(
SourceLocation const& _location,
string const& _description,
SecondarySourceLocation const& _secondaryLocation
)
{
error(Error::Type::Warning, _location, _secondaryLocation, _description);
}

void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
{
auto err = make_shared<Error>(_type);
Expand Down
22 changes: 11 additions & 11 deletions libsolidity/interface/ErrorReporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,30 @@ class ErrorReporter

ErrorReporter& operator=(ErrorReporter const& _errorReporter);

void warning(std::string const& _description = std::string());
void warning(std::string const& _description);

void warning(SourceLocation const& _location, std::string const& _description);

void warning(
SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string()
SourceLocation const& _location,
std::string const& _description,
SecondarySourceLocation const& _secondaryLocation
);

void error(
Error::Type _type,
SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string()
);

void declarationError(
SourceLocation const& _location,
SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(),
std::string const& _description = std::string()
std::string const& _description
);

void declarationError(
SourceLocation const& _location,
std::string const& _description = std::string()
SecondarySourceLocation const& _secondaryLocation,
std::string const& _description
);

void declarationError(SourceLocation const& _location, std::string const& _description);

void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);

void parserError(SourceLocation const& _location, std::string const& _description);
Expand Down
69 changes: 67 additions & 2 deletions test/libsolidity/Imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@
* Tests for high level features like import.
*/

#include <string>
#include <boost/test/unit_test.hpp>
#include <test/libsolidity/ErrorCheck.h>

#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h>

#include <boost/test/unit_test.hpp>

#include <string>

using namespace std;

namespace dev
Expand Down Expand Up @@ -202,6 +206,67 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent)
BOOST_CHECK(d.compile());
}

BOOST_AUTO_TEST_CASE(shadowing_via_import)
{
CompilerStack c;
c.addSource("a", "library A {} pragma solidity >=0.0;");
c.addSource("b", "library A {} pragma solidity >=0.0;");
c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";");
BOOST_CHECK(!c.compile());
}

BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports)
{
CompilerStack c;
c.addSource("B.sol", "contract X {} pragma solidity >=0.0;");
c.addSource("b", R"(
pragma solidity >=0.0;
import * as msg from "B.sol";
contract C {
}
)");
BOOST_CHECK(c.compile());
auto numErrors = c.errors().size();
// Sometimes we get the prerelease warning, sometimes not.
BOOST_CHECK(2 <= numErrors && numErrors <= 3);
for (auto const& e: c.errors())
{
string const* msg = e->comment();
BOOST_REQUIRE(msg);
BOOST_CHECK(
msg->find("pre-release") != string::npos ||
msg->find("shadows a builtin symbol") != string::npos
);
}
}

BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports)
{
CompilerStack c;
c.addSource("B.sol", "contract msg {} contract block{} pragma solidity >=0.0;");
c.addSource("b", R"(
pragma solidity >=0.0;
import {msg, block} from "B.sol";
contract C {
}
)");
BOOST_CHECK(c.compile());
auto numErrors = c.errors().size();
// Sometimes we get the prerelease warning, sometimes not.
BOOST_CHECK(4 <= numErrors && numErrors <= 5);
for (auto const& e: c.errors())
{
string const* msg = e->comment();
BOOST_REQUIRE(msg);
BOOST_CHECK(
msg->find("pre-release") != string::npos ||
msg->find("shadows a builtin symbol") != string::npos
);
}
}



BOOST_AUTO_TEST_SUITE_END()

}
Expand Down
Loading