Skip to content

[analyzer] Allow recursive functions to be trivial. #91876

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 6 commits into from
May 25, 2024

Conversation

rniwa
Copy link
Contributor

@rniwa rniwa commented May 12, 2024

No description provided.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels May 12, 2024
@llvmbot
Copy link
Member

llvmbot commented May 12, 2024

@llvm/pr-subscribers-clang-static-analyzer-1

@llvm/pr-subscribers-clang

Author: Ryosuke Niwa (rniwa)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/91876.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp (+9-9)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp (+6)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index ad493587affa0..dd930ea4b4ab5 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -498,22 +498,22 @@ class TrivialFunctionAnalysisVisitor
 
 bool TrivialFunctionAnalysis::isTrivialImpl(
     const Decl *D, TrivialFunctionAnalysis::CacheTy &Cache) {
-  // If the function isn't in the cache, conservatively assume that
-  // it's not trivial until analysis completes. This makes every recursive
-  // function non-trivial. This also guarantees that each function
-  // will be scanned at most once.
-  auto [It, IsNew] = Cache.insert(std::make_pair(D, false));
+  // Treat every recursive function as trivial until otherwise proven.
+  // This guarantees each function is evaluated at most once.
+  auto [It, IsNew] = Cache.insert(std::make_pair(D, true));
   if (!IsNew)
     return It->second;
 
   const Stmt *Body = D->getBody();
-  if (!Body)
-    return false;
+  if (!Body) {
+    Cache[D] = false;
+    return false;    
+  }
 
   TrivialFunctionAnalysisVisitor V(Cache);
   bool Result = V.Visit(Body);
-  if (Result)
-    Cache[D] = true;
+  if (!Result)
+    Cache[D] = false;
 
   return Result;
 }
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
index 073f3252160ee..39bc76197d204 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp
@@ -181,6 +181,8 @@ class RefCounted {
   void method();
   void someFunction();
   int otherFunction();
+  unsigned recursiveFunction(int n) { return !n ? 1 : recursiveFunction(n - 1);  }
+  unsigned recursiveComplexFunction(int n) { return !n ? otherFunction() : recursiveComplexFunction(n - 1);  }
 
   int trivial1() { return 123; }
   float trivial2() { return 0.3; }
@@ -417,6 +419,10 @@ class UnrelatedClass {
     RefCounted::singleton().trivial18(); // no-warning
     RefCounted::singleton().someFunction(); // no-warning
 
+    getFieldTrivial().recursiveFunction(7); // no-warning
+    getFieldTrivial().recursiveComplexFunction(9);
+    // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
+
     getFieldTrivial().someFunction();
     // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}}
     getFieldTrivial().nonTrivial1();

@rniwa rniwa force-pushed the allow-recursive-trivial-function branch from aac9ea1 to a4b877b Compare May 12, 2024 03:20
@rniwa rniwa changed the title [analyzer] Allow recurisve functions to be trivial. [analyzer] Allow recursive functions to be trivial. May 12, 2024
Copy link

github-actions bot commented May 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@rniwa rniwa requested a review from haoNoQ May 12, 2024 03:29
@MitalAshok
Copy link
Contributor

You should add a test for mutually recursive functions. I suspect something like this doesn't work:

    int non_trivial();
    int f(bool b) { return g(!b) + non_trivial(); }
    int g(bool b) { return b ? f(b) : 1; }
    
getFieldTrivial().f(true);  // expected-warning {{...}}
getFieldTrivial().g(true);  // expected-warning {{...}}

Since when analyzing f, f enters the cache as trivial, g is analyzed and sees f in the cache as trivial, so g is marked trivial, but then f is marked as non-trivial (so g should have been marked as non-trivial, but is marked trivial).

If that is an issue, one way to do this "properly" would be to create a graph, and a function is non-trivial if it has a path to a non-trivial function (this might be too expensive to implement directly). Or we could special case simple recursion only, by changing TrivialFunctionAnalysisVisitor to not call isTrivialImpl for the current function only.

@rniwa
Copy link
Contributor Author

rniwa commented May 12, 2024

You should add a test for mutually recursive functions. I suspect something like this doesn't work:

    int non_trivial();
    int f(bool b) { return g(!b) + non_trivial(); }
    int g(bool b) { return b ? f(b) : 1; }
    
getFieldTrivial().f(true);  // expected-warning {{...}}
getFieldTrivial().g(true);  // expected-warning {{...}}

Duh, of course. Thanks for pointing out the obvious flaw.

Ryosuke Niwa added 5 commits May 15, 2024 18:20
… functions.

Instead of assuming every function to be initially trivial, we explicitly track
the set of functions that we're currently visting. When one of the currently visited
function is determined to be not trivial, we clear this set to signal that all
mutually recursive functions are non-trivial. We conclude that a function is trivial
when Visit() call on the function body returned true **AND** the set still contains
the function.

To implement this new algorithm, a new public function, IsFunctionTrivial,
is introduced to TrivialFunctionAnalysisVisitor, and various Visit functions in
TrivialFunctionAnalysisVisitor has been updated to use this function instead of
TrivialFunctionAnalysis::isTrivialImpl, which is now a wrapper for the function.
…letion.

Also fix a bug that IsFunctionTrivial would do a redundant traversal when
a function had been determined to be non-trivial because it's indistinguishable
if a given function had not been traversed or had been found to be non-trivial
because we were using the absense of the function in the hash set to indicate
the non-triviality of a function. Use a hash map of a function to a boolean
instead to explicitly track whether a given function had been visited or not,
and whether a given function had been determined to be non-trivial or not.
@rniwa rniwa force-pushed the allow-recursive-trivial-function branch from 6d46b0b to f638d18 Compare May 16, 2024 03:00
Copy link
Collaborator

@haoNoQ haoNoQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that is an issue, one way to do this "properly" would be to create a graph

Note that we do have a CallGraph class. The static analyzer uses it to identify top-level entry points. It doesn't look like it has a readily available solution for identifying recursive functions. But it may be a good idea to simply use the class and implement it there.

Copy link
Collaborator

@haoNoQ haoNoQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok LGTM then!

@rniwa
Copy link
Contributor Author

rniwa commented May 25, 2024

Thanks for the review!

@rniwa rniwa merged commit 1c90de5 into llvm:main May 25, 2024
8 checks passed
@rniwa rniwa deleted the allow-recursive-trivial-function branch May 25, 2024 00:48
rniwa added a commit to rniwa/llvm-project that referenced this pull request Feb 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants