This revision might be the cause of https://bugs.llvm.org/show_bug.cgi?id=34869. I'm still working on a reduced test case.
On Sat, Sep 30, 2017 at 2:03 AM, George Karpenkov via cfe-commits < cfe-commits@lists.llvm.org> wrote: > 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 RefersToEnclosingVariableOrCap > ture, > + 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, > + RefersToEnclosingVariableOrCap > ture, > + 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}} > +} > > > _______________________________________________ > cfe-commits mailing list > cfe-commits@lists.llvm.org > http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits >
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits