Merge pull request #1637 from ethereum/warn-shadowing-globals

Warn if shadowing built-ins
This commit is contained in:
chriseth 2017-07-26 17:30:27 +02:00 committed by GitHub
commit 925569bfa3
7 changed files with 269 additions and 78 deletions

View File

@ -8,6 +8,7 @@ Features:
* Type Checker: Include types in explicit conversion error message. * Type Checker: Include types in explicit conversion error message.
* Type Checker: Raise proper error for arrays too large for ABI encoding. * Type Checker: Raise proper error for arrays too large for ABI encoding.
* Type checker: Warn if using ``this`` in a constructor. * Type checker: Warn if using ``this`` in a constructor.
* Type checker: Warn when existing symbols, including builtins, are overwritten.
Bugfixes: Bugfixes:
* Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs. * Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs.

View File

@ -104,29 +104,18 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
} }
else else
for (Declaration const* declaration: declarations) for (Declaration const* declaration: declarations)
{ if (!DeclarationRegistrationHelper::registerDeclaration(
ASTString const* name = alias.second ? alias.second.get() : &declaration->name(); target, *declaration, alias.second.get(), &imp->location(), true, m_errorReporter
if (!target.registerDeclaration(*declaration, name)) ))
{
m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + *name + "\" already declared."
);
error = true; error = true;
}
}
} }
else if (imp->name().empty()) else if (imp->name().empty())
for (auto const& nameAndDeclaration: scope->second->declarations()) for (auto const& nameAndDeclaration: scope->second->declarations())
for (auto const& declaration: nameAndDeclaration.second) for (auto const& declaration: nameAndDeclaration.second)
if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first)) if (!DeclarationRegistrationHelper::registerDeclaration(
{ target, *declaration, &nameAndDeclaration.first, &imp->location(), true, m_errorReporter
m_errorReporter.declarationError( ))
imp->location(), error = true;
"Identifier \"" + nameAndDeclaration.first + "\" already declared."
);
error = true;
}
} }
return !error; return !error;
} }
@ -450,6 +439,75 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper(
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); 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) bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
{ {
if (!m_scopes[&_sourceUnit]) if (!m_scopes[&_sourceUnit])
@ -590,30 +648,21 @@ void DeclarationRegistrationHelper::closeCurrentScope()
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
{ {
solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope."); 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) bool warnAboutShadowing = true;
{ // Do not warn about shadowing for structs and enums because their members are
firstDeclarationLocation = _declaration.location(); // not accessible without prefixes.
secondDeclarationLocation = conflictingDeclaration->location(); if (
} dynamic_cast<StructDefinition const*>(m_currentScope) ||
else dynamic_cast<EnumDefinition const*>(m_currentScope)
{ )
firstDeclarationLocation = conflictingDeclaration->location(); warnAboutShadowing = false;
secondDeclarationLocation = _declaration.location(); // 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( registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, m_errorReporter);
secondDeclarationLocation,
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
);
}
_declaration.setScope(m_currentScope); _declaration.setScope(m_currentScope);
if (_opensScope) if (_opensScope)

View File

@ -136,6 +136,15 @@ public:
ASTNode const* _currentScope = nullptr ASTNode const* _currentScope = nullptr
); );
static bool registerDeclaration(
DeclarationContainer& _container,
Declaration const& _declaration,
std::string const* _name,
SourceLocation const* _errorLocation,
bool _warnOnShadow,
ErrorReporter& _errorReporter
);
private: private:
bool visit(SourceUnit& _sourceUnit) override; bool visit(SourceUnit& _sourceUnit) override;
void endVisit(SourceUnit& _sourceUnit) override; void endVisit(SourceUnit& _sourceUnit) override;

View File

@ -42,11 +42,23 @@ void ErrorReporter::warning(string const& _description)
error(Error::Type::Warning, SourceLocation(), _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); 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) void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
{ {
auto err = make_shared<Error>(_type); auto err = make_shared<Error>(_type);

View File

@ -41,29 +41,29 @@ public:
ErrorReporter& operator=(ErrorReporter const& _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( void warning(
SourceLocation const& _location = SourceLocation(), SourceLocation const& _location,
std::string const& _description = std::string() std::string const& _description,
SecondarySourceLocation const& _secondaryLocation
); );
void error( void error(
Error::Type _type, Error::Type _type,
SourceLocation const& _location = SourceLocation(), SourceLocation const& _location,
std::string const& _description = std::string() std::string const& _description
); );
void declarationError( void declarationError(
SourceLocation const& _location, SourceLocation const& _location,
SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(), SecondarySourceLocation const& _secondaryLocation,
std::string const& _description = std::string() std::string const& _description
); );
void declarationError( void declarationError(SourceLocation const& _location, std::string const& _description);
SourceLocation const& _location,
std::string const& _description = std::string()
);
void fatalDeclarationError(SourceLocation const& _location, std::string const& _description); void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);

View File

@ -20,11 +20,15 @@
* Tests for high level features like import. * Tests for high level features like import.
*/ */
#include <string> #include <test/libsolidity/ErrorCheck.h>
#include <boost/test/unit_test.hpp>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <boost/test/unit_test.hpp>
#include <string>
using namespace std; using namespace std;
namespace dev namespace dev
@ -202,6 +206,67 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent)
BOOST_CHECK(d.compile()); 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() BOOST_AUTO_TEST_SUITE_END()
} }

