Refactor TypeChecker visitor for MemberAccess expression.#16448
Refactor TypeChecker visitor for MemberAccess expression.#16448
TypeChecker visitor for MemberAccess expression.#16448Conversation
|
There was an error when running Please check that your changes are working as intended. |
cf38fdd to
fe24189
Compare
1131cb9 to
bf9c42e
Compare
TypeChecker visitor od MemberAccess expression.TypeChecker visitor for MemberAccess expression.
There was a problem hiding this comment.
Pull request overview
This PR refactors the TypeChecker::visit(MemberAccess) implementation by extracting three helper methods and reorganizing the code into a more structured switch-case pattern. The changes aim to improve code maintainability and readability without altering the underlying logic.
Changes:
- Extracted three helper methods:
performOverloadedResolution,handleUnresolvedMemberAccessErrors, andvalidateAccessMemberFunctionType - Reorganized the main visitor logic using exhaustive switch statements for better structure
- Improved variable naming for clarity (e.g.,
exprType→expressionObjectType)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| libsolidity/analysis/TypeChecker.h | Adds declarations for three new helper methods with detailed documentation |
| libsolidity/analysis/TypeChecker.cpp | Refactors MemberAccess visitor by extracting helpers and reorganizing with switch statements; removes unused includes |
However, I found several critical and moderate issues:
- Critical: Null pointer dereference at line 3278 when dereferencing
argumentswithout checking if it's set - Moderate: Four instances of unsafe
reinterpret_castusage instead of the codebase's standarddynamic_castpattern - Nit: Confusing variable naming with double "Type" suffix
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
libsolidity/analysis/TypeChecker.cpp
Outdated
| expressionObjectType, | ||
| memberName, | ||
| _memberAccess.location(), | ||
| (*arguments).numArguments() == 0, |
There was a problem hiding this comment.
Potential null pointer dereference: arguments is dereferenced without checking if it's set. The arguments field is of type std::optional<FuncCallArguments> and is only set when the MemberAccess is used in a function call context. When accessing a member function without calling it (e.g., array.push instead of array.push()), arguments will not be set, causing undefined behavior. This is a pre-existing issue that was carried over during refactoring. Consider guarding this with arguments && (*arguments).numArguments() == 0 or passing arguments ? (*arguments).numArguments() == 0 : true to provide a safe default.
| (*arguments).numArguments() == 0, | |
| arguments ? (*arguments).numArguments() == 0 : true, |
| isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData); | ||
| break; | ||
| case Type::Category::Function: | ||
| { | ||
| if ( | ||
| auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType); |
There was a problem hiding this comment.
This refactoring changes the cast from dynamic_cast (used in the original code and throughout the codebase) to reinterpret_cast, which is unsafe and deviates from established codebase conventions. The original code would have used dynamic_cast<StructType const*> with a null check. While the switch statement provides some guarantee about the category, reinterpret_cast bypasses all type safety and could lead to undefined behavior if the type hierarchy or category logic changes. This contradicts the PR description stating "Intentionally no logic has changed". Please revert to using dynamic_cast to maintain consistency with the rest of the codebase (see usage at lines 2742, 4308).
| isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData); | |
| break; | |
| case Type::Category::Function: | |
| { | |
| if ( | |
| auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType); | |
| { | |
| if (auto const* structType = dynamic_cast<StructType const*>(expressionObjectType)) | |
| isAccessedMemberLValue = !structType->dataStoredIn(DataLocation::CallData); | |
| break; | |
| } | |
| case Type::Category::Function: | |
| { | |
| if ( | |
| auto const* expressionObjectFunctionType = dynamic_cast<FunctionType const*>(expressionObjectType); | |
| expressionObjectFunctionType && |
| isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData); | ||
| break; | ||
| case Type::Category::Function: | ||
| { | ||
| if ( | ||
| auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType); |
There was a problem hiding this comment.
This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.
| isAccessedMemberLValue = !reinterpret_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData); | |
| break; | |
| case Type::Category::Function: | |
| { | |
| if ( | |
| auto const* expressionObjectFunctionType = reinterpret_cast<FunctionType const*>(expressionObjectType); | |
| isAccessedMemberLValue = !dynamic_cast<StructType const*>(expressionObjectType)->dataStoredIn(DataLocation::CallData); | |
| break; | |
| case Type::Category::Function: | |
| { | |
| if ( | |
| auto const* expressionObjectFunctionType = dynamic_cast<FunctionType const*>(expressionObjectType); |
| if (tt->actualType()->category() == Type::Category::Array) | ||
| // TODO some members might be pure, but for example `address(0x123).balance` is not pure | ||
| // although every subexpression is, so leaving this limited for now. | ||
| auto const* expressionObjectTypeType = reinterpret_cast<TypeType const*>(expressionObjectType); |
There was a problem hiding this comment.
This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.
| auto const* expressionObjectTypeType = reinterpret_cast<TypeType const*>(expressionObjectType); | |
| auto const* expressionObjectTypeType = dynamic_cast<TypeType const*>(expressionObjectType); |
| } | ||
| case Type::Category::Magic: | ||
| { | ||
| auto const* expressionObjectMagicType = reinterpret_cast<MagicType const*>(expressionObjectType); |
There was a problem hiding this comment.
This refactoring changes the cast from dynamic_cast to reinterpret_cast, which is unsafe and deviates from established codebase conventions. Please revert to using dynamic_cast as in the original code to maintain type safety and consistency.
| auto const* expressionObjectMagicType = reinterpret_cast<MagicType const*>(expressionObjectType); | |
| auto const* expressionObjectMagicType = dynamic_cast<MagicType const*>(expressionObjectType); |
| VirtualLookup requiredLookup = VirtualLookup::Static; | ||
|
|
||
| if (auto funType = dynamic_cast<FunctionType const*>(annotation.type)) | ||
| if (auto accessedMemberFunctionTypeType = dynamic_cast<FunctionType const*>(accessedMemberAnnotation.type)) |
There was a problem hiding this comment.
The variable name accessedMemberFunctionTypeType is confusing with "Type" appearing twice. Since this variable holds a pointer to the FunctionType of the accessed member, it should be named accessedMemberFunctionType for clarity. The double "Type" doesn't add meaningful information and makes the code harder to read.
Remove unnecessary includes
Fix arguments checking.
bf9c42e to
06bb313
Compare
This PR refactors implementation of the
TypeCheckervisitor forMemberAccessexpression. It is needed based on the outcome from the discussion on changes in #16376 (comment).Intentionally no logic has changed.