vsavchenko created this revision. vsavchenko added reviewers: NoQ, dcoughlin. Herald added subscribers: cfe-commits, ASDenysPetrov, martong, Charusso, dkrupp, donat.nagy, Szelethus, mikhail.ramalho, a.sidorin, szepet, baloghadamsoftware, xazax.hun. Herald added a project: clang.
Objective-C Class objects can be used to do a dynamic dispatch on class methods. The analyzer had a few places where we tried to overcome the dynamic nature of it and still guess the actual function that is being called. That was done mostly using some simple heuristics covering the most widespread cases (e.g. [[self class] classmethod]). This solution introduces a way to track types represented by Class objects and work with that instead of direct AST matching. rdar://problem/50739539 Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D78286 Files: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp clang/lib/StaticAnalyzer/Core/CallEvent.cpp clang/lib/StaticAnalyzer/Core/DynamicType.cpp clang/test/Analysis/cast-value-state-dump.cpp clang/test/Analysis/class-object-state-dump.m clang/test/Analysis/inlining/InlineObjCClassMethod.m clang/test/Analysis/inlining/ObjCDynTypePopagation.m clang/test/Analysis/inlining/ObjCDynTypePropagation.m clang/test/Analysis/retain-release-inline.m
Index: clang/test/Analysis/retain-release-inline.m =================================================================== --- clang/test/Analysis/retain-release-inline.m +++ clang/test/Analysis/retain-release-inline.m @@ -13,6 +13,7 @@ // It includes the basic definitions for the test cases below. //===----------------------------------------------------------------------===// #define NULL 0 +#define nil ((id)0) typedef unsigned int __darwin_natural_t; typedef unsigned long uintptr_t; typedef unsigned int uint32_t; @@ -21,14 +22,14 @@ typedef signed long CFIndex; typedef CFIndex CFByteOrder; typedef struct { - CFIndex location; - CFIndex length; + CFIndex location; + CFIndex length; } CFRange; static __inline__ __attribute__((always_inline)) CFRange CFRangeMake(CFIndex loc, CFIndex len) { - CFRange range; - range.location = loc; - range.length = len; - return range; + CFRange range; + range.location = loc; + range.length = len; + return range; } typedef const void * CFTypeRef; typedef const struct __CFString * CFStringRef; @@ -91,6 +92,7 @@ - (BOOL)isEqual:(id)object; - (id)retain; - (oneway void)release; +- (Class)class; - (id)autorelease; - (id)init; @end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @@ -100,6 +102,7 @@ @interface NSObject <NSObject> {} + (id)allocWithZone:(NSZone *)zone; + (id)alloc; ++ (Class)class; - (void)dealloc; @end @interface NSObject (NSCoderMethods) @@ -481,3 +484,33 @@ [self test_inline_tiny_when_reanalyzing]; } @end + +// Original problem: rdar://problem/50739539 +@interface MyClassThatLeaksDuringInit : NSObject + ++ (MyClassThatLeaksDuringInit *)getAnInstance1; ++ (MyClassThatLeaksDuringInit *)getAnInstance2; + +@end + +@implementation MyClassThatLeaksDuringInit + ++ (MyClassThatLeaksDuringInit *)getAnInstance1 { + return [[[MyClassThatLeaksDuringInit alloc] init] autorelease]; // expected-warning{{leak}} +} + ++ (MyClassThatLeaksDuringInit *)getAnInstance2 { + return [[[[self class] alloc] init] autorelease]; // expected-warning{{leak}} +} + +- (instancetype)init { + if (1) { + return nil; + } + + if (nil != (self = [super init])) { + } + return self; +} + +@end Index: clang/test/Analysis/inlining/ObjCDynTypePropagation.m =================================================================== --- clang/test/Analysis/inlining/ObjCDynTypePropagation.m +++ clang/test/Analysis/inlining/ObjCDynTypePropagation.m @@ -7,68 +7,67 @@ PublicSubClass2 *getObj(); @implementation PublicParent -- (int) getZeroOverridden { - return 1; +- (int)getZeroOverridden { + return 1; } -- (int) getZero { - return 0; +- (int)getZero { + return 0; } @end @implementation PublicSubClass2 -- (int) getZeroOverridden { - return 0; +- (int)getZeroOverridden { + return 0; } /* Test that we get the right type from call to alloc. */ -+ (void) testAllocSelf { ++ (void)testAllocSelf { id a = [self alloc]; clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} } - -+ (void) testAllocClass { ++ (void)testAllocClass { id a = [PublicSubClass2 alloc]; clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} } -+ (void) testAllocSuperOverriden { ++ (void)testAllocSuperOverriden { id a = [super alloc]; // Evaluates to 1 in the parent. - clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{FALSE}} + clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{FALSE}} } -+ (void) testAllocSuper { ++ (void)testAllocSuper { id a = [super alloc]; clang_analyzer_eval([a getZero] == 0); // expected-warning{{TRUE}} } -+ (void) testAllocInit { ++ (void)testAllocInit { id a = [[self alloc] init]; clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} } -+ (void) testNewSelf { ++ (void)testNewSelf { id a = [self new]; clang_analyzer_eval([a getZeroOverridden] == 0); // expected-warning{{TRUE}} } -// Casting to parent should not pessimize the dynamic type. -+ (void) testCastToParent { - id a = [[self alloc] init]; - PublicParent *p = a; +// Casting to parent should not pessimize the dynamic type. ++ (void)testCastToParent { + id a = [[self alloc] init]; + PublicParent *p = a; clang_analyzer_eval([p getZeroOverridden] == 0); // expected-warning{{TRUE}} } // The type of parameter gets used. -+ (void)testTypeFromParam:(PublicParent*) p { ++ (void)testTypeFromParam:(PublicParent *)p { clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}} } // Test implicit cast. // Note, in this case, p could also be a subclass of MyParent. -+ (void) testCastFromId:(id) a { - PublicParent *p = a; ++ (void)testCastFromId:(id)a { + PublicParent *p = a; clang_analyzer_eval([p getZero] == 0); // expected-warning{{TRUE}} } @end @@ -76,26 +75,28 @@ // TODO: Would be nice to handle the case of dynamically obtained class info // as well. We need a MemRegion for class types for this. int testDynamicClass(BOOL coin) { - Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]); - id x = [[AllocClass alloc] init]; - if (coin) - return [x getZero]; - return 1; + Class AllocClass = (coin ? [NSObject class] : [PublicSubClass2 class]); + id x = [[AllocClass alloc] init]; + if (coin) + return [x getZero]; + return 1; } @interface UserClass : NSObject -- (PublicSubClass2 *) _newPublicSubClass2; -- (int) getZero; -- (void) callNew; +- (PublicSubClass2 *)_newPublicSubClass2; +- (int)getZero; +- (void)callNew; @end @implementation UserClass -- (PublicSubClass2 *) _newPublicSubClass2 { +- (PublicSubClass2 *)_newPublicSubClass2 { return [[PublicSubClass2 alloc] init]; } -- (int) getZero { return 5; } -- (void) callNew { +- (int)getZero { + return 5; +} +- (void)callNew { PublicSubClass2 *x = [self _newPublicSubClass2]; clang_analyzer_eval([x getZero] == 0); //expected-warning{{TRUE}} } @end \ No newline at end of file Index: clang/test/Analysis/inlining/InlineObjCClassMethod.m =================================================================== --- clang/test/Analysis/inlining/InlineObjCClassMethod.m +++ clang/test/Analysis/inlining/InlineObjCClassMethod.m @@ -6,18 +6,25 @@ // Test inlining of ObjC class methods. typedef signed char BOOL; +#define YES ((BOOL)1) +#define NO ((BOOL)0) typedef struct objc_class *Class; typedef struct objc_object { - Class isa; -} *id; -@protocol NSObject - (BOOL)isEqual:(id)object; @end + Class isa; +} * id; +@protocol NSObject +- (BOOL)isEqual:(id)object; +@end @interface NSObject <NSObject> {} -+(id)alloc; --(id)init; --(id)autorelease; --(id)copy; ++ (id)alloc; ++ (Class)class; ++ (Class)superclass; +- (id)init; +- (id)autorelease; +- (id)copy; - (Class)class; --(id)retain; +- (instancetype)self; +- (id)retain; @end // Vanila: ObjC class method is called by name. @@ -133,10 +140,7 @@ } @end - -// False negative. // ObjC class method call through a decl with a known type. -// We should be able to track the type of currentClass and inline this call. // Note, [self class] could be a subclass. Do we still want to inline here? @interface MyClassKT : NSObject @end @@ -152,7 +156,7 @@ - (int)testClassMethodByKnownVarDecl { Class currentClass = [self class]; int y = [currentClass getInt]; - return 5/y; // Would be great to get a warning here. + return 5 / y; // expected-warning{{Division by zero}} } @end @@ -253,24 +257,97 @@ @end @implementation SelfClassTest --(unsigned)returns10 { return 10; } -+(unsigned)returns20 { return 20; } -+(unsigned)returns30 { return 30; } -+(void)classMethod { +- (unsigned)returns10 { + return 10; +} ++ (unsigned)returns20 { + return 20; +} ++ (unsigned)returns30 { + return 30; +} ++ (BOOL)isClass { + return YES; +} +- (BOOL)isClass { + return NO; +} ++ (SelfClassTest *)create { + return [[self alloc] init]; +} ++ (void)classMethod { unsigned result1 = [self returns20]; clang_analyzer_eval(result1 == 20); // expected-warning{{TRUE}} + unsigned result2 = [[self class] returns30]; clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}} + unsigned result3 = [[super class] returns30]; - clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(result3 == 100); // expected-warning{{TRUE}} + + // Check that class info is propagated with data + Class class41 = [self class]; + Class class42 = class41; + unsigned result4 = [class42 returns30]; + clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}} + + Class class51 = [super class]; + Class class52 = class51; + unsigned result5 = [class52 returns30]; + clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}} } --(void)instanceMethod { +- (void)instanceMethod { unsigned result0 = [self returns10]; clang_analyzer_eval(result0 == 10); // expected-warning{{TRUE}} + unsigned result2 = [[self class] returns30]; clang_analyzer_eval(result2 == 30); // expected-warning{{TRUE}} + unsigned result3 = [[super class] returns30]; - clang_analyzer_eval(result3 == 100); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(result3 == 100); // expected-warning{{TRUE}} + + // Check that class info is propagated with data + Class class41 = [self class]; + Class class42 = class41; + unsigned result4 = [class42 returns30]; + clang_analyzer_eval(result4 == 30); // expected-warning{{TRUE}} + + Class class51 = [super class]; + Class class52 = class51; + unsigned result5 = [class52 returns30]; + clang_analyzer_eval(result5 == 100); // expected-warning{{TRUE}} + + // Check that we inline class methods when class object is a receiver + Class class6 = [self class]; + BOOL calledClassMethod = [class6 isClass]; + clang_analyzer_eval(calledClassMethod == YES); // expected-warning{{TRUE}} + + // Check that class info is propagated through the 'self' method + Class class71 = [self class]; + Class class72 = [class71 self]; + unsigned result7 = [class72 returns30]; + clang_analyzer_eval(result7 == 30); // expected-warning{{TRUE}} + + // Check that 'class' and 'super' info from direct invocation of the + // corresponding class methods is propagated with data + Class class8 = [SelfClassTest class]; + unsigned result8 = [class8 returns30]; + clang_analyzer_eval(result8 == 30); // expected-warning{{TRUE}} + + Class class9 = [SelfClassTest superclass]; + unsigned result9 = [class9 returns30]; + clang_analyzer_eval(result9 == 100); // expected-warning{{TRUE}} + + // Check that we get class from a propagated type + SelfClassTestParent *selfAsParent10 = [[SelfClassTest alloc] init]; + Class class10 = [selfAsParent10 class]; + unsigned result10 = [class10 returns30]; + clang_analyzer_eval(result10 == 30); // expected-warning{{TRUE}} + + SelfClassTestParent *selfAsParent11 = [[[self class] alloc] init]; + Class class11 = [selfAsParent11 class]; + unsigned result11 = [class11 returns30]; + clang_analyzer_eval(result11 == 30); // expected-warning{{TRUE}} } @end Index: clang/test/Analysis/class-object-state-dump.m =================================================================== --- /dev/null +++ clang/test/Analysis/class-object-state-dump.m @@ -0,0 +1,33 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -verify %s 2>&1 | FileCheck %s + +// expected-no-diagnostics + +void clang_analyzer_printState(); + +@interface NSObject { +} ++ (id)alloc; ++ (Class)class; +- (id)init; +- (Class)class; +@end + +@interface Parent : NSObject +@end +@interface Child : Parent +@end + +@implementation Child ++ (void)test { + id ClassAsID = [self class]; + id Object = [[ClassAsID alloc] init]; + Class TheSameClass = [Object class]; + + clang_analyzer_printState(); + // CHECK: "class_object_types": [ + // CHECK-NEXT: { "symbol": "conj_$[[#]]{Class, LC[[#]], S[[#]], #[[#]]}", "dyn_type": "Child", "sub_classable": true }, + // CHECK-NEXT: { "symbol": "conj_$[[#]]{Class, LC[[#]], S[[#]], #[[#]]}", "dyn_type": "Child", "sub_classable": true } + // CHECK-NEXT: ] +} +@end Index: clang/test/Analysis/cast-value-state-dump.cpp =================================================================== --- clang/test/Analysis/cast-value-state-dump.cpp +++ clang/test/Analysis/cast-value-state-dump.cpp @@ -37,7 +37,7 @@ // CHECK: { "region": "SymRegion{reg_$0<const struct clang::Shape * S>}", "casts": [ // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" }, // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" } - // CHECK-NEXT: ]} + // CHECK-NEXT: ] } (void)(1 / !C); // expected-note@-1 {{'C' is non-null}} Index: clang/lib/StaticAnalyzer/Core/DynamicType.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -34,6 +34,10 @@ REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, CastSet) +// A map from Class object symbols to the most likely contained type. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicClassObjectMap, clang::ento::SymbolRef, + clang::ento::DynamicTypeInfo) + namespace clang { namespace ento { @@ -76,6 +80,13 @@ return nullptr; } +DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym) { + if (const DynamicTypeInfo *DTI = State->get<DynamicClassObjectMap>(Sym)) + return *DTI; + return {}; +} + ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, DynamicTypeInfo NewTy) { State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy); @@ -118,6 +129,20 @@ return State; } +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, + DynamicTypeInfo NewTy) { + State = State->set<DynamicClassObjectMap>(Sym, NewTy); + return State; +} + +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, QualType NewTy, + bool CanBeSubClassed) { + return setClassObjectDynamicTypeInfo(State, Sym, + DynamicTypeInfo(NewTy, CanBeSubClassed)); +} + template <typename MapTy> ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, SymbolReaper &SR) { @@ -136,93 +161,121 @@ return removeDead(State, State->get<DynamicCastMap>(), SR); } -static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_types\": "; +//===----------------------------------------------------------------------===// +// Implementation of the 'printer-to-JSON' function +//===----------------------------------------------------------------------===// - const DynamicTypeMapTy &Map = State->get<DynamicTypeMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; - } +static raw_ostream &printJson(const MemRegion *Region, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"region\": \"" << Region << "\""; +} - ++Space; - Out << '[' << NL; - for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const DynamicTypeInfo &DTI = I->second; - Indent(Out, Space, IsDot) - << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; - if (!DTI.isValid()) { - Out << "null"; - } else { - Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\", \"sub_classable\": " - << (DTI.canBeASubClass() ? "true" : "false"); - } - Out << " }"; - - if (std::next(I) != Map.end()) - Out << ','; - Out << NL; +static raw_ostream &printJson(const SymExpr *Symbol, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"symbol\": \"" << Symbol << "\""; +} + +static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"dyn_type\": "; + if (!DTI.isValid()) { + Out << "null"; + } else { + QualType ToPrint = DTI.getType(); + if (ToPrint->isAnyPointerType()) + ToPrint = ToPrint->getPointeeType(); + + Out << '\"' << ToPrint.getAsString() << "\", \"sub_classable\": " + << (DTI.canBeASubClass() ? "true" : "false"); } + return Out; +} - --Space; - Indent(Out, Space, IsDot) << "]," << NL; +static raw_ostream &printJson(const DynamicCastInfo &DCI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"from\": \"" << DCI.from().getAsString() << "\", \"to\": \"" + << DCI.to().getAsString() << "\", \"kind\": \"" + << (DCI.succeeds() ? "success" : "fail") << "\""; } -static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; +template <class T, class U> +static raw_ostream &printJson(const std::pair<T, U> &Pair, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + printJson(Pair.first, Out, NL, Space, IsDot) << ", "; + return printJson(Pair.second, Out, NL, Space, IsDot); +} - const DynamicCastMapTy &Map = State->get<DynamicCastMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; +template <class ContainerTy> +static raw_ostream &printJsonContainer(const ContainerTy &Container, + raw_ostream &Out, const char *NL, + unsigned int Space, bool IsDot) { + if (Container.isEmpty()) { + return Out << "null"; } ++Space; Out << '[' << NL; - for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const CastSet &Set = I->second; - - Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": "; - if (Set.isEmpty()) { - Out << "null "; - } else { - ++Space; - Out << '[' << NL; - for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { - Indent(Out, Space, IsDot) - << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" - << SI->to().getAsString() << "\", \"kind\": \"" - << (SI->succeeds() ? "success" : "fail") << "\" }"; - - if (std::next(SI) != Set.end()) - Out << ','; - Out << NL; - } - --Space; - Indent(Out, Space, IsDot) << ']'; - } - Out << '}'; - - if (std::next(I) != Map.end()) + for (auto I = Container.begin(); I != Container.end(); ++I) { + const auto &Element = *I; + + Indent(Out, Space, IsDot) << "{ "; + printJson(Element, Out, NL, Space, IsDot) << " }"; + + if (std::next(I) != Container.end()) Out << ','; Out << NL; } --Space; - Indent(Out, Space, IsDot) << "]," << NL; + return Indent(Out, Space, IsDot) << "]"; +} + +static raw_ostream &printJson(const CastSet &Set, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"casts\": "; + return printJsonContainer(Set, Out, NL, Space, IsDot); +} + +template <class MapTy> +static void printJsonImpl(raw_ostream &Out, ProgramStateRef State, + const char *Name, const char *NL, unsigned int Space, + bool IsDot, bool PrintEvenIfEmpty = true) { + const auto &Map = State->get<MapTy>(); + if (Map.isEmpty() && !PrintEvenIfEmpty) + return; + + Indent(Out, Space, IsDot) << "\"" << Name << "\": "; + printJsonContainer(Map, Out, NL, Space, IsDot) << "," << NL; +} + +static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicTypeMap>(Out, State, "dynamic_types", NL, Space, IsDot); +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicCastMap>(Out, State, "dynamic_casts", NL, Space, IsDot); +} + +static void printClassObjectDynamicTypesJson(raw_ostream &Out, + ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + // Let's print Class object type information only if we have something + // meaningful to print. + printJsonImpl<DynamicClassObjectMap>(Out, State, "class_object_types", NL, + Space, IsDot, + /*PrintEvenIfEmpty=*/false); } void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, const char *NL, unsigned int Space, bool IsDot) { printDynamicTypesJson(Out, State, NL, Space, IsDot); printDynamicCastsJson(Out, State, NL, Space, IsDot); + printClassObjectDynamicTypesJson(Out, State, NL, Space, IsDot); } } // namespace ento Index: clang/lib/StaticAnalyzer/Core/CallEvent.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -1173,23 +1173,75 @@ return MD; } -static bool isCallToSelfClass(const ObjCMessageExpr *ME) { - const Expr* InstRec = ME->getInstanceReceiver(); - if (!InstRec) - return false; - const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts()); +struct PrivateMethodKey { + const ObjCInterfaceDecl *Interface; + Selector LookupSelector; + bool IsClassMethod; +}; + +template <> struct llvm::DenseMapInfo<PrivateMethodKey> { + using InterfaceInfo = DenseMapInfo<const ObjCInterfaceDecl *>; + using SelectorInfo = DenseMapInfo<Selector>; + + static inline PrivateMethodKey getEmptyKey() { + return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), false}; + } - // Check that receiver is called 'self'. - if (!InstRecIg || !InstRecIg->getFoundDecl() || - !InstRecIg->getFoundDecl()->getName().equals("self")) - return false; + static inline PrivateMethodKey getTombstoneKey() { + return {InterfaceInfo::getTombstoneKey(), SelectorInfo::getTombstoneKey(), + true}; + } - // Check that the method name is 'class'. - if (ME->getSelector().getNumArgs() != 0 || - !ME->getSelector().getNameForSlot(0).equals("class")) - return false; + static unsigned getHashValue(const PrivateMethodKey &Key) { + return llvm::hash_combine( + llvm::hash_code(InterfaceInfo::getHashValue(Key.Interface)), + llvm::hash_code(SelectorInfo::getHashValue(Key.LookupSelector)), + Key.IsClassMethod); + } - return true; + static bool isEqual(const PrivateMethodKey &LHS, + const PrivateMethodKey &RHS) { + return InterfaceInfo::isEqual(LHS.Interface, RHS.Interface) && + SelectorInfo::isEqual(LHS.LookupSelector, RHS.LookupSelector) && + LHS.IsClassMethod == RHS.IsClassMethod; + } +}; + +const ObjCMethodDecl * +lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, + Selector LookupSelector, bool InstanceMethod) { + // Repeatedly calling lookupPrivateMethod() is expensive, especially + // when in many cases it returns null. We cache the results so + // that repeated queries on the same ObjCIntefaceDecl and Selector + // don't incur the same cost. On some test cases, we can see the + // same query being issued thousands of times. + // + // NOTE: This cache is essentially a "global" variable, but it + // only gets lazily created when we get here. The value of the + // cache probably comes from it being global across ExprEngines, + // where the same queries may get issued. If we are worried about + // concurrency, or possibly loading/unloading ASTs, etc., we may + // need to revisit this someday. In terms of memory, this table + // stays around until clang quits, which also may be bad if we + // need to release memory. + using PrivateMethodCache = + llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; + + static PrivateMethodCache PMC; + Optional<const ObjCMethodDecl *> &Val = + PMC[{Interface, LookupSelector, InstanceMethod}]; + + // Query lookupPrivateMethod() if the cache does not hit. + if (!Val.hasValue()) { + Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod); + + if (!*Val) { + // Query 'lookupMethod' as a backup. + Val = Interface->lookupMethod(LookupSelector, InstanceMethod); + } + } + + return Val.getValue(); } RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { @@ -1199,8 +1251,9 @@ if (E->isInstanceMessage()) { // Find the receiver type. - const ObjCObjectPointerType *ReceiverT = nullptr; + const ObjCObjectType *ReceiverT = nullptr; bool CanBeSubClassed = false; + bool LookingForInstanceMethod = true; QualType SupersType = E->getSuperType(); const MemRegion *Receiver = nullptr; @@ -1208,7 +1261,7 @@ // The receiver is guaranteed to be 'super' in this case. // Super always means the type of immediate predecessor to the method // where the call occurs. - ReceiverT = cast<ObjCObjectPointerType>(SupersType); + ReceiverT = cast<ObjCObjectPointerType>(SupersType)->getObjectType(); } else { Receiver = getReceiverSVal().getAsRegion(); if (!Receiver) @@ -1223,100 +1276,58 @@ QualType DynType = DTI.getType(); CanBeSubClassed = DTI.canBeASubClass(); - ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); - if (ReceiverT && CanBeSubClassed) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) - if (!canBeOverridenInSubclass(IDecl, Sel)) - CanBeSubClassed = false; - } + const auto *ReceiverDynT = + dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); + + if (ReceiverDynT) { + ReceiverT = ReceiverDynT->getObjectType(); - // Handle special cases of '[self classMethod]' and - // '[[self class] classMethod]', which are treated by the compiler as - // instance (not class) messages. We will statically dispatch to those. - if (auto *PT = dyn_cast_or_null<ObjCObjectPointerType>(ReceiverT)) { - // For [self classMethod], return the compiler visible declaration. - if (PT->getObjectType()->isObjCClass() && - Receiver == getSelfSVal().getAsRegion()) - return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); - - // Similarly, handle [[self class] classMethod]. - // TODO: We are currently doing a syntactic match for this pattern with is - // limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m - // shows. A better way would be to associate the meta type with the symbol - // using the dynamic type info tracking and use it here. We can add a new - // SVal for ObjC 'Class' values that know what interface declaration they - // come from. Then 'self' in a class method would be filled in with - // something meaningful in ObjCMethodCall::getReceiverSVal() and we could - // do proper dynamic dispatch for class methods just like we do for - // instance methods now. - if (E->getInstanceReceiver()) - if (const auto *M = dyn_cast<ObjCMessageExpr>(E->getInstanceReceiver())) - if (isCallToSelfClass(M)) + // It can be actually class methods called with Class object as a + // receiver. This type of messages is treated by the compiler as + // instance (not class). + if (ReceiverT->isObjCClass()) { + + // For [self classMethod], return compiler visible declaration. + if (Receiver == getSelfSVal().getAsRegion()) { return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); + } + + // Otherwise, let's check if we know something about the type + // inside of this class object. + if (SymbolRef ReceiverSym = getReceiverSVal().getAsSymbol()) { + DynamicTypeInfo DTI = + getClassObjectDynamicTypeInfo(getState(), ReceiverSym); + if (DTI.isValid()) { + // Let's use this type for lookup. + ReceiverT = + cast<ObjCObjectType>(DTI.getType().getCanonicalType()); + + CanBeSubClassed = DTI.canBeASubClass(); + // And it should be a class method instead. + LookingForInstanceMethod = false; + } + } + } + + if (CanBeSubClassed) + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) + // Even if `DynamicTypeInfo` told us that it can be + // not necessarily this type, but its descendants, we still want + // to check again if this + CanBeSubClassed = canBeOverridenInSubclass(IDecl, Sel); + } } // Lookup the instance method implementation. if (ReceiverT) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) { - // Repeatedly calling lookupPrivateMethod() is expensive, especially - // when in many cases it returns null. We cache the results so - // that repeated queries on the same ObjCIntefaceDecl and Selector - // don't incur the same cost. On some test cases, we can see the - // same query being issued thousands of times. - // - // NOTE: This cache is essentially a "global" variable, but it - // only gets lazily created when we get here. The value of the - // cache probably comes from it being global across ExprEngines, - // where the same queries may get issued. If we are worried about - // concurrency, or possibly loading/unloading ASTs, etc., we may - // need to revisit this someday. In terms of memory, this table - // stays around until clang quits, which also may be bad if we - // need to release memory. - using PrivateMethodKey = std::pair<const ObjCInterfaceDecl *, Selector>; - using PrivateMethodCache = - llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; - - static PrivateMethodCache PMC; - Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)]; - - // Query lookupPrivateMethod() if the cache does not hit. - if (!Val.hasValue()) { - Val = IDecl->lookupPrivateMethod(Sel); - - // If the method is a property accessor, we should try to "inline" it - // even if we don't actually have an implementation. - if (!*Val) - if (const ObjCMethodDecl *CompileTimeMD = E->getMethodDecl()) - if (CompileTimeMD->isPropertyAccessor()) { - if (!CompileTimeMD->getSelfDecl() && - isa<ObjCCategoryDecl>(CompileTimeMD->getDeclContext())) { - // If the method is an accessor in a category, and it doesn't - // have a self declaration, first - // try to find the method in a class extension. This - // works around a bug in Sema where multiple accessors - // are synthesized for properties in class - // extensions that are redeclared in a category and the - // the implicit parameters are not filled in for - // the method on the category. - // This ensures we find the accessor in the extension, which - // has the implicit parameters filled in. - auto *ID = CompileTimeMD->getClassInterface(); - for (auto *CatDecl : ID->visible_extensions()) { - Val = CatDecl->getMethod(Sel, - CompileTimeMD->isInstanceMethod()); - if (*Val) - break; - } - } - if (!*Val) - Val = IDecl->lookupInstanceMethod(Sel); - } - } + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) { + const ObjCMethodDecl *MD = + lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod); - const ObjCMethodDecl *MD = Val.getValue(); if (MD && !MD->hasBody()) MD = MD->getCanonicalDecl(); + if (CanBeSubClassed) return RuntimeDefinition(MD, Receiver); else Index: clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -109,6 +109,109 @@ /// This value is set to true, when the Generics checker is turned on. DefaultBool CheckGenerics; }; + +bool isObjCClassType(QualType Type) { + if (const auto *PointerType = dyn_cast<ObjCObjectPointerType>(Type)) { + return PointerType->getObjectType()->isObjCClass(); + } + return false; +} + +struct RuntimeType { + const ObjCObjectType *Type = nullptr; + bool Precise = false; + + operator bool() const { return Type != nullptr; } +}; + +RuntimeType inferReceiverType(const ObjCMessageExpr *MsgE, CheckerContext &C) { + // Check if we can statically infer the actual type precisely. + // + // 1. Class is written directly in the message: + // \code + // [ActualClass classMethod]; + // \endcode + if (MsgE->getReceiverKind() == ObjCMessageExpr::Class) { + return {MsgE->getClassReceiver()->getAs<ObjCObjectType>(), true}; + } + + // 2. Receiver is 'super' from a class method (a.k.a 'super' is a + // class object). + // \code + // [super classMethod]; + // \endcode + if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) { + return {MsgE->getSuperType()->getAs<ObjCObjectType>(), true}; + } + + // 3. Receiver is 'super' from an instance method (a.k.a 'super' is an + // instance of a super class). + // \code + // [super instanceMethod]; + // \encode + if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperInstance) { + if (const auto *ObjTy = + MsgE->getSuperType()->getAs<ObjCObjectPointerType>()) + return {ObjTy->getObjectType(), true}; + } + + const Expr *RecE = MsgE->getInstanceReceiver(); + + if (!RecE) + return {}; + + // Check if the receiver is 'self'. + if (const DeclRefExpr *DRE = + dyn_cast<DeclRefExpr>(RecE->IgnoreParenImpCasts())) { + const StackFrameContext *SFCtx = C.getStackFrame(); + if (DRE->getDecl() == SFCtx->getSelfDecl()) { + // Return type of the enclosing class declaration. + if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(SFCtx->getDecl())) + if (const ObjCObjectType *ObjTy = dyn_cast<ObjCObjectType>( + MD->getClassInterface()->getTypeForDecl())) + return {ObjTy}; + } + } + + // Otherwise, let's try to get type information from our estimations of + // runtime types. + QualType InferredType; + SVal ReceiverSVal = C.getSVal(RecE); + ProgramStateRef State = C.getState(); + + if (const MemRegion *ReceiverRegion = ReceiverSVal.getAsRegion()) { + if (DynamicTypeInfo DTI = getDynamicTypeInfo(State, ReceiverRegion)) { + InferredType = DTI.getType().getCanonicalType(); + } + } + + if (SymbolRef ReceiverSymbol = ReceiverSVal.getAsSymbol()) { + if (InferredType.isNull()) { + InferredType = ReceiverSymbol->getType(); + } + + // If receiver is a Class object, we might have some info on what + // type is contained in there. + if (isObjCClassType(InferredType)) { + if (DynamicTypeInfo DTI = + getClassObjectDynamicTypeInfo(State, ReceiverSymbol)) { + // Types in Class objects can be ONLY Objective-C types + return {cast<ObjCObjectType>(DTI.getType())}; + } + } + } + + if (InferredType.isNull()) { + return {}; + } + + if (const auto *ReceiverInferredType = + dyn_cast<ObjCObjectPointerType>(InferredType)) { + return {ReceiverInferredType->getObjectType()}; + } + + return {}; +} } // end anonymous namespace void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, @@ -210,11 +313,19 @@ case OMF_new: { // Get the type of object that will get created. const ObjCMessageExpr *MsgE = Msg->getOriginExpr(); - const ObjCObjectType *ObjTy = getObjectTypeForAllocAndNew(MsgE, C); + RuntimeType ObjTy = inferReceiverType(MsgE, C); + if (!ObjTy) return; + QualType DynResTy = - C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0)); + C.getASTContext().getObjCObjectPointerType(QualType(ObjTy.Type, 0)); + // We used to assume that whatever type we got from inferring the + // type is actually precise (which is not exactly correct), and some + // the features of the analyzer depend directly on this. + // + // TODO: We should rework this part so we can use imprecise information + // and get all the expected results. C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false)); break; } @@ -303,40 +414,6 @@ /*CanBeSubClassed=*/false)); } -const ObjCObjectType * -DynamicTypePropagation::getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, - CheckerContext &C) const { - if (MsgE->getReceiverKind() == ObjCMessageExpr::Class) { - if (const ObjCObjectType *ObjTy - = MsgE->getClassReceiver()->getAs<ObjCObjectType>()) - return ObjTy; - } - - if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) { - if (const ObjCObjectType *ObjTy - = MsgE->getSuperType()->getAs<ObjCObjectType>()) - return ObjTy; - } - - const Expr *RecE = MsgE->getInstanceReceiver(); - if (!RecE) - return nullptr; - - RecE= RecE->IgnoreParenImpCasts(); - if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(RecE)) { - const StackFrameContext *SFCtx = C.getStackFrame(); - // Are we calling [self alloc]? If this is self, get the type of the - // enclosing ObjC class. - if (DRE->getDecl() == SFCtx->getSelfDecl()) { - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(SFCtx->getDecl())) - if (const ObjCObjectType *ObjTy = - dyn_cast<ObjCObjectType>(MD->getClassInterface()->getTypeForDecl())) - return ObjTy; - } - } - return nullptr; -} - // Return a better dynamic type if one can be derived from the cast. // Compare the current dynamic type of the region and the new type to which we // are casting. If the new type is lower in the inheritance hierarchy, pick it. @@ -821,25 +898,56 @@ Selector Sel = MessageExpr->getSelector(); ProgramStateRef State = C.getState(); - // Inference for class variables. - // We are only interested in cases where the class method is invoked on a - // class. This method is provided by the runtime and available on all classes. - if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && - Sel.getAsString() == "class") { - QualType ReceiverType = MessageExpr->getClassReceiver(); - const auto *ReceiverClassType = ReceiverType->castAs<ObjCObjectType>(); - if (!ReceiverClassType->isSpecialized()) - return; - QualType ReceiverClassPointerType = - C.getASTContext().getObjCObjectPointerType( - QualType(ReceiverClassType, 0)); - const auto *InferredType = - ReceiverClassPointerType->castAs<ObjCObjectPointerType>(); + // Here we try to propagate information on Class objects. + if (Sel.getAsString() == "class") { + // We try to figure out the type from the receiver of the 'class' message. + if (RuntimeType ReceiverRuntimeType = inferReceiverType(MessageExpr, C)) { + + ReceiverRuntimeType.Type->getSuperClassType(); + QualType ReceiverClassType(ReceiverRuntimeType.Type, 0); + + // We want to consider only precise information on generics. + if (ReceiverRuntimeType.Type->isSpecialized() && + ReceiverRuntimeType.Precise) { + QualType ReceiverClassPointerType = + C.getASTContext().getObjCObjectPointerType(ReceiverClassType); + const auto *InferredType = + ReceiverClassPointerType->castAs<ObjCObjectPointerType>(); + State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); + } + + // Constrain the resulting class object to the inferred type. + State = setClassObjectDynamicTypeInfo(State, RetSym, ReceiverClassType, + !ReceiverRuntimeType.Precise); - State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); - C.addTransition(State); - return; + C.addTransition(State); + return; + } + } + + if (Sel.getAsString() == "superclass") { + // We try to figure out the type from the receiver of the 'superclass' + // message. + if (RuntimeType ReceiverRuntimeType = inferReceiverType(MessageExpr, C)) { + + // Result type would be a super class of the receiver's type. + QualType ReceiversSuperClass = + ReceiverRuntimeType.Type->getSuperClassType(); + + // Check if it really had super class. + // + // TODO: we can probably pay closer attention to cases when the class + // object can be 'nil' as the result of such message. + if (!ReceiversSuperClass.isNull()) { + // Constrain the resulting class object to the inferred type. + State = setClassObjectDynamicTypeInfo( + State, RetSym, ReceiversSuperClass, !ReceiverRuntimeType.Precise); + + C.addTransition(State); + } + return; + } } // Tracking for return types. Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h @@ -33,6 +33,8 @@ /// Returns the currently inferred upper bound on the runtime type. QualType getType() const { return DynTy; } + operator bool() const { return isValid(); } + bool operator==(const DynamicTypeInfo &RHS) const { return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass; } Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h @@ -36,6 +36,10 @@ const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR); +/// Get dynamic type information stored in a class object represented by \p Sym. +DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym); + /// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR. const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, const MemRegion *MR, @@ -50,6 +54,16 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, QualType NewTy, bool CanBeSubClassed = true); +/// Set constraint on a type contained in a class object; return the new state. +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, + DynamicTypeInfo NewTy); + +/// Set constraint on a type contained in a class object; return the new state. +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, QualType NewTy, + bool CanBeSubClassed = true); + /// Set dynamic type and cast information of the region; return the new state. ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, const MemRegion *MR,
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits