[clang] nolock/noalloc attributes (PR #84983)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 12 16:22:56 PDT 2024
================
@@ -2380,6 +2382,1239 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
};
} // namespace
+// =============================================================================
+
+// Temporary debugging option
+#define FX_ANALYZER_VERIFY_DECL_LIST 1
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+ None = 0, // sentinel for an empty Diagnostic
+ Throws,
+ Catches,
+ CallsObjC,
+ AllocatesMemory,
+ HasStaticLocal,
+ AccessesThreadLocal,
+
+ // These only apply to callees, where the analysis stops at the Decl
+ DeclWithoutConstraintOrInference,
+
+ CallsUnsafeDecl,
+ CallsDisallowedExpr,
+};
+
+struct Diagnostic {
+ const FunctionEffect *Effect = nullptr;
+ const Decl *Callee = nullptr; // only valid for Calls*
+ SourceLocation Loc;
+ DiagnosticID ID = DiagnosticID::None;
+
+ Diagnostic() = default;
+
+ Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+ : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+ Unknown,
+ Function,
+ Virtual,
+ Block
+ // unknown: probably function pointer
+};
+
+// Return whether the function CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+ if (!(FD->hasBody() || FD->isInlined())) {
+ // externally defined; we couldn't verify if we wanted to.
+ return false;
+ }
+ if (FD->isTrivial()) {
+ // Otherwise `struct x { int a; };` would have an unverifiable default
+ // constructor.
+ return true;
+ }
+ return true;
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer...
+struct CallableInfo {
+ const Decl *CDecl;
+ mutable std::optional<std::string>
+ MaybeName; // mutable because built on demand in const method
+ SpecialFuncType FuncType = SpecialFuncType::None;
+ FunctionEffectSet Effects;
+ CallType CType = CallType::Unknown;
+
+ CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+ : CDecl(&CD), FuncType(FT) {
+ // llvm::errs() << "CallableInfo " << name() << "\n";
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+ assert(FD->getCanonicalDecl() == FD);
+ // Use the function's definition, if any.
+ if (auto *Def = FD->getDefinition()) {
+ CDecl = FD = Def;
+ }
+ CType = CallType::Function;
+ if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
+ if (Method->isVirtual()) {
+ CType = CallType::Virtual;
+ }
+ }
+ Effects = FD->getFunctionEffects();
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+ CType = CallType::Block;
+ Effects = BD->getFunctionEffects();
+ } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
+ // ValueDecl is function, enum, or variable, so just look at the type.
+ Effects = FunctionEffectSet::get(*VD->getType());
+ }
+ }
+
+ bool isDirectCall() const {
+ return CType == CallType::Function || CType == CallType::Block;
+ }
+
+ bool isVerifiable() const {
+ switch (CType) {
+ case CallType::Unknown:
+ case CallType::Virtual:
+ break;
+ case CallType::Block:
+ return true;
+ case CallType::Function:
+ return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+ }
+ return false;
+ }
+
+ /// Generate a name for logging and diagnostics.
+ std::string name(Sema &Sem) const {
+ if (!MaybeName) {
+ 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);
+ }
+ MaybeName = Name;
+ }
+ return *MaybeName;
+ }
+};
+
+// ----------
+// Map effects to single diagnostics.
+class EffectToDiagnosticMap {
+ // Since we currently only have a tiny number of effects (typically no more
+ // than 1), use a sorted SmallVector.
+ using Element = std::pair<const FunctionEffect *, Diagnostic>;
+ using ImplVec = llvm::SmallVector<Element>;
+ std::unique_ptr<ImplVec> Impl;
+
+public:
+ Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
+ if (Impl == nullptr) {
+ Impl = std::make_unique<llvm::SmallVector<Element>>();
+ auto &Item = Impl->emplace_back();
+ Item.first = Key;
+ return Item.second;
+ }
+ Element Elem(Key, {});
+ auto Iter = _find(Elem);
+ if (Iter != Impl->end() && Iter->first == Key) {
+ return Iter->second;
+ }
+ Iter = Impl->insert(Iter, Elem);
+ return Iter->second;
+ }
+
+ const Diagnostic *lookup(const FunctionEffect *key) {
+ if (Impl == nullptr) {
+ return nullptr;
+ }
+ Element elem(key, {});
+ auto iter = _find(elem);
+ if (iter != Impl->end() && iter->first == key) {
+ return &iter->second;
+ }
+ return nullptr;
+ }
+
+ size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+ ImplVec::iterator _find(const Element &elem) {
+ return std::lower_bound(Impl->begin(), Impl->end(), elem,
+ [](const Element &lhs, const Element &rhs) {
+ return lhs.first < rhs.first;
+ });
+ }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked. Since there are
+// potentially a large number of these objects, it needs care about size.
+class PendingFunctionAnalysis {
+ // Current size: 5 pointers
+ friend class CompleteFunctionAnalysis;
+
+ struct DirectCall {
+ const Decl *Callee;
+ SourceLocation CallLoc;
+ };
+
+public:
+ // We always have two disjoint sets of effects to verify:
+ // 1. Effects declared explicitly by this function.
+ // 2. All other inferrable effects needing verification.
+ FunctionEffectSet DeclaredVerifiableEffects;
+ FunctionEffectSet FXToInfer;
+
+private:
+ // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr
+ // to optimize size for the case of 0 diagnostics.
+ std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
+
+ // Potential diagnostics pertaining to other, non-explicit, inferrable
+ // effects.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+ std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
+
+public:
+ PendingFunctionAnalysis(const CallableInfo &cinfo,
+ FunctionEffectSet AllInferrableEffectsToVerify) {
+ MutableFunctionEffectSet fx;
+ for (const auto *effect : cinfo.Effects) {
+ if (effect->flags() & FunctionEffect::kRequiresVerification) {
+ fx.insert(effect);
+ }
+ }
+ DeclaredVerifiableEffects = FunctionEffectSet::create(fx);
+
+ // Check for effects we are not allowed to infer
+ fx.clear();
+ for (const auto *effect : AllInferrableEffectsToVerify) {
+ if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
+ fx.insert(effect);
+ } else {
+ // Add a diagnostic for this effect if a caller were to
+ // try to infer it.
+ auto &diag =
+ InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
+ diag =
+ Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
+ cinfo.CDecl->getLocation());
+ }
+ }
+ // fx is now the set of inferrable effects which are not prohibited
+ FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) -
+ DeclaredVerifiableEffects);
+ }
+
+ // Hide the way that diagnostics for explicitly required effects vs. inferred
+ // ones are handled differently.
+ void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+ if (!Inferring) {
+ if (DiagnosticsForExplicitFX == nullptr) {
+ DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
+ }
+ DiagnosticsForExplicitFX->push_back(NewDiag);
+ } else {
+ auto &Diag =
+ InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
+ if (Diag.ID == DiagnosticID::None) {
+ Diag = NewDiag;
+ }
+ }
+ }
+
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
+ if (UnverifiedDirectCalls == nullptr) {
+ UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
+ }
+ UnverifiedDirectCalls->emplace_back(DirectCall{D, CallLoc});
+ }
+
+ // Analysis is complete when there are no unverified direct calls.
+ bool isComplete() const {
+ return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
+ }
+
+ const Diagnostic *
+ diagnosticForInferrableEffect(const FunctionEffect *effect) {
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ const SmallVector<DirectCall> &unverifiedCalls() const {
+ assert(!isComplete());
+ return *UnverifiedDirectCalls;
+ }
+
+ SmallVector<Diagnostic> *getDiagnosticsForExplicitFX() const {
+ return DiagnosticsForExplicitFX.get();
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "Pending: Declared ";
+ DeclaredVerifiableEffects.dump(OS);
+ OS << ", "
+ << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0)
+ << " diags; ";
+ OS << " Infer ";
+ FXToInfer.dump(OS);
+ OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\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.
+ FunctionEffectSet VerifiedEffects;
+
+private:
+ // This is used to generate notes about failed inference.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+ CompleteFunctionAnalysis(PendingFunctionAnalysis &pending,
+ FunctionEffectSet funcFX,
+ FunctionEffectSet AllInferrableEffectsToVerify) {
+ MutableFunctionEffectSet verified;
+ verified |= funcFX;
+ for (const auto *effect : AllInferrableEffectsToVerify) {
+ if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
+ verified.insert(effect);
+ }
+ }
+ VerifiedEffects = FunctionEffectSet::create(verified);
+
+ InferrableEffectToFirstDiagnostic =
+ std::move(pending.InferrableEffectToFirstDiagnostic);
+ }
+
+ const Diagnostic *firstDiagnosticForEffect(const FunctionEffect *effect) {
+ // TODO: is this correct?
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "Complete: Verified ";
+ VerifiedEffects.dump(OS);
+ OS << "; Infer ";
+ OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ }
+};
+
+/*
+ TODO: nolock and noalloc imply noexcept
+ if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
+ if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
+ != clang::CT_Cannot) {
+ S.Diag(Callable->getBeginLoc(),
+ diag::warn_perf_annotation_implies_noexcept)
+ << getPerfAnnotationSpelling(CInfo.PerfAnnot);
+ }
+ }
+*/
+
+// ==========
+class Analyzer {
+ constexpr static int kDebugLogLevel = 3;
+
+ // --
+ Sema &Sem;
+
+ // used from Sema:
+ // SmallVector<const Decl *> DeclsWithUnverifiedEffects
+
+ // Subset of Sema.AllEffectsToVerify
+ FunctionEffectSet 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.
+ struct AnalysisMap : public llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+
+ ~AnalysisMap();
+
+ // use lookup()
+
+ /// Shortcut for the case where we only care about completed analysis.
+ CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
+ if (auto AP = lookup(D)) {
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ return AP.get<CompleteFunctionAnalysis *>();
+ }
+ }
+ return nullptr;
+ }
+
+ void dump(Sema &S, llvm::raw_ostream &OS) {
+ OS << "AnalysisMap:\n";
+ for (const auto &item : *this) {
+ CallableInfo CI(*item.first);
+ const auto AP = item.second;
+ OS << item.first << " " << CI.name(S) << " : ";
+ 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(OS);
+ } else
+ llvm_unreachable("never");
+ }
+ }
+ };
+ AnalysisMap DeclAnalysis;
+
+public:
+ Analyzer(Sema &S) : Sem(S) {}
+
+ void run(const TranslationUnitDecl &TU) {
+#if FX_ANALYZER_VERIFY_DECL_LIST
+ verifyRootDecls(TU);
+#endif
+ // Gather all of the effects to be verified to see what operations need to
+ // be checked, and to see which ones are inferrable.
+ {
+ MutableFunctionEffectSet inferrableEffects;
+ for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
+ const auto Flags = effect->flags();
+ if (Flags & FunctionEffect::kInferrableOnCallees) {
+ inferrableEffects.insert(effect);
+ }
+ }
+ AllInferrableEffectsToVerify =
+ FunctionEffectSet::create(inferrableEffects);
+ llvm::outs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::outs());
+ llvm::outs() << "\n";
+ }
+
+ SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
+
+ // It's useful to use DeclsWithUnverifiedEffects as a stack for a
+ // depth-first traversal rather than have a secondary container. But first,
+ // reverse it, so Decls are verified in the order they are declared.
+ std::reverse(verifyQueue.begin(), verifyQueue.end());
+
+ while (!verifyQueue.empty()) {
+ const Decl *D = verifyQueue.back();
+ if (auto AP = DeclAnalysis.lookup(D)) {
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ // already done
+ verifyQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto *pending = AP.get<PendingFunctionAnalysis *>();
+ finishPendingAnalysis(D, pending);
+ verifyQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("shouldn't happen");
+ }
+
+ auto *Pending = verifyDecl(D);
+ if (Pending == nullptr) {
+ // completed now
+ verifyQueue.pop_back();
+ continue;
+ }
+
+ for (const auto &Call : Pending->unverifiedCalls()) {
+ // This lookup could be optimized out if the results could have been
+ // saved from followCall when we traversed the caller's AST. It would
+ // however make the check for recursion more complex.
+ auto AP = DeclAnalysis.lookup(Call.Callee);
+ if (AP.isNull()) {
+ verifyQueue.push_back(Call.Callee);
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+ __builtin_trap();
----------------
Sirraide wrote:
I’m assuming this is just temporary.
https://github.com/llvm/llvm-project/pull/84983
More information about the cfe-commits
mailing list