mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #2437 from ethereum/warnDoubleCopyStorage
Warn about copies in storage that might overwrite unexpectedly.
This commit is contained in:
commit
bc31d4969c
@ -11,6 +11,7 @@ Features:
|
||||
* Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias.
|
||||
* Inline Assembly: ``for`` and ``switch`` statements.
|
||||
* Inline Assembly: function definitions and function calls.
|
||||
* Type Checker: Warn about copies in storage that might overwrite unexpectedly.
|
||||
* Code Generator: Added the Whiskers template system.
|
||||
* Remove obsolete Why3 output.
|
||||
|
||||
|
@ -364,6 +364,35 @@ void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract)
|
||||
m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables");
|
||||
}
|
||||
|
||||
void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
|
||||
{
|
||||
TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide()));
|
||||
TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide()));
|
||||
|
||||
bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front());
|
||||
size_t storageToStorageCopies = 0;
|
||||
size_t toStorageCopies = 0;
|
||||
for (size_t i = 0; i < lhs.components().size(); ++i)
|
||||
{
|
||||
ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get());
|
||||
if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer())
|
||||
continue;
|
||||
size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i);
|
||||
solAssert(rhsPos < rhs.components().size(), "");
|
||||
toStorageCopies++;
|
||||
if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage))
|
||||
storageToStorageCopies++;
|
||||
}
|
||||
if (storageToStorageCopies >= 1 && toStorageCopies >= 2)
|
||||
m_errorReporter.warning(
|
||||
_assignment.location(),
|
||||
"This assignment performs two copies to storage. Since storage copies do not first "
|
||||
"copy to a temporary location, one of them might be overwritten before the second "
|
||||
"is executed and thus may have unexpected effects. It is safer to perform the copies "
|
||||
"separately or assign to storage pointers first."
|
||||
);
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
||||
{
|
||||
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
|
||||
@ -1047,6 +1076,8 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
// Sequenced assignments of tuples is not valid, make the result a "void" type.
|
||||
_assignment.annotation().type = make_shared<TupleType>();
|
||||
expectType(_assignment.rightHandSide(), *tupleType);
|
||||
|
||||
checkDoubleStorageAssignment(_assignment);
|
||||
}
|
||||
else if (t->category() == Type::Category::Mapping)
|
||||
{
|
||||
|
@ -69,6 +69,9 @@ private:
|
||||
void checkContractExternalTypeClashes(ContractDefinition const& _contract);
|
||||
/// Checks that all requirements for a library are fulfilled if this is a library.
|
||||
void checkLibraryRequirements(ContractDefinition const& _contract);
|
||||
/// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage.
|
||||
/// Should only be called if the left hand side is tuple-typed.
|
||||
void checkDoubleStorageAssignment(Assignment const& _assignment);
|
||||
|
||||
virtual void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||
virtual void endVisit(UsingForDirective const& _usingFor) override;
|
||||
|
@ -4483,6 +4483,38 @@ BOOST_AUTO_TEST_CASE(array_copy_including_mapping)
|
||||
BOOST_CHECK(storageEmpty(m_contractAddress));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(swap_in_storage_overwrite)
|
||||
{
|
||||
// This tests a swap in storage which does not work as one
|
||||
// might expect because we do not have temporary storage.
|
||||
// (x, y) = (y, x) is the same as
|
||||
// y = x;
|
||||
// x = y;
|
||||
char const* sourceCode = R"(
|
||||
contract c {
|
||||
struct S { uint a; uint b; }
|
||||
S public x;
|
||||
S public y;
|
||||
function set() {
|
||||
x.a = 1; x.b = 2;
|
||||
y.a = 3; y.b = 4;
|
||||
}
|
||||
function swap() {
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0), u256(0)));
|
||||
BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(0), u256(0)));
|
||||
BOOST_CHECK(callContractFunction("set()") == encodeArgs());
|
||||
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2)));
|
||||
BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(3), u256(4)));
|
||||
BOOST_CHECK(callContractFunction("swap()") == encodeArgs());
|
||||
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2)));
|
||||
BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(1), u256(2)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
|
@ -5816,6 +5816,80 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop)
|
||||
success(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
struct S { uint a; uint b; }
|
||||
S x; S y;
|
||||
function f() {
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "This assignment performs two copies to storage.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
struct S { uint a; uint b; }
|
||||
S x; S y;
|
||||
function f() {
|
||||
(x, y, ) = (y, x, 1, 2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "This assignment performs two copies to storage.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
struct S { uint a; uint b; }
|
||||
S x; S y;
|
||||
function f() {
|
||||
(,x, y) = (1, 2, y, x);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_WARNING(text, "This assignment performs two copies to storage.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(nowarn_swap_memory)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
struct S { uint a; uint b; }
|
||||
function f() {
|
||||
S memory x;
|
||||
S memory y;
|
||||
(x, y) = (y, x);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
struct S { uint a; uint b; }
|
||||
S x; S y;
|
||||
function f() {
|
||||
S storage x_local = x;
|
||||
S storage y_local = y;
|
||||
S storage z_local = x;
|
||||
(x, y_local, x_local, z_local) = (y, x_local, y_local, y);
|
||||
}
|
||||
}
|
||||
)";
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(warn_unused_local)
|
||||
{
|
||||
char const* text = R"(
|
||||
|
Loading…
Reference in New Issue
Block a user