-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
type conversion checker for array should be stronger #4904
Conversation
libsolidity/ast/Types.cpp
Outdated
@@ -1560,7 1560,9 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) | |||
return false; | |||
// memory/calldata to storage can be converted, but only to a direct storage reference | |||
if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this is weird - shoudn't convertTo
be a pointer in the example case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some print in the function. It seems it is not a pointer.
Actually, the assert in the codegen part does not require it to be a pointer because it is conjunction in the assertion.
I will do more investigation about why it is not a pointer.
810 solAssert( 811 (targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) && 812 typeOnStack.location() == DataLocation::Storage, 813 "Invalid conversion to storage type." 814 );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is converted in the function "ReferenceResolver::endVisit" by the following code:
401 TypePointer type = _variable.typeName()->annotation().type;
402 if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
403 {
404 bool isPointer = !_variable.isStateVariable();
405 type = ref->copyForLocation(typeLoc, isPointer);
406 }
gdb proof:
(gdb) l
396 break;
397 case Location::Unspecified:
398 solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
399 }
400
401 TypePointer type = _variable.typeName()->annotation().type;
402 if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
403 {
404 bool isPointer = !_variable.isStateVariable();
405 type = ref->copyForLocation(typeLoc, isPointer);
(gdb) p _variable.typeName()->annotation().type->toString()
$15 = "int256[10] storage pointer"
(gdb) p _variable.annotation().type->toString()
$16 = "int256[10] storage ref"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, what is the difference of pointer and reference in Solidity. Solidity does not support pointer arithmatic and it seems the pointer or reference is additional info to a type, e.g. the main types in this example are both int[] or int[10].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type is basically a ArrayType with m_isPointer set to true, i.e. "int256[10] storage pointer".
Since ArrayType is always a ReferenceType, shown by code class ReferenceType: public Type
, it always passes the dynamic_cast to reference type test. I think finally all array type will always be a reference type. All array pointer will be replaced to array reference in current implimentation.
@chriseth Sorry, I should try to pass the isoltest first. :-) |
I think the solution should be losing the restrict on the solcAssert in codegen because when the target is a reference it is possible for a convert from memory to storage. The convert from memory to storage reference will copy the content of the memory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted the change.
fix by losing the solcAssert in codegen.
libsolidity/ast/Types.cpp
Outdated
@@ -1560,7 1560,9 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) | |||
return false; | |||
// memory/calldata to storage can be converted, but only to a direct storage reference | |||
if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some print in the function. It seems it is not a pointer.
Actually, the assert in the codegen part does not require it to be a pointer because it is conjunction in the assertion.
I will do more investigation about why it is not a pointer.
810 solAssert( 811 (targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) && 812 typeOnStack.location() == DataLocation::Storage, 813 "Invalid conversion to storage type." 814 );
libsolidity/ast/Types.cpp
Outdated
@@ -1560,7 1560,9 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) | |||
return false; | |||
// memory/calldata to storage can be converted, but only to a direct storage reference | |||
if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is converted in the function "ReferenceResolver::endVisit" by the following code:
401 TypePointer type = _variable.typeName()->annotation().type;
402 if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
403 {
404 bool isPointer = !_variable.isStateVariable();
405 type = ref->copyForLocation(typeLoc, isPointer);
406 }
gdb proof:
(gdb) l
396 break;
397 case Location::Unspecified:
398 solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
399 }
400
401 TypePointer type = _variable.typeName()->annotation().type;
402 if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
403 {
404 bool isPointer = !_variable.isStateVariable();
405 type = ref->copyForLocation(typeLoc, isPointer);
(gdb) p _variable.typeName()->annotation().type->toString()
$15 = "int256[10] storage pointer"
(gdb) p _variable.annotation().type->toString()
$16 = "int256[10] storage ref"
libsolidity/ast/Types.cpp
Outdated
@@ -1560,7 1560,9 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) | |||
return false; | |||
// memory/calldata to storage can be converted, but only to a direct storage reference | |||
if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, what is the difference of pointer and reference in Solidity. Solidity does not support pointer arithmatic and it seems the pointer or reference is additional info to a type, e.g. the main types in this example are both int[] or int[10].
libsolidity/ast/Types.cpp
Outdated
@@ -1560,7 1560,9 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) | |||
return false; | |||
// memory/calldata to storage can be converted, but only to a direct storage reference | |||
if (convertTo.location() == DataLocation::Storage && location() != DataLocation::Storage && convertTo.isPointer()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type is basically a ArrayType with m_isPointer set to true, i.e. "int256[10] storage pointer".
Since ArrayType is always a ReferenceType, shown by code class ReferenceType: public Type
, it always passes the dynamic_cast to reference type test. I think finally all array type will always be a reference type. All array pointer will be replaced to array reference in current implimentation.
Codecov Report
@@ Coverage Diff @@
## develop #4904 /- ##
============================================
Coverage 28.51% 87.88% 59.36%
============================================
Files 314 314
Lines 31496 31510 14
Branches 3730 3721 -9
============================================
Hits 8981 27691 18710
Misses 21835 2557 -19278
- Partials 680 1262 582
|
Finally, I found that explicit type conversion for reference type will always try to convert the type to a reference type. Solidity will modify the is_pointer and location part if it is a explicit type conversion. However, the explicit type conversion for an array will call the implicit type conversion. But, the check for an implicit conversion to a storage reference is very weak because the function believes that this conversion will cause data copy. But, explicit type conversion will not cause any data copy. So, this bug is fixed by distinguishing the explicit type conversion with implicit type conversion. |
Ah great, that sounds much better! I'm wondering whether we should rather fix that explicit conversion converts to reference, because that does not sound right. I actually wonder where we have a valid conversion to storage pointer anyway. Did you find an instance of that? Perhaps |
Hm, so perhaps an earlier version of yours did go in the right direction: conversion between string and bytes is a very special case, because the base types are different, but we can convert without actually copying anything. |
Yeah, two types can convert if and only if the layout and the size are the same. |
Except that we want to have some constraint on the memory. |
Could you add an assert or check that an explicit conversion to pointer is only allowed for a byte/string conversion? |
FYI, the following code has no problem since it is a conversion to "int[] storage ref" that caused a copy.
I am not quite sure if I understand you well on adding the assertion. Current version only allow the basetype to be the same which is more restrict than the assertion you mentioned. The assertion seems useless. |
The methodology for this fix is that the implicit conversion to reference causes copy because an expression that takes a reference as parameter requires that. However, the explicit type conversion will never cause a copy even if it is converting to a reference type. Actually, Solidity tries to avoid implicitly converting a reference to a pointer, for some reason I do not know, by changing the target pointer type to reference type automatically. So, this fix distinguishes the explicit type conversions from implicit ones and regard them as a non-copy conversion. |
I think you want to only allow the explicit conversion to a pointer for target types of bytes or string.
|
@chriseth Hi, do you have any further comments on this PR. Thanks :-) |
The issue I see with this PR is that in some cases now implicit conversions are less restrictive than explicit conversions. In general explicit conversions should always be less restrictive than implicit ones. Do you think it might make sense to not let an explicit conversion change from a storage reference to a storage pointer instead and thus allowing the following, instead of disallowing it?
I also think the following should be valid:
Since in both cases the same is valid as an implicit instead of an explicit conversion. @chriseth, what do you think? |
In Solidity, we will regard a conversion to reference type will need to copy. Essentially, this means Solidity always regard the explicit conversion for an array will need a copy. (I thought the implicit conversion will never copy anything. I think this should be wrong. So, this PR needs to be updated accordingly.) I guess this might be the reason why array pointer type will always be changed to a reference type because the result of an explicit conversion is an RValue (I do not have a concrete proof yet). This might be the difference between implicit conversion and explicit conversion, e.g. implicit conversion will never happen for the x in "x=exp" but may happen for explicit conversion. |
The following only applies to reference types: Implicit conversion can copy. For example if you do |
We REALLY have to re-architect the copy and conversion rules... There was a proposal to require things like |
I much prefer this version. Note that there is also an exception in |
test/libsolidity/syntaxTests/conversion/explicit_conversion_from_storage_array_ref.sol
Outdated
Show resolved
Hide resolved
From the name of the function, I can understand that copyForLocationIfReference will copy the location, but I am not quite sure why should we set the _isPointer. |
If we want to change the behaviour of |
I mean only this location not set it by adding an argument with default value false. |
That might make sense, yes. |
Shall I try this way or wait for more discussion? |
Instead of modifying |
@ekpyron |
For the external tests to pass you need to rebase the PR (they depend on #4926 being merged now). |
I think the latest change is looking good, though! |
rebased. Thx :-) |
test/libsolidity/syntaxTests/conversion/not_allowed_conversion_to_int_array_pointer2.sol
Outdated
Show resolved
Hide resolved
libsolidity/analysis/TypeChecker.cpp
Outdated
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType.get()); | ||
solAssert( | ||
!argArrayType || argArrayType->location() != DataLocation::Storage || | ||
((resultArrayType->isPointer() || (argArrayType->isByteArray() && resultArrayType->isByteArray())) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think resultArrayType
could be nullptr
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assertion should probably only happen if the type are explicitly convertible (note that m_errorReporter.typeErrror
does not throw).
For example, the following will crash this way:
contract C {
uint[] a;
function f() public pure {
uint[] storage b = a;
uint c = uint(uint[](b));
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this should be fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed and added one test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
resultArrayType
could benullptr
here.
In the function convertType, it shows that when argument type is array the target type is array. So, I think it should be ok to also assert it here. :-)
646 switch (stackTypeCategory)
647 {
...
816 case Type::Category::Array:
817 {
818 solAssert(targetTypeCategory == stackTypeCategory, "");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean in CompilerUtils.cpp
? Yes, that part isn't run, if there are errors in the type checker - so we have to gracefully create a error here in the TypeChecker, so that we can later assert in convertType :-). But yes, the latest change will take care of that!
contract C { | ||
int[] x; | ||
function f() public { | ||
int[] storage a = x; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation is off.
As I said earlier, I think the whole concept of reference, pointer and convertability has to be reworked, but I think this PR is a very good solution for now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the liberty to move the type checker assertion and to "rephrase" it slightly, so that it is easier to read in the future, and to add another assertion for resultArrayType
, just to be sure (I hope you don't mind @liangdzou).
With this slight change I'm fine with merging this now.
@ekpyron I am fine with your change. Very much easy to read now. 👍 |
@liangdzou In this case it was only the latest commit that I modified, so I just used |
@ekpyron Thanks 👍 |
Your checklist for this pull request
Please review the guidelines for contributing to this repository.
Please also note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
Checklist
Description
This fix is for the issue #4901.
The type conversion checker for array should be stronger.