Skip to content
This repository was archived by the owner on Nov 1, 2021. It is now read-only.

Commit f0708ca

Browse files
committed
[analyzer] Model getters of known-@Synthesized Objective-C properties.
...by synthesizing their body to be "return self->_prop;", with an extra nudge to RetainCountChecker to still treat the value as +0 if we have no other information. This doesn't handle weak properties, but that's mostly correct anyway, since they can go to nil at any time. This also doesn't apply to properties whose implementations we can't see, since they may not be backed by an ivar at all. And finally, this doesn't handle properties of C++ class type, because we can't invoke the copy constructor. (Sema has actually done this work already, but the AST it synthesizes is one the analyzer doesn't quite handle -- it has an rvalue DeclRefExpr.) Modeling setters is likely to be more difficult (since it requires handling strong/copy), but not impossible. <rdar://problem/11956898> git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@198953 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 33ab4d0 commit f0708ca

File tree

7 files changed

+276
-8
lines changed

7 files changed

+276
-8
lines changed

Diff for: lib/Analysis/AnalysisDeclContext.cpp

+12-5
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,21 @@ Stmt *AnalysisDeclContext::getBody(bool &IsAutosynthesized) const {
9494
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
9595
Stmt *Body = FD->getBody();
9696
if (!Body && Manager && Manager->synthesizeBodies()) {
97-
IsAutosynthesized = true;
98-
return getBodyFarm(getASTContext()).getBody(FD);
97+
Body = getBodyFarm(getASTContext()).getBody(FD);
98+
if (Body)
99+
IsAutosynthesized = true;
99100
}
100101
return Body;
101102
}
102-
else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D))
103-
return MD->getBody();
104-
else if (const BlockDecl *BD = dyn_cast<BlockDecl>(D))
103+
else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
104+
Stmt *Body = MD->getBody();
105+
if (!Body && Manager && Manager->synthesizeBodies()) {
106+
Body = getBodyFarm(getASTContext()).getBody(MD);
107+
if (Body)
108+
IsAutosynthesized = true;
109+
}
110+
return Body;
111+
} else if (const BlockDecl *BD = dyn_cast<BlockDecl>(D))
105112
return BD->getBody();
106113
else if (const FunctionTemplateDecl *FunTmpl
107114
= dyn_cast_or_null<FunctionTemplateDecl>(D))

Diff for: lib/Analysis/BodyFarm.cpp

+70
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class ASTMaker {
7474

7575
/// Create an Objective-C bool literal.
7676
ObjCBoolLiteralExpr *makeObjCBool(bool Val);
77+
78+
/// Create an Objective-C ivar reference.
79+
ObjCIvarRefExpr *makeObjCIvarRef(const Expr *Base, const ObjCIvarDecl *IVar);
7780

7881
/// Create a Return statement.
7982
ReturnStmt *makeReturn(const Expr *RetVal);
@@ -147,6 +150,15 @@ ObjCBoolLiteralExpr *ASTMaker::makeObjCBool(bool Val) {
147150
return new (C) ObjCBoolLiteralExpr(Val, Ty, SourceLocation());
148151
}
149152

153+
ObjCIvarRefExpr *ASTMaker::makeObjCIvarRef(const Expr *Base,
154+
const ObjCIvarDecl *IVar) {
155+
return new (C) ObjCIvarRefExpr(const_cast<ObjCIvarDecl*>(IVar),
156+
IVar->getType(), SourceLocation(),
157+
SourceLocation(), const_cast<Expr*>(Base),
158+
/*arrow=*/true, /*free=*/false);
159+
}
160+
161+
150162
ReturnStmt *ASTMaker::makeReturn(const Expr *RetVal) {
151163
return new (C) ReturnStmt(SourceLocation(), const_cast<Expr*>(RetVal), 0);
152164
}
@@ -374,3 +386,61 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) {
374386
return Val.getValue();
375387
}
376388

