//=== SemaFunctionEffects.cpp - Sema handling of function effects ---------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements Sema handling of function effects. // //===----------------------------------------------------------------------===// #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DynamicRecursiveASTVisitor.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtObjC.h" #include "clang/AST/Type.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/SemaInternal.h" #define DEBUG_TYPE "effectanalysis" using namespace clang; namespace { enum class ViolationID : uint8_t { None = 0, // Sentinel for an empty Violation. // These first 5 map to a %select{} in one of several FunctionEffects // diagnostics, e.g. warn_func_effect_violation. BaseDiagnosticIndex, AllocatesMemory = BaseDiagnosticIndex, ThrowsOrCatchesExceptions, HasStaticLocalVariable, AccessesThreadLocalVariable, AccessesObjCMethodOrProperty, // These only apply to callees, where the analysis stops at the Decl. DeclDisallowsInference, // These both apply to indirect calls. The difference is that sometimes // we have an actual Decl (generally a variable) which is the function // pointer being called, and sometimes, typically due to a cast, we only // have an expression. CallsDeclWithoutEffect, CallsExprWithoutEffect, }; // Information about the AST context in which a violation was found, so // that diagnostics can point to the correct source. class ViolationSite { public: enum class Kind : uint8_t { Default, // Function body. MemberInitializer, DefaultArgExpr }; private: llvm::PointerIntPair Impl; public: ViolationSite() = default; explicit ViolationSite(CXXDefaultArgExpr *E) : Impl(E, Kind::DefaultArgExpr) {} Kind kind() const { return static_cast(Impl.getInt()); } CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); } void setKind(Kind K) { Impl.setPointerAndInt(nullptr, K); } }; // Represents a violation of the rules, potentially for the entire duration of // the analysis phase, in order to refer to it when explaining why a caller has // been made unsafe by a callee. Can be transformed into either a Diagnostic // (warning or a note), depending on whether the violation pertains to a // function failing to be verifed as holding an effect vs. a function failing to // be inferred as holding that effect. struct Violation { FunctionEffect Effect; std::optional CalleeEffectPreventingInference; // Only for certain IDs; can be nullopt. ViolationID ID = ViolationID::None; ViolationSite Site; SourceLocation Loc; const Decl *Callee = nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect. Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS, SourceLocation Loc, const Decl *Callee = nullptr, std::optional CalleeEffect = std::nullopt) : Effect(Effect), CalleeEffectPreventingInference(CalleeEffect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) {} unsigned diagnosticSelectIndex() const { return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex); } }; enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; enum class CallableType : uint8_t { // Unknown: probably function pointer. Unknown, Function, Virtual, Block }; // Return whether a function's effects CAN be verified. // The question of whether it SHOULD be verified is independent. static bool functionIsVerifiable(const FunctionDecl *FD) { if (FD->isTrivial()) { // Otherwise `struct x { int a; };` would have an unverifiable default // constructor. return true; } return FD->hasBody(); } static bool isNoexcept(const FunctionDecl *FD) { const auto *FPT = FD->getType()->getAs(); return FPT && (FPT->isNothrow() || FD->hasAttr()); } // This list is probably incomplete. // FIXME: Investigate: // __builtin_eh_return? // __builtin_allow_runtime_check? // __builtin_unwind_init and other similar things that sound exception-related. // va_copy? // coroutines? static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { FunctionEffectKindSet Result; switch (BuiltinID) { case 0: // Not builtin. default: // By default, builtins have no known effects. break; // These allocate/deallocate heap memory. case Builtin::ID::BI__builtin_calloc: case Builtin::ID::BI__builtin_malloc: case Builtin::ID::BI__builtin_realloc: case Builtin::ID::BI__builtin_free: case Builtin::ID::BI__builtin_operator_delete: case Builtin::ID::BI__builtin_operator_new: case Builtin::ID::BIaligned_alloc: case Builtin::ID::BIcalloc: case Builtin::ID::BImalloc: case Builtin::ID::BImemalign: case Builtin::ID::BIrealloc: case Builtin::ID::BIfree: case Builtin::ID::BIfopen: case Builtin::ID::BIpthread_create: case Builtin::ID::BI_Block_object_dispose: Result.insert(FunctionEffect(FunctionEffect::Kind::Allocating)); break; // These block in some other way than allocating memory. // longjmp() and friends are presumed unsafe because they are the moral // equivalent of throwing a C++ exception, which is unsafe. case Builtin::ID::BIlongjmp: case Builtin::ID::BI_longjmp: case Builtin::ID::BIsiglongjmp: case Builtin::ID::BI__builtin_longjmp: case Builtin::ID::BIobjc_exception_throw: // Objective-C runtime. case Builtin::ID::BIobjc_msgSend: case Builtin::ID::BIobjc_msgSend_fpret: case Builtin::ID::BIobjc_msgSend_fp2ret: case Builtin::ID::BIobjc_msgSend_stret: case Builtin::ID::BIobjc_msgSendSuper: case Builtin::ID::BIobjc_getClass: case Builtin::ID::BIobjc_getMetaClass: case Builtin::ID::BIobjc_enumerationMutation: case Builtin::ID::BIobjc_assign_ivar: case Builtin::ID::BIobjc_assign_global: case Builtin::ID::BIobjc_sync_enter: case Builtin::ID::BIobjc_sync_exit: case Builtin::ID::BINSLog: case Builtin::ID::BINSLogv: // stdio.h case Builtin::ID::BIfread: case Builtin::ID::BIfwrite: // stdio.h: printf family. case Builtin::ID::BIprintf: case Builtin::ID::BI__builtin_printf: case Builtin::ID::BIfprintf: case Builtin::ID::BIsnprintf: case Builtin::ID::BIsprintf: case Builtin::ID::BIvprintf: case Builtin::ID::BIvfprintf: case Builtin::ID::BIvsnprintf: case Builtin::ID::BIvsprintf: // stdio.h: scanf family. case Builtin::ID::BIscanf: case Builtin::ID::BIfscanf: case Builtin::ID::BIsscanf: case Builtin::ID::BIvscanf: case Builtin::ID::BIvfscanf: case Builtin::ID::BIvsscanf: Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking)); break; } return Result; } // Transitory, more extended information about a callable, which can be a // function, block, or function pointer. struct CallableInfo { // CDecl holds the function's definition, if any. // FunctionDecl if CallableType::Function or Virtual // BlockDecl if CallableType::Block const Decl *CDecl; // Remember whether the callable is a function, block, virtual method, // or (presumed) function pointer. CallableType CType = CallableType::Unknown; // Remember whether the callable is an operator new or delete function, // so that calls to them are reported more meaningfully, as memory // allocations. SpecialFuncType FuncType = SpecialFuncType::None; // We inevitably want to know the callable's declared effects, so cache them. FunctionEffectKindSet Effects; CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None) : CDecl(&CD), FuncType(FT) { FunctionEffectsRef DeclEffects; if (auto *FD = dyn_cast(CDecl)) { // Use the function's definition, if any. if (const FunctionDecl *Def = FD->getDefinition()) CDecl = FD = Def; CType = CallableType::Function; if (auto *Method = dyn_cast(FD); Method && Method->isVirtual()) CType = CallableType::Virtual; DeclEffects = FD->getFunctionEffects(); } else if (auto *BD = dyn_cast(CDecl)) { CType = CallableType::Block; DeclEffects = BD->getFunctionEffects(); } else if (auto *VD = dyn_cast(CDecl)) { // ValueDecl is function, enum, or variable, so just look at its type. DeclEffects = FunctionEffectsRef::get(VD->getType()); } Effects = FunctionEffectKindSet(DeclEffects); } CallableType type() const { return CType; } bool isCalledDirectly() const { return CType == CallableType::Function || CType == CallableType::Block; } bool isVerifiable() const { switch (CType) { case CallableType::Unknown: case CallableType::Virtual: return false; case CallableType::Block: return true; case CallableType::Function: return functionIsVerifiable(dyn_cast(CDecl)); } llvm_unreachable("undefined CallableType"); } /// Generate a name for logging and diagnostics. std::string getNameForDiagnostic(Sema &S) const { std::string Name; llvm::raw_string_ostream OS(Name); if (auto *FD = dyn_cast(CDecl)) FD->getNameForDiagnostic(OS, S.getPrintingPolicy(), /*Qualified=*/true); else if (auto *BD = dyn_cast(CDecl)) OS << "(block " << BD->getBlockManglingNumber() << ")"; else if (auto *VD = dyn_cast(CDecl)) VD->printQualifiedName(OS); return Name; } }; // ---------- // Map effects to single Violations, to hold the first (of potentially many) // violations pertaining to an effect, per function. class EffectToViolationMap { // Since we currently only have a tiny number of effects (typically no more // than 1), use a SmallVector with an inline capacity of 1. Since it // is often empty, use a unique_ptr to the SmallVector. // Note that Violation itself contains a FunctionEffect which is the key. // FIXME: Is there a way to simplify this using existing data structures? using ImplVec = llvm::SmallVector; std::unique_ptr Impl; public: // Insert a new Violation if we do not already have one for its effect. void maybeInsert(const Violation &Viol) { if (Impl == nullptr) Impl = std::make_unique(); else if (lookup(Viol.Effect) != nullptr) return; Impl->push_back(Viol); } const Violation *lookup(FunctionEffect Key) { if (Impl == nullptr) return nullptr; auto *Iter = llvm::find_if( *Impl, [&](const auto &Item) { return Item.Effect == Key; }); return Iter != Impl->end() ? &*Iter : nullptr; } size_t size() const { return Impl ? Impl->size() : 0; } }; // ---------- // State pertaining to a function whose AST is walked and whose effect analysis // is dependent on a subsequent analysis of other functions. class PendingFunctionAnalysis { friend class CompleteFunctionAnalysis; public: struct DirectCall { const Decl *Callee; SourceLocation CallLoc; // Not all recursive calls are detected, just enough // to break cycles. bool Recursed = false; ViolationSite VSite; DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite) : Callee(D), CallLoc(CallLoc), VSite(VSite) {} }; // We always have two disjoint sets of effects to verify: // 1. Effects declared explicitly by this function. // 2. All other inferrable effects needing verification. FunctionEffectKindSet DeclaredVerifiableEffects; FunctionEffectKindSet EffectsToInfer; private: // Violations pertaining to the function's explicit effects. SmallVector ViolationsForExplicitEffects; // Violations pertaining to other, non-explicit, inferrable effects. EffectToViolationMap InferrableEffectToFirstViolation; // These unverified direct calls are what keeps the analysis "pending", // until the callees can be verified. SmallVector UnverifiedDirectCalls; public: PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo, FunctionEffectKindSet AllInferrableEffectsToVerify) : DeclaredVerifiableEffects(CInfo.Effects) { // Check for effects we are not allowed to infer. FunctionEffectKindSet InferrableEffects; for (FunctionEffect effect : AllInferrableEffectsToVerify) { std::optional ProblemCalleeEffect = effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects); if (!ProblemCalleeEffect) InferrableEffects.insert(effect); else { // Add a Violation for this effect if a caller were to // try to infer it. InferrableEffectToFirstViolation.maybeInsert(Violation( effect, ViolationID::DeclDisallowsInference, ViolationSite{}, CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect)); } } // InferrableEffects is now the set of inferrable effects which are not // prohibited. EffectsToInfer = FunctionEffectKindSet::difference( InferrableEffects, DeclaredVerifiableEffects); } // Hide the way that Violations for explicitly required effects vs. inferred // ones are handled differently. void checkAddViolation(bool Inferring, const Violation &NewViol) { if (!Inferring) ViolationsForExplicitEffects.push_back(NewViol); else InferrableEffectToFirstViolation.maybeInsert(NewViol); } void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite) { UnverifiedDirectCalls.emplace_back(D, CallLoc, VSite); } // Analysis is complete when there are no unverified direct calls. bool isComplete() const { return UnverifiedDirectCalls.empty(); } const Violation *violationForInferrableEffect(FunctionEffect effect) { return InferrableEffectToFirstViolation.lookup(effect); } // Mutable because caller may need to set a DirectCall's Recursing flag. MutableArrayRef unverifiedCalls() { assert(!isComplete()); return UnverifiedDirectCalls; } ArrayRef getSortedViolationsForExplicitEffects(SourceManager &SM) { if (!ViolationsForExplicitEffects.empty()) llvm::sort(ViolationsForExplicitEffects, [&SM](const Violation &LHS, const Violation &RHS) { return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); }); return ViolationsForExplicitEffects; } void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { OS << "Pending: Declared "; DeclaredVerifiableEffects.dump(OS); OS << ", " << ViolationsForExplicitEffects.size() << " violations; "; OS << " Infer "; EffectsToInfer.dump(OS); OS << ", " << InferrableEffectToFirstViolation.size() << " violations"; if (!UnverifiedDirectCalls.empty()) { OS << "; Calls: "; for (const DirectCall &Call : UnverifiedDirectCalls) { CallableInfo CI(*Call.Callee); OS << " " << CI.getNameForDiagnostic(SemaRef); } } OS << "\n"; } }; // ---------- class CompleteFunctionAnalysis { // Current size: 2 pointers public: // Has effects which are both the declared ones -- not to be inferred -- plus // ones which have been successfully inferred. These are all considered // "verified" for the purposes of callers; any issue with verifying declared // effects has already been reported and is not the problem of any caller. FunctionEffectKindSet VerifiedEffects; private: // This is used to generate notes about failed inference. EffectToViolationMap InferrableEffectToFirstViolation; public: // The incoming Pending analysis is consumed (member(s) are moved-from). CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending, FunctionEffectKindSet DeclaredEffects, FunctionEffectKindSet AllInferrableEffectsToVerify) : VerifiedEffects(DeclaredEffects) { for (FunctionEffect effect : AllInferrableEffectsToVerify) if (Pending.violationForInferrableEffect(effect) == nullptr) VerifiedEffects.insert(effect); InferrableEffectToFirstViolation = std::move(Pending.InferrableEffectToFirstViolation); } const Violation *firstViolationForEffect(FunctionEffect Effect) { return InferrableEffectToFirstViolation.lookup(Effect); } void dump(llvm::raw_ostream &OS) const { OS << "Complete: Verified "; VerifiedEffects.dump(OS); OS << "; Infer "; OS << InferrableEffectToFirstViolation.size() << " violations\n"; } }; // ========== class Analyzer { Sema &S; // Subset of Sema.AllEffectsToVerify FunctionEffectKindSet AllInferrableEffectsToVerify; using FuncAnalysisPtr = llvm::PointerUnion; // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger // than complete state, so use different objects to represent them. // The state pointers are owned by the container. class AnalysisMap : llvm::DenseMap { using Base = llvm::DenseMap; public: ~AnalysisMap(); // Use non-public inheritance in order to maintain the invariant // that lookups and insertions are via the canonical Decls. FuncAnalysisPtr lookup(const Decl *Key) const { return Base::lookup(Key->getCanonicalDecl()); } FuncAnalysisPtr &operator[](const Decl *Key) { return Base::operator[](Key->getCanonicalDecl()); } /// Shortcut for the case where we only care about completed analysis. CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const { if (FuncAnalysisPtr AP = lookup(D); isa_and_nonnull(AP)) return cast(AP); return nullptr; } void dump(Sema &SemaRef, llvm::raw_ostream &OS) { OS << "\nAnalysisMap:\n"; for (const auto &item : *this) { CallableInfo CI(*item.first); const auto AP = item.second; OS << item.first << " " << CI.getNameForDiagnostic(SemaRef) << " : "; if (AP.isNull()) { OS << "null\n"; } else if (auto *CFA = dyn_cast(AP)) { OS << CFA << " "; CFA->dump(OS); } else if (auto *PFA = dyn_cast(AP)) { OS << PFA << " "; PFA->dump(SemaRef, OS); } else llvm_unreachable("never"); } OS << "---\n"; } }; AnalysisMap DeclAnalysis; public: Analyzer(Sema &S) : S(S) {} void run(const TranslationUnitDecl &TU) { // Gather all of the effects to be verified to see what operations need to // be checked, and to see which ones are inferrable. for (FunctionEffect Effect : S.AllEffectsToVerify) { const FunctionEffect::Flags Flags = Effect.flags(); if (Flags & FunctionEffect::FE_InferrableOnCallees) AllInferrableEffectsToVerify.insert(Effect); } LLVM_DEBUG(llvm::dbgs() << "AllInferrableEffectsToVerify: "; AllInferrableEffectsToVerify.dump(llvm::dbgs()); llvm::dbgs() << "\n";); // We can use DeclsWithEffectsToVerify as a stack for a // depth-first traversal; there's no need for a second container. But first, // reverse it, so when working from the end, Decls are verified in the order // they are declared. SmallVector &VerificationQueue = S.DeclsWithEffectsToVerify; std::reverse(VerificationQueue.begin(), VerificationQueue.end()); while (!VerificationQueue.empty()) { const Decl *D = VerificationQueue.back(); if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { if (auto *Pending = dyn_cast(AP)) { // All children have been traversed; finish analysis. finishPendingAnalysis(D, Pending); } VerificationQueue.pop_back(); continue; } // Not previously visited; begin a new analysis for this Decl. PendingFunctionAnalysis *Pending = verifyDecl(D); if (Pending == nullptr) { // Completed now. VerificationQueue.pop_back(); continue; } // Analysis remains pending because there are direct callees to be // verified first. Push them onto the queue. for (PendingFunctionAnalysis::DirectCall &Call : Pending->unverifiedCalls()) { FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee); if (AP.isNull()) { VerificationQueue.push_back(Call.Callee); continue; } // This indicates recursion (not necessarily direct). For the // purposes of effect analysis, we can just ignore it since // no effects forbid recursion. assert(isa(AP)); Call.Recursed = true; } } } private: // Verify a single Decl. Return the pending structure if that was the result, // else null. This method must not recurse. PendingFunctionAnalysis *verifyDecl(const Decl *D) { CallableInfo CInfo(*D); bool isExternC = false; if (const FunctionDecl *FD = dyn_cast(D)) isExternC = FD->getCanonicalDecl()->isExternCContext(); // For C++, with non-extern "C" linkage only - if any of the Decl's declared // effects forbid throwing (e.g. nonblocking) then the function should also // be declared noexcept. if (S.getLangOpts().CPlusPlus && !isExternC) { for (FunctionEffect Effect : CInfo.Effects) { if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow)) continue; bool IsNoexcept = false; if (auto *FD = D->getAsFunction()) { IsNoexcept = isNoexcept(FD); } else if (auto *BD = dyn_cast(D)) { if (auto *TSI = BD->getSignatureAsWritten()) { auto *FPT = TSI->getType()->castAs(); IsNoexcept = FPT->isNothrow() || BD->hasAttr(); } } if (!IsNoexcept) S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept) << GetCallableDeclKind(D, nullptr) << Effect.name(); break; } } // Build a PendingFunctionAnalysis on the stack. If it turns out to be // complete, we'll have avoided a heap allocation; if it's incomplete, it's // a fairly trivial move to a heap-allocated object. PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify); LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.getNameForDiagnostic(S) << " "; FAnalysis.dump(S, llvm::dbgs());); FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo); Visitor.run(); if (FAnalysis.isComplete()) { completeAnalysis(CInfo, std::move(FAnalysis)); return nullptr; } // Move the pending analysis to the heap and save it in the map. PendingFunctionAnalysis *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis)); DeclAnalysis[D] = PendingPtr; LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n"; DeclAnalysis.dump(S, llvm::dbgs());); return PendingPtr; } // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis, // inserted in the container. void completeAnalysis(const CallableInfo &CInfo, PendingFunctionAnalysis &&Pending) { if (ArrayRef Viols = Pending.getSortedViolationsForExplicitEffects(S.getSourceManager()); !Viols.empty()) emitDiagnostics(Viols, CInfo); CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( S.getASTContext(), std::move(Pending), CInfo.Effects, AllInferrableEffectsToVerify); DeclAnalysis[CInfo.CDecl] = CompletePtr; LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; DeclAnalysis.dump(S, llvm::dbgs());); } // Called after all direct calls requiring inference have been found -- or // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without // the possibility of inference. Deletes Pending. void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) { CallableInfo Caller(*D); LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for " << Caller.getNameForDiagnostic(S) << " : "; Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";); for (const PendingFunctionAnalysis::DirectCall &Call : Pending->unverifiedCalls()) { if (Call.Recursed) continue; CallableInfo Callee(*Call.Callee); followCall(Caller, *Pending, Callee, Call.CallLoc, /*AssertNoFurtherInference=*/true, Call.VSite); } completeAnalysis(Caller, std::move(*Pending)); delete Pending; } // Here we have a call to a Decl, either explicitly via a CallExpr or some // other AST construct. PFA pertains to the caller. void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA, const CallableInfo &Callee, SourceLocation CallLoc, bool AssertNoFurtherInference, ViolationSite VSite) { const bool DirectCall = Callee.isCalledDirectly(); // Initially, the declared effects; inferred effects will be added. FunctionEffectKindSet CalleeEffects = Callee.Effects; bool IsInferencePossible = DirectCall; if (DirectCall) if (CompleteFunctionAnalysis *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) { // Combine declared effects with those which may have been inferred. CalleeEffects.insert(CFA->VerifiedEffects); IsInferencePossible = false; // We've already traversed it. } if (AssertNoFurtherInference) { assert(!IsInferencePossible); } if (!Callee.isVerifiable()) IsInferencePossible = false; LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.getNameForDiagnostic(S) << " to " << Callee.getNameForDiagnostic(S) << "; verifiable: " << Callee.isVerifiable() << "; callee "; CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n"; llvm::dbgs() << " callee " << Callee.CDecl << " canonical " << Callee.CDecl->getCanonicalDecl() << "\n";); auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects)) return; // If inference is not allowed, or the target is indirect (virtual // method/function ptr?), generate a Violation now. if (!IsInferencePossible || !(Effect.flags() & FunctionEffect::FE_InferrableOnCallees)) { if (Callee.FuncType == SpecialFuncType::None) PFA.checkAddViolation(Inferring, {Effect, ViolationID::CallsDeclWithoutEffect, VSite, CallLoc, Callee.CDecl}); else PFA.checkAddViolation( Inferring, {Effect, ViolationID::AllocatesMemory, VSite, CallLoc}); } else { // Inference is allowed and necessary; defer it. PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite); } }; for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects) Check1Effect(Effect, false); for (FunctionEffect Effect : PFA.EffectsToInfer) Check1Effect(Effect, true); } // Describe a callable Decl for a diagnostic. // (Not an enum class because the value is always converted to an integer for // use in a diagnostic.) enum CallableDeclKind { CDK_Function, CDK_Constructor, CDK_Destructor, CDK_Lambda, CDK_Block, CDK_MemberInitializer, }; // Describe a call site or target using an enum mapping to a %select{} // in a diagnostic, e.g. warn_func_effect_violation, // warn_perf_constraint_implies_noexcept, and others. static CallableDeclKind GetCallableDeclKind(const Decl *D, const Violation *V) { if (V != nullptr && V->Site.kind() == ViolationSite::Kind::MemberInitializer) return CDK_MemberInitializer; if (isa(D)) return CDK_Block; if (auto *Method = dyn_cast(D)) { if (isa(D)) return CDK_Constructor; if (isa(D)) return CDK_Destructor; const CXXRecordDecl *Rec = Method->getParent(); if (Rec->isLambda()) return CDK_Lambda; } return CDK_Function; }; // Should only be called when function's analysis is determined to be // complete. void emitDiagnostics(ArrayRef Viols, const CallableInfo &CInfo) { if (Viols.empty()) return; auto MaybeAddTemplateNote = [&](const Decl *D) { if (const FunctionDecl *FD = dyn_cast(D)) { while (FD != nullptr && FD->isTemplateInstantiation() && FD->getPointOfInstantiation().isValid()) { S.Diag(FD->getPointOfInstantiation(), diag::note_func_effect_from_template); FD = FD->getTemplateInstantiationPattern(); } } }; // For note_func_effect_call_indirect. enum { Indirect_VirtualMethod, Indirect_FunctionPtr }; auto MaybeAddSiteContext = [&](const Decl *D, const Violation &V) { // If a violation site is a member initializer, add a note pointing to // the constructor which invoked it. if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) { unsigned ImplicitCtor = 0; if (auto *Ctor = dyn_cast(D); Ctor && Ctor->isImplicit()) ImplicitCtor = 1; S.Diag(D->getLocation(), diag::note_func_effect_in_constructor) << ImplicitCtor; } // If a violation site is a default argument expression, add a note // pointing to the call site using the default argument. else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr) S.Diag(V.Site.defaultArgExpr()->getUsedLocation(), diag::note_in_evaluating_default_argument); }; // Top-level violations are warnings. for (const Violation &Viol1 : Viols) { StringRef effectName = Viol1.Effect.name(); switch (Viol1.ID) { case ViolationID::None: case ViolationID::DeclDisallowsInference: // Shouldn't happen // here. llvm_unreachable("Unexpected violation kind"); break; case ViolationID::AllocatesMemory: case ViolationID::ThrowsOrCatchesExceptions: case ViolationID::HasStaticLocalVariable: case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol1.Loc, diag::warn_func_effect_violation) << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName << Viol1.diagnosticSelectIndex(); MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsDeclWithoutEffect: { CallableInfo CalleeInfo(*Viol1.Callee); std::string CalleeName = CalleeInfo.getNameForDiagnostic(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << CalleeName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); // Emit notes explaining the transitive chain of inferences: Why isn't // the callee safe? for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) { std::optional MaybeNextCallee; CompleteFunctionAnalysis *Completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); if (Completed == nullptr) { // No result - could be // - non-inline and extern // - indirect (virtual or through function pointer) // - effect has been explicitly disclaimed (e.g. "blocking") CallableType CType = CalleeInfo.type(); if (CType == CallableType::Virtual) S.Diag(Callee->getLocation(), diag::note_func_effect_call_indirect) << Indirect_VirtualMethod << effectName; else if (CType == CallableType::Unknown) S.Diag(Callee->getLocation(), diag::note_func_effect_call_indirect) << Indirect_FunctionPtr << effectName; else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind())) S.Diag(Callee->getLocation(), diag::note_func_effect_call_disallows_inference) << GetCallableDeclKind(CInfo.CDecl, nullptr) << effectName << FunctionEffect(Viol1.Effect.oppositeKind()).name(); else if (const FunctionDecl *FD = dyn_cast(Callee); FD == nullptr || FD->getBuiltinID() == 0) { // A builtin callee generally doesn't have a useful source // location at which to insert a note. S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName; } break; } const Violation *PtrViol2 = Completed->firstViolationForEffect(Viol1.Effect); if (PtrViol2 == nullptr) break; const Violation &Viol2 = *PtrViol2; switch (Viol2.ID) { case ViolationID::None: llvm_unreachable("Unexpected violation kind"); break; case ViolationID::DeclDisallowsInference: S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << effectName << Viol2.CalleeEffectPreventingInference->name(); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect) << Indirect_FunctionPtr << effectName; break; case ViolationID::AllocatesMemory: case ViolationID::ThrowsOrCatchesExceptions: case ViolationID::HasStaticLocalVariable: case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol2.Loc, diag::note_func_effect_violation) << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName << Viol2.diagnosticSelectIndex(); MaybeAddSiteContext(CalleeInfo.CDecl, Viol2); break; case ViolationID::CallsDeclWithoutEffect: MaybeNextCallee.emplace(*Viol2.Callee); S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName << GetCallableDeclKind(Viol2.Callee, nullptr) << MaybeNextCallee->getNameForDiagnostic(S); break; } MaybeAddTemplateNote(Callee); Callee = Viol2.Callee; if (MaybeNextCallee) { CalleeInfo = *MaybeNextCallee; CalleeName = CalleeInfo.getNameForDiagnostic(S); } } } break; } } } // ---------- // This AST visitor is used to traverse the body of a function during effect // verification. This happens in 2 situations: // [1] The function has declared effects which need to be validated. // [2] The function has not explicitly declared an effect in question, and is // being checked for implicit conformance. // // Violations are always routed to a PendingFunctionAnalysis. struct FunctionBodyASTVisitor : DynamicRecursiveASTVisitor { Analyzer &Outer; PendingFunctionAnalysis &CurrentFunction; CallableInfo &CurrentCaller; ViolationSite VSite; const Expr *TrailingRequiresClause = nullptr; const Expr *NoexceptExpr = nullptr; FunctionBodyASTVisitor(Analyzer &Outer, PendingFunctionAnalysis &CurrentFunction, CallableInfo &CurrentCaller) : Outer(Outer), CurrentFunction(CurrentFunction), CurrentCaller(CurrentCaller) { ShouldVisitImplicitCode = true; ShouldWalkTypesOfTypeLocs = false; } // -- Entry point -- void run() { // The target function may have implicit code paths beyond the // body: member and base destructors. Visit these first. if (auto *Dtor = dyn_cast(CurrentCaller.CDecl)) followDestructor(dyn_cast(Dtor->getParent()), Dtor); if (auto *FD = dyn_cast(CurrentCaller.CDecl)) { TrailingRequiresClause = FD->getTrailingRequiresClause(); // Note that FD->getType->getAs() can yield a // noexcept Expr which has been boiled down to a constant expression. // Going through the TypeSourceInfo obtains the actual expression which // will be traversed as part of the function -- unless we capture it // here and have TraverseStmt skip it. if (TypeSourceInfo *TSI = FD->getTypeSourceInfo()) { if (FunctionProtoTypeLoc TL = TSI->getTypeLoc().getAs()) if (const FunctionProtoType *FPT = TL.getTypePtr()) NoexceptExpr = FPT->getNoexceptExpr(); } } // Do an AST traversal of the function/block body TraverseDecl(const_cast(CurrentCaller.CDecl)); } // -- Methods implementing common logic -- // Handle a language construct forbidden by some effects. Only effects whose // flags include the specified flag receive a violation. \p Flag describes // the construct. void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, ViolationID VID, SourceLocation Loc, const Decl *Callee = nullptr) { // If there are any declared verifiable effects which forbid the construct // represented by the flag, store just one violation. for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) { if (Effect.flags() & Flag) { addViolation(/*inferring=*/false, Effect, VID, Loc, Callee); break; } } // For each inferred effect which forbids the construct, store a // violation, if we don't already have a violation for that effect. for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) if (Effect.flags() & Flag) addViolation(/*inferring=*/true, Effect, VID, Loc, Callee); } void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID, SourceLocation Loc, const Decl *Callee = nullptr) { CurrentFunction.checkAddViolation( Inferring, Violation(Effect, VID, VSite, Loc, Callee)); } // Here we have a call to a Decl, either explicitly via a CallExpr or some // other AST construct. CallableInfo pertains to the callee. void followCall(CallableInfo &CI, SourceLocation CallLoc) { // Check for a call to a builtin function, whose effects are // handled specially. if (const auto *FD = dyn_cast(CI.CDecl)) { if (unsigned BuiltinID = FD->getBuiltinID()) { CI.Effects = getBuiltinFunctionEffects(BuiltinID); if (CI.Effects.empty()) { // A builtin with no known effects is assumed safe. return; } // A builtin WITH effects doesn't get any special treatment for // being noreturn/noexcept, e.g. longjmp(), so we skip the check // below. } else { // If the callee is both `noreturn` and `noexcept`, it presumably // terminates. Ignore it for the purposes of effect analysis. // If not C++, `noreturn` alone is sufficient. if (FD->isNoReturn() && (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD))) return; } } Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*AssertNoFurtherInference=*/false, VSite); } void checkIndirectCall(CallExpr *Call, QualType CalleeType) { FunctionEffectKindSet CalleeEffects; if (FunctionEffectsRef Effects = FunctionEffectsRef::get(CalleeType); !Effects.empty()) CalleeEffects.insert(Effects); auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { if (Effect.shouldDiagnoseFunctionCall( /*direct=*/false, CalleeEffects)) addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect, Call->getBeginLoc()); }; for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) Check1Effect(Effect, false); for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) Check1Effect(Effect, true); } // This destructor's body should be followed by the caller, but here we // follow the field and base destructors. void followDestructor(const CXXRecordDecl *Rec, const CXXDestructorDecl *Dtor) { SourceLocation DtorLoc = Dtor->getLocation(); for (const FieldDecl *Field : Rec->fields()) followTypeDtor(Field->getType(), DtorLoc); if (const auto *Class = dyn_cast(Rec)) for (const CXXBaseSpecifier &Base : Class->bases()) followTypeDtor(Base.getType(), DtorLoc); } void followTypeDtor(QualType QT, SourceLocation CallSite) { const Type *Ty = QT.getTypePtr(); while (Ty->isArrayType()) { const ArrayType *Arr = Ty->getAsArrayTypeUnsafe(); QT = Arr->getElementType(); Ty = QT.getTypePtr(); } if (Ty->isRecordType()) { if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { if (CXXDestructorDecl *Dtor = Class->getDestructor(); Dtor && !Dtor->isDeleted()) { CallableInfo CI(*Dtor); followCall(CI, CallSite); } } } } // -- Methods for use of RecursiveASTVisitor -- bool VisitCXXThrowExpr(CXXThrowExpr *Throw) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, ViolationID::ThrowsOrCatchesExceptions, Throw->getThrowLoc()); return true; } bool VisitCXXCatchStmt(CXXCatchStmt *Catch) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, ViolationID::ThrowsOrCatchesExceptions, Catch->getCatchLoc()); return true; } bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, ViolationID::ThrowsOrCatchesExceptions, Throw->getThrowLoc()); return true; } bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, ViolationID::ThrowsOrCatchesExceptions, Catch->getAtCatchLoc()); return true; } bool VisitObjCAtFinallyStmt(ObjCAtFinallyStmt *Finally) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, ViolationID::ThrowsOrCatchesExceptions, Finally->getAtFinallyLoc()); return true; } bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, ViolationID::AccessesObjCMethodOrProperty, Msg->getBeginLoc()); return true; } bool VisitObjCAutoreleasePoolStmt(ObjCAutoreleasePoolStmt *ARP) override { // Under the hood, @autorelease (potentially?) allocates memory and // invokes ObjC methods. We don't currently have memory allocation as // a "language construct" but we do have ObjC messaging, so diagnose that. diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, ViolationID::AccessesObjCMethodOrProperty, ARP->getBeginLoc()); return true; } bool VisitObjCAtSynchronizedStmt(ObjCAtSynchronizedStmt *Sync) override { // Under the hood, this calls objc_sync_enter and objc_sync_exit, wrapped // in a @try/@finally block. Diagnose this generically as "ObjC // messaging". diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, ViolationID::AccessesObjCMethodOrProperty, Sync->getBeginLoc()); return true; } bool VisitSEHExceptStmt(SEHExceptStmt *Exc) override { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, ViolationID::ThrowsOrCatchesExceptions, Exc->getExceptLoc()); return true; } bool VisitCallExpr(CallExpr *Call) override { LLVM_DEBUG(llvm::dbgs() << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.S.SourceMgr) << "\n";); Expr *CalleeExpr = Call->getCallee(); if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { CallableInfo CI(*Callee); followCall(CI, Call->getBeginLoc()); return true; } if (isa(CalleeExpr)) { // Just destroying a scalar, fine. return true; } // No Decl, just an Expr. Just check based on its type. checkIndirectCall(Call, CalleeExpr->getType()); return true; } bool VisitVarDecl(VarDecl *Var) override { LLVM_DEBUG(llvm::dbgs() << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.S.SourceMgr) << "\n";); if (Var->isStaticLocal()) diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, ViolationID::HasStaticLocalVariable, Var->getLocation()); const QualType::DestructionKind DK = Var->needsDestruction(Outer.S.getASTContext()); if (DK == QualType::DK_cxx_destructor) followTypeDtor(Var->getType(), Var->getLocation()); return true; } bool VisitCXXNewExpr(CXXNewExpr *New) override { // RecursiveASTVisitor does not visit the implicit call to operator new. if (FunctionDecl *FD = New->getOperatorNew()) { CallableInfo CI(*FD, SpecialFuncType::OperatorNew); followCall(CI, New->getBeginLoc()); } // It's a bit excessive to check operator delete here, since it's // just a fallback for operator new followed by a failed constructor. // We could check it via New->getOperatorDelete(). // It DOES however visit the called constructor return true; } bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) override { // RecursiveASTVisitor does not visit the implicit call to operator // delete. if (FunctionDecl *FD = Delete->getOperatorDelete()) { CallableInfo CI(*FD, SpecialFuncType::OperatorDelete); followCall(CI, Delete->getBeginLoc()); } // It DOES however visit the called destructor return true; } bool VisitCXXConstructExpr(CXXConstructExpr *Construct) override { LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : " << Construct->getBeginLoc().printToString( Outer.S.SourceMgr) << "\n";); // RecursiveASTVisitor does not visit the implicit call to the // constructor. const CXXConstructorDecl *Ctor = Construct->getConstructor(); CallableInfo CI(*Ctor); followCall(CI, Construct->getLocation()); return true; } bool TraverseStmt(Stmt *Statement) override { // If this statement is a `requires` clause from the top-level function // being traversed, ignore it, since it's not generating runtime code. // We skip the traversal of lambdas (beyond their captures, see // TraverseLambdaExpr below), so just caching this from our constructor // should suffice. if (Statement != TrailingRequiresClause && Statement != NoexceptExpr) return DynamicRecursiveASTVisitor::TraverseStmt(Statement); return true; } bool TraverseConstructorInitializer(CXXCtorInitializer *Init) override { ViolationSite PrevVS = VSite; if (Init->isAnyMemberInitializer()) VSite.setKind(ViolationSite::Kind::MemberInitializer); bool Result = DynamicRecursiveASTVisitor::TraverseConstructorInitializer(Init); VSite = PrevVS; return Result; } bool TraverseCXXDefaultArgExpr(CXXDefaultArgExpr *E) override { LLVM_DEBUG(llvm::dbgs() << "TraverseCXXDefaultArgExpr : " << E->getUsedLocation().printToString(Outer.S.SourceMgr) << "\n";); ViolationSite PrevVS = VSite; if (VSite.kind() == ViolationSite::Kind::Default) VSite = ViolationSite{E}; bool Result = DynamicRecursiveASTVisitor::TraverseCXXDefaultArgExpr(E); VSite = PrevVS; return Result; } bool TraverseLambdaExpr(LambdaExpr *Lambda) override { // We override this so as to be able to skip traversal of the lambda's // body. We have to explicitly traverse the captures. Why not return // false from shouldVisitLambdaBody()? Because we need to visit a lambda's // body when we are verifying the lambda itself; we only want to skip it // in the context of the outer function. for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I, Lambda->capture_init_begin()[I]); return true; } bool TraverseBlockExpr(BlockExpr * /*unused*/) override { // As with lambdas, don't traverse the block's body. // TODO: are the capture expressions (ctor call?) safe? return true; } bool VisitDeclRefExpr(DeclRefExpr *E) override { const ValueDecl *Val = E->getDecl(); if (const auto *Var = dyn_cast(Val)) { if (Var->getTLSKind() != VarDecl::TLS_None) { // At least on macOS, thread-local variables are initialized on // first access, including a heap allocation. diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars, ViolationID::AccessesThreadLocalVariable, E->getLocation()); } } return true; } bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) override { return TraverseStmt(Node->getResultExpr()); } bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) override { return true; } bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) override { return true; } bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) override { return true; } bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) override { return true; } bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override { return true; } // Skip concept requirements since they don't generate code. bool TraverseConceptRequirement(concepts::Requirement *R) override { return true; } }; }; Analyzer::AnalysisMap::~AnalysisMap() { for (const auto &Item : *this) { FuncAnalysisPtr AP = Item.second; if (auto *PFA = dyn_cast(AP)) delete PFA; else delete cast(AP); } } } // anonymous namespace namespace clang { bool Sema::diagnoseConflictingFunctionEffect( const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC, SourceLocation NewAttrLoc) { // If the new effect has a condition, we can't detect conflicts until the // condition is resolved. if (NewEC.Cond.getCondition() != nullptr) return false; // Diagnose the new attribute as incompatible with a previous one. auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) { Diag(NewAttrLoc, diag::err_attributes_are_not_compatible) << ("'" + NewEC.description() + "'") << ("'" + PrevEC.description() + "'") << false; // We don't necessarily have the location of the previous attribute, // so no note. return true; }; // Compare against previous attributes. FunctionEffect::Kind NewKind = NewEC.Effect.kind(); for (const FunctionEffectWithCondition &PrevEC : FX) { // Again, can't check yet when the effect is conditional. if (PrevEC.Cond.getCondition() != nullptr) continue; FunctionEffect::Kind PrevKind = PrevEC.Effect.kind(); // Note that we allow PrevKind == NewKind; it's redundant and ignored. if (PrevEC.Effect.oppositeKind() == NewKind) return Incompatible(PrevEC); // A new allocating is incompatible with a previous nonblocking. if (PrevKind == FunctionEffect::Kind::NonBlocking && NewKind == FunctionEffect::Kind::Allocating) return Incompatible(PrevEC); // A new nonblocking is incompatible with a previous allocating. if (PrevKind == FunctionEffect::Kind::Allocating && NewKind == FunctionEffect::Kind::NonBlocking) return Incompatible(PrevEC); } return false; } void Sema::diagnoseFunctionEffectMergeConflicts( const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc, SourceLocation OldLoc) { for (const FunctionEffectSet::Conflict &Conflict : Errs) { Diag(NewLoc, diag::warn_conflicting_func_effects) << Conflict.Kept.description() << Conflict.Rejected.description(); Diag(OldLoc, diag::note_previous_declaration); } } // Decl should be a FunctionDecl or BlockDecl. void Sema::maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { if (!D->hasBody()) { if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody()) return; } if (Diags.getIgnoreAllWarnings() || (Diags.getSuppressSystemWarnings() && SourceMgr.isInSystemHeader(D->getLocation()))) return; if (hasUncompilableErrorOccurred()) return; // For code in dependent contexts, we'll do this at instantiation time. // Without this check, we would analyze the function based on placeholder // template parameters, and potentially generate spurious diagnostics. if (cast(D)->isDependentContext()) return; addDeclWithEffects(D, FX); } void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { // To avoid the possibility of conflict, don't add effects which are // not FE_InferrableOnCallees and therefore not verified; this removes // blocking/allocating but keeps nonblocking/nonallocating. // Also, ignore any conditions when building the list of effects. bool AnyVerifiable = false; for (const FunctionEffectWithCondition &EC : FX) if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) { AllEffectsToVerify.insert(EC.Effect); AnyVerifiable = true; } // Record the declaration for later analysis. if (AnyVerifiable) DeclsWithEffectsToVerify.push_back(D); } void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) { if (hasUncompilableErrorOccurred() || Diags.getIgnoreAllWarnings()) return; if (TU == nullptr) return; Analyzer{*this}.run(*TU); } Sema::FunctionEffectDiffVector::FunctionEffectDiffVector( const FunctionEffectsRef &Old, const FunctionEffectsRef &New) { FunctionEffectsRef::iterator POld = Old.begin(); FunctionEffectsRef::iterator OldEnd = Old.end(); FunctionEffectsRef::iterator PNew = New.begin(); FunctionEffectsRef::iterator NewEnd = New.end(); while (true) { int cmp = 0; if (POld == OldEnd) { if (PNew == NewEnd) break; cmp = 1; } else if (PNew == NewEnd) cmp = -1; else { FunctionEffectWithCondition Old = *POld; FunctionEffectWithCondition New = *PNew; if (Old.Effect.kind() < New.Effect.kind()) cmp = -1; else if (New.Effect.kind() < Old.Effect.kind()) cmp = 1; else { cmp = 0; if (Old.Cond.getCondition() != New.Cond.getCondition()) { // FIXME: Cases where the expressions are equivalent but // don't have the same identity. push_back(FunctionEffectDiff{ Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch, Old, New}); } } } if (cmp < 0) { // removal FunctionEffectWithCondition Old = *POld; push_back(FunctionEffectDiff{Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, std::nullopt}); ++POld; } else if (cmp > 0) { // addition FunctionEffectWithCondition New = *PNew; push_back(FunctionEffectDiff{New.Effect.kind(), FunctionEffectDiff::Kind::Added, std::nullopt, New}); ++PNew; } else { ++POld; ++PNew; } } } bool Sema::FunctionEffectDiff::shouldDiagnoseConversion( QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType, const FunctionEffectsRef &DstFX) const { switch (EffectKind) { case FunctionEffect::Kind::NonAllocating: // nonallocating can't be added (spoofed) during a conversion, unless we // have nonblocking. if (DiffKind == Kind::Added) { for (const auto &CFE : SrcFX) { if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking) return false; } } [[fallthrough]]; case FunctionEffect::Kind::NonBlocking: // nonblocking can't be added (spoofed) during a conversion. switch (DiffKind) { case Kind::Added: return true; case Kind::Removed: return false; case Kind::ConditionMismatch: // FIXME: Condition mismatches are too coarse right now -- expressions // which are equivalent but don't have the same identity are detected as // mismatches. We're going to diagnose those anyhow until expression // matching is better. return true; } break; case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return false; } llvm_unreachable("unknown effect kind"); } bool Sema::FunctionEffectDiff::shouldDiagnoseRedeclaration( const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX, const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const { switch (EffectKind) { case FunctionEffect::Kind::NonAllocating: case FunctionEffect::Kind::NonBlocking: // nonblocking/nonallocating can't be removed in a redeclaration. switch (DiffKind) { case Kind::Added: return false; // No diagnostic. case Kind::Removed: return true; // Issue diagnostic. case Kind::ConditionMismatch: // All these forms of mismatches are diagnosed. return true; } break; case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return false; } llvm_unreachable("unknown effect kind"); } Sema::FunctionEffectDiff::OverrideResult Sema::FunctionEffectDiff::shouldDiagnoseMethodOverride( const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const { switch (EffectKind) { case FunctionEffect::Kind::NonAllocating: case FunctionEffect::Kind::NonBlocking: switch (DiffKind) { // If added on an override, that's fine and not diagnosed. case Kind::Added: return OverrideResult::NoAction; // If missing from an override (removed), propagate from base to derived. case Kind::Removed: return OverrideResult::Merge; // If there's a mismatch involving the effect's polarity or condition, // issue a warning. case Kind::ConditionMismatch: return OverrideResult::Warn; } break; case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return OverrideResult::NoAction; } llvm_unreachable("unknown effect kind"); } } // namespace clang