[analyzer] [NFC] Split up summary generation in RetainCountChecker in two methods

Differential Revision: https://reviews.llvm.org/D50830

llvm-svn: 340093
This commit is contained in:
George Karpenkov 2018-08-17 21:41:37 +00:00
parent 70c2ee30bc
commit cab604e9c7
2 changed files with 201 additions and 210 deletions

View File

@ -57,6 +57,202 @@ RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) {
return Summ;
}
const RetainSummary *
RetainSummaryManager::generateSummary(const FunctionDecl *FD,
bool &AllowAnnotations) {
// We generate "stop" summaries for implicitly defined functions.
if (FD->isImplicit()) {
return getPersistentStopSummary();
}
// [PR 3337] Use 'getAs<FunctionType>' to strip away any typedefs on the
// function's type.
const FunctionType *FT = FD->getType()->getAs<FunctionType>();
const IdentifierInfo *II = FD->getIdentifier();
if (!II)
return getDefaultSummary();
StringRef FName = II->getName();
// Strip away preceding '_'. Doing this here will effect all the checks
// down below.
FName = FName.substr(FName.find_first_not_of('_'));
// Inspect the result type.
QualType RetTy = FT->getReturnType();
std::string RetTyName = RetTy.getAsString();
// FIXME: This should all be refactored into a chain of "summary lookup"
// filters.
assert(ScratchArgs.isEmpty());
if (FName == "pthread_create" || FName == "pthread_setspecific") {
// Part of: <rdar://problem/7299394> and <rdar://problem/11282706>.
// This will be addressed better with IPA.
return getPersistentStopSummary();
} else if (FName == "CFPlugInInstanceCreate") {
return getPersistentSummary(RetEffect::MakeNoRet());
} else if (FName == "IORegistryEntrySearchCFProperty" ||
(RetTyName == "CFMutableDictionaryRef" &&
(FName == "IOBSDNameMatching" || FName == "IOServiceMatching" ||
FName == "IOServiceNameMatching" ||
FName == "IORegistryEntryIDMatching" ||
FName == "IOOpenFirmwarePathMatching"))) {
// Part of <rdar://problem/6961230>. (IOKit)
// This should be addressed using a API table.
return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF), DoNothing,
DoNothing);
} else if (FName == "IOServiceGetMatchingService" ||
FName == "IOServiceGetMatchingServices") {
// FIXES: <rdar://problem/6326900>
// This should be addressed using a API table. This strcmp is also
// a little gross, but there is no need to super optimize here.
ScratchArgs = AF.add(ScratchArgs, 1, DecRef);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "IOServiceAddNotification" ||
FName == "IOServiceAddMatchingNotification") {
// Part of <rdar://problem/6961230>. (IOKit)
// This should be addressed using a API table.
ScratchArgs = AF.add(ScratchArgs, 2, DecRef);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "CVPixelBufferCreateWithBytes") {
// FIXES: <rdar://problem/7283567>
// Eventually this can be improved by recognizing that the pixel
// buffer passed to CVPixelBufferCreateWithBytes is released via
// a callback and doing full IPA to make sure this is done correctly.
// FIXME: This function has an out parameter that returns an
// allocated object.
ScratchArgs = AF.add(ScratchArgs, 7, StopTracking);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "CGBitmapContextCreateWithData") {
// FIXES: <rdar://problem/7358899>
// Eventually this can be improved by recognizing that 'releaseInfo'
// passed to CGBitmapContextCreateWithData is released via
// a callback and doing full IPA to make sure this is done correctly.
ScratchArgs = AF.add(ScratchArgs, 8, StopTracking);
return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF), DoNothing,
DoNothing);
} else if (FName == "CVPixelBufferCreateWithPlanarBytes") {
// FIXES: <rdar://problem/7283567>
// Eventually this can be improved by recognizing that the pixel
// buffer passed to CVPixelBufferCreateWithPlanarBytes is released
// via a callback and doing full IPA to make sure this is done
// correctly.
ScratchArgs = AF.add(ScratchArgs, 12, StopTracking);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "VTCompressionSessionEncodeFrame") {
// The context argument passed to VTCompressionSessionEncodeFrame()
// is passed to the callback specified when creating the session
// (e.g. with VTCompressionSessionCreate()) which can release it.
// To account for this possibility, conservatively stop tracking
// the context.
ScratchArgs = AF.add(ScratchArgs, 5, StopTracking);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "dispatch_set_context" ||
FName == "xpc_connection_set_context") {
// <rdar://problem/11059275> - The analyzer currently doesn't have
// a good way to reason about the finalizer function for libdispatch.
// If we pass a context object that is memory managed, stop tracking it.
// <rdar://problem/13783514> - Same problem, but for XPC.
// FIXME: this hack should possibly go away once we can handle
// libdispatch and XPC finalizers.
ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName.startswith("NSLog")) {
return getDoNothingSummary();
} else if (FName.startswith("NS") &&
(FName.find("Insert") != StringRef::npos)) {
// Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
// be deallocated by NSMapRemove. (radar://11152419)
ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
ScratchArgs = AF.add(ScratchArgs, 2, StopTracking);
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
}
if (RetTy->isPointerType()) {
// For CoreFoundation ('CF') types.
if (cocoa::isRefType(RetTy, "CF", FName)) {
if (RetainSummary::isRetain(FD, FName)) {
// CFRetain isn't supposed to be annotated. However, this may as well
// be a user-made "safe" CFRetain function that is incorrectly
// annotated as cf_returns_retained due to lack of better options.
// We want to ignore such annotation.
AllowAnnotations = false;
return getUnarySummary(FT, cfretain);
} else if (RetainSummary::isAutorelease(FD, FName)) {
// The headers use cf_consumed, but we can fully model CFAutorelease
// ourselves.
AllowAnnotations = false;
return getUnarySummary(FT, cfautorelease);
} else {
return getCFCreateGetRuleSummary(FD);
}
}
// For CoreGraphics ('CG') and CoreVideo ('CV') types.
if (cocoa::isRefType(RetTy, "CG", FName) ||
cocoa::isRefType(RetTy, "CV", FName)) {
if (RetainSummary::isRetain(FD, FName))
return getUnarySummary(FT, cfretain);
else
return getCFCreateGetRuleSummary(FD);
}
// For all other CF-style types, use the Create/Get
// rule for summaries but don't support Retain functions
// with framework-specific prefixes.
if (coreFoundation::isCFObjectRef(RetTy)) {
return getCFCreateGetRuleSummary(FD);
}
if (FD->hasAttr<CFAuditedTransferAttr>()) {
return getCFCreateGetRuleSummary(FD);
}
}
// Check for release functions, the only kind of functions that we care
// about that don't return a pointer type.
if (FName.size() >= 2 && FName[0] == 'C' &&
(FName[1] == 'F' || FName[1] == 'G')) {
// Test for 'CGCF'.
FName = FName.substr(FName.startswith("CGCF") ? 4 : 2);
if (RetainSummary::isRelease(FD, FName))
return getUnarySummary(FT, cfrelease);
else {
assert(ScratchArgs.isEmpty());
// Remaining CoreFoundation and CoreGraphics functions.
// We use to assume that they all strictly followed the ownership idiom
// and that ownership cannot be transferred. While this is technically
// correct, many methods allow a tracked object to escape. For example:
//
// CFMutableDictionaryRef x = CFDictionaryCreateMutable(...);
// CFDictionaryAddValue(y, key, x);
// CFRelease(x);
// ... it is okay to use 'x' since 'y' has a reference to it
//
// We handle this and similar cases with the follow heuristic. If the
// function name contains "InsertValue", "SetValue", "AddValue",
// "AppendValue", or "SetAttribute", then we assume that arguments may
// "escape." This means that something else holds on to the object,
// allowing it be used even after its local retain count drops to 0.
ArgEffect E = (StrInStrNoCase(FName, "InsertValue") != StringRef::npos ||
StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
StrInStrNoCase(FName, "AppendValue") != StringRef::npos ||
StrInStrNoCase(FName, "SetAttribute") != StringRef::npos)
? MayEscape
: DoNothing;
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, E);
}
}
return getDefaultSummary();
}
const RetainSummary *
RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) {
// If we don't know what function we're calling, use our default summary.
@ -69,217 +265,8 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) {
return I->second;
// No summary? Generate one.
const RetainSummary *S = nullptr;
bool AllowAnnotations = true;
do {
// We generate "stop" summaries for implicitly defined functions.
if (FD->isImplicit()) {
S = getPersistentStopSummary();
break;
}
// [PR 3337] Use 'getAs<FunctionType>' to strip away any typedefs on the
// function's type.
const FunctionType* FT = FD->getType()->getAs<FunctionType>();
const IdentifierInfo *II = FD->getIdentifier();
if (!II)
break;
StringRef FName = II->getName();
// Strip away preceding '_'. Doing this here will effect all the checks
// down below.
FName = FName.substr(FName.find_first_not_of('_'));
// Inspect the result type.
QualType RetTy = FT->getReturnType();
std::string RetTyName = RetTy.getAsString();
// FIXME: This should all be refactored into a chain of "summary lookup"
// filters.
assert(ScratchArgs.isEmpty());
if (FName == "pthread_create" || FName == "pthread_setspecific") {
// Part of: <rdar://problem/7299394> and <rdar://problem/11282706>.
// This will be addressed better with IPA.
S = getPersistentStopSummary();
} else if (FName == "CFPlugInInstanceCreate") {
S = getPersistentSummary(RetEffect::MakeNoRet());
} else if (FName == "IORegistryEntrySearchCFProperty"
|| (RetTyName == "CFMutableDictionaryRef" && (
FName == "IOBSDNameMatching" ||
FName == "IOServiceMatching" ||
FName == "IOServiceNameMatching" ||
FName == "IORegistryEntryIDMatching" ||
FName == "IOOpenFirmwarePathMatching"
))) {
// Part of <rdar://problem/6961230>. (IOKit)
// This should be addressed using a API table.
S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF),
DoNothing, DoNothing);
} else if (FName == "IOServiceGetMatchingService" ||
FName == "IOServiceGetMatchingServices") {
// FIXES: <rdar://problem/6326900>
// This should be addressed using a API table. This strcmp is also
// a little gross, but there is no need to super optimize here.
ScratchArgs = AF.add(ScratchArgs, 1, DecRef);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "IOServiceAddNotification" ||
FName == "IOServiceAddMatchingNotification") {
// Part of <rdar://problem/6961230>. (IOKit)
// This should be addressed using a API table.
ScratchArgs = AF.add(ScratchArgs, 2, DecRef);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "CVPixelBufferCreateWithBytes") {
// FIXES: <rdar://problem/7283567>
// Eventually this can be improved by recognizing that the pixel
// buffer passed to CVPixelBufferCreateWithBytes is released via
// a callback and doing full IPA to make sure this is done correctly.
// FIXME: This function has an out parameter that returns an
// allocated object.
ScratchArgs = AF.add(ScratchArgs, 7, StopTracking);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "CGBitmapContextCreateWithData") {
// FIXES: <rdar://problem/7358899>
// Eventually this can be improved by recognizing that 'releaseInfo'
// passed to CGBitmapContextCreateWithData is released via
// a callback and doing full IPA to make sure this is done correctly.
ScratchArgs = AF.add(ScratchArgs, 8, StopTracking);
S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF),
DoNothing, DoNothing);
} else if (FName == "CVPixelBufferCreateWithPlanarBytes") {
// FIXES: <rdar://problem/7283567>
// Eventually this can be improved by recognizing that the pixel
// buffer passed to CVPixelBufferCreateWithPlanarBytes is released
// via a callback and doing full IPA to make sure this is done
// correctly.
ScratchArgs = AF.add(ScratchArgs, 12, StopTracking);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "VTCompressionSessionEncodeFrame") {
// The context argument passed to VTCompressionSessionEncodeFrame()
// is passed to the callback specified when creating the session
// (e.g. with VTCompressionSessionCreate()) which can release it.
// To account for this possibility, conservatively stop tracking
// the context.
ScratchArgs = AF.add(ScratchArgs, 5, StopTracking);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName == "dispatch_set_context" ||
FName == "xpc_connection_set_context") {
// <rdar://problem/11059275> - The analyzer currently doesn't have
// a good way to reason about the finalizer function for libdispatch.
// If we pass a context object that is memory managed, stop tracking it.
// <rdar://problem/13783514> - Same problem, but for XPC.
// FIXME: this hack should possibly go away once we can handle
// libdispatch and XPC finalizers.
ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
} else if (FName.startswith("NSLog")) {
S = getDoNothingSummary();
} else if (FName.startswith("NS") &&
(FName.find("Insert") != StringRef::npos)) {
// Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
// be deallocated by NSMapRemove. (radar://11152419)
ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
ScratchArgs = AF.add(ScratchArgs, 2, StopTracking);
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
}
// Did we get a summary?
if (S)
break;
if (RetTy->isPointerType()) {
// For CoreFoundation ('CF') types.
if (cocoa::isRefType(RetTy, "CF", FName)) {
if (RetainSummary::isRetain(FD, FName)) {
S = getUnarySummary(FT, cfretain);
// CFRetain isn't supposed to be annotated. However, this may as well
// be a user-made "safe" CFRetain function that is incorrectly
// annotated as cf_returns_retained due to lack of better options.
// We want to ignore such annotation.
AllowAnnotations = false;
} else if (RetainSummary::isAutorelease(FD, FName)) {
S = getUnarySummary(FT, cfautorelease);
// The headers use cf_consumed, but we can fully model CFAutorelease
// ourselves.
AllowAnnotations = false;
} else {
S = getCFCreateGetRuleSummary(FD);
}
break;
}
// For CoreGraphics ('CG') and CoreVideo ('CV') types.
if (cocoa::isRefType(RetTy, "CG", FName) ||
cocoa::isRefType(RetTy, "CV", FName)) {
if (RetainSummary::isRetain(FD, FName))
S = getUnarySummary(FT, cfretain);
else
S = getCFCreateGetRuleSummary(FD);
break;
}
// For all other CF-style types, use the Create/Get
// rule for summaries but don't support Retain functions
// with framework-specific prefixes.
if (coreFoundation::isCFObjectRef(RetTy)) {
S = getCFCreateGetRuleSummary(FD);
break;
}
if (FD->hasAttr<CFAuditedTransferAttr>()) {
S = getCFCreateGetRuleSummary(FD);
break;
}
break;
}
// Check for release functions, the only kind of functions that we care
// about that don't return a pointer type.
if (FName.size() >= 2 &&
FName[0] == 'C' && (FName[1] == 'F' || FName[1] == 'G')) {
// Test for 'CGCF'.
FName = FName.substr(FName.startswith("CGCF") ? 4 : 2);
if (RetainSummary::isRelease(FD, FName))
S = getUnarySummary(FT, cfrelease);
else {
assert (ScratchArgs.isEmpty());
// Remaining CoreFoundation and CoreGraphics functions.
// We use to assume that they all strictly followed the ownership idiom
// and that ownership cannot be transferred. While this is technically
// correct, many methods allow a tracked object to escape. For example:
//
// CFMutableDictionaryRef x = CFDictionaryCreateMutable(...);
// CFDictionaryAddValue(y, key, x);
// CFRelease(x);
// ... it is okay to use 'x' since 'y' has a reference to it
//
// We handle this and similar cases with the follow heuristic. If the
// function name contains "InsertValue", "SetValue", "AddValue",
// "AppendValue", or "SetAttribute", then we assume that arguments may
// "escape." This means that something else holds on to the object,
// allowing it be used even after its local retain count drops to 0.
ArgEffect E = (StrInStrNoCase(FName, "InsertValue") != StringRef::npos||
StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
StrInStrNoCase(FName, "SetAttribute") != StringRef::npos)
? MayEscape : DoNothing;
S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, E);
}
}
}
while (0);
// If we got all the way here without any luck, use a default summary.
if (!S)
S = getDefaultSummary();
const RetainSummary *S = generateSummary(FD, AllowAnnotations);
// Annotations override defaults.
if (AllowAnnotations)

View File

@ -495,6 +495,10 @@ public:
RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; }
private:
const RetainSummary * generateSummary(const FunctionDecl *FD,
bool &AllowAnnotations);
friend class RetainSummaryTemplate;
};