389+
static Stmt *createObjCPropertyGetter(ASTContext &Ctx,
390+
const ObjCPropertyDecl *Prop) {
391+
const ObjCIvarDecl *IVar = Prop->getPropertyIvarDecl();
392+
if (!IVar)
393+
return 0;
394+
if (Prop->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_weak)
395+
return 0;
396+
if (IVar->getType().getCanonicalType() !=
397+
Prop->getType().getNonReferenceType().getCanonicalType())
398+
return 0;
399+
400+
// C++ records require copy constructors, so we can't just synthesize an AST.
401+
// FIXME: Use ObjCPropertyImplDecl's already-synthesized AST. Currently it's
402+
// not in a form the analyzer can use.
403+
if (Prop->getType()->getAsCXXRecordDecl())
404+
return 0;
405+
406+
ASTMaker M(Ctx);
407+
408+
const VarDecl *selfVar = Prop->getGetterMethodDecl()->getSelfDecl();
409+
410+
Expr *loadedIVar =
411+
M.makeObjCIvarRef(
412+
M.makeLvalueToRvalue(
413+
M.makeDeclRefExpr(selfVar),
414+
selfVar->getType()),
415+
IVar);
416+
417+
if (!Prop->getType()->isReferenceType())
418+
loadedIVar = M.makeLvalueToRvalue(loadedIVar, IVar->getType());
419+
420+
return M.makeReturn(loadedIVar);
421+
}
422+
423+
Stmt *BodyFarm::getBody(const ObjCMethodDecl *D, const ObjCPropertyDecl *Prop) {
424+
if (!D->isPropertyAccessor())
425+
return 0;
426+
427+
D = D->getCanonicalDecl();
428+
429+
Optional<Stmt *> &Val = Bodies[D];
430+
if (Val.hasValue())
431+
return Val.getValue();
432+
Val = 0;
433+
434+
if (!Prop)
435+
Prop = D->findPropertyDecl();
436+
if (!Prop)
437+
return 0;
438+
439+
if (D->param_size() != 0)
440+
return 0;
441+
442+
Val = createObjCPropertyGetter(C, Prop);
443+
444+
return Val.getValue();
445+
}
446+

Diff for: lib/Analysis/BodyFarm.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ namespace clang {
2424
class ASTContext;
2525
class Decl;
2626
class FunctionDecl;
27+
class ObjCMethodDecl;
28+
class ObjCPropertyDecl;
2729
class Stmt;
2830

2931
class BodyFarm {
@@ -32,7 +34,10 @@ class BodyFarm {
3234

3335
/// Factory method for creating bodies for ordinary functions.
3436
Stmt *getBody(const FunctionDecl *D);
35-
37+
38+
/// Factory method for creating bodies for Objective-C properties.
39+
Stmt *getBody(const ObjCMethodDecl *D, const ObjCPropertyDecl *Prop = 0);
40+
3641
private:
3742
typedef llvm::DenseMap<const Decl *, Optional<Stmt *> > BodyMap;
3843

Diff for: lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -2735,6 +2735,16 @@ static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) {
27352735
return RetTy;
27362736
}
27372737

2738+
static bool wasSynthesizedProperty(const ObjCMethodCall *Call,
2739+
ExplodedNode *N) {
2740+
if (!Call || !Call->getDecl()->isPropertyAccessor())
2741+
return false;
2742+
2743+
CallExitEnd PP = N->getLocation().castAs<CallExitEnd>();
2744+
const StackFrameContext *Frame = PP.getCalleeContext();
2745+
return Frame->getAnalysisDeclContext()->isBodyAutosynthesized();
2746+
}
2747+
27382748
// We don't always get the exact modeling of the function with regards to the
27392749
// retain count checker even when the function is inlined. For example, we need
27402750
// to stop tracking the symbols which were marked with StopTrackingHard.
@@ -2769,6 +2779,15 @@ void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
27692779
SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol();
27702780
if (Sym)
27712781
state = removeRefBinding(state, Sym);
2782+
} else if (RE.getKind() == RetEffect::NotOwnedSymbol) {
2783+
if (wasSynthesizedProperty(MsgInvocation, C.getPredecessor())) {
2784+
// Believe the summary if we synthesized the body and the return value is
2785+
// untracked. This handles property getters.
2786+
SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol();
2787+
if (Sym && !getRefBinding(state, Sym))
2788+
state = setRefBinding(state, Sym, RefVal::makeNotOwned(RE.getObjKind(),
2789+
Sym->getType()));
2790+
}
27722791
}
27732792

27742793
C.addTransition(state);

Diff for: lib/StaticAnalyzer/Core/CallEvent.cpp

+9-1
Original file line numberDiff line numberDiff line change
@@ -863,9 +863,17 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const {
863863
Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)];
864864

