[Analyzer] Synthesize function body for std::call_once

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

llvm-svn: 314571
This commit is contained in:
George Karpenkov 2017-09-30 00:03:22 +00:00
parent ac63d63543
commit 657a5896b1
2 changed files with 521 additions and 28 deletions

View File

@ -14,11 +14,18 @@
#include "BodyFarm.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/Analysis/CodeInjector.h"
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "body-farm"
using namespace clang;
@ -55,7 +62,9 @@ public:
CompoundStmt *makeCompound(ArrayRef<Stmt*>);
/// Create a new DeclRefExpr for the referenced variable.
DeclRefExpr *makeDeclRefExpr(const VarDecl *D);
DeclRefExpr *makeDeclRefExpr(const VarDecl *D,
bool RefersToEnclosingVariableOrCapture = false,
bool GetNonReferenceType = false);
/// Create a new UnaryOperator representing a dereference.
UnaryOperator *makeDereference(const Expr *Arg, QualType Ty);
@ -66,9 +75,24 @@ public:
/// Create an implicit cast to a builtin boolean type.
ImplicitCastExpr *makeIntegralCastToBoolean(const Expr *Arg);
// Create an implicit cast for lvalue-to-rvaluate conversions.
/// Create an implicit cast for lvalue-to-rvaluate conversions.
ImplicitCastExpr *makeLvalueToRvalue(const Expr *Arg, QualType Ty);
/// Create an implicit cast for lvalue-to-rvaluate conversions.
ImplicitCastExpr *makeLvalueToRvalue(const Expr *Arg,
bool GetNonReferenceType = false);
/// Make RValue out of variable declaration, creating a temporary
/// DeclRefExpr in the process.
ImplicitCastExpr *
makeLvalueToRvalue(const VarDecl *Decl,
bool RefersToEnclosingVariableOrCapture = false,
bool GetNonReferenceType = false);
/// Create an implicit cast of the given type.
ImplicitCastExpr *makeImplicitCast(const Expr *Arg, QualType Ty,
CastKind CK = CK_LValueToRValue);
/// Create an Objective-C bool literal.
ObjCBoolLiteralExpr *makeObjCBool(bool Val);
@ -78,6 +102,18 @@ public:
/// Create a Return statement.
ReturnStmt *makeReturn(const Expr *RetVal);
/// Create an integer literal.
IntegerLiteral *makeIntegerLiteral(uint64_t value);
/// Create a member expression.
MemberExpr *makeMemberExpression(Expr *base, ValueDecl *MemberDecl,
bool IsArrow = false,
ExprValueKind ValueKind = VK_LValue);
/// Returns a *first* member field of a record declaration with a given name.
/// \return an nullptr if no member with such a name exists.
NamedDecl *findMemberField(const CXXRecordDecl *RD, StringRef Name);
private:
ASTContext &C;
};
@ -106,16 +142,16 @@ CompoundStmt *ASTMaker::makeCompound(ArrayRef<Stmt *> Stmts) {
return new (C) CompoundStmt(C, Stmts, SourceLocation(), SourceLocation());
}
DeclRefExpr *ASTMaker::makeDeclRefExpr(const VarDecl *D) {
DeclRefExpr *DR =
DeclRefExpr::Create(/* Ctx = */ C,
/* QualifierLoc = */ NestedNameSpecifierLoc(),
/* TemplateKWLoc = */ SourceLocation(),
/* D = */ const_cast<VarDecl*>(D),
/* RefersToEnclosingVariableOrCapture = */ false,
/* NameLoc = */ SourceLocation(),
/* T = */ D->getType(),
/* VK = */ VK_LValue);
DeclRefExpr *ASTMaker::makeDeclRefExpr(const VarDecl *D,
bool RefersToEnclosingVariableOrCapture,
bool GetNonReferenceType) {
auto Type = D->getType();
if (GetNonReferenceType)
Type = Type.getNonReferenceType();
DeclRefExpr *DR = DeclRefExpr::Create(
C, NestedNameSpecifierLoc(), SourceLocation(), const_cast<VarDecl *>(D),
RefersToEnclosingVariableOrCapture, SourceLocation(), Type, VK_LValue);
return DR;
}
@ -125,8 +161,38 @@ UnaryOperator *ASTMaker::makeDereference(const Expr *Arg, QualType Ty) {
}
ImplicitCastExpr *ASTMaker::makeLvalueToRvalue(const Expr *Arg, QualType Ty) {
return ImplicitCastExpr::Create(C, Ty, CK_LValueToRValue,
const_cast<Expr*>(Arg), nullptr, VK_RValue);
return makeImplicitCast(Arg, Ty, CK_LValueToRValue);
}
ImplicitCastExpr *ASTMaker::makeLvalueToRvalue(const Expr *Arg,
bool GetNonReferenceType) {
QualType Type = Arg->getType();
if (GetNonReferenceType)
Type = Type.getNonReferenceType();
return makeImplicitCast(Arg, Type, CK_LValueToRValue);
}
ImplicitCastExpr *
ASTMaker::makeLvalueToRvalue(const VarDecl *Arg,
bool RefersToEnclosingVariableOrCapture,
bool GetNonReferenceType) {
auto Type = Arg->getType();
if (GetNonReferenceType)
Type = Type.getNonReferenceType();
return makeLvalueToRvalue(makeDeclRefExpr(Arg,
RefersToEnclosingVariableOrCapture,
GetNonReferenceType),
Type);
}
ImplicitCastExpr *ASTMaker::makeImplicitCast(const Expr *Arg, QualType Ty,
CastKind CK) {
return ImplicitCastExpr::Create(C, Ty,
/* CastKind= */ CK,
/* Expr= */ const_cast<Expr *>(Arg),
/* CXXCastPath= */ nullptr,
/* ExprValueKind= */ VK_RValue);
}
Expr *ASTMaker::makeIntegralCast(const Expr *Arg, QualType Ty) {
@ -161,12 +227,196 @@ ReturnStmt *ASTMaker::makeReturn(const Expr *RetVal) {
nullptr);
}
IntegerLiteral *ASTMaker::makeIntegerLiteral(uint64_t value) {
return IntegerLiteral::Create(C,
llvm::APInt(
/*numBits=*/C.getTypeSize(C.IntTy), value),
/*QualType=*/C.IntTy, SourceLocation());
}
MemberExpr *ASTMaker::makeMemberExpression(Expr *base, ValueDecl *MemberDecl,
bool IsArrow,
ExprValueKind ValueKind) {
DeclAccessPair FoundDecl = DeclAccessPair::make(MemberDecl, AS_public);
return MemberExpr::Create(
C, base, IsArrow, SourceLocation(), NestedNameSpecifierLoc(),
SourceLocation(), MemberDecl, FoundDecl,
DeclarationNameInfo(MemberDecl->getDeclName(), SourceLocation()),
/* TemplateArgumentListInfo= */ nullptr, MemberDecl->getType(), ValueKind,
OK_Ordinary);
}
NamedDecl *ASTMaker::findMemberField(const CXXRecordDecl *RD, StringRef Name) {
CXXBasePaths Paths(
/* FindAmbiguities=*/false,
/* RecordPaths=*/false,
/* DetectVirtual= */ false);
const IdentifierInfo &II = C.Idents.get(Name);
DeclarationName DeclName = C.DeclarationNames.getIdentifier(&II);
DeclContextLookupResult Decls = RD->lookup(DeclName);
for (NamedDecl *FoundDecl : Decls)
if (!FoundDecl->getDeclContext()->isFunctionOrMethod())
return FoundDecl;
return nullptr;
}
//===----------------------------------------------------------------------===//
// Creation functions for faux ASTs.
//===----------------------------------------------------------------------===//
typedef Stmt *(*FunctionFarmer)(ASTContext &C, const FunctionDecl *D);
static CallExpr *
create_call_once_funcptr_call(ASTContext &C, ASTMaker M,
const ParmVarDecl *Callback,
SmallVectorImpl<Expr *> &CallArgs) {
return new (C) CallExpr(
/*ASTContext=*/C,
/*StmtClass=*/M.makeLvalueToRvalue(/*Expr=*/Callback),
/*args=*/CallArgs,
/*QualType=*/C.VoidTy,
/*ExprValueType=*/VK_RValue,
/*SourceLocation=*/SourceLocation());
}
static CallExpr *
create_call_once_lambda_call(ASTContext &C, ASTMaker M,
const ParmVarDecl *Callback, QualType CallbackType,
SmallVectorImpl<Expr *> &CallArgs) {
CXXRecordDecl *CallbackDecl = CallbackType->getAsCXXRecordDecl();
assert(CallbackDecl != nullptr);
assert(CallbackDecl->isLambda());
FunctionDecl *callOperatorDecl = CallbackDecl->getLambdaCallOperator();
assert(callOperatorDecl != nullptr);
DeclRefExpr *callOperatorDeclRef =
DeclRefExpr::Create(/* Ctx = */ C,
/* QualifierLoc = */ NestedNameSpecifierLoc(),
/* TemplateKWLoc = */ SourceLocation(),
const_cast<FunctionDecl *>(callOperatorDecl),
/* RefersToEnclosingVariableOrCapture= */ false,
/* NameLoc = */ SourceLocation(),
/* T = */ callOperatorDecl->getType(),
/* VK = */ VK_LValue);
CallArgs.insert(
CallArgs.begin(),
M.makeDeclRefExpr(Callback,
/* RefersToEnclosingVariableOrCapture= */ true,
/* GetNonReferenceType= */ true));
return new (C)
CXXOperatorCallExpr(/*AstContext=*/C, OO_Call, callOperatorDeclRef,
/*args=*/CallArgs,
/*QualType=*/C.VoidTy,
/*ExprValueType=*/VK_RValue,
/*SourceLocation=*/SourceLocation(), FPOptions());
}
/// Create a fake body for std::call_once.
/// Emulates the following function body:
///
/// \code
/// typedef struct once_flag_s {
/// unsigned long __state = 0;
/// } once_flag;
/// template<class Callable>
/// void call_once(once_flag& o, Callable func) {
/// if (!o.__state) {
/// func();
/// }
/// o.__state = 1;
/// }
/// \endcode
static Stmt *create_call_once(ASTContext &C, const FunctionDecl *D) {
DEBUG(llvm::dbgs() << "Generating body for call_once\n");
// We need at least two parameters.
if (D->param_size() < 2)
return nullptr;
ASTMaker M(C);
const ParmVarDecl *Flag = D->getParamDecl(0);
const ParmVarDecl *Callback = D->getParamDecl(1);
QualType CallbackType = Callback->getType().getNonReferenceType();
SmallVector<Expr *, 5> CallArgs;
// All arguments past first two ones are passed to the callback.
for (unsigned int i = 2; i < D->getNumParams(); i++)
CallArgs.push_back(M.makeLvalueToRvalue(D->getParamDecl(i)));
CallExpr *CallbackCall;
if (CallbackType->getAsCXXRecordDecl() &&
CallbackType->getAsCXXRecordDecl()->isLambda()) {
CallbackCall =
create_call_once_lambda_call(C, M, Callback, CallbackType, CallArgs);
} else {
// Function pointer case.
CallbackCall = create_call_once_funcptr_call(C, M, Callback, CallArgs);
}
QualType FlagType = Flag->getType().getNonReferenceType();
DeclRefExpr *FlagDecl =
M.makeDeclRefExpr(Flag,
/* RefersToEnclosingVariableOrCapture=*/true,
/* GetNonReferenceType=*/true);
CXXRecordDecl *FlagCXXDecl = FlagType->getAsCXXRecordDecl();
// Note: here we are assuming libc++ implementation of call_once,
// which has a struct with a field `__state_`.
// Body farming might not work for other `call_once` implementations.
NamedDecl *FoundDecl = M.findMemberField(FlagCXXDecl, "__state_");
ValueDecl *FieldDecl;
if (FoundDecl) {
FieldDecl = dyn_cast<ValueDecl>(FoundDecl);
} else {
DEBUG(llvm::dbgs() << "No field __state_ found on std::once_flag struct, "
<< "unable to synthesize call_once body, ignoring "
<< "the call.\n");
return nullptr;
}
MemberExpr *Deref = M.makeMemberExpression(FlagDecl, FieldDecl);
assert(Deref->isLValue());
QualType DerefType = Deref->getType();
// Negation predicate.
UnaryOperator *FlagCheck = new (C) UnaryOperator(
/* input= */
M.makeImplicitCast(M.makeLvalueToRvalue(Deref, DerefType), DerefType,
CK_IntegralToBoolean),
/* opc= */ UO_LNot,
/* QualType= */ C.IntTy,
/* ExprValueKind= */ VK_RValue,
/* ExprObjectKind= */ OK_Ordinary, SourceLocation());
// Create assignment.
BinaryOperator *FlagAssignment = M.makeAssignment(
Deref, M.makeIntegralCast(M.makeIntegerLiteral(1), DerefType), DerefType);
IfStmt *Out = new (C)
IfStmt(C, SourceLocation(),
/* IsConstexpr= */ false,
/* init= */ nullptr,
/* var= */ nullptr,
/* cond= */ FlagCheck,
/* then= */ M.makeCompound({CallbackCall, FlagAssignment}));
return Out;
}
/// Create a fake body for dispatch_once.
static Stmt *create_dispatch_once(ASTContext &C, const FunctionDecl *D) {
// Check if we have at least two parameters.
@ -202,15 +452,17 @@ static Stmt *create_dispatch_once(ASTContext &C, const FunctionDecl *D) {
ASTMaker M(C);
// (1) Create the call.
DeclRefExpr *DR = M.makeDeclRefExpr(Block);
ImplicitCastExpr *ICE = M.makeLvalueToRvalue(DR, Ty);
CallExpr *CE = new (C) CallExpr(C, ICE, None, C.VoidTy, VK_RValue,
SourceLocation());
CallExpr *CE = new (C) CallExpr(
/*ASTContext=*/C,
/*StmtClass=*/M.makeLvalueToRvalue(/*Expr=*/Block),
/*args=*/None,
/*QualType=*/C.VoidTy,
/*ExprValueType=*/VK_RValue,
/*SourceLocation=*/SourceLocation());
// (2) Create the assignment to the predicate.
IntegerLiteral *IL =
IntegerLiteral::Create(C, llvm::APInt(C.getTypeSize(C.IntTy), (uint64_t) 1),
C.IntTy, SourceLocation());
IntegerLiteral *IL = M.makeIntegerLiteral(1);
BinaryOperator *B =
M.makeAssignment(
M.makeDereference(
@ -234,13 +486,20 @@ static Stmt *create_dispatch_once(ASTContext &C, const FunctionDecl *D) {
PredicateTy),
PredicateTy);
UnaryOperator *UO = new (C) UnaryOperator(LValToRval, UO_LNot, C.IntTy,
VK_RValue, OK_Ordinary,
SourceLocation());
UnaryOperator *UO = new (C) UnaryOperator(
/* input= */ LValToRval,
/* opc= */ UO_LNot,
/* QualType= */ C.IntTy,
/* ExprValueKind= */ VK_RValue,
/* ExprObjectKind= */ OK_Ordinary, SourceLocation());
// (5) Create the 'if' statement.
IfStmt *If = new (C) IfStmt(C, SourceLocation(), false, nullptr, nullptr,
UO, CS);
IfStmt *If = new (C) IfStmt(C, SourceLocation(),
/* IsConstexpr= */ false,
/* init= */ nullptr,
/* var= */ nullptr,
/* cond= */ UO,
/* then= */ CS);
return If;
}
@ -370,8 +629,9 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) {
if (Name.startswith("OSAtomicCompareAndSwap") ||
Name.startswith("objc_atomicCompareAndSwap")) {
FF = create_OSAtomicCompareAndSwap;
}
else {
} else if (Name == "call_once" && D->getDeclContext()->isStdNamespace()) {
FF = create_call_once;
} else {
FF = llvm::StringSwitch<FunctionFarmer>(Name)
.Case("dispatch_sync", create_dispatch_sync)
.Case("dispatch_once", create_dispatch_once)

View File

@ -0,0 +1,233 @@
// RUN: %clang_analyze_cc1 -std=c++11 -fblocks -analyzer-checker=core,debug.ExprInspection -w -verify %s
void clang_analyzer_eval(bool);
// Faking std::std::call_once implementation.
namespace std {
typedef struct once_flag_s {
unsigned long __state_ = 0;
} once_flag;
template <class Callable, class... Args>
void call_once(once_flag &o, Callable func, Args... args);
} // namespace std
// Check with Lambdas.
void test_called_warning() {
std::once_flag g_initialize;
int z;
std::call_once(g_initialize, [&] {
int *x = nullptr;
int y = *x; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
z = 200;
});
}
void test_called_on_path_inside_no_warning() {
std::once_flag g_initialize;
int *x = nullptr;
int y = 100;
int z;
std::call_once(g_initialize, [&] {
z = 200;
x = &z;
});
*x = 100; // no-warning
clang_analyzer_eval(z == 100); // expected-warning{{TRUE}}
}
void test_called_on_path_no_warning() {
std::once_flag g_initialize;
int *x = nullptr;
int y = 100;
std::call_once(g_initialize, [&] {
x = &y;
});
*x = 100; // no-warning
}
void test_called_on_path_warning() {
std::once_flag g_initialize;
int y = 100;
int *x = &y;
std::call_once(g_initialize, [&] {
x = nullptr;
});
*x = 100; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
}
void test_called_once_warning() {
std::once_flag g_initialize;
int *x = nullptr;
int y = 100;
std::call_once(g_initialize, [&] {
x = nullptr;
});
std::call_once(g_initialize, [&] {
x = &y;
});
*x = 100; // expected-warning{{Dereference of null pointer (loaded from variable 'x')}}
}
void test_called_once_no_warning() {
std::once_flag g_initialize;
int *x = nullptr;
int y = 100;
std::call_once(g_initialize, [&] {
x = &y;
});
std::call_once(g_initialize, [&] {
x = nullptr;
});
*x = 100; // no-warning
}
static int global = 0;
void funcPointer() {
global = 1;
}
void test_func_pointers() {
static std::once_flag flag;
std::call_once(flag, &funcPointer);
clang_analyzer_eval(global == 1); // expected-warning{{TRUE}}
}
template <class _Fp>
class function; // undefined
template <class _Rp, class... _ArgTypes>
struct function<_Rp(_ArgTypes...)> {
_Rp operator()(_ArgTypes...) const;
template <class _Fp>
function(_Fp);
};
// Note: currently we do not support calls to std::function,
// but the analyzer should not crash either.
void test_function_objects_warning() {
int x = 0;
int *y = &x;
std::once_flag flag;
function<void()> func = [&]() {
y = nullptr;
};
std::call_once(flag, func);
func();
int z = *y;
}
void test_param_passing_lambda() {
std::once_flag flag;
int x = 120;
int y = 0;
std::call_once(flag, [&](int p) {
y = p;
},
x);
clang_analyzer_eval(y == 120); // expected-warning{{TRUE}}
}
void test_param_passing_lambda_false() {
std::once_flag flag;
int x = 120;
std::call_once(flag, [&](int p) {
x = 0;
},
x);
clang_analyzer_eval(x == 120); // expected-warning{{FALSE}}
}
void test_param_passing_stored_lambda() {
std::once_flag flag;
int x = 120;
int y = 0;
auto lambda = [&](int p) {
y = p;
};
std::call_once(flag, lambda, x);
clang_analyzer_eval(y == 120); // expected-warning{{TRUE}}
}
void test_multiparam_passing_lambda() {
std::once_flag flag;
int x = 120;
std::call_once(flag, [&](int a, int b, int c) {
x = a + b + c;
},
1, 2, 3);
clang_analyzer_eval(x == 120); // expected-warning{{FALSE}}
clang_analyzer_eval(x == 6); // expected-warning{{TRUE}}
}
static int global2 = 0;
void test_param_passing_lambda_global() {
std::once_flag flag;
global2 = 0;
std::call_once(flag, [&](int a, int b, int c) {
global2 = a + b + c;
},
1, 2, 3);
clang_analyzer_eval(global2 == 6); // expected-warning{{TRUE}}
}
static int global3 = 0;
void funcptr(int a, int b, int c) {
global3 = a + b + c;
}
void test_param_passing_funcptr() {
std::once_flag flag;
global3 = 0;
std::call_once(flag, &funcptr, 1, 2, 3);
clang_analyzer_eval(global3 == 6); // expected-warning{{TRUE}}
}
void test_blocks() {
global3 = 0;
std::once_flag flag;
std::call_once(flag, ^{
global3 = 120;
});
clang_analyzer_eval(global3 == 120); // expected-warning{{TRUE}}
}
int call_once() {
return 5;
}
void test_non_std_call_once() {
int x = call_once();
clang_analyzer_eval(x == 5); // expected-warning{{TRUE}}
}