View File

@ -5498,22 +5498,6 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_library)
CHECK_SUCCESS_NO_WARNINGS(text); CHECK_SUCCESS_NO_WARNINGS(text);
} }
BOOST_AUTO_TEST_CASE(does_not_warn_non_magic_msg_value)
{
char const* text = R"(
contract C {
struct msg {
uint256 value;
}
function f() {
msg.value;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_modifier_following_non_payable_public_function) BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_modifier_following_non_payable_public_function)
{ {
char const* text = R"( char const* text = R"(
@ -6127,6 +6111,85 @@ BOOST_AUTO_TEST_CASE(no_unused_inline_asm)
CHECK_SUCCESS_NO_WARNINGS(text); CHECK_SUCCESS_NO_WARNINGS(text);
} }
BOOST_AUTO_TEST_CASE(shadowing_builtins_with_functions)
{
char const* text = R"(
contract C {
function keccak256() {}
}
)";
CHECK_WARNING(text, "shadows a builtin symbol");
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_with_variables)
{
char const* text = R"(
contract C {
function f() {
uint msg;
msg;
}
}
)";
CHECK_WARNING(text, "shadows a builtin symbol");
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_with_parameters)
{
char const* text = R"(
contract C {
function f(uint require) {
require = 2;
}
}
)";
CHECK_WARNING(text, "shadows a builtin symbol");
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_with_return_parameters)
{
char const* text = R"(
contract C {
function f() returns (uint require) {
require = 2;
}
}
)";
CHECK_WARNING(text, "shadows a builtin symbol");
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_with_events)
{
char const* text = R"(
contract C {
event keccak256();
}
)";
CHECK_WARNING(text, "shadows a builtin symbol");
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_struct)
{
char const* text = R"(
contract C {
struct a {
uint msg;
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_constructor)
{
char const* text = R"(
contract C {
function C() {}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(callable_crash) BOOST_AUTO_TEST_CASE(callable_crash)
{ {
char const* text = R"( char const* text = R"(
@ -6245,14 +6308,6 @@ BOOST_AUTO_TEST_CASE(create2_as_variable)
CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name"); CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name");
} }
BOOST_AUTO_TEST_CASE(shadowing_warning_can_be_removed)
{
char const* text = R"(
contract C {function f() {assembly {}}}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(warn_unspecified_storage) BOOST_AUTO_TEST_CASE(warn_unspecified_storage)
{ {
char const* text = R"( char const* text = R"(