Skip to content

Commit e1f3062

Browse files
committed
[CFG] [analyzer] Add construction contexts for returning C++ objects in ObjC++.
Like any normal funciton, Objective-C message can return a C++ object in Objective-C++. Such object would require a construction context. This patch, therefore, is an extension of r327343 onto Objective-C++. Differential Revision: https://reviews.llvm.org/D48608 llvm-svn: 338426
1 parent bd880fe commit e1f3062

File tree

4 files changed

+148
-39
lines changed

4 files changed

+148
-39
lines changed

clang/include/clang/Analysis/CFG.h

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
#ifndef LLVM_CLANG_ANALYSIS_CFG_H
1616
#define LLVM_CLANG_ANALYSIS_CFG_H
1717

18-
#include "clang/AST/ExprCXX.h"
1918
#include "clang/Analysis/Support/BumpVector.h"
2019
#include "clang/Analysis/ConstructionContext.h"
20+
#include "clang/AST/ExprCXX.h"
21+
#include "clang/AST/ExprObjC.h"
2122
#include "clang/Basic/LLVM.h"
2223
#include "llvm/ADT/DenseMap.h"
2324
#include "llvm/ADT/GraphTraits.h"
@@ -179,15 +180,18 @@ class CFGCXXRecordTypedCall : public CFGStmt {
179180
public:
180181
/// Returns true when call expression \p CE needs to be represented
181182
/// by CFGCXXRecordTypedCall, as opposed to a regular CFGStmt.
182-
static bool isCXXRecordTypedCall(CallExpr *CE, const ASTContext &ACtx) {
183-
return CE->getCallReturnType(ACtx).getCanonicalType()->getAsCXXRecordDecl();
184-
}
185-
186-
explicit CFGCXXRecordTypedCall(CallExpr *CE, const ConstructionContext *C)
187-
: CFGStmt(CE, CXXRecordTypedCall) {
188-
// FIXME: This is not protected against squeezing a non-record-typed-call
189-
// into the constructor. An assertion would require passing an ASTContext
190-
// which would mean paying for something we don't use.
183+
static bool isCXXRecordTypedCall(Expr *E) {
184+
assert(isa<CallExpr>(E) || isa<ObjCMessageExpr>(E));
185+
// There is no such thing as reference-type expression. If the function
186+
// returns a reference, it'll return the respective lvalue or xvalue
187+
// instead, and we're only interested in objects.
188+
return !E->isGLValue() &&
189+
E->getType().getCanonicalType()->getAsCXXRecordDecl();
190+
}
191+
192+
explicit CFGCXXRecordTypedCall(Expr *E, const ConstructionContext *C)
193+
: CFGStmt(E, CXXRecordTypedCall) {
194+
assert(isCXXRecordTypedCall(E));
191195
assert(C && (isa<TemporaryObjectConstructionContext>(C) ||
192196
// These are possible in C++17 due to mandatory copy elision.
193197
isa<ReturnedValueConstructionContext>(C) ||
@@ -874,10 +878,10 @@ class CFGBlock {
874878
Elements.push_back(CFGConstructor(CE, CC), C);
875879
}
876880

877-
void appendCXXRecordTypedCall(CallExpr *CE,
881+
void appendCXXRecordTypedCall(Expr *E,
878882
const ConstructionContext *CC,
879883
BumpVectorContext &C) {
880-
Elements.push_back(CFGCXXRecordTypedCall(CE, CC), C);
884+
Elements.push_back(CFGCXXRecordTypedCall(E, CC), C);
881885
}
882886

883887
void appendInitializer(CXXCtorInitializer *initializer,

clang/lib/Analysis/CFG.cpp

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,19 @@ class CFGBuilder {
743743

744744
void addLocalScopeAndDtors(Stmt *S);
745745

746+
const ConstructionContext *retrieveAndCleanupConstructionContext(Expr *E) {
747+
if (!BuildOpts.AddRichCXXConstructors)
748+
return nullptr;
749+
750+
const ConstructionContextLayer *Layer = ConstructionContextMap.lookup(E);
751+
if (!Layer)
752+
return nullptr;
753+
754+
cleanupConstructionContext(E);
755+
return ConstructionContext::createFromLayers(cfg->getBumpVectorContext(),
756+
Layer);
757+
}
758+
746759
// Interface to CFGBlock - adding CFGElements.
747760

748761
void appendStmt(CFGBlock *B, const Stmt *S) {
@@ -755,16 +768,10 @@ class CFGBuilder {
755768
}
756769

757770
void appendConstructor(CFGBlock *B, CXXConstructExpr *CE) {
758-
if (BuildOpts.AddRichCXXConstructors) {
759-
if (const ConstructionContextLayer *Layer =
760-
ConstructionContextMap.lookup(CE)) {
761-
cleanupConstructionContext(CE);
762-
if (const auto *CC = ConstructionContext::createFromLayers(
763-
cfg->getBumpVectorContext(), Layer)) {
764-
B->appendConstructor(CE, CC, cfg->getBumpVectorContext());
765-
return;
766-
}
767-
}
771+
if (const ConstructionContext *CC =
772+
retrieveAndCleanupConstructionContext(CE)) {
773+
B->appendConstructor(CE, CC, cfg->getBumpVectorContext());
774+
return;
768775
}
769776

770777
// No valid construction context found. Fall back to statement.
@@ -775,18 +782,10 @@ class CFGBuilder {
775782
if (alwaysAdd(CE) && cachedEntry)
776783
cachedEntry->second = B;
777784

778-
if (BuildOpts.AddRichCXXConstructors) {
779-
if (CFGCXXRecordTypedCall::isCXXRecordTypedCall(CE, *Context)) {
780-
if (const ConstructionContextLayer *Layer =
781-
ConstructionContextMap.lookup(CE)) {
782-
cleanupConstructionContext(CE);
783-
if (const auto *CC = ConstructionContext::createFromLayers(
784-
cfg->getBumpVectorContext(), Layer)) {
785-
B->appendCXXRecordTypedCall(CE, CC, cfg->getBumpVectorContext());
786-
return;
787-
}
788-
}
789-
}
785+
if (const ConstructionContext *CC =
786+
retrieveAndCleanupConstructionContext(CE)) {
787+
B->appendCXXRecordTypedCall(CE, CC, cfg->getBumpVectorContext());
788+
return;
790789
}
791790

792791
// No valid construction context found. Fall back to statement.
@@ -809,6 +808,20 @@ class CFGBuilder {
809808
B->appendMemberDtor(FD, cfg->getBumpVectorContext());
810809
}
811810

811+
void appendObjCMessage(CFGBlock *B, ObjCMessageExpr *ME) {
812+
if (alwaysAdd(ME) && cachedEntry)
813+
cachedEntry->second = B;
814+
815+
if (const ConstructionContext *CC =
816+
retrieveAndCleanupConstructionContext(ME)) {
817+
B->appendCXXRecordTypedCall(ME, CC, cfg->getBumpVectorContext());
818+
return;
819+
}
820+
821+
B->appendStmt(const_cast<ObjCMessageExpr *>(ME),
822+
cfg->getBumpVectorContext());
823+
}
824+
812825
void appendTemporaryDtor(CFGBlock *B, CXXBindTemporaryExpr *E) {
813826
B->appendTemporaryDtor(E, cfg->getBumpVectorContext());
814827
}
@@ -1254,6 +1267,8 @@ static const VariableArrayType *FindVA(const Type *t) {
12541267

12551268
void CFGBuilder::consumeConstructionContext(
12561269
const ConstructionContextLayer *Layer, Expr *E) {
1270+
assert((isa<CXXConstructExpr>(E) || isa<CallExpr>(E) ||
1271+
isa<ObjCMessageExpr>(E)) && "Expression cannot construct an object!");
12571272
if (const ConstructionContextLayer *PreviouslyStoredLayer =
12581273
ConstructionContextMap.lookup(E)) {
12591274
(void)PreviouslyStoredLayer;
@@ -1297,10 +1312,11 @@ void CFGBuilder::findConstructionContexts(
12971312
case Stmt::CallExprClass:
12981313
case Stmt::CXXMemberCallExprClass:
12991314
case Stmt::CXXOperatorCallExprClass:
1300-
case Stmt::UserDefinedLiteralClass: {
1301-
auto *CE = cast<CallExpr>(Child);
1302-
if (CFGCXXRecordTypedCall::isCXXRecordTypedCall(CE, *Context))
1303-
consumeConstructionContext(Layer, CE);
1315+
case Stmt::UserDefinedLiteralClass:
1316+
case Stmt::ObjCMessageExprClass: {
1317+
auto *E = cast<Expr>(Child);
1318+
if (CFGCXXRecordTypedCall::isCXXRecordTypedCall(E))
1319+
consumeConstructionContext(Layer, E);
13041320
break;
13051321
}
13061322
case Stmt::ExprWithCleanupsClass: {
@@ -3605,7 +3621,7 @@ CFGBlock *CFGBuilder::VisitObjCMessageExpr(ObjCMessageExpr *ME,
36053621
findConstructionContextsForArguments(ME);
36063622

36073623
autoCreateBlock();
3608-
appendStmt(Block, ME);
3624+
appendObjCMessage(Block, ME);
36093625

36103626
return VisitChildren(ME);
36113627
}

clang/test/Analysis/cfg-rich-constructors.mm

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
@interface E {}
1717
-(void) foo: (D) d;
18+
-(D) bar;
1819
@end
1920

2021
// FIXME: Find construction context for the argument.
@@ -39,3 +40,27 @@ -(void) foo: (D) d;
3940
void passArgumentIntoMessage(E *e) {
4041
[e foo: D()];
4142
}
43+
44+
// CHECK: void returnObjectFromMessage(E *e)
45+
// CHECK: [B1]
46+
// CHECK-NEXT: 1: e
47+
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, LValueToRValue, E *)
48+
// Double brackets trigger FileCheck variables, escape.
49+
// CXX11-ELIDE-NEXT: 3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.4], [B1.6], [B1.7])
50+
// CXX11-NOELIDE-NEXT: 3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.4], [B1.6])
51+
// CXX11-NEXT: 4: [B1.3] (BindTemporary)
52+
// CXX11-NEXT: 5: [B1.4] (ImplicitCastExpr, NoOp, const class D)
53+
// CXX11-NEXT: 6: [B1.5]
54+
// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.8], class D)
55+
// CXX11-NEXT: 8: D d = [e bar];
56+
// CXX11-NEXT: 9: ~D() (Temporary object destructor)
57+
// CXX11-NEXT: 10: [B1.8].~D() (Implicit destructor)
58+
// Double brackets trigger FileCheck variables, escape.
59+
// CXX17-NEXT: 3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4])
60+
// CXX17-NEXT: 4: [B1.3] (BindTemporary)
61+
// CXX17-NEXT: 5: D d = [e bar];
62+
// CXX17-NEXT: 6: ~D() (Temporary object destructor)
63+
// CXX17-NEXT: 7: [B1.5].~D() (Implicit destructor)
64+
void returnObjectFromMessage(E *e) {
65+
D d = [e bar];
66+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -verify %s
2+
// RUN: %clang_analyze_cc1 -Wno-unused -std=c++17 -analyzer-checker=core,debug.ExprInspection -verify %s
3+
// RUN: %clang_analyze_cc1 -Wno-unused -std=c++11 -analyzer-checker=core,debug.ExprInspection -DMOVES -verify %s
4+
// RUN: %clang_analyze_cc1 -Wno-unused -std=c++17 -analyzer-checker=core,debug.ExprInspection -DMOVES -verify %s
5+
6+
void clang_analyzer_eval(bool);
7+
void clang_analyzer_checkInlined(bool);
8+
9+
template <typename T> struct AddressVector {
10+
T *buf[10];
11+
int len;
12+
13+
AddressVector() : len(0) {}
14+
15+
void push(T *t) {
16+
buf[len] = t;
17+
++len;
18+
}
19+
};
20+
21+
class C {
22+
AddressVector<C> &v;
23+
24+
public:
25+
C(AddressVector<C> &v) : v(v) { v.push(this); }
26+
~C() { v.push(this); }
27+
28+
#ifdef MOVES
29+
C(C &&c) : v(c.v) { v.push(this); }
30+
#endif
31+
32+
// Note how return-statements prefer move-constructors when available.
33+
C(const C &c) : v(c.v) {
34+
#ifdef MOVES
35+
clang_analyzer_checkInlined(false); // no-warning
36+
#else
37+
v.push(this);
38+
#endif
39+
} // no-warning
40+
};
41+
42+
@interface NSObject {}
43+
@end;
44+
@interface Foo: NSObject {}
45+
-(C) make: (AddressVector<C> &)v;
46+
@end
47+
48+
@implementation Foo
49+
-(C) make: (AddressVector<C> &)v {
50+
return C(v);
51+
}
52+
@end
53+
54+
void testReturnByValueFromMessage(Foo *foo) {
55+
AddressVector<C> v;
56+
{
57+
const C &c = [foo make: v];
58+
}
59+
// 0. Construct the return value of -make (copy/move elided) and
60+
// lifetime-extend it directly via reference 'c',
61+
// 1. Destroy the temporary lifetime-extended by 'c'.
62+
clang_analyzer_eval(v.len == 2); // expected-warning{{TRUE}}
63+
clang_analyzer_eval(v.buf[0] == v.buf[1]); // expected-warning{{TRUE}}
64+
}

0 commit comments

Comments
 (0)