]> granicus.if.org Git - clang/commitdiff
Add basic checking for passing NULL to CFRetain/CFRelease, since those functions
authorTed Kremenek <kremenek@apple.com>
Tue, 14 Jul 2009 00:43:42 +0000 (00:43 +0000)
committerTed Kremenek <kremenek@apple.com>
Tue, 14 Jul 2009 00:43:42 +0000 (00:43 +0000)
are not explicitly marked as not accepting NULL pointers. This check illustrates
how we need more refactoring in the custom-check logic.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@75570 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Analysis/BasicObjCFoundationChecks.cpp
lib/Analysis/BasicObjCFoundationChecks.h
test/Analysis/retain-release.m

index b454a3605c19260454045b35282404b8cac1b705..8bbf94c072e9bdc04d2c8a874f3be39653dcc6f3 100644 (file)
@@ -457,8 +457,85 @@ clang::CreateAuditCFNumberCreate(ASTContext& Ctx, BugReporter& BR) {
   return new AuditCFNumberCreate(Ctx, BR);
 }
 
+//===----------------------------------------------------------------------===//
+// CFRetain/CFRelease auditing for null arguments.
+//===----------------------------------------------------------------------===//
+
+namespace {
+class VISIBILITY_HIDDEN AuditCFRetainRelease : public GRSimpleAPICheck {
+  APIMisuse *BT;
+  
+  // FIXME: Either this should be refactored into GRSimpleAPICheck, or
+  //   it should always be passed with a call to Audit.  The latter
+  //   approach makes this class more stateless.
+  ASTContext& Ctx;
+  IdentifierInfo *Retain, *Release;
+  BugReporter& BR;
+  
+public:
+  AuditCFRetainRelease(ASTContext& ctx, BugReporter& br) 
+  : BT(0), Ctx(ctx),
+    Retain(&Ctx.Idents.get("CFRetain")), Release(&Ctx.Idents.get("CFRelease")),
+    BR(br){}
+  
+  ~AuditCFRetainRelease() {}
+  
+  bool Audit(ExplodedNode<GRState>* N, GRStateManager&);
+};
+} // end anonymous namespace
+
+
+bool AuditCFRetainRelease::Audit(ExplodedNode<GRState>* N, GRStateManager&) {
+  CallExpr* CE = cast<CallExpr>(cast<PostStmt>(N->getLocation()).getStmt());
+  
+  // If the CallExpr doesn't have exactly 1 argument just give up checking.
+  if (CE->getNumArgs() != 1)
+    return false;
+  
+  // Check if we called CFRetain/CFRelease.
+  const GRState* state = N->getState();
+  SVal X = state->getSVal(CE->getCallee());
+  const FunctionDecl* FD = X.getAsFunctionDecl();
+  
+  if (!FD)
+    return false;
+  
+  const IdentifierInfo *FuncII = FD->getIdentifier();  
+  if (!(FuncII == Retain || FuncII == Release))
+    return false;
+  
+  // Finally, check if the argument is NULL.
+  // FIXME: We should be able to bifurcate the state here, as a successful
+  // check will result in the value not being NULL afterwards.
+  // FIXME: Need a way to register vistors for the BugReporter.  Would like
+  // to benefit from the same diagnostics that regular null dereference
+  // reporting has.
+  if (state->getStateManager().isEqual(state, CE->getArg(0), 0)) {
+    if (!BT)
+      BT = new APIMisuse("null passed to CFRetain/CFRelease");
+    
+    const char *description = (FuncII == Retain)
+                            ? "Null pointer argument in call to CFRetain"
+                            : "Null pointer argument in call to CFRelease";
+
+    RangedBugReport *report = new RangedBugReport(*BT, description, N);
+    report->addRange(CE->getArg(0)->getSourceRange());
+    BR.EmitReport(report);
+    return true;
+  }
+
+  return false;
+}
+    
+    
+GRSimpleAPICheck*
+clang::CreateAuditCFRetainRelease(ASTContext& Ctx, BugReporter& BR) {  
+  return new AuditCFRetainRelease(Ctx, BR);
+}
+
 //===----------------------------------------------------------------------===//
 // Check registration.
+//===----------------------------------------------------------------------===//
 
 void clang::RegisterAppleChecks(GRExprEngine& Eng) {
   ASTContext& Ctx = Eng.getContext();
@@ -466,9 +543,8 @@ void clang::RegisterAppleChecks(GRExprEngine& Eng) {
 
   Eng.AddCheck(CreateBasicObjCFoundationChecks(Ctx, BR),
                Stmt::ObjCMessageExprClass);
-
-  Eng.AddCheck(CreateAuditCFNumberCreate(Ctx, BR),
-               Stmt::CallExprClass);
+  Eng.AddCheck(CreateAuditCFNumberCreate(Ctx, BR), Stmt::CallExprClass);  
+  Eng.AddCheck(CreateAuditCFRetainRelease(Ctx, BR), Stmt::CallExprClass);
   
   RegisterNSErrorChecks(BR, Eng);
 }
index 5c9701ecdd36facd21c180f204f9a59cfc571356..13f55fcb969bf6f97c4da38e19890237730ed316 100644 (file)
@@ -32,12 +32,15 @@ class GRStateManager;
 class BugReporter;
 class GRExprEngine;
   
-GRSimpleAPICheckCreateBasicObjCFoundationChecks(ASTContext& Ctx,
+GRSimpleAPICheck *CreateBasicObjCFoundationChecks(ASTContext& Ctx,
                                                   BugReporter& BR);
   
-GRSimpleAPICheckCreateAuditCFNumberCreate(ASTContext& Ctx,
+GRSimpleAPICheck *CreateAuditCFNumberCreate(ASTContext& Ctx,
                                             BugReporter& BR);
   
+GRSimpleAPICheck *CreateAuditCFRetainRelease(ASTContext& Ctx,
+                                             BugReporter& BR);
+  
 void RegisterNSErrorChecks(BugReporter& BR, GRExprEngine &Eng);
   
 } // end clang namespace
index 9dcef365d6adaac9274d9287553b2f81a63e0b68..edae948141b75824ec1a039c4a1275fb41feb212 100644 (file)
@@ -390,6 +390,18 @@ void f15() {
   CFRelease(*B);  // no-warning
 }
 
+// Test when we pass NULL to CFRetain/CFRelease.
+void f16(int x, CFTypeRef p) {
+  if (p)
+    return;
+
+  if (x) {
+    CFRelease(p); // expected-warning{{Null pointer argument in call to CFRelease}}
+  }
+  else {
+    CFRetain(p); // expected-warning{{Null pointer argument in call to CFRetain}}
+  }
+}
 
 // Test basic tracking of ivars associated with 'self'.  For the retain/release
 // checker we currently do not want to flag leaks associated with stores