Skip to content

Commit

Permalink
[CFG] [analyzer] Add stubs for constructor and message argument const…
Browse files Browse the repository at this point in the history
…ructors.

CFG now correctly identifies construction context for temporaries constructed
for the purpose of passing into a function as an argument.

Such context is still not fully implemented because the information it provides
is not rich enough: it doens't contain information about argument index.
It will be addresssed later.

This patch is an extension of r330377 to C++ construct-expressions and
Objective-C message expressions which aren't call-expressions but require
similar handling. C++ new-expressions with placement arguments still remain to
be handled.

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

llvm-svn: 338425
  • Loading branch information
haoNoQ committed Jul 31, 2018
1 parent 959fe03 commit bd880fe
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 12 deletions.
47 changes: 41 additions & 6 deletions clang/lib/Analysis/CFG.cpp
Expand Up @@ -569,6 +569,7 @@ class CFGBuilder {
CFGBlock *VisitObjCAtTryStmt(ObjCAtTryStmt *S);
CFGBlock *VisitObjCAutoreleasePoolStmt(ObjCAutoreleasePoolStmt *S);
CFGBlock *VisitObjCForCollectionStmt(ObjCForCollectionStmt *S);
CFGBlock *VisitObjCMessageExpr(ObjCMessageExpr *E, AddStmtChoice asc);
CFGBlock *VisitPseudoObjectExpr(PseudoObjectExpr *E);
CFGBlock *VisitReturnStmt(ReturnStmt *R);
CFGBlock *VisitSEHExceptStmt(SEHExceptStmt *S);
Expand Down Expand Up @@ -683,6 +684,27 @@ class CFGBuilder {
void findConstructionContexts(const ConstructionContextLayer *Layer,
Stmt *Child);

// Scan all arguments of a call expression for a construction context.
// These sorts of call expressions don't have a common superclass,
// hence strict duck-typing.
template <typename CallLikeExpr,
typename = typename std::enable_if<
std::is_same<CallLikeExpr, CallExpr>::value ||
std::is_same<CallLikeExpr, CXXConstructExpr>::value ||
std::is_same<CallLikeExpr, ObjCMessageExpr>::value>>
void findConstructionContextsForArguments(CallLikeExpr *E) {
// A stub for the code that'll eventually be used for finding construction
// contexts for constructors of C++ object-type arguments passed into
// call-like expression E.
// FIXME: Once actually implemented, this construction context layer should
// include the index of the argument as well.
for (auto Arg : E->arguments())
if (Arg->getType()->getAsCXXRecordDecl() && !Arg->isGLValue())
findConstructionContexts(
ConstructionContextLayer::create(cfg->getBumpVectorContext(), E),
Arg);
}

// Unset the construction context after consuming it. This is done immediately
// after adding the CFGConstructor or CFGCXXRecordTypedCall element, so
// there's no need to do this manually in every Visit... function.
Expand Down Expand Up @@ -2101,6 +2123,9 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) {
case Stmt::ObjCForCollectionStmtClass:
return VisitObjCForCollectionStmt(cast<ObjCForCollectionStmt>(S));

case Stmt::ObjCMessageExprClass:
return VisitObjCMessageExpr(cast<ObjCMessageExpr>(S), asc);

case Stmt::OpaqueValueExprClass:
return Block;

Expand Down Expand Up @@ -2383,12 +2408,7 @@ CFGBlock *CFGBuilder::VisitCallExpr(CallExpr *C, AddStmtChoice asc) {
if (!boundType.isNull()) calleeType = boundType;
}

// FIXME: Once actually implemented, this construction context layer should
// include the number of the argument as well.
for (auto Arg: C->arguments()) {
findConstructionContexts(
ConstructionContextLayer::create(cfg->getBumpVectorContext(), C), Arg);
}
findConstructionContextsForArguments(C);

// If this is a call to a no-return function, this stops the block here.
bool NoReturn = getFunctionExtInfo(*calleeType).getNoReturn();
Expand Down Expand Up @@ -3580,6 +3600,16 @@ CFGBlock *CFGBuilder::VisitObjCAtThrowStmt(ObjCAtThrowStmt *S) {
return VisitStmt(S, AddStmtChoice::AlwaysAdd);
}

CFGBlock *CFGBuilder::VisitObjCMessageExpr(ObjCMessageExpr *ME,
AddStmtChoice asc) {
findConstructionContextsForArguments(ME);

autoCreateBlock();
appendStmt(Block, ME);

return VisitChildren(ME);
}

CFGBlock *CFGBuilder::VisitCXXThrowExpr(CXXThrowExpr *T) {
// If we were in the middle of a block we stop processing that block.
if (badCFG)
Expand Down Expand Up @@ -4244,6 +4274,11 @@ CFGBlock *CFGBuilder::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E,

CFGBlock *CFGBuilder::VisitCXXConstructExpr(CXXConstructExpr *C,
AddStmtChoice asc) {
// If the constructor takes objects as arguments by value, we need to properly
// construct these objects. Construction contexts we find here aren't for the
// constructor C, they're for its arguments only.
findConstructionContextsForArguments(C);

autoCreateBlock();
appendConstructor(Block, C);

Expand Down
9 changes: 7 additions & 2 deletions clang/lib/Analysis/ConstructionContext.cpp
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

#include "clang/Analysis/ConstructionContext.h"
#include "clang/AST/ExprObjC.h"

using namespace clang;

Expand Down Expand Up @@ -111,7 +112,9 @@ const ConstructionContext *ConstructionContext::createFromLayers(
assert(ParentLayer->isLast());

// This is a constructor into a function argument. Not implemented yet.
if (isa<CallExpr>(ParentLayer->getTriggerStmt()))
if (isa<CallExpr>(ParentLayer->getTriggerStmt()) ||
isa<CXXConstructExpr>(ParentLayer->getTriggerStmt()) ||
isa<ObjCMessageExpr>(ParentLayer->getTriggerStmt()))
return nullptr;
// This is C++17 copy-elided construction into return statement.
if (auto *RS = dyn_cast<ReturnStmt>(ParentLayer->getTriggerStmt())) {
Expand Down Expand Up @@ -173,7 +176,9 @@ const ConstructionContext *ConstructionContext::createFromLayers(
return create<SimpleReturnedValueConstructionContext>(C, RS);
}
// This is a constructor into a function argument. Not implemented yet.
if (isa<CallExpr>(TopLayer->getTriggerStmt()))
if (isa<CallExpr>(TopLayer->getTriggerStmt()) ||
isa<CXXConstructExpr>(TopLayer->getTriggerStmt()) ||
isa<ObjCMessageExpr>(TopLayer->getTriggerStmt()))
return nullptr;
llvm_unreachable("Unexpected construction context with statement!");
} else if (const CXXCtorInitializer *I = TopLayer->getTriggerInit()) {
Expand Down
31 changes: 31 additions & 0 deletions clang/test/Analysis/cfg-rich-constructors.cpp
Expand Up @@ -817,6 +817,11 @@ class D {
~D();
};

class E {
public:
E(D d);
};

void useC(C c);
void useCByReference(const C &c);
void useD(D d);
Expand Down Expand Up @@ -880,6 +885,32 @@ void passArgumentWithDestructor() {
void passArgumentWithDestructorByReference() {
useDByReference(D());
}

// FIXME: Find construction context for the argument.
// CHECK: void passArgumentIntoAnotherConstructor()
// CXX11: 1: argument_constructors::D() (CXXConstructExpr, [B1.2], [B1.4], class argument_constructors::D)
// CXX11-NEXT: 2: [B1.1] (BindTemporary)
// CXX11-NEXT: 3: [B1.2] (ImplicitCastExpr, NoOp, const class argument_constructors::D)
// CXX11-NEXT: 4: [B1.3]
// CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, class argument_constructors::D)
// CXX11-NEXT: 6: [B1.5] (BindTemporary)
// CXX11-ELIDE-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.9], [B1.10], class argument_constructors::E)
// CXX11-NOELIDE-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.9], class argument_constructors::E)
// CXX11-NEXT: 8: argument_constructors::E([B1.7]) (CXXFunctionalCastExpr, ConstructorConversion, class argument_constructors::E)
// CXX11-NEXT: 9: [B1.8]
// CXX11-NEXT: 10: [B1.9] (CXXConstructExpr, [B1.11], class argument_constructors::E)
// CXX11-NEXT: 11: argument_constructors::E e = argument_constructors::E(argument_constructors::D());
// CXX11-NEXT: 12: ~argument_constructors::D() (Temporary object destructor)
// CXX11-NEXT: 13: ~argument_constructors::D() (Temporary object destructor)
// CXX17: 1: argument_constructors::D() (CXXConstructExpr, class argument_constructors::D)
// CXX17-NEXT: 2: [B1.1] (BindTemporary)
// CXX17-NEXT: 3: [B1.2] (CXXConstructExpr, [B1.5], class argument_constructors::E)
// CXX17-NEXT: 4: argument_constructors::E([B1.3]) (CXXFunctionalCastExpr, ConstructorConversion, class argument_constructors::E)
// CXX17-NEXT: 5: argument_constructors::E e = argument_constructors::E(argument_constructors::D());
// CXX17-NEXT: 6: ~argument_constructors::D() (Temporary object destructor)
void passArgumentIntoAnotherConstructor() {
E e = E(D());
}
} // end namespace argument_constructors

namespace copy_elision_with_extra_arguments {
Expand Down
41 changes: 41 additions & 0 deletions clang/test/Analysis/cfg-rich-constructors.mm
@@ -0,0 +1,41 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c++11 -w %s > %t 2>&1
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,CXX11,ELIDE,CXX11-ELIDE %s
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c++17 -w %s > %t 2>&1
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,CXX17,ELIDE,CXX17-ELIDE %s
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c++11 -w -analyzer-config elide-constructors=false %s > %t 2>&1
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,CXX11,NOELIDE,CXX11-NOELIDE %s
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c++17 -w -analyzer-config elide-constructors=false %s > %t 2>&1
// RUN: FileCheck --input-file=%t -check-prefixes=CHECK,CXX17,NOELIDE,CXX17-NOELIDE %s

class D {
public:
D();
~D();
};

@interface E {}
-(void) foo: (D) d;
@end

// FIXME: Find construction context for the argument.
// CHECK: void passArgumentIntoMessage(E *e)
// CHECK: 1: e
// CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, LValueToRValue, E *)
// CXX11-NEXT: 3: D() (CXXConstructExpr, [B1.4], [B1.6], class D)
// CXX11-NEXT: 4: [B1.3] (BindTemporary)
// CXX11-NEXT: 5: [B1.4] (ImplicitCastExpr, NoOp, const class D)
// CXX11-NEXT: 6: [B1.5]
// CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, class D)
// CXX11-NEXT: 8: [B1.7] (BindTemporary)
// Double brackets trigger FileCheck variables, escape.
// CXX11-NEXT: 9: {{\[}}[B1.2] foo:[B1.8]]
// CXX11-NEXT: 10: ~D() (Temporary object destructor)
// CXX11-NEXT: 11: ~D() (Temporary object destructor)
// CXX17-NEXT: 3: D() (CXXConstructExpr, class D)
// CXX17-NEXT: 4: [B1.3] (BindTemporary)
// Double brackets trigger FileCheck variables, escape.
// CXX17-NEXT: 5: {{\[}}[B1.2] foo:[B1.4]]
// CXX17-NEXT: 6: ~D() (Temporary object destructor)
void passArgumentIntoMessage(E *e) {
[e foo: D()];
}
34 changes: 30 additions & 4 deletions clang/test/Analysis/temporaries.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -verify -w -std=c++03 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -verify -w -std=c++11 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -DTEMPORARY_DTORS -verify -w -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true %s -std=c++11
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -DTEMPORARY_DTORS -w -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true %s -std=c++17
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -verify -w -std=c++03 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,debug.ExprInspection -analyzer-config cfg-temporary-dtors=false -verify -w -std=c++11 %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,debug.ExprInspection -DTEMPORARY_DTORS -verify -w -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true %s -std=c++11
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,debug.ExprInspection -DTEMPORARY_DTORS -w -analyzer-config cfg-temporary-dtors=true,c++-temp-dtor-inlining=true %s -std=c++17

// Note: The C++17 run-line doesn't -verify yet - it is a no-crash test.

Expand Down Expand Up @@ -945,3 +945,29 @@ C &&foo2();
const C &bar1() { return foo1(); } // no-crash
C &&bar2() { return foo2(); } // no-crash
} // end namespace pass_references_through


namespace ctor_argument {
// Stripped down unique_ptr<int>
struct IntPtr {
IntPtr(): i(new int) {}
IntPtr(IntPtr &&o): i(o.i) { o.i = 0; }
~IntPtr() { delete i; }

int *i;
};

struct Foo {
Foo(IntPtr);
void bar();

IntPtr i;
};

void bar() {
IntPtr ptr;
int *i = ptr.i;
Foo f(static_cast<IntPtr &&>(ptr));
*i = 99; // no-warning
}
} // namespace ctor_argument
23 changes: 23 additions & 0 deletions clang/test/Analysis/temporaries.mm
@@ -0,0 +1,23 @@
// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus -verify %s

// expected-no-diagnostics

// Stripped down unique_ptr<int>
struct IntPtr {
IntPtr(): i(new int) {}
IntPtr(IntPtr &&o): i(o.i) { o.i = nullptr; }
~IntPtr() { delete i; }

int *i;
};

@interface Foo {}
-(void) foo: (IntPtr)arg;
@end

void bar(Foo *f) {
IntPtr ptr;
int *i = ptr.i;
[f foo: static_cast<IntPtr &&>(ptr)];
*i = 99; // no-warning
}

0 comments on commit bd880fe

Please sign in to comment.