This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] Detect use-after-free scenarios in -dealloc after calling [super dealloc]
Needs ReviewPublic

Authored by ddkilzer on Aug 23 2014, 5:19 PM.

Details

Summary

Use ASTMatchers (with manual matching code for finding calls to [super dealloc]) to detect use-after-free scenarios in the -dealloc method.

Fixes rdar://problem/6953275.

Unresolved issues:

  • Had to add libclangASTMatchers.a to USEDLIBS in tools/clang-check/Makefile and tools/driver/Makefile. Not sure if that's a direction the clang project wants to go.

Diff Detail

Event Timeline

ddkilzer updated this revision to Diff 12884.Aug 23 2014, 5:19 PM
ddkilzer retitled this revision from to [analyzer] Detect use-after-free scenarios in -dealloc after calling [super dealloc].
ddkilzer updated this object.
ddkilzer edited the test plan for this revision. (Show Details)
ddkilzer added reviewers: jordan_rose, krememek.
ddkilzer added a subscriber: Unknown Object (MLST).
ddkilzer updated this object.Aug 23 2014, 5:22 PM
ddkilzer updated this object.Aug 23 2014, 5:27 PM
  • Crash in ASTMatchers running test/Analysis/PR2978.m test, possibly due to invalid code in -dealloc method. Haven't figured out how to make a stand-alone test case yet.

Something is going horribly wrong dereferencing a clang::Stmt * into a const clang::Stmt & in this call:

Finder.match(*S, Ctx);

Changing the pointer type to const clang::Stmt * as passed into the method had no effect. I also tried this to no avail:

const Stmt &SRef = *S;
Finder.match(SRef, Ctx);

Here's an lldb session showing the badness. I know something is going wrong because the value of Node.Aligner in the match() method is the same value as the S pointer (0x000000010a033ce0) prior to the call:

(lldb) run
Process 6895 launched: '/Volumes/Data/clang.git/llvm/Debug+Asserts/bin/clang' (x86_64)
Process 6895 stopped
* thread #1: tid = 0x39e920, 0x00000001010a5250 clang`scan_dealloc_for_self_after_super_dealloc(S=0x000000010a033ce0, Callback=0x00007fff5fbfc410, Ctx=0x000000010a822000) + 624 at CheckObjCDealloc.cpp:70, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001010a5250 clang`scan_dealloc_for_self_after_super_dealloc(S=0x000000010a033ce0, Callback=0x00007fff5fbfc410, Ctx=0x000000010a822000) + 624 at CheckObjCDealloc.cpp:70
   67  	      stmt(hasDescendant(declRefExpr(to(varDecl(hasName("self"))))
   68  	                             .bind("self"))).bind("stmt");
   69  	  Finder.addMatcher(Matcher, &Callback);
-> 70  	  Finder.match(*S, Ctx);
   71  	  if (Callback.FoundMatch())
   72  	    return true;
   73  	
(lldb) p *S
(clang::Stmt) $4 = {
   = {
    Aligner = 0x000000000000008d
    StmtBits = (sClass = 141)
    CompoundStmtBits = (NumStmts = 0)
    ExprBits = {
      ValueKind = 0
      ObjectKind = 0
      TypeDependent = 0
      ValueDependent = 0
      InstantiationDependent = 0
      ContainsUnexpandedParameterPack = 0
    }
    CharacterLiteralBits = (Kind = 0)
    FloatingLiteralBits = (Semantics = 0, IsExact = 0)
    UnaryExprOrTypeTraitExprBits = (Kind = 0, IsType = 0)
    DeclRefExprBits = {
      HasQualifier = 0
      HasTemplateKWAndArgsInfo = 0
      HasFoundDecl = 0
      HadMultipleCandidates = 0
      RefersToEnclosingLocal = 0
    }
    CastExprBits = (Kind = 0, BasePathSize = 0)
    CallExprBits = (NumPreArgs = 0)
    ExprWithCleanupsBits = (NumObjects = 0)
    PseudoObjectExprBits = (NumSubExprs = 0, ResultIndex = 0)
    ObjCIndirectCopyRestoreExprBits = (ShouldCopy = 0)
    InitListExprBits = (HadArrayRangeDesignator = 0)
    TypeTraitExprBits = (Kind = 0, Value = 0, NumArgs = 0)
  }
}
(lldb) p S->getStmtClassName()
(const char *) $5 = 0x00000001044ea946 "ReturnStmt"
(lldb) p (*S).getStmtClassName()
(const char *) $6 = 0x00000001044ea946 "ReturnStmt"
(lldb) s
Process 6895 stopped
* thread #1: tid = 0x39e920, 0x00000001010b0b7a clang`void clang::ast_matchers::MatchFinder::match<clang::Stmt>(this=0x00007fff5fbfbe38, Node=0x000000010a033ce0, Context=0x000000010a822000) + 42 at ASTMatchFinder.h:160, queue = 'com.apple.main-thread', stop reason = step in
    frame #0: 0x00000001010b0b7a clang`void clang::ast_matchers::MatchFinder::match<clang::Stmt>(this=0x00007fff5fbfbe38, Node=0x000000010a033ce0, Context=0x000000010a822000) + 42 at ASTMatchFinder.h:160
   157 	  ///
   158 	  /// @{
   159 	  template <typename T> void match(const T &Node, ASTContext &Context) {
-> 160 	    match(clang::ast_type_traits::DynTypedNode::create(Node), Context);
   161 	  }
   162 	  void match(const clang::ast_type_traits::DynTypedNode &Node,
   163 	             ASTContext &Context);
