Improve NULL-checking for CFRetain/CFRelease. We now remember that the argument was non-NULL, and we report where the null assumption came from (like AttrNonNullChecker already did).

llvm-svn: 107633
This commit is contained in:
Jordy Rose 2010-07-06 02:34:42 +00:00
parent 2b6ac78e07
commit 40c5c24c06
3 changed files with 66 additions and 44 deletions

View File

@ -415,59 +415,72 @@ clang::CreateAuditCFNumberCreate(ASTContext& Ctx, BugReporter& BR) {
}
//===----------------------------------------------------------------------===//
// CFRetain/CFRelease auditing for null arguments.
// CFRetain/CFRelease checking for null arguments.
//===----------------------------------------------------------------------===//
namespace {
class AuditCFRetainRelease : public GRSimpleAPICheck {
class CFRetainReleaseChecker : public CheckerVisitor<CFRetainReleaseChecker> {
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){}
CFRetainReleaseChecker(ASTContext& Ctx): BT(NULL),
Retain(&Ctx.Idents.get("CFRetain")), Release(&Ctx.Idents.get("CFRelease"))
{}
~AuditCFRetainRelease() {}
static void *getTag() { static int x = 0; return &x; }
bool Audit(ExplodedNode* N, GRStateManager&);
void PreVisitCallExpr(CheckerContext& C, const CallExpr* CE);
};
} // end anonymous namespace
bool AuditCFRetainRelease::Audit(ExplodedNode* N, GRStateManager&) {
const CallExpr* CE = cast<CallExpr>(cast<PostStmt>(N->getLocation()).getStmt());
void CFRetainReleaseChecker::PreVisitCallExpr(CheckerContext& C,
const CallExpr* CE) {
// If the CallExpr doesn't have exactly 1 argument just give up checking.
if (CE->getNumArgs() != 1)
return false;
return;
// Check if we called CFRetain/CFRelease.
const GRState* state = N->getState();
// Get the function declaration of the callee.
const GRState* state = C.getState();
SVal X = state->getSVal(CE->getCallee());
const FunctionDecl* FD = X.getAsFunctionDecl();
if (!FD)
return false;
return;
// Check if we called CFRetain/CFRelease.
const IdentifierInfo *FuncII = FD->getIdentifier();
if (!(FuncII == Retain || FuncII == Release))
return false;
return;
// FIXME: The rest of this just checks that the argument is non-null.
// It should probably be refactored and combined with AttrNonNullChecker.
// Get the argument's value.
const Expr *Arg = CE->getArg(0);
SVal ArgVal = state->getSVal(Arg);
DefinedSVal *DefArgVal = dyn_cast<DefinedSVal>(&ArgVal);
if (!DefArgVal)
return;
// Get a NULL value.
ValueManager &ValMgr = C.getValueManager();
DefinedSVal Zero = cast<DefinedSVal>(ValMgr.makeZeroVal(Arg->getType()));
// Make an expression asserting that they're equal.
SValuator &SVator = ValMgr.getSValuator();
DefinedOrUnknownSVal ArgIsNull = SVator.EvalEQ(state, Zero, *DefArgVal);
// Are they equal?
const GRState *stateTrue, *stateFalse;
llvm::tie(stateTrue, stateFalse) = state->Assume(ArgIsNull);
if (stateTrue && !stateFalse) {
ExplodedNode *N = C.GenerateSink(stateTrue);
if (!N)
return;
// 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");
@ -475,19 +488,16 @@ bool AuditCFRetainRelease::Audit(ExplodedNode* N, GRStateManager&) {
? "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;
EnhancedBugReport *report = new EnhancedBugReport(*BT, description, N);
report->addRange(Arg->getSourceRange());
report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue, Arg);
C.EmitReport(report);
return;
}
return false;
}
GRSimpleAPICheck*
clang::CreateAuditCFRetainRelease(ASTContext& Ctx, BugReporter& BR) {
return new AuditCFRetainRelease(Ctx, BR);
// From here on, we know the argument is non-null.
C.addTransition(stateFalse);
}
//===----------------------------------------------------------------------===//
@ -569,9 +579,10 @@ void clang::RegisterAppleChecks(GRExprEngine& Eng, const Decl &D) {
Eng.AddCheck(CreateBasicObjCFoundationChecks(Ctx, BR),
Stmt::ObjCMessageExprClass);
Eng.AddCheck(CreateAuditCFNumberCreate(Ctx, BR), Stmt::CallExprClass);
Eng.AddCheck(CreateAuditCFRetainRelease(Ctx, BR), Stmt::CallExprClass);
RegisterNSErrorChecks(BR, Eng, D);
RegisterNSAutoreleasePoolChecks(Eng);
Eng.registerCheck(new CFRetainReleaseChecker(Ctx));
Eng.registerCheck(new ClassReleaseChecker(Ctx));
}

View File

@ -30,9 +30,6 @@ GRSimpleAPICheck *CreateBasicObjCFoundationChecks(ASTContext& Ctx,
GRSimpleAPICheck *CreateAuditCFNumberCreate(ASTContext& Ctx,
BugReporter& BR);
GRSimpleAPICheck *CreateAuditCFRetainRelease(ASTContext& Ctx,
BugReporter& BR);
void RegisterNSErrorChecks(BugReporter& BR, GRExprEngine &Eng, const Decl &D);
void RegisterNSAutoreleasePoolChecks(GRExprEngine &Eng);

View File

@ -468,6 +468,20 @@ void f16(int x, CFTypeRef p) {
}
}
// Test that an object is non-null after being CFRetained/CFReleased.
void f17(int x, CFTypeRef p) {
if (x) {
CFRelease(p);
if (!p)
CFRelease(0); // no-warning
}
else {
CFRetain(p);
if (!p)
CFRetain(0); // no-warning
}
}
// Test basic tracking of ivars associated with 'self'. For the retain/release
// checker we currently do not want to flag leaks associated with stores
// of tracked objects to ivars.