After a lengthy design discussion, add support for "ownership attributes" for malloc/free checking. Patch by Andrew McGregor!

llvm-svn: 109939
This commit is contained in:
Ted Kremenek 2010-07-31 01:52:11 +00:00
parent 5300d37aa7
commit d21139a34f
10 changed files with 640 additions and 24 deletions

View File

@ -369,6 +369,121 @@ public:
static bool classof(const NonNullAttr *A) { return true; }
};
/// OwnershipAttr
/// Ownership attributes are used to annotate pointers that own a resource
/// in order for the analyzer to check correct allocation and deallocation.
/// There are three attributes, ownership_returns, ownership_holds and
/// ownership_takes, represented by subclasses of OwnershipAttr
class OwnershipAttr: public AttrWithString {
protected:
unsigned* ArgNums;
unsigned Size;
public:
enum OwnershipKind { Holds, Takes, Returns, None };
OwnershipKind OKind;
attr::Kind AKind;
public:
OwnershipAttr(attr::Kind AK, ASTContext &C, unsigned* arg_nums, unsigned size,
llvm::StringRef module, OwnershipKind kind);
virtual void Destroy(ASTContext &C);
OwnershipKind getKind() const {
return OKind;
}
bool isKind(const OwnershipKind k) const {
return OKind == k;
}
/// Ownership attributes have a 'module', which is the name of a kind of
/// resource that can be checked.
/// The Malloc checker uses the module 'malloc'.
llvm::StringRef getModule() const {
return getString();
}
void setModule(ASTContext &C, llvm::StringRef module) {
ReplaceString(C, module);
}
bool isModule(const char *m) const {
return getModule().equals(m);
}
typedef const unsigned *iterator;
iterator begin() const {
return ArgNums;
}
iterator end() const {
return ArgNums + Size;
}
unsigned size() const {
return Size;
}
virtual Attr *clone(ASTContext &C) const;
static bool classof(const Attr *A) {
return true;
}
static bool classof(const OwnershipAttr *A) {
return true;
}
};
class OwnershipTakesAttr: public OwnershipAttr {
public:
OwnershipTakesAttr(ASTContext &C, unsigned* arg_nums, unsigned size,
llvm::StringRef module);
virtual Attr *clone(ASTContext &C) const;
static bool classof(const Attr *A) {
return A->getKind() == attr::OwnershipTakes;
}
static bool classof(const OwnershipTakesAttr *A) {
return true;
}
static bool classof(const OwnershipAttr *A) {
return A->OKind == Takes;
}
};
class OwnershipHoldsAttr: public OwnershipAttr {
public:
OwnershipHoldsAttr(ASTContext &C, unsigned* arg_nums, unsigned size,
llvm::StringRef module);
virtual Attr *clone(ASTContext &C) const;
static bool classof(const Attr *A) {
return A->getKind() == attr::OwnershipHolds;
}
static bool classof(const OwnershipHoldsAttr *A) {
return true;
}
static bool classof(const OwnershipAttr *A) {
return A->OKind == Holds;
}
};
class OwnershipReturnsAttr: public OwnershipAttr {
public:
OwnershipReturnsAttr(ASTContext &C, unsigned* arg_nums, unsigned size,
llvm::StringRef module);
virtual Attr *clone(ASTContext &C) const;
static bool classof(const Attr *A) {
return A->getKind() == attr::OwnershipReturns;
}
static bool classof(const OwnershipReturnsAttr *A) {
return true;
}
static bool classof(const OwnershipAttr *A) {
return A->OKind == Returns;
}
};
class FormatAttr : public AttrWithString {
int formatIdx, firstArg;
public:

View File

@ -297,6 +297,21 @@ def Overloadable : Attr {
let Spellings = ["overloadable"];
}
def OwnershipReturns : Attr {
let Spellings = ["ownership_returns"];
let Args = [StringArgument<"Module">, IntArgument<"SizeIdx">];
}
def OwnershipTakes : Attr {
let Spellings = ["ownership_takes"];
let Args = [StringArgument<"Module">, IntArgument<"PtrIdx">];
}
def OwnershipHolds : Attr {
let Spellings = ["ownership_holds"];
let Args = [StringArgument<"Module">, IntArgument<"PtrIdx">];
}
def Packed : Attr {
let Spellings = ["packed"];
}

View File

@ -838,6 +838,8 @@ def err_attribute_requires_objc_interface : Error<
"attribute may only be applied to an Objective-C interface">;
def err_nonnull_pointers_only : Error<
"nonnull attribute only applies to pointer arguments">;
def err_ownership_type : Error<
"%0 attribute only applies to %1 arguments">;
def err_format_strftime_third_parameter : Error<
"strftime format attribute requires 3rd parameter to be 0">;
def err_format_attribute_requires_variadic : Error<

View File

@ -97,6 +97,9 @@ public:
AT_ns_returns_retained, // Clang-specific.
AT_objc_gc,
AT_overloadable, // Clang-specific.
AT_ownership_holds, // Clang-specific.
AT_ownership_returns, // Clang-specific.
AT_ownership_takes, // Clang-specific.
AT_packed,
AT_pure,
AT_regparm,

View File

@ -48,6 +48,43 @@ NonNullAttr::NonNullAttr(ASTContext &C, unsigned* arg_nums, unsigned size)
memcpy(ArgNums, arg_nums, sizeof(*ArgNums)*size);
}
OwnershipAttr::OwnershipAttr(attr::Kind AK, ASTContext &C, unsigned* arg_nums,
unsigned size,
llvm::StringRef module,
OwnershipKind kind) :
AttrWithString(AK, C, module), ArgNums(0), Size(0), OKind(kind) {
if (size == 0)
return;
assert(arg_nums);
ArgNums = new (C) unsigned[size];
Size = size;
AKind = AK;
OKind = kind;
memcpy(ArgNums, arg_nums, sizeof(*ArgNums) * size);
}
void OwnershipAttr::Destroy(ASTContext &C) {
if (ArgNums)
C.Deallocate(ArgNums);
}
OwnershipTakesAttr::OwnershipTakesAttr(ASTContext &C, unsigned* arg_nums,
unsigned size, llvm::StringRef module) :
OwnershipAttr(attr::OwnershipTakes, C, arg_nums, size, module, Takes) {
}
OwnershipHoldsAttr::OwnershipHoldsAttr(ASTContext &C, unsigned* arg_nums,
unsigned size, llvm::StringRef module) :
OwnershipAttr(attr::OwnershipHolds, C, arg_nums, size, module, Holds) {
}
OwnershipReturnsAttr::OwnershipReturnsAttr(ASTContext &C, unsigned* arg_nums,
unsigned size,
llvm::StringRef module) :
OwnershipAttr(attr::OwnershipReturns, C, arg_nums, size, module, Returns) {
}
#define DEF_SIMPLE_ATTR_CLONE(ATTR) \
Attr *ATTR##Attr::clone(ASTContext &C) const { \
return ::new (C) ATTR##Attr; \
@ -147,6 +184,22 @@ Attr *NonNullAttr::clone(ASTContext &C) const {
return ::new (C) NonNullAttr(C, ArgNums, Size);
}
Attr *OwnershipAttr::clone(ASTContext &C) const {
return ::new (C) OwnershipAttr(AKind, C, ArgNums, Size, getModule(), OKind);
}
Attr *OwnershipReturnsAttr::clone(ASTContext &C) const {
return ::new (C) OwnershipReturnsAttr(C, ArgNums, Size, getModule());
}
Attr *OwnershipTakesAttr::clone(ASTContext &C) const {
return ::new (C) OwnershipTakesAttr(C, ArgNums, Size, getModule());
}
Attr *OwnershipHoldsAttr::clone(ASTContext &C) const {
return ::new (C) OwnershipHoldsAttr(C, ArgNums, Size, getModule());
}
Attr *FormatAttr::clone(ASTContext &C) const {
return ::new (C) FormatAttr(C, getType(), formatIdx, firstArg);
}

