-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Rework purity flag setting for constant variables accessed via member access. #16376
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
base: develop
Are you sure you want to change the base?
Conversation
43069b6 to
ac1500e
Compare
5a4a8e4 to
154b9bc
Compare
154b9bc to
157e1bb
Compare
libsolidity/analysis/TypeChecker.cpp
Outdated
| else if (exprType->category() == Type::Category::Module) | ||
| { | ||
| annotation.isPure = *_memberAccess.expression().annotation().isPure; | ||
| // Very similar as for `ContractType`, but additionally we have to handle `Mod.C` case, where `C` in contract |
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.
| // Very similar as for `ContractType`, but additionally we have to handle `Mod.C` case, where `C` in contract | |
| // Very similar as for `ContractType`, but additionally we have to handle `Mod.C` case, where `C` is contract |
libsolidity/analysis/TypeChecker.cpp
Outdated
| auto const* typeTypeMember = dynamic_cast<TypeType const*>(annotation.type); | ||
| typeTypeMember && | ||
| ( | ||
| typeTypeMember->actualType()->category() == Type::Category::Struct || | ||
| typeTypeMember->actualType()->category() == Type::Category::Enum || | ||
| typeTypeMember->actualType()->category() == Type::Category::Contract | ||
| ) |
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 would add UserDefinedValueType as well.
And if you add that I think this covers pretty much every category that is possible inside a module via language syntax. So I'd simply make it always true for TypeType and just keep the condition as an assert to make sure we're correct about this assumption.
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.
Same thing for the contract case. UserDefinedValueType and definitions are allowed inside contracts. Contract is not, but if it was, I should be marked pure as well, so I'd include it.
This makes the code the same as for modules. And this sounds about right - I don't think this logic is in any way specific to modules. TypeType should generally always be constant. This is what we already do when type-checking Identifier:
solidity/libsolidity/analysis/TypeChecker.cpp
Lines 3701 to 3702 in 2673c4b
| else if (dynamic_cast<TypeType const*>(annotation.type)) | |
| annotation.isPure = true; |
libsolidity/analysis/TypeChecker.cpp
Outdated
| if (auto const* functionTypeMember = dynamic_cast<FunctionType const*>(annotation.type); | ||
| functionTypeMember && | ||
| ( | ||
| functionTypeMember->isPure() || | ||
| functionTypeMember->kind() == FunctionType::Kind::Internal || | ||
| functionTypeMember->kind() == FunctionType::Kind::Event | ||
| ) |
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 this is missing Error. Same for the contract case.
And, again, with that this covers all possible function types you can have as members on a module, so you can just assert and reduce the condition to if (functionTypeMember).
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.
Well, technically you could try to tack an External function onto a module this way:
import "m.sol" as M;
library L {
function f(uint x) external {}
}
contract C {
using L for *;
function test() public {
M.f; // Error: Member "f" not found or not visible after argument-dependent lookup in module "m.sol".
}
}using for attaches the function to all types, even those unnameable, so you can use this to attach functions even to things which cannot otherwise be the type of x, e.g. to literals. And we cannot filter out non-matching types simply by checking if the type is the same, because we have to take into account implicit conversions so there is a chance that you could attach your function to weird things this way, even if you won't be able to call it.
Not sure if we're doing more complex filtering here or if it's just hardcoded to only accept some specific types, but either way this fortunately does not compile, so we do not have to worry about external functions after all.
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.
Error and Declaration are cover by isPure.
libsolidity/analysis/TypeChecker.cpp
Outdated
| // See `ContractType::nativeMembers` for details. | ||
| solAssert(annotation.referencedDeclaration); | ||
| annotation.isLValue = annotation.referencedDeclaration->isLValue(); | ||
| // Expressions like `C.foo`, `C.Ev` are pure and they must generate `Statement has no effect.` warning. |
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.
What about function pointers?
contract C {
function() internal returns (uint) fi;
function() internal returns (uint) gi;
function() external returns (uint) fe;
function() external returns (uint) ge;
function test() public {
C.fi = C.gi;
C.fe = C.ge;
}
}And generally it looks like you can refer to member variables via contract name, which means that you can have non-pure things as contract type members:
contract C {
uint x;
uint y;
function test() public {
C.x = C.y;
}
}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 comment was a little unclear. I was thinking of C.foo;, C.Ev; statements. The same should apply to C.fi;, C.gi; ,C.fe;, C.ge;, C.x; and C.y;, the warning should be generated. Problem is that it's generated based on isPure flag, but this flag is also used to mark that an expression can be used to initialize a constant (compile-time) variable.
If we set that all the expression have isPure == true, it makes
contract C {
uint y;
uint constant x = C.y;
}compiles. Which is obviously wrong.
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 have decided to leave internal call kind function as not pure. So C.foo is never pure. In the #8263 the pure flag was set to true for external call kind functions accessed via contract name. Should we revert this or it's ok? @cameel
For the function pointers we mark them pure only if the pointer variable is constant. (Similar to other variables.) For now it never happens because there is no way to initialize constant function pointer (#16339).
libsolidity/analysis/TypeChecker.cpp
Outdated
| // In case `Base.value` or `Lib.value` and when `value` is constant, the expression is pure. | ||
| else if ( | ||
| auto const* varDeclMember = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration); | ||
| varDeclMember && | ||
| varDeclMember->isConstant() | ||
| ) | ||
| annotation.isPure = *_memberAccess.expression().annotation().isPure; | ||
| annotation.isPure = true; |
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.
Currently you're checking isConstant() only on contract types and modules, assuming that the only other possible case is access via a contract variable (which means it must be a getter function). This is probably true, but we should guard it with an assert which will fail if we missed any other case.
Or it might be simpler to just keep the check as it was and only specifically exclude the contract variable 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 prefer to add separated asset.
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.
BTW this can be also a variable declaration in a struct, accessed via a member access.
libsolidity/ast/AST.h
Outdated
| /// @returns the type for members of the containing contract type that refer to this declaration. | ||
| /// This can only be called once types of variable declarations have already been resolved. | ||
| virtual Type const* typeViaContractName() const { return type(); } | ||
| virtual Type const* typeViaContractName(bool) const { return type(); } |
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 new argument should be documented.
Also, what exactly does it mean? inDerivingScope sounds a little ambiguous, maybe the name should be adjusted? Is it saying whether we're still within the scope that contains the definition or something? EDIT: After reading further I see that's a preexisting name and literally refers to the derived contract. How about calling the parameter inherited?
I would also consider making it an enum. These boolean args we already have are not very readable at the point of call and I usually suggest adding a comment that mentions the parameter name when calling the function anyway. An enum would make that unnecessary.
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.
Reworked. I separated additional function typeViaForeignContractName and removed the flag.
libsolidity/ast/AST.cpp
Outdated
| Type const* FunctionDefinition::typeViaContractName(bool inDerivingScope) const | ||
| { | ||
| solAssert( | ||
| libraryFunction() || visibility() != Visibility::Private, | ||
| "Private function members are not available via contract type name." | ||
| ); |
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.
Maybe also assert that if inDerivedScope, the function cannot be a library function? That combination would not make sense.
And isVisibleInDerivedContracts() must also be true for that function. On the other hand when not in derived scope, isVisibleInContract() should still be true.
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.
Also, when libraryFunction(), assert that isVisibleAsLibraryMember() is true. This will not let you call this with private library functions, but those are not callable as Lib.foo(), only foo() anyway.
And that isImplemented() is true for library functions.
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.
There are cases when accessing private library members is allowed. I.e. in using statement.
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.
We also cannot isImplemented at this stage as it's checked later in TypeChecker::visit(FunctionDefinition const& _function). Test triggering this error syntaxTests/nameAndTypeResolution/229_call_to_library_function
libsolidity/ast/Types.cpp
Outdated
| { | ||
| // In case of regular contract being accessed from by external contract (not in deriving scope), | ||
| // add only externally visible members. | ||
| if (declaration->isVisibleViaContractTypeAccess()) |
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.
We should rename this function to something like isVisibleViaContractInstance(). I thought it was referring to access like C.foo() until I looked at the definition.
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.
Done
libsolidity/ast/Types.cpp
Outdated
| // In case of regular contract being accessed from by external contract (not in deriving scope), | ||
| // add only externally visible members. | ||
| if (declaration->isVisibleViaContractTypeAccess()) | ||
| members.emplace_back(declaration, declaration->typeViaContractName(inDerivingScope)); |
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.
All three branches here now call typeViaContractName(), so you could simplify it all into a single condition.
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.
Not any more. :)
libsolidity/ast/Types.cpp
Outdated
| // In case of regular contract (not library) and member is in the same deriving scope, add all | ||
| // members which are not private. Private members cannot be accessed via contract type name | ||
| // i.e C.fooPrivate. | ||
| if (declaration->visibility() > Visibility::Private) |
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 this will add external functions while previously isVisibleInDerivedContracts() would filter them out.
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.
Yes. And the Declaration kind was set below. Now it's handled by the typeViaContractName(ContractNameAccessKind _accessKind)
157e1bb to
d16558d
Compare
libsolidity/analysis/TypeChecker.cpp
Outdated
| // TODO: because of different kind. Left-hand side of the variable declaration never has `Declaration` kind. | ||
| if (auto const* functionTypeMember = dynamic_cast<FunctionType const*>(annotation.type)) | ||
| { | ||
| if( |
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.
Coding style error
libsolidity/analysis/TypeChecker.cpp
Outdated
| annotation.isPure = *_memberAccess.expression().annotation().isPure; | ||
| if (auto const* functionTypeMember = dynamic_cast<FunctionType const*>(annotation.type)) | ||
| { | ||
| if( |
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.
Coding style error
20bf12c to
800369b
Compare
In this PR:
TypeCheckerrework forMemberAccessimplementationisPuresetting forVariableDeclarationof constant variable to the context of accessing it viaTypeType, not viathis.TypeTypecase forContractTypefor to properly handle events, errors, structs and enums when accessing via contract name.nativeMembersimplementation andtypeViaContractNameto return function type with call kind properly set for any context.