865865
// Query lookupPrivateMethod() if the cache does not hit.
866-
if (!Val.hasValue())
866+
if (!Val.hasValue()) {
867867
Val = IDecl->lookupPrivateMethod(Sel);
868868

869+
// If the method is a property accessor, we should try to "inline" it
870+
// even if we don't actually have an implementation.
871+
if (!*Val)
872+
if (const ObjCMethodDecl *CompileTimeMD = E->getMethodDecl())
873+
if (CompileTimeMD->isPropertyAccessor())
874+
Val = IDecl->lookupInstanceMethod(Sel);
875+
}
876+
869877
const ObjCMethodDecl *MD = Val.getValue();
870878
if (CanBeSubClassed)
871879
return RuntimeDefinition(MD, Receiver);

Diff for: test/Analysis/properties.m

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
// RUN: %clang_cc1 -analyze -analyzer-checker=core,osx.cocoa.RetainCount -analyzer-store=region -verify -Wno-objc-root-class %s
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=core,osx.cocoa.RetainCount,debug.ExprInspection -analyzer-store=region -verify -Wno-objc-root-class %s
2+
3+
void clang_analyzer_eval(int);
24

35
typedef signed char BOOL;
46
typedef unsigned int NSUInteger;
@@ -14,6 +16,7 @@ -(id)init;
1416
-(id)autorelease;
1517
-(id)copy;
1618
-(id)retain;
19+
-(oneway void)release;
1720
@end
1821
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSCoding>
1922
- (NSUInteger)length;
@@ -166,3 +169,79 @@ -(void)testAnalyzer2 {
166169
@end
167170

168171

172+
//------
173+
// Property accessor synthesis
174+
//------
175+
176+
void testConsistency(Person *p) {
177+
clang_analyzer_eval(p.name == p.name); // expected-warning{{TRUE}}
178+
179+
extern void doSomethingWithPerson(Person *p);
180+
id origName = p.name;
181+
clang_analyzer_eval(p.name == origName); // expected-warning{{TRUE}}
182+
doSomethingWithPerson(p);
183+
clang_analyzer_eval(p.name == origName); // expected-warning{{UNKNOWN}}
184+
}
185+
186+
void testOverrelease(Person *p) {
187+
[p.name release]; // expected-warning{{not owned}}
188+
}
189+
190+
@interface IntWrapper
191+
@property int value;
192+
@end
193+
194+
@implementation IntWrapper
195+
@synthesize value;
196+
@end
197+
198+
void testConsistencyInt(IntWrapper *w) {
199+
clang_analyzer_eval(w.value == w.value); // expected-warning{{TRUE}}
200+
201+
int origValue = w.value;
202+
if (origValue != 42)
203+
return;
204+
205+
clang_analyzer_eval(w.value == 42); // expected-warning{{TRUE}}
206+
}
207+
208+
void testConsistencyInt2(IntWrapper *w) {
209+
if (w.value != 42)
210+
return;
211+
212+
clang_analyzer_eval(w.value == 42); // expected-warning{{TRUE}}
213+
}
214+
215+
typedef struct {
216+
int value;
217+
} IntWrapperStruct;
218+
219+
@interface StructWrapper
220+
@property IntWrapperStruct inner;
221+
@end
222+
223+
@implementation StructWrapper
224+
@synthesize inner;
225+
@end
226+
227+
void testConsistencyStruct(StructWrapper *w) {
228+
clang_analyzer_eval(w.inner.value == w.inner.value); // expected-warning{{TRUE}}
229+
230+
int origValue = w.inner.value;
231+
if (origValue != 42)
232+
return;
233+
234+
clang_analyzer_eval(w.inner.value == 42); // expected-warning{{TRUE}}
235+
}
236+
237+
238+
@interface OpaqueIntWrapper
239+
@property int value;
240+
@end
241+
242+
// For now, don't assume a property is implemented using an ivar unless we can
243+
// actually see that it is.
244+
void testOpaqueConsistency(OpaqueIntWrapper *w) {
245+
clang_analyzer_eval(w.value == w.value); // expected-warning{{UNKNOWN}}
246+
}
247+

Diff for: test/Analysis/properties.mm

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=core,osx.cocoa.RetainCount,debug.ExprInspection -analyzer-store=region -verify -Wno-objc-root-class %s
2+
3+
void clang_analyzer_eval(bool);
4+
void clang_analyzer_checkInlined(bool);
5+
6+
@interface IntWrapper
7+
@property (readonly) int &value;
8+
@end
9+
10+
@implementation IntWrapper
11+
@synthesize value;
12+
@end
13+
14+
void testReferenceConsistency(IntWrapper *w) {
15+
clang_analyzer_eval(w.value == w.value); // expected-warning{{TRUE}}
16+
clang_analyzer_eval(&w.value == &w.value); // expected-warning{{TRUE}}
17+
18+
if (w.value != 42)
19+
return;
20+
21+
clang_analyzer_eval(w.value == 42); // expected-warning{{TRUE}}
22+
}
23+
24+
void testReferenceAssignment(IntWrapper *w) {
25+
w.value = 42;
26+
clang_analyzer_eval(w.value == 42); // expected-warning{{TRUE}}
27+
}
28+
29+
30+
// FIXME: Handle C++ structs, which need to go through the copy constructor.
31+
32+
struct IntWrapperStruct {
33+
int value;
34+
};
35+
36+
@interface StructWrapper
37+
@property IntWrapperStruct inner;
38+
@end
39+
40+
@implementation StructWrapper
41+
@synthesize inner;
42+
@end
43+
44+
void testConsistencyStruct(StructWrapper *w) {
45+
clang_analyzer_eval(w.inner.value == w.inner.value); // expected-warning{{UNKNOWN}}
46+
47+
int origValue = w.inner.value;
48+
if (origValue != 42)
49+
return;
50+
51+
clang_analyzer_eval(w.inner.value == 42); // expected-warning{{UNKNOWN}}
52+
}
53+
54+
55+
class CustomCopy {
56+
public:
57+
CustomCopy() : value(0) {}
58+
CustomCopy(const CustomCopy &other) {
59+
clang_analyzer_checkInlined(false);
60+
}
61+
int value;
62+
};
63+
64+
@interface CustomCopyWrapper
65+
@property CustomCopy inner;
66+
@end
67+
68+
@implementation CustomCopyWrapper
69+
@synthesize inner;
70+
@end
71+
72+
void testConsistencyCustomCopy(CustomCopyWrapper *w) {
73+
clang_analyzer_eval(w.inner.value == w.inner.value); // expected-warning{{UNKNOWN}}
74+
75+
int origValue = w.inner.value;
76+
if (origValue != 42)
77+
return;
78+
79+
clang_analyzer_eval(w.inner.value == 42); // expected-warning{{UNKNOWN}}
80+
}

0 commit comments

Comments
 (0)