View File

@ -24,15 +24,17 @@ using namespace clang;
namespace {
class RefState {
enum Kind { AllocateUnchecked, AllocateFailed, Released, Escaped } K;
enum Kind { AllocateUnchecked, AllocateFailed, Released, Escaped, Relinquished } K;
const Stmt *S;
public:
RefState(Kind k, const Stmt *s) : K(k), S(s) {}
bool isAllocated() const { return K == AllocateUnchecked; }
bool isFailed() const { return K == AllocateFailed; }
bool isReleased() const { return K == Released; }
bool isEscaped() const { return K == Escaped; }
bool isRelinquished() const { return K == Relinquished; }
bool operator==(const RefState &X) const {
return K == X.K && S == X.S;
@ -46,6 +48,7 @@ public:
}
static RefState getReleased(const Stmt *s) { return RefState(Released, s); }
static RefState getEscaped(const Stmt *s) { return RefState(Escaped, s); }
static RefState getRelinquished(const Stmt *s) { return RefState(Relinquished, s); }
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(K);
@ -59,12 +62,13 @@ class MallocChecker : public CheckerVisitor<MallocChecker> {
BuiltinBug *BT_DoubleFree;
BuiltinBug *BT_Leak;
BuiltinBug *BT_UseFree;
BuiltinBug *BT_UseRelinquished;
BuiltinBug *BT_BadFree;
IdentifierInfo *II_malloc, *II_free, *II_realloc, *II_calloc;
public:
MallocChecker()
: BT_DoubleFree(0), BT_Leak(0), BT_UseFree(0), BT_BadFree(0),
: BT_DoubleFree(0), BT_Leak(0), BT_UseFree(0), BT_UseRelinquished(0), BT_BadFree(0),
II_malloc(0), II_free(0), II_realloc(0), II_calloc(0) {}
static void *getTag();
bool EvalCallExpr(CheckerContext &C, const CallExpr *CE);
@ -73,9 +77,13 @@ public:
void PreVisitReturnStmt(CheckerContext &C, const ReturnStmt *S);
const GRState *EvalAssume(const GRState *state, SVal Cond, bool Assumption);
void VisitLocation(CheckerContext &C, const Stmt *S, SVal l);
virtual void PreVisitBind(CheckerContext &C, const Stmt *AssignE,
const Stmt *StoreE, SVal location,
SVal val);
private:
void MallocMem(CheckerContext &C, const CallExpr *CE);
void MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att);
const GRState *MallocMemAux(CheckerContext &C, const CallExpr *CE,
const Expr *SizeEx, SVal Init,
const GRState *state) {
@ -86,8 +94,9 @@ private:
const GRState *state);
void FreeMem(CheckerContext &C, const CallExpr *CE);
void FreeMemAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att);
const GRState *FreeMemAux(CheckerContext &C, const CallExpr *CE,
const GRState *state);
const GRState *state, unsigned Num, bool Hold);
void ReallocMem(CheckerContext &C, const CallExpr *CE);
void CallocMem(CheckerContext &C, const CallExpr *CE);
@ -156,7 +165,31 @@ bool MallocChecker::EvalCallExpr(CheckerContext &C, const CallExpr *CE) {
return true;
}
return false;
// Check all the attributes, if there are any.
// There can be multiple of these attributes.
bool rv = false;
if (FD->hasAttrs()) {
for (const Attr *attr = FD->getAttrs(); attr; attr = attr->getNext()) {
if(const OwnershipAttr* Att = dyn_cast<OwnershipAttr>(attr)) {
switch (Att->getKind()) {
case OwnershipAttr::Returns: {
MallocMemReturnsAttr(C, CE, Att);
rv = true;
break;
}
case OwnershipAttr::Takes:
case OwnershipAttr::Holds: {
FreeMemAttr(C, CE, Att);
rv = true;
break;
}
default:
break;
}
}
}
}
return rv;
}
void MallocChecker::MallocMem(CheckerContext &C, const CallExpr *CE) {
@ -165,6 +198,23 @@ void MallocChecker::MallocMem(CheckerContext &C, const CallExpr *CE) {
C.addTransition(state);
}
void MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE,
const OwnershipAttr* Att) {
if (!Att->isModule("malloc"))
return;
const unsigned *I = Att->begin(), *E = Att->end();
if (I != E) {
const GRState *state =
MallocMemAux(C, CE, CE->getArg(*I), UndefinedVal(), C.getState());
C.addTransition(state);
return;
}
const GRState *state = MallocMemAux(C, CE, UnknownVal(), UndefinedVal(),
C.getState());
C.addTransition(state);
}
const GRState *MallocChecker::MallocMemAux(CheckerContext &C,
const CallExpr *CE,
SVal Size, SVal Init,
@ -196,25 +246,52 @@ const GRState *MallocChecker::MallocMemAux(CheckerContext &C,
}
void MallocChecker::FreeMem(CheckerContext &C, const CallExpr *CE) {
const GRState *state = FreeMemAux(C, CE, C.getState());
const GRState *state = FreeMemAux(C, CE, C.getState(), 0, false);
if (state)
C.addTransition(state);
}
void MallocChecker::FreeMemAttr(CheckerContext &C, const CallExpr *CE,
const OwnershipAttr* Att) {
if (!Att->isModule("malloc"))
return;
for (const unsigned *I = Att->begin(), *E = Att->end(); I != E; ++I) {
const GRState *state =
FreeMemAux(C, CE, C.getState(), *I, Att->isKind(OwnershipAttr::Holds));
if (state)
C.addTransition(state);
}
}
const GRState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE,
const GRState *state) {
const Expr *ArgExpr = CE->getArg(0);
const GRState *state, unsigned Num,
bool Hold) {
const Expr *ArgExpr = CE->getArg(Num);
SVal ArgVal = state->getSVal(ArgExpr);
// If ptr is NULL, no operation is preformed.
if (ArgVal.isZeroConstant())
DefinedOrUnknownSVal location = cast<DefinedOrUnknownSVal>(ArgVal);
// Check for null dereferences.
if (!isa<Loc>(location))
return state;
// FIXME: Technically using 'Assume' here can result in a path
// bifurcation. In such cases we need to return two states, not just one.
const GRState *notNullState, *nullState;
llvm::tie(notNullState, nullState) = state->Assume(location);
// The explicit NULL case, no operation is performed.
if (nullState && !notNullState)
return nullState;
assert(notNullState);
// Unknown values could easily be okay
// Undefined values are handled elsewhere
if (ArgVal.isUnknownOrUndef())
return state;
return notNullState;
const MemRegion *R = ArgVal.getAsRegion();
@ -253,7 +330,7 @@ const GRState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE,
// Various cases could lead to non-symbol values here.
// For now, ignore them.
if (!SR)
return state;
return notNullState;
SymbolRef Sym = SR->getSymbol();
@ -263,14 +340,15 @@ const GRState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE,
// called on a pointer that does not get its pointee directly from malloc().
// Full support of this requires inter-procedural analysis.
if (!RS)
return state;
return notNullState;
// Check double free.
if (RS->isReleased()) {
ExplodedNode *N = C.GenerateSink();
if (N) {
if (!BT_DoubleFree)
BT_DoubleFree = new BuiltinBug("Double free",
BT_DoubleFree
= new BuiltinBug("Double free",
"Try to free a memory block that has been released");
// FIXME: should find where it's freed last time.
BugReport *R = new BugReport(*BT_DoubleFree,
@ -281,7 +359,9 @@ const GRState *MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE,
}
// Normal free.
return state->set<RegionState>(Sym, RefState::getReleased(CE));
if (Hold)
return notNullState->set<RegionState>(Sym, RefState::getRelinquished(CE));
return notNullState->set<RegionState>(Sym, RefState::getReleased(CE));
}
bool MallocChecker::SummarizeValue(llvm::raw_ostream& os, SVal V) {
@ -446,13 +526,13 @@ void MallocChecker::ReallocMem(CheckerContext &C, const CallExpr *CE) {
ValMgr.makeIntValWithPtrWidth(0, false));
if (const GRState *stateSizeZero = stateNotEqual->Assume(SizeZero, true)) {
const GRState *stateFree = FreeMemAux(C, CE, stateSizeZero);
const GRState *stateFree = FreeMemAux(C, CE, stateSizeZero, 0, false);
if (stateFree)
C.addTransition(stateFree->BindExpr(CE, UndefinedVal(), true));
}
if (const GRState *stateSizeNotZero=stateNotEqual->Assume(SizeZero,false)) {
const GRState *stateFree = FreeMemAux(C, CE, stateSizeNotZero);
const GRState *stateFree = FreeMemAux(C, CE, stateSizeNotZero, 0, false);
if (stateFree) {
// FIXME: We should copy the content of the original buffer.
const GRState *stateRealloc = MallocMemAux(C, CE, CE->getArg(1),
@ -581,3 +661,62 @@ void MallocChecker::VisitLocation(CheckerContext &C, const Stmt *S, SVal l) {
}
}
}
void MallocChecker::PreVisitBind(CheckerContext &C,
const Stmt *AssignE,
const Stmt *StoreE,
SVal location,
SVal val) {
// The PreVisitBind implements the same algorithm as already used by the
// Objective C ownership checker: if the pointer escaped from this scope by
// assignment, let it go. However, assigning to fields of a stack-storage
// structure does not transfer ownership.
const GRState *state = C.getState();
DefinedOrUnknownSVal l = cast<DefinedOrUnknownSVal>(location);
// Check for null dereferences.
if (!isa<Loc>(l))
return;
// Before checking if the state is null, check if 'val' has a RefState.
// Only then should we check for null and bifurcate the state.
SymbolRef Sym = val.getLocSymbolInBase();
if (Sym) {
if (const RefState *RS = state->get<RegionState>(Sym)) {
// If ptr is NULL, no operation is performed.
const GRState *notNullState, *nullState;
llvm::tie(notNullState, nullState) = state->Assume(l);
// Generate a transition for 'nullState' to record the assumption
// that the state was null.
if (nullState)
C.addTransition(nullState);
if (!notNullState)
return;
if (RS->isAllocated()) {
// Something we presently own is being assigned somewhere.
const MemRegion *AR = location.getAsRegion();
if (!AR)
return;
AR = AR->StripCasts()->getBaseRegion();
do {
// If it is on the stack, we still own it.
if (AR->hasStackNonParametersStorage())
break;
// If the state can't represent this binding, we still own it.
if (notNullState == (notNullState->bindLoc(cast<Loc>(location), UnknownVal())))
break;
// We no longer own this pointer.
notNullState = notNullState->set<RegionState>(Sym, RefState::getRelinquished(StoreE));
}
while (false);
}
C.addTransition(notNullState);
}
}
}

View File

@ -506,6 +506,9 @@ static bool HasFeature(const Preprocessor &PP, const IdentifierInfo *II) {
.Case("cxx_static_assert", LangOpts.CPlusPlus0x)
.Case("objc_nonfragile_abi", LangOpts.ObjCNonFragileABI)
.Case("objc_weak_class", LangOpts.ObjCNonFragileABI)
.Case("ownership_holds", true)
.Case("ownership_returns", true)
.Case("ownership_takes", true)
//.Case("cxx_concepts", false)
//.Case("cxx_lambdas", false)
//.Case("cxx_nullptr", false)

View File

@ -118,6 +118,9 @@ AttributeList::Kind AttributeList::getKind(const IdentifierInfo *Name) {
.Case("ns_returns_retained", AT_ns_returns_retained)
.Case("cf_returns_not_retained", AT_cf_returns_not_retained)
.Case("cf_returns_retained", AT_cf_returns_retained)
.Case("ownership_returns", AT_ownership_returns)
.Case("ownership_holds", AT_ownership_holds)
.Case("ownership_takes", AT_ownership_takes)
.Case("reqd_work_group_size", AT_reqd_wg_size)
.Case("init_priority", AT_init_priority)
.Case("no_instrument_function", AT_no_instrument_function)

View File

@ -346,10 +346,175 @@ static void HandleNonNullAttr(Decl *d, const AttributeList &Attr, Sema &S) {
unsigned* start = &NonNullArgs[0];
unsigned size = NonNullArgs.size();
std::sort(start, start + size);
llvm::array_pod_sort(start, start + size);
d->addAttr(::new (S.Context) NonNullAttr(S.Context, start, size));
}
static void HandleOwnershipAttr(Decl *d, const AttributeList &AL, Sema &S) {
// This attribute must be applied to a function declaration.
// The first argument to the attribute must be a string,
// the name of the resource, for example "malloc".
// The following arguments must be argument indexes, the arguments must be
// of integer type for Returns, otherwise of pointer type.
// The difference between Holds and Takes is that a pointer may still be used
// after being held. free() should be __attribute((ownership_takes)), whereas a list
// append function may well be __attribute((ownership_holds)).
if (!AL.getParameterName()) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_n_not_string)
<< AL.getName()->getName() << 1;
return;
}
// Figure out our Kind, and check arguments while we're at it.
OwnershipAttr::OwnershipKind K = OwnershipAttr::None;
if (AL.getName()->getName().equals("ownership_takes")) {
K = OwnershipAttr::Takes;
if (AL.getNumArgs() < 1) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << 2;
return;
}
} else if (AL.getName()->getName().equals("ownership_holds")) {
K = OwnershipAttr::Holds;
if (AL.getNumArgs() < 1) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << 2;
return;
}
} else if (AL.getName()->getName().equals("ownership_returns")) {
K = OwnershipAttr::Returns;
if (AL.getNumArgs() > 1) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments)
<< AL.getNumArgs() + 1;
return;
}
}
// This should never happen given how we are called.
if (K == OwnershipAttr::None) {
return;
}
if (!isFunction(d) || !hasFunctionProto(d)) {
S.Diag(AL.getLoc(), diag::warn_attribute_wrong_decl_type) << AL.getName()
<< 0 /*function*/;
return;
}
unsigned NumArgs = getFunctionOrMethodNumArgs(d);
llvm::StringRef Module = AL.getParameterName()->getName();
// Normalize the argument, __foo__ becomes foo.
if (Module.startswith("__") && Module.endswith("__"))
Module = Module.substr(2, Module.size() - 4);
llvm::SmallVector<unsigned, 10> OwnershipArgs;
for (AttributeList::arg_iterator I = AL.arg_begin(), E = AL.arg_end(); I != E; ++I) {
Expr *IdxExpr = static_cast<Expr *>(*I);
llvm::APSInt ArgNum(32);
if (IdxExpr->isTypeDependent() || IdxExpr->isValueDependent()
|| !IdxExpr->isIntegerConstantExpr(ArgNum, S.Context)) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_not_int)
<< AL.getName()->getName() << IdxExpr->getSourceRange();
continue;
}
unsigned x = (unsigned) ArgNum.getZExtValue();
if (x > NumArgs || x < 1) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_bounds)
<< AL.getName()->getName() << x << IdxExpr->getSourceRange();
continue;
}
--x;
switch (K) {
case OwnershipAttr::Takes:
case OwnershipAttr::Holds: {
// Is the function argument a pointer type?
QualType T = getFunctionOrMethodArgType(d, x);
if (!T->isAnyPointerType() && !T->isBlockPointerType()) {
// FIXME: Should also highlight argument in decl.
S.Diag(AL.getLoc(), diag::err_ownership_type)
<< ((K==OwnershipAttr::Takes)?"ownership_takes":"ownership_holds")
<< "pointer"
<< IdxExpr->getSourceRange();
continue;
}
break;
}
case OwnershipAttr::Returns: {
if (AL.getNumArgs() > 1) {
// Is the function argument an integer type?
Expr *IdxExpr = static_cast<Expr *>(AL.getArg(0));
llvm::APSInt ArgNum(32);
if (IdxExpr->isTypeDependent() || IdxExpr->isValueDependent()
|| !IdxExpr->isIntegerConstantExpr(ArgNum, S.Context)) {
S.Diag(AL.getLoc(), diag::err_ownership_type)
<< "ownership_returns" << "integer"
<< IdxExpr->getSourceRange();
return;
}
}
break;
}
// Should never happen, here to silence a warning.
default: {
return;
}
} // switch
// Check we don't have a conflict with another ownership attribute.
if (d->hasAttrs()) {
for (const Attr *attr = d->getAttrs(); attr; attr = attr->getNext()) {
if (const OwnershipAttr* Att = dyn_cast<OwnershipAttr>(attr)) {
// Two ownership attributes of the same kind can't conflict,
// except returns attributes.
if (K == OwnershipAttr::Returns || Att->getKind() != K) {
for (const unsigned *I = Att->begin(), *E = Att->end(); I != E; ++I) {
if (x == *I) {
S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible)
<< AL.getName()->getName() << "ownership_*";
}
}
}
}
}
}
OwnershipArgs.push_back(x);
}
unsigned* start = OwnershipArgs.data();
unsigned size = OwnershipArgs.size();
llvm::array_pod_sort(start, start + size);
switch (K) {
case OwnershipAttr::Takes: {
if (OwnershipArgs.empty()) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << 2;
return;
}
d->addAttr(::new (S.Context) OwnershipTakesAttr(S.Context, start, size,
Module));
break;
}
case OwnershipAttr::Holds: {
if (OwnershipArgs.empty()) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << 2;
return;
}
d->addAttr(::new (S.Context) OwnershipHoldsAttr(S.Context, start, size,
Module));
break;
}
case OwnershipAttr::Returns: {
d->addAttr(::new (S.Context) OwnershipReturnsAttr(S.Context, start, size,
Module));
break;
}
default:
break;
}
}
static bool isStaticVarOrStaticFunciton(Decl *D) {
if (VarDecl *VD = dyn_cast<VarDecl>(D))
return VD->getStorageClass() == VarDecl::Static;
@ -379,7 +544,7 @@ static void HandleWeakRefAttr(Decl *d, const AttributeList &Attr, Sema &S) {
Ctx = Ctx->getLookupContext();
if (!isa<TranslationUnitDecl>(Ctx) && !isa<NamespaceDecl>(Ctx) ) {
S.Diag(Attr.getLoc(), diag::err_attribute_weakref_not_global_context) <<
dyn_cast<NamedDecl>(d)->getNameAsString();
dyn_cast<NamedDecl>(d)->getNameAsString();
return;
}
}
@ -2009,6 +2174,10 @@ static void ProcessDeclAttribute(Scope *scope, Decl *D,
case AttributeList::AT_mode: HandleModeAttr (D, Attr, S); break;
case AttributeList::AT_malloc: HandleMallocAttr (D, Attr, S); break;
case AttributeList::AT_nonnull: HandleNonNullAttr (D, Attr, S); break;
case AttributeList::AT_ownership_returns:
case AttributeList::AT_ownership_takes:
case AttributeList::AT_ownership_holds:
HandleOwnershipAttr (D, Attr, S); break;
case AttributeList::AT_noreturn: HandleNoReturnAttr (D, Attr, S); break;
case AttributeList::AT_nothrow: HandleNothrowAttr (D, Attr, S); break;
case AttributeList::AT_override: HandleOverrideAttr (D, Attr, S); break;
@ -2087,7 +2256,7 @@ void Sema::ProcessDeclAttributeList(Scope *S, Decl *D, const AttributeList *Attr
// but that looks really pointless. We reject it.
if (D->hasAttr<WeakRefAttr>() && !D->hasAttr<AliasAttr>()) {
Diag(AttrList->getLoc(), diag::err_attribute_weakref_without_alias) <<
dyn_cast<NamedDecl>(D)->getNameAsString();
dyn_cast<NamedDecl>(D)->getNameAsString();
return;
}
}

View File

@ -4,22 +4,136 @@ void *malloc(size_t);
void free(void *);
void *realloc(void *ptr, size_t size);
void *calloc(size_t nmemb, size_t size);
void __attribute((ownership_returns(malloc))) *my_malloc(size_t);
void __attribute((ownership_takes(malloc, 1))) my_free(void *);
void __attribute((ownership_returns(malloc, 1))) *my_malloc2(size_t);
void __attribute((ownership_holds(malloc, 1))) my_hold(void *);
// Duplicate attributes are silly, but not an error.
// Duplicate attribute has no extra effect.
// If two are of different kinds, that is an error and reported as such.
void __attribute((ownership_holds(malloc, 1)))
__attribute((ownership_holds(malloc, 1)))
__attribute((ownership_holds(malloc, 3))) my_hold2(void *, void *, void *);
void *my_malloc3(size_t);
void *myglobalpointer;
struct stuff {
void *somefield;
};
struct stuff myglobalstuff;
void f1() {
int *p = malloc(12);
return; // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
void f1_b() {
int *p = malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
void f2() {
int *p = malloc(12);
free(p);
free(p); // expected-warning{{Try to free a memory block that has been released}}
}
// ownership attributes tests
void naf1() {
int *p = my_malloc3(12);
return; // no-warning
}
void n2af1() {
int *p = my_malloc2(12);
return; // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
void af1() {
int *p = my_malloc(12);
return; // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
void af1_b() {
int *p = my_malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
void af1_c() {
myglobalpointer = my_malloc(12); // no-warning
}
void af1_d() {
struct stuff mystuff;
mystuff.somefield = my_malloc(12); // expected-warning{{Allocated memory never released. Potential memory leak.}}
}
// Test that we can pass out allocated memory via pointer-to-pointer.
void af1_e(void **pp) {
*pp = my_malloc(42); // no-warning
}
void af1_f(struct stuff *somestuff) {
somestuff->somefield = my_malloc(12); // no-warning
}
// Allocating memory for a field via multiple indirections to our arguments is OK.
void af1_g(struct stuff **pps) {
*pps = my_malloc(sizeof(struct stuff)); // no-warning
(*pps)->somefield = my_malloc(42); // no-warning
}
void af2() {
int *p = my_malloc(12);
my_free(p);
free(p); // expected-warning{{Try to free a memory block that has been released}}
}
void af2b() {
int *p = my_malloc(12);
free(p);
my_free(p); // expected-warning{{Try to free a memory block that has been released}}
}
void af2c() {
int *p = my_malloc(12);
free(p);
my_hold(p); // expected-warning{{Try to free a memory block that has been released}}
}
void af2d() {
int *p = my_malloc(12);
free(p);
my_hold2(0, 0, p); // expected-warning{{Try to free a memory block that has been released}}
}
// No leak if malloc returns null.
void af2e() {
int *p = my_malloc(12);
if (!p)
return; // no-warning
free(p); // no-warning
}
// This case would inflict a double-free elsewhere.
// However, this case is considered an analyzer bug since it causes false-positives.
void af3() {
int *p = my_malloc(12);
my_hold(p);
free(p); // no-warning
}
// This case would inflict a double-free elsewhere.
// However, this case is considered an analyzer bug since it causes false-positives.
int * af4() {
int *p = my_malloc(12);
my_free(p);
return p; // no-warning
}
// This case is (possibly) ok, be conservative
int * af5() {
int *p = my_malloc(12);
my_hold(p);
return p; // no-warning
}
// This case tests that storing malloc'ed memory to a static variable which is
// then returned is not leaked. In the absence of known contracts for functions
// or inter-procedural analysis, this is a conservative answer.