(lldb) p Node.getStmtClassName()
(const char *) $7 = 0x0000002000000000
(lldb) p Node
(const clang::Stmt) $8 = {
   = {
    Aligner = 0x000000010a033ce0
    StmtBits = (sClass = 224)
    CompoundStmtBits = (NumStmts = 656188)
    ExprBits = {
      ValueKind = 0
      ObjectKind = 3
      TypeDependent = 1
      ValueDependent = 1
      InstantiationDependent = 0
      ContainsUnexpandedParameterPack = 0
    }
    CharacterLiteralBits = (Kind = 3)
    FloatingLiteralBits = (Semantics = 3, IsExact = 0)
    UnaryExprOrTypeTraitExprBits = (Kind = 3, IsType = 0)
    DeclRefExprBits = {
      HasQualifier = 1
      HasTemplateKWAndArgsInfo = 1
      HasFoundDecl = 0
      HadMultipleCandidates = 0
      RefersToEnclosingLocal = 0
    }
    CastExprBits = (Kind = 3, BasePathSize = 40)
    CallExprBits = (NumPreArgs = 1)
    ExprWithCleanupsBits = (NumObjects = 2563)
    PseudoObjectExprBits = (NumSubExprs = 3, ResultIndex = 10)
    ObjCIndirectCopyRestoreExprBits = (ShouldCopy = 1)
    InitListExprBits = (HadArrayRangeDesignator = 1)
    TypeTraitExprBits = (Kind = 3, Value = 0, NumArgs = 5)
  }
}
(lldb)

One thing I can't tell is if make is compiling with the trunk clang I'm building, or whether it's using the clang I have installed with Xcode.

  • Crash in ASTMatchers running test/Analysis/PR2978.m test, possibly due to invalid code in -dealloc method. Haven't figured out how to make a stand-alone test case yet.

Doh! This was due to a stupid typo that caused infinite recursion:

@@ -74,7 +74,7 @@ static bool scan_dealloc_for_self_after_super_dealloc(
   // Recurse to children.
   for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I != E;
        ++I)
-    if (*I && scan_dealloc_for_self_after_super_dealloc(S, Callback, Ctx))
+    if (*I && scan_dealloc_for_self_after_super_dealloc(*I, Callback, Ctx))
       return true;
 
   return false;

Will post new patch momentarily.

ddkilzer updated this revision to Diff 12885.Aug 24 2014, 8:44 AM

Fix typo in last patch that caused infinite recursion

ddkilzer updated this object.Aug 24 2014, 8:47 AM
ddkilzer updated this revision to Diff 12920.Aug 25 2014, 3:35 PM
ddkilzer updated this object.

Removed unnecessary tree walking code since ASTMatcher is used in scan_dealloc_for_self_after_super_dealloc().

ddkilzer updated this revision to Diff 13122.Aug 30 2014, 1:56 AM

Rebase after Diff 13121 was added to D5023.

jordan_rose edited edge metadata.Sep 4 2014, 6:40 PM

Here's an example that won't be caught without at least flow-sensitive analysis:

- (void)dealloc {
  if (!_everInitialized) {
    [super dealloc];
    // should return here, but forgot
  }
  carefullyDisposeOfLazyData(self->_lazilyConstructedData);
  [super dealloc];
}

In this case I think we'll get a double-dealloc warning from the retain-count checker, but even so. Do you think this is worth moving to a path-sensitive model for?

(Also, please make sure you're following the LLVM naming conventions. The function names and m_ member names are the offenders, I think.)

zaks.anna edited edge metadata.Sep 4 2014, 10:34 PM

I agree with Jordan. We would probably want to create a path sensitive check here instead of using the matchers. Another advantage would be that you would get inter procedural analysis (within the same translation unit), so if the dealloc delegates the deallocation to another method whose implementation is within the same TU, you would get the checking as if the callee has been inlined.

(I am not sure if you watched our presentation about writing path sensitive checkers. If not, I highly recommend it. It's called Building a Checker in 24 hours http://www.llvm.org/devmtg/2012-11/)

I started a path-sensitive checker in D5238 for [super dealloc] calls. Once that lands, I can re-implement these checks using that checker.

I implemented the "duplicate [super dealloc]" check since that seemed the most logical (and simpler) to start with.

Thanks for the tip about the video, Anna! That was a big help.

lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
74–78

And I just realized this code is unnecessary when using the ASTMatcher. Will upload another patch later today.