This is an archive of the discontinued LLVM Phabricator instance.

[Analysis] Bug fix for exploded graph branching in evalCall for constructor
ClosedPublic

Authored by vrnithinkumar on Aug 11 2020, 4:39 PM.

Details

Summary

Fix for the bug in the implementation of evalCall() for constructors.
The StmtNodeBuilder created isn't actually used during runCheckersForEvalCall(). A new builder is used instead, inside runCheckersForEvalCall(). So NodeBuilder believes that no transitions were added inside runCheckersForEvalCall(), and therefore the analysis must continue from the predecessor node. And it was causing the exploded graph branching in evalCall for a constructor.

Fixing it by passing the NodeBuilder to runCheckersForEvalCall() and use it instead of new NodeBuilder

Diff Detail

Event Timeline

vrnithinkumar created this revision.Aug 11 2020, 4:39 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 11 2020, 4:39 PM
vrnithinkumar requested review of this revision.Aug 11 2020, 4:39 PM
vrnithinkumar edited the summary of this revision. (Show Details)Aug 11 2020, 4:52 PM
NoQ added a comment.Aug 11 2020, 5:36 PM

Thanks!!

I'm having second thoughts on re-using the existing builder. Most other runCheckers...() methods are building their own. Given how confusing this entire node builder business is, i believe we should not deviate from known working patterns.

Please forgive me if i at some point decide to take over this patch and do this myself. I have strong opinions about this entire NodeBuilder API and i'm pretty much screaming internally when i try to understand why it's made the way it's made, so there's a high chance i'll want to introduce some invasive sanity into it on my own.

clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
619

We should probably delete the copy-constructor for node builders. I've no idea what it's supposed to do anyway and the whole problem that we're having here is due to there being too many of them already.

clang/test/Analysis/smart-ptr-text-output.cpp
39–40

Ok, these notes shouldn't be there; a note on .release() is sufficient to understand the warning and it looks like that's one more place where we should mark the region as uninteresting.

Can you try to debug why did they suddenly show up?

clang/test/Analysis/smart-ptr.cpp
123–124

That's indeed the intended consequence of your patch: we're no longer exploring a bogus execution path that's parallel to the previous null dereference on line 133, therefore we're no longer emitting this warning but instead correctly interrupting the entire analysis after we've found that other null dereference.

That said, we should preserve the test so that it was still testing whatever it was testing previously. Can you split up this function into smaller functions so that each test was running independently?

vrnithinkumar added inline comments.Aug 12 2020, 4:38 PM
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
619

So we should disable the copying of NodeBuilder and create a heap allocated NodeBuilder and use pointer to pass around functions?

clang/test/Analysis/smart-ptr-text-output.cpp
39–40

I checked the exploded graph for this test case.
Before the bug fix, there exists a path where the no Note Tag is added to the corresponding CXXConstructExpr. After the fix removed this branching theres always a Note Tag on Ctr.

Since the note on .release() is sufficient to understand the warning and I agree we should mark this region as uninteresting.

NoQ added inline comments.Aug 12 2020, 10:02 PM
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
619

No-no, keep it on the stack and don't pass it around *at all*.

clang/test/Analysis/smart-ptr-text-output.cpp
39–40

Ok, fair enough! Let's add a FIXME.

  • Addressing review comments
vrnithinkumar marked 5 inline comments as done.Aug 13 2020, 3:56 PM
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
619

Sorry, I am still little confused.

Do we have to make the fix without passing around the NodeBuilder?
And delete copy-constructor for NodeBuilder in this patch?

clang/test/Analysis/smart-ptr-text-output.cpp
39–40

Added the FIXME

clang/test/Analysis/smart-ptr.cpp
123–124

Separated the function into smaller functions.

NoQ added inline comments.Aug 13 2020, 9:40 PM
clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
619

runCheckersForEvalCall() already has its own builder, you don't need another.

vrnithinkumar marked 3 inline comments as done.
  • Fix without passing the NodeBuilder
vrnithinkumar added inline comments.Aug 14 2020, 2:47 PM
clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
682 ↗(On Diff #285763)

runCheckersForEvalCall() already has its own builder, you don't need another.

In that case is it okay to clear the ExplodedNodeSet DstEvaluated passed as Dst which contains parent node contains parent node ?

When the any of the evaluated checker is evaluated the node successfully we can clear the Dst which is part of the StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); before inserting new nodes.

