Skip to content

[alpha.webkit.UncountedLocalVarsChecker] Recursive functions are erroneously treated as non-trivial #110973

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

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 22 additions & 36 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,16 +281,29 @@ class TrivialFunctionAnalysisVisitor
return true;
}

template <typename CheckFunction>
bool WithCachedResult(const Stmt *S, CheckFunction Function) {
// If the statement isn't in the cache, conservatively assume that
// it's not trivial until analysis completes. Insert false to the cache
// first to avoid infinite recursion.
auto [It, IsNew] = Cache.insert(std::make_pair(S, false));
template <typename StmtOrDecl, typename CheckFunction>
bool WithCachedResult(const StmtOrDecl *S, CheckFunction Function) {
auto CacheIt = Cache.find(S);
if (CacheIt != Cache.end())
return CacheIt->second;

// Treat a recursive statement to be trivial until proven otherwise.
auto [RecursiveIt, IsNew] = RecursiveFn.insert(std::make_pair(S, true));
if (!IsNew)
return It->second;
return RecursiveIt->second;

bool Result = Function();

if (!Result) {
for (auto &It : RecursiveFn)
It.second = false;
}
RecursiveIt = RecursiveFn.find(S);
assert(RecursiveIt != RecursiveFn.end());
Result = RecursiveIt->second;
RecursiveFn.erase(RecursiveIt);
Cache[S] = Result;

return Result;
}

Expand All @@ -300,16 +313,7 @@ class TrivialFunctionAnalysisVisitor
TrivialFunctionAnalysisVisitor(CacheTy &Cache) : Cache(Cache) {}

bool IsFunctionTrivial(const Decl *D) {
auto CacheIt = Cache.find(D);
if (CacheIt != Cache.end())
return CacheIt->second;

// Treat a recursive function call to be trivial until proven otherwise.
auto [RecursiveIt, IsNew] = RecursiveFn.insert(std::make_pair(D, true));
if (!IsNew)
return RecursiveIt->second;

bool Result = [&]() {
return WithCachedResult(D, [&]() {
if (auto *CtorDecl = dyn_cast<CXXConstructorDecl>(D)) {
for (auto *CtorInit : CtorDecl->inits()) {
if (!Visit(CtorInit->getInit()))
Expand All @@ -320,20 +324,7 @@ class TrivialFunctionAnalysisVisitor
if (!Body)
return false;
return Visit(Body);
}();

if (!Result) {
// D and its mutually recursive callers are all non-trivial.
for (auto &It : RecursiveFn)
It.second = false;
}
RecursiveIt = RecursiveFn.find(D);
assert(RecursiveIt != RecursiveFn.end());
Result = RecursiveIt->second;
RecursiveFn.erase(RecursiveIt);
Cache[D] = Result;

return Result;
});
}

bool VisitStmt(const Stmt *S) {
Expand Down Expand Up @@ -590,11 +581,6 @@ bool TrivialFunctionAnalysis::isTrivialImpl(

bool TrivialFunctionAnalysis::isTrivialImpl(
const Stmt *S, TrivialFunctionAnalysis::CacheTy &Cache) {
// If the statement isn't in the cache, conservatively assume that
// it's not trivial until analysis completes. Unlike a function case,
// we don't insert an entry into the cache until Visit returns
// since Visit* functions themselves make use of the cache.

TrivialFunctionAnalysisVisitor V(Cache);
bool Result = V.Visit(S);
assert(Cache.contains(S) && "Top-level statement not properly cached!");
Expand Down
39 changes: 39 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,42 @@ void foo() {
}

} // namespace local_assignment_to_global

namespace local_var_in_recursive_function {

struct TreeNode {
Ref<TreeNode> create() { return Ref(*new TreeNode); }

void ref() const { ++refCount; }
void deref() const {
if (!--refCount)
delete this;
}

int recursiveCost();
int recursiveWeight();
int weight();

int cost { 0 };
mutable unsigned refCount { 0 };
TreeNode* nextSibling { nullptr };
TreeNode* firstChild { nullptr };
};

int TreeNode::recursiveCost() {
// no warnings
unsigned totalCost = cost;
for (TreeNode* node = firstChild; node; node = node->nextSibling)
totalCost += recursiveCost();
return totalCost;
}

int TreeNode::recursiveWeight() {
unsigned totalCost = weight();
for (TreeNode* node = firstChild; node; node = node->nextSibling)
// expected-warning@-1{{Local variable 'node' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}}
totalCost += recursiveWeight();
return totalCost;
}

} // namespace local_var_in_recursive_function
12 changes: 12 additions & 0 deletions clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,15 @@ class RefCounted {
void mutuallyRecursive8() { mutuallyRecursive9(); someFunction(); }
void mutuallyRecursive9() { mutuallyRecursive8(); }

int recursiveCost() {
unsigned totalCost = 0;
for (unsigned i = 0; i < sizeof(children)/sizeof(*children); ++i) {
if (auto* child = children[i])
totalCost += child->recursiveCost();
}
return totalCost;
}

int trivial1() { return 123; }
float trivial2() { return 0.3; }
float trivial3() { return (float)0.4; }
Expand Down Expand Up @@ -431,6 +440,7 @@ class RefCounted {
Number* number { nullptr };
ComplexNumber complex;
Enum enumValue { Enum::Value1 };
RefCounted* children[4];
};

unsigned RefCounted::s_v = 0;
Expand Down Expand Up @@ -539,6 +549,8 @@ class UnrelatedClass {
getFieldTrivial().mutuallyRecursive9();
// expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}

getFieldTrivial().recursiveCost(); // no-warning

getFieldTrivial().someFunction();
// expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
getFieldTrivial().nonTrivial1();
Expand Down
Loading