-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix owned iterator arrow false positives #8888
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3512,6 +3512,88 @@ bool isLeafDot(const Token* tok) | |
| return isLeafDot(parent); | ||
| } | ||
|
|
||
| static const Variable* singlePlainDataMember(const Scope* scope) | ||
| { | ||
| if (!scope || (scope->type != ScopeType::eStruct && scope->type != ScopeType::eClass) || | ||
| !scope->definedType || !scope->definedType->derivedFrom.empty() || scope->numConstructors != 0) | ||
| return nullptr; | ||
| if (std::any_of(scope->functionList.cbegin(), scope->functionList.cend(), [](const Function& function) { | ||
| return function.hasVirtualSpecifier() || function.hasOverrideSpecifier(); | ||
| })) | ||
| return nullptr; | ||
| if (std::any_of(scope->nestedList.cbegin(), scope->nestedList.cend(), [](const Scope* nested) { | ||
| return nested->type == ScopeType::eUnion; | ||
| })) | ||
| return nullptr; | ||
| const Variable* member = nullptr; | ||
| for (const Variable& var : scope->varlist) { | ||
| if (var.isStatic()) | ||
| continue; | ||
| if (member || !var.isPublic() || var.isArray() || var.isPointer() || var.isReference() || | ||
| var.isRValueReference() || var.isVolatile() || var.hasDefault()) | ||
| return nullptr; | ||
| member = &var; | ||
| } | ||
| return member; | ||
| } | ||
|
|
||
| const Variable* getSingleMemberArrowWriteTarget(const Token* tok) | ||
| { | ||
| if (!Token::Match(tok, ". %name%") || tok->originalName() != "->" || !tok->astOperand1()) | ||
| return nullptr; | ||
| // Exclude bindings, conditional/unevaluated operands and nested writes | ||
| // whose evaluation order is not established by this projection. | ||
| const Token* operation = tok->astParent(); | ||
| if (!operation || operation->astParent() || operation->astOperand1() != tok || | ||
| (!operation->isAssignmentOp() && !Token::Match(operation, "++|--"))) | ||
| return nullptr; | ||
| const Variable* receiver = tok->astOperand1()->variable(); | ||
| if (!receiver || !receiver->isLocal() || receiver->isPointer() || receiver->isArray() || receiver->isReference() || | ||
| receiver->isRValueReference() || receiver->isVolatile()) | ||
| return nullptr; | ||
| // A deferred lambda body does not initialize a captured outer object. | ||
| for (const Scope* enclosing = tok->scope(); enclosing && enclosing != receiver->scope(); enclosing = enclosing->nestedIn) { | ||
| if (enclosing->type == ScopeType::eLambda || enclosing->type == ScopeType::eFunction) | ||
| return nullptr; | ||
| } | ||
| const Scope* scope = receiver->typeScope(); | ||
| const Variable* member = singlePlainDataMember(scope); | ||
| if (!member) | ||
| return nullptr; | ||
| const Variable* leaf = singlePlainDataMember(member->typeScope()); | ||
| if (!leaf || !leaf->valueType() || !leaf->valueType()->isPrimitive() || | ||
| tok->astOperand2() != tok->next() || tok->strAt(1) != leaf->name() || | ||
| (tok->next()->variable() && tok->next()->variable() != leaf)) | ||
| return nullptr; | ||
|
|
||
| const auto operators = scope->functionMap.equal_range("operator->"); | ||
| if (operators.first == operators.second) | ||
| return nullptr; | ||
| for (auto it = operators.first; it != operators.second; ++it) { | ||
| const Function* function = it->second; | ||
| if (!function->functionScope || !Function::returnsPointer(function) || | ||
| function->retType != member->type() || function->argCount() != 0 || function->isVolatile()) | ||
| return nullptr; | ||
| const Token* body = function->functionScope->bodyStart; | ||
| if (!Token::simpleMatch(body, "{ return &") || !body->tokAt(2)->isUnaryOp("&")) | ||
| return nullptr; | ||
| const Token* memberToken = body->tokAt(3); | ||
| if (Token::simpleMatch(memberToken, "this .")) | ||
| memberToken = memberToken->tokAt(2); | ||
| if (!Token::Match(memberToken, "%var% ; }") || memberToken->variable() != member || | ||
| memberToken->tokAt(2) != function->functionScope->bodyEnd) | ||
| return nullptr; | ||
| } | ||
|
|
||
| // A member, free or friend operator& can change the returned address. | ||
| // Keep the proof independent of overload resolution for address-of. | ||
| if (std::any_of(scope->symdb.scopeList.cbegin(), scope->symdb.scopeList.cend(), [](const Scope& candidate) { | ||
| return candidate.functionMap.count("operator&") != 0; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an AI review. Take it with a grain of salt and feel free to reject it by resolving the comment. Performance: this loops over the whole |
||
| })) | ||
| return nullptr; | ||
| return member; | ||
| } | ||
|
|
||
| ExprUsage getExprUsage(const Token* tok, int indirect, const Settings& settings) | ||
| { | ||
| const Token* parent = tok->astParent(); | ||
|
|
||
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 is an AI review. Take it with a grain of salt and feel free to reject it by resolving the comment.
I am worried that the uninitvar part is tailored to the exact example in the ticket. I built the PR and tried small variations of it:
Only the variant with exactly one member in both the iterator and the pointee is fixed. That adds about 80 lines of very specific shape matching to astutils, plus hooks in
ValueFlowAnalyzer::analyzeMatch(),MemberExpressionAnalyzerand two places in checkuninitvar. That is a lot of special-casing in core code for one layout.Maybe split the PR? The checkstl
eraseDereferencepart is self-contained. For the uninit part, a more general approach could mapit->xtoit.m_ptr.xwhenoperator->is known to return&m_ptr, so the existing member tracking handles any layout. Alternatively, treat a call to a non-const useroperator->on an uninitialized object like other non-const member calls.