NoQ added inline comments.Aug 15 2020, 2:52 PM
clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
682 ↗(On Diff #285763)

Hmm actually your code is now incorrect because runCheckersForEvalCall() is in fact invoked multiple times in a loop (if there were state splits in PreStmt/PreCall), therefore it's possible that Dst does in fact already contain nodes.

That also means that i can't put in the assertions that i thought of; the destination set is indeed potentially non-empty in many cases.

Anyway, here's what i meant:

   ExplodedNodeSet DstPreCall;
   getCheckerManager().runCheckersForPreCall(DstPreCall, PreInitialized,
                                             *Call, *this);

   ExplodedNodeSet DstEvaluated;
-  StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx);

   if (CE && CE->getConstructor()->isTrivial() &&
       CE->getConstructor()->isCopyOrMoveConstructor() &&
       !CallOpts.IsArrayCtorOrDtor) {
+    StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx);
     // FIXME: Handle other kinds of trivial constructors as well.
     for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end();
          I != E; ++I)
       performTrivialCopy(Bldr, *I, *Call);

   } else {
     for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end();
          I != E; ++I)
       getCheckerManager().runCheckersForEvalCall(DstEvaluated, *I, *Call, *this,
                                                  CallOpts);
   }

   // If the CFG was constructed without elements for temporary destructors
   // and the just-called constructor created a temporary object then
   // stop exploration if the temporary object has a noreturn constructor.
   // This can lose coverage because the destructor, if it were present
   // in the CFG, would be called at the end of the full expression or
   // later (for life-time extended temporaries) -- but avoids infeasible
   // paths when no-return temporary destructors are used for assertions.
+  ExplodedNodeSet DstEvaluatedPostProcessed;
+  StmtNodeBuilder Bldr(DstEvaluated, DstEvaluatedPostProcessed, *currBldrCtx);
   const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext();
   if (!ADC->getCFGBuildOptions().AddTemporaryDtors) {
     if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) &&
         cast<CXXConstructorDecl>(Call->getDecl())
             ->getParent()
             ->isAnyDestructorNoReturn()) {

       // If we've inlined the constructor, then DstEvaluated would be empty.
       // In this case we still want a sink, which could be implemented
       // in processCallExit. But we don't have that implemented at the moment,
       // so if you hit this assertion, see if you can avoid inlining
       // the respective constructor when analyzer-config cfg-temporary-dtors
       // is set to false.
       // Otherwise there's nothing wrong with inlining such constructor.
       assert(!DstEvaluated.empty() &&
              "We should not have inlined this constructor!");

       for (ExplodedNode *N : DstEvaluated) {
         Bldr.generateSink(E, N, N->getState());
       }

       // There is no need to run the PostCall and PostStmt checker
       // callbacks because we just generated sinks on all nodes in th
       // frontier.
       return;
     }
   }

   ExplodedNodeSet DstPostArgumentCleanup;
-  for (ExplodedNode *I : DstEvaluated)
+  for (ExplodedNode *I : DstEvaluatedPostProcessed)
     finishArgumentConstruction(DstPostArgumentCleanup, I, *Call);

This way exactly one builder exists at any given moment of time and exactly one builder operates on each pair of source-destination sets.

Also this entire AddTemporaryDtors option could probably be banned already which would make things a whole lot easier; i'll look into that.

  • Make exactly single NodeBuilder exists at any given time
vrnithinkumar marked an inline comment as done.Aug 17 2020, 5:07 AM
vrnithinkumar added inline comments.
clang/lib/StaticAnalyzer/Core/CheckerManager.cpp
682 ↗(On Diff #285763)

Hmm actually your code is now incorrect because runCheckersForEvalCall() is in fact invoked multiple times in a loop (if there were state splits in PreStmt/PreCall), therefore it's possible that Dst does in fact already contain nodes.

Oh, I missed that point.

Made the above suggested changes to make sure exactly one NodeBuilder exists at any given time.

vrnithinkumar marked 2 inline comments as done.Aug 17 2020, 5:08 AM
NoQ accepted this revision.Aug 17 2020, 10:15 AM

Thanks!~

clang/test/Analysis/smart-ptr.cpp
44

I'm sooo fond of these sanity checks.

This revision is now accepted and ready to land.Aug 17 2020, 10:15 AM
This revision was landed with ongoing or failed builds.Aug 18 2020, 3:03 PM
This revision was automatically updated to reflect the committed changes.