r314571 - [Analyzer] Synthesize function body for std::call_once
George Karpenkov via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 29 17:03:22 PDT 2017
Author: george.karpenkov
Date: Fri Sep 29 17:03:22 2017
New Revision: 314571
URL: http://llvm.org/viewvc/llvm-project?rev=314571&view=rev
Log:
[Analyzer] Synthesize function body for std::call_once
Differential Revision: https://reviews.llvm.org/D37840
Added:
cfe/trunk/test/Analysis/call_once.cpp
Modified:
cfe/trunk/lib/Analysis/BodyFarm.cpp
Modified: cfe/trunk/lib/Analysis/BodyFarm.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Analysis/BodyFarm.cpp?rev=314571&r1=314570&r2=314571&view=diff
==============================================================================
--- cfe/trunk/lib/Analysis/BodyFarm.cpp (original)
+++ cfe/trunk/lib/Analysis/BodyFarm.cpp Fri Sep 29 17:03:22 2017
@@ -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(Arr
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
}
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 E
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(ASTCon
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(ASTCon
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 FunctionDe
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)
Added: cfe/trunk/test/Analysis/call_once.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Analysis/call_once.cpp?rev=314571&view=auto
==============================================================================
--- cfe/trunk/test/Analysis/call_once.cpp (added)
+++ cfe/trunk/test/Analysis/call_once.cpp Fri Sep 29 17:03:22 2017
@@ -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}}
+}
More information about the cfe-commits
mailing list