[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 14 07:54:10 PDT 2024
================
@@ -0,0 +1,1199 @@
+//=== EffectAnalysis.cpp - Sema warnings for 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 caller/callee analysis for function effects.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Sema/SemaInternal.h"
+
+#define DEBUG_TYPE "fxanalysis"
+
+using namespace clang;
+
+namespace {
+
+enum class ViolationID : uint8_t {
+ None = 0, // sentinel for an empty Violation
+ Throws,
+ Catches,
+ CallsObjC,
+ AllocatesMemory,
+ HasStaticLocal,
+ AccessesThreadLocal,
+
+ // These only apply to callees, where the analysis stops at the Decl
+ DeclDisallowsInference,
+
+ CallsDeclWithoutEffect,
+ CallsExprWithoutEffect,
+};
+
+// 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;
+ FunctionEffect CalleeEffectPreventingInference; // only for certain IDs
+ ViolationID ID = ViolationID::None;
+ SourceLocation Loc;
+ const Decl *Callee = nullptr; // only valid for Calls*
+
+ Violation() = default;
+
+ Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr,
+ const FunctionEffect *CalleeEffect = nullptr)
+ : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
+ if (CalleeEffect != nullptr)
+ CalleeEffectPreventingInference = *CalleeEffect;
+ }
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallableType {
+ // 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()->castAs<FunctionProtoType>();
+ if (FPT->isNothrow() || FD->hasAttr<NoThrowAttr>())
+ return true;
+ return false;
+}
+
+// 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<FunctionDecl>(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<CXXMethodDecl>(FD);
+ Method && Method->isVirtual())
+ CType = CallableType::Virtual;
+ DeclEffects = FD->getFunctionEffects();
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+ CType = CallableType::Block;
+ DeclEffects = BD->getFunctionEffects();
+ } else if (auto *VD = dyn_cast<ValueDecl>(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<FunctionDecl>(CDecl));
+ }
+ llvm_unreachable("undefined CallableType");
+ }
+
+ /// Generate a name for logging and diagnostics.
+ std::string name(Sema &Sem) const {
+ std::string Name;
+ llvm::raw_string_ostream OS(Name);
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+ FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+ else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
+ OS << "(block " << BD->getBlockManglingNumber() << ")";
+ else if (auto *VD = dyn_cast<NamedDecl>(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 sorted 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.
+ using ImplVec = llvm::SmallVector<Violation, 1>;
+ std::unique_ptr<ImplVec> 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<ImplVec>();
+ auto *Iter = _find(Viol.Effect);
+ if (Iter != Impl->end() && Iter->Effect == Viol.Effect)
+ return;
+
+ Impl->insert(Iter, Viol);
+ }
+
+ const Violation *lookup(FunctionEffect Key) {
+ if (Impl == nullptr)
+ return nullptr;
+
+ auto *Iter = _find(Key);
+ if (Iter != Impl->end() && Iter->Effect == Key)
+ return &*Iter;
+
+ return nullptr;
+ }
+
+ size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+ ImplVec::iterator _find(const FunctionEffect &key) {
+ // A linear search suffices for a tiny number of possible effects.
+ auto *End = Impl->end();
+ for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
+ if (!(Iter->Effect < key))
+ return Iter;
+ return End;
+ }
+};
+
+// ----------
+// 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;
+
+ DirectCall(const Decl *D, SourceLocation CallLoc)
+ : Callee(D), CallLoc(CallLoc) {}
+ };
+
+ // 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 FXToInfer;
+
+private:
+ // Violations pertaining to the function's explicit effects.
+ SmallVector<Violation, 0> ViolationsForExplicitFX;
+
+ // 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<DirectCall, 0> UnverifiedDirectCalls;
+
+public:
+ PendingFunctionAnalysis(
+ Sema &Sem, const CallableInfo &CInfo,
+ const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ : DeclaredVerifiableEffects(CInfo.Effects) {
+ // Check for effects we are not allowed to infer
+ FunctionEffectKindSet InferrableFX;
+
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+ std::optional<FunctionEffect> ProblemCalleeEffect =
+ effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
+ if (!ProblemCalleeEffect)
+ InferrableFX.insert(effect);
+ else {
+ // Add a Violation for this effect if a caller were to
+ // try to infer it.
+ InferrableEffectToFirstViolation.maybeInsert(Violation(
+ effect, ViolationID::DeclDisallowsInference,
+ CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
+ }
+ }
+ // InferrableFX is now the set of inferrable effects which are not
+ // prohibited
+ FXToInfer = FunctionEffectKindSet::difference(InferrableFX,
+ 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)
+ ViolationsForExplicitFX.push_back(NewViol);
+ else
+ InferrableEffectToFirstViolation.maybeInsert(NewViol);
+ }
+
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
+ UnverifiedDirectCalls.emplace_back(D, CallLoc);
+ }
+
+ // 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);
+ }
+
+ SmallVector<DirectCall, 0> &unverifiedCalls() {
+ assert(!isComplete());
+ return UnverifiedDirectCalls;
+ }
+
+ SmallVector<Violation, 0> &getViolationsForExplicitFX() {
+ return ViolationsForExplicitFX;
+ }
+
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
+ OS << "Pending: Declared ";
+ DeclaredVerifiableEffects.dump(OS);
+ OS << ", " << ViolationsForExplicitFX.size() << " violations; ";
+ OS << " Infer ";
+ FXToInfer.dump(OS);
+ OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
+ if (!UnverifiedDirectCalls.empty()) {
+ OS << "; Calls: ";
+ for (const DirectCall &Call : UnverifiedDirectCalls) {
+ CallableInfo CI(*Call.Callee);
+ OS << " " << CI.name(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,
+ const FunctionEffectKindSet &DeclaredEffects,
+ const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ : VerifiedEffects(DeclaredEffects) {
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+ if (Pending.violationForInferrableEffect(effect) == nullptr)
+ VerifiedEffects.insert(effect);
+
+ InferrableEffectToFirstViolation =
+ std::move(Pending.InferrableEffectToFirstViolation);
+ }
+
+ const Violation *firstViolationForEffect(const 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";
+ }
+};
+
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+ if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ FD = FD->getCanonicalDecl();
+ assert(FD != nullptr);
+ return FD;
+ }
+ return D;
+}
+
+// ==========
+class Analyzer {
+ Sema &Sem;
+
+ // Subset of Sema.AllEffectsToVerify
+ FunctionEffectKindSet AllInferrableEffectsToVerify;
+
+ using FuncAnalysisPtr =
+ llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
+
+ // 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 : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
+
+ 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(CanonicalFunctionDecl(Key));
+ }
+
+ FuncAnalysisPtr &operator[](const Decl *Key) {
+ return Base::operator[](CanonicalFunctionDecl(Key));
+ }
+
+ /// 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<CompleteFunctionAnalysis *>(AP))
+ return AP.get<CompleteFunctionAnalysis *>();
+ 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.name(SemaRef) << " : ";
+ if (AP.isNull())
+ OS << "null\n";
+ else if (isa<CompleteFunctionAnalysis *>(AP)) {
+ auto *CFA = AP.get<CompleteFunctionAnalysis *>();
+ OS << CFA << " ";
+ CFA->dump(OS);
+ } else if (isa<PendingFunctionAnalysis *>(AP)) {
+ auto *PFA = AP.get<PendingFunctionAnalysis *>();
+ OS << PFA << " ";
+ PFA->dump(SemaRef, OS);
+ } else
+ llvm_unreachable("never");
+ }
+ OS << "---\n";
+ }
+ };
+ AnalysisMap DeclAnalysis;
+
+public:
+ Analyzer(Sema &S) : Sem(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 (const FunctionEffect &Effect : Sem.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<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify;
+ std::reverse(VerificationQueue.begin(), VerificationQueue.end());
+
+ while (!VerificationQueue.empty()) {
+ const Decl *D = VerificationQueue.back();
+ if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ // already done
+ VerificationQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto *Pending = AP.get<PendingFunctionAnalysis *>();
+ finishPendingAnalysis(D, Pending);
+ VerificationQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+
+ // 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;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // This indicates recursion (not necessarily direct). For the
+ // purposes of effect analysis, we can just ignore it since
+ // no effects forbid recursion.
+ Call.Recursed = true;
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+ }
+ }
+
+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<FunctionDecl>(D)) {
+ assert(FD->getBuiltinID() == 0);
+ 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 (Sem.getLangOpts().CPlusPlus && !isExternC) {
+ for (const 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<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten()) {
+ auto *FPT = TSI->getType()->getAs<FunctionProtoType>();
+ IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
+ }
+ }
+ if (!IsNoexcept)
+ Sem.Diag(D->getBeginLoc(),
+ diag::warn_perf_constraint_implies_noexcept)
+ << 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(Sem, CInfo, AllInferrableEffectsToVerify);
+
+ LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
+ FAnalysis.dump(Sem, llvm::dbgs()););
+
+ FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
+
+ Visitor.run();
+ if (FAnalysis.isComplete()) {
+ completeAnalysis(CInfo, 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(Sem, llvm::dbgs()););
+ return PendingPtr;
+ }
+
+ // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
+ // inserted in the container.
+ void completeAnalysis(const CallableInfo &CInfo,
+ PendingFunctionAnalysis &Pending) {
+ if (SmallVector<Violation, 0> &Viols = Pending.getViolationsForExplicitFX();
+ !Viols.empty())
+ emitDiagnostics(Viols, CInfo, Sem);
+
+ CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+ Sem.getASTContext(), Pending, CInfo.Effects,
+ AllInferrableEffectsToVerify);
+ DeclAnalysis[CInfo.CDecl] = CompletePtr;
+ LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, 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.name(Sem) << " : ";
+ Pending->dump(Sem, 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);
+ }
+ completeAnalysis(Caller, *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) {
+ 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.name(Sem) << " to "
+ << Callee.name(Sem) << "; verifiable: "
+ << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
+ llvm::dbgs()
+ << " callee " << Callee.CDecl << " canonical "
+ << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+ for (Decl *D
+ : Callee.CDecl->redecls()) llvm::dbgs()
+ << " " << D;
+
+ llvm::dbgs() << "\n";);
+
+ auto check1Effect = [&](const 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,
+ CallLoc, Callee.CDecl});
+ else
+ PFA.checkAddViolation(
+ Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ }
+ };
+
+ for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : PFA.FXToInfer)
+ check1Effect(Effect, true);
+ }
+
+ // Should only be called when function's analysis is determined to be
+ // complete.
+ void emitDiagnostics(SmallVector<Violation, 0> &Viols,
+ const CallableInfo &CInfo, Sema &S) {
+ if (Viols.empty())
+ return;
+ const SourceManager &SM = S.getSourceManager();
+ std::sort(Viols.begin(), Viols.end(),
+ [&SM](const Violation &LHS, const Violation &RHS) {
+ return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+ });
+
+ auto checkAddTemplateNote = [&](const Decl *D) {
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ while (FD != nullptr && FD->isTemplateInstantiation()) {
+ S.Diag(FD->getPointOfInstantiation(),
+ diag::note_func_effect_from_template);
+ FD = FD->getTemplateInstantiationPattern();
+ }
+ }
+ };
+
+ // 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:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case ViolationID::Throws:
+ case ViolationID::Catches:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case ViolationID::HasStaticLocal:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case ViolationID::AccessesThreadLocal:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case ViolationID::CallsObjC:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case ViolationID::CallsExprWithoutEffect:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+
+ case ViolationID::CallsDeclWithoutEffect: {
+ CallableInfo CalleeInfo(*Viol1.Callee);
+ std::string CalleeName = CalleeInfo.name(S);
+
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
+ << effectName << CalleeName;
+ checkAddTemplateNote(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<CallableInfo> 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_virtual)
+ << effectName;
+ else if (CType == CallableType::Unknown)
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_func_ptr)
+ << effectName;
+ else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_disallows_inference)
+ << effectName
+ << FunctionEffect(Viol1.Effect.oppositeKind()).name();
+ else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(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)
+ << effectName << Viol2.CalleeEffectPreventingInference.name();
+ break;
+ case ViolationID::CallsExprWithoutEffect:
+ S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr)
+ << effectName;
+ break;
+ case ViolationID::AllocatesMemory:
+ S.Diag(Viol2.Loc, diag::note_func_effect_allocates) << effectName;
+ break;
+ case ViolationID::Throws:
+ case ViolationID::Catches:
+ S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches)
+ << effectName;
+ break;
+ case ViolationID::HasStaticLocal:
+ S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local)
+ << effectName;
+ break;
+ case ViolationID::AccessesThreadLocal:
+ S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local)
+ << effectName;
+ break;
+ case ViolationID::CallsObjC:
+ S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
+ break;
+ case ViolationID::CallsDeclWithoutEffect:
+ MaybeNextCallee.emplace(*Viol2.Callee);
+ S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
+ << effectName << MaybeNextCallee->name(S);
+ break;
+ }
+ checkAddTemplateNote(Callee);
+ Callee = Viol2.Callee;
+ if (MaybeNextCallee) {
+ CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(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
+ : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+
+ Analyzer &Outer;
+ PendingFunctionAnalysis &CurrentFunction;
+ CallableInfo &CurrentCaller;
+
+ FunctionBodyASTVisitor(Analyzer &outer,
+ PendingFunctionAnalysis &CurrentFunction,
+ CallableInfo &CurrentCaller)
+ : Outer(outer), CurrentFunction(CurrentFunction),
+ CurrentCaller(CurrentCaller) {}
+
+ // -- 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<CXXDestructorDecl>(CurrentCaller.CDecl))
+ followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+
+ // Do an AST traversal of the function/block body
+ TraverseDecl(const_cast<Decl *>(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 (const 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 (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ if (Effect.flags() & Flag)
+ addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
+ }
+
+ void addViolation(bool Inferring, const FunctionEffect &Effect,
+ ViolationID D, SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ CurrentFunction.checkAddViolation(Inferring,
+ Violation(Effect, D, 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(const CallableInfo &CI, SourceLocation CallLoc) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
+ FD && isSafeBuiltinFunction(FD))
+ return;
+
+ Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+ /*AssertNoFurtherInference=*/false);
+ }
+
+ // FIXME: This is currently specific to the `nonblocking` and
+ // `nonallocating` effects. More ideally, the builtin functions themselves
+ // would have the `allocating` attribute.
+ static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
+ unsigned BuiltinID = FD->getBuiltinID();
+ switch (BuiltinID) {
+ case 0: // not builtin
+ return false;
+ default: // not disallowed via cases below
+ return true;
+
+ // Disallow list
+ case Builtin::ID::BIaligned_alloc:
+ 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::BIcalloc:
+ case Builtin::ID::BImalloc:
+ case Builtin::ID::BImemalign:
+ case Builtin::ID::BIrealloc:
+ case Builtin::ID::BIfree:
+ return false;
+ }
+ llvm_unreachable("above switch is exhaustive");
+ }
+
+ void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
+ auto *FPT =
+ CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+ FunctionEffectKindSet CalleeFX;
+ if (FPT)
+ CalleeFX.insert(FPT->getFunctionEffects());
+
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+ /*direct=*/false, CalleeFX))
+ addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
+ Call->getBeginLoc());
+ };
+
+ for (const FunctionEffect &Effect :
+ CurrentFunction.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ 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) {
+ for (const FieldDecl *Field : Rec->fields())
+ followTypeDtor(Field->getType(), Dtor);
+
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ for (const CXXBaseSpecifier &Base : Class->bases())
+ followTypeDtor(Base.getType(), Dtor);
+
+ for (const CXXBaseSpecifier &Base : Class->vbases())
+ followTypeDtor(Base.getType(), Dtor);
+ }
+ }
+
+ void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) {
+ 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()) {
+ CallableInfo CI(*Dtor);
+ followCall(CI, OuterDtor->getLocation());
+ }
+ }
+ }
+ }
+
+ // -- Methods for use of RecursiveASTVisitor --
+
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+ bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ ViolationID::Throws, Throw->getThrowLoc());
+ return true;
+ }
+
+ bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ ViolationID::Catches, Catch->getCatchLoc());
+ return true;
+ }
+
+ bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ ViolationID::Throws, Throw->getThrowLoc());
+ return true;
+ }
+
+ bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ ViolationID::Catches, Catch->getAtCatchLoc());
+ return true;
+ }
+
+ bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+ ViolationID::CallsObjC, Msg->getBeginLoc());
+ return true;
+ }
+
+ bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ ViolationID::Catches, Exc->getExceptLoc());
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr *Call) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "VisitCallExpr : "
+ << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";);
+
+ Expr *CalleeExpr = Call->getCallee();
+ if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+ CallableInfo CI(*Callee);
+ followCall(CI, Call->getBeginLoc());
+ return true;
+ }
+
+ if (isa<CXXPseudoDestructorExpr>(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) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "VisitVarDecl : "
+ << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";);
+
+ if (Var->isStaticLocal())
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
+ ViolationID::HasStaticLocal,
+ Var->getLocation());
+
+ const QualType::DestructionKind DK =
+ Var->needsDestruction(Outer.Sem.getASTContext());
+ if (DK == QualType::DK_cxx_destructor) {
+ QualType QT = Var->getType();
+ if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+ if (const auto *CxxRec =
+ dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+ if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
+ CallableInfo CI(*Dtor);
+ followCall(CI, Var->getLocation());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ bool VisitCXXNewExpr(CXXNewExpr *New) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the 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) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the 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) {
+ LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
+ << Construct->getBeginLoc().printToString(
+ Outer.Sem.SourceMgr)
+ << "\n";);
+
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to the constructor.
+ const CXXConstructorDecl *Ctor = Construct->getConstructor();
+ CallableInfo CI(*Ctor);
+ followCall(CI, Construct->getLocation());
+
+ return true;
+ }
+
+ bool TraverseLambdaExpr(LambdaExpr *Lambda) {
+ // We override this so as the 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*/) {
+ // TODO: are the capture expressions (ctor call?) safe?
+ return true;
+ }
+
+ bool VisitDeclRefExpr(const DeclRefExpr *E) {
+ const ValueDecl *Val = E->getDecl();
+ if (isa<VarDecl>(Val)) {
+ const VarDecl *Var = cast<VarDecl>(Val);
+ VarDecl::TLSKind TLSK = Var->getTLSKind();
+ if (TLSK != VarDecl::TLS_None) {
+ // At least on macOS, thread-local variables are initialized on
+ // first access, including a heap allocation.
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
+ ViolationID::AccessesThreadLocal,
+ E->getLocation());
+ }
+ }
+ return true;
+ }
+
+ bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+ return TraverseStmt(Node->getResultExpr());
+ }
+ bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+ return true;
+ }
+
+ bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return true; }
+
+ bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return true; }
+
+ bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return true; }
+
+ bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; }
+ };
+};
+
+Analyzer::AnalysisMap::~AnalysisMap() {
+ for (const auto &Item : *this) {
+ FuncAnalysisPtr AP = Item.second;
+ if (isa<PendingFunctionAnalysis *>(AP))
+ delete AP.get<PendingFunctionAnalysis *>();
+ else
+ delete AP.get<CompleteFunctionAnalysis *>();
+ }
+}
+
+} // anonymous namespace
+
+namespace clang {
+
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
+ if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+ // exit if having uncompilable errors or ignoring all warnings:
+ return;
+ if (TU == nullptr)
+ return;
----------------
Sirraide wrote:
Fascinating. I guess it doesn’t hurt?
https://github.com/llvm/llvm-project/pull/99656
More information about the cfe-commits
mailing list