From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sun, 5 May 2024 12:36:53 -0700 Subject: [PATCH 01/63] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis/verification --- clang/include/clang/AST/Type.h | 2 +- clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 49 + clang/include/clang/Sema/Sema.h | 13 + .../include/clang/Serialization/ASTBitCodes.h | 4 + clang/include/clang/Serialization/ASTReader.h | 3 + clang/include/clang/Serialization/ASTWriter.h | 1 + clang/lib/Sema/AnalysisBasedWarnings.cpp | 1259 +++++++++++++++++ clang/lib/Sema/SemaDecl.cpp | 56 + clang/lib/Sema/SemaExpr.cpp | 4 + clang/lib/Sema/SemaLambda.cpp | 5 + clang/lib/Serialization/ASTReader.cpp | 19 + clang/lib/Serialization/ASTWriter.cpp | 12 + .../Sema/attr-nonblocking-constraints.cpp | 194 +++ clang/test/Sema/attr-nonblocking-syntax.cpp | 1 + .../attr-nonblocking-constraints.mm | 23 + 16 files changed, 1645 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 25defea58c2dc23..08141f75de8dbc5 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4699,7 +4699,7 @@ class FunctionEffect { private: LLVM_PREFERRED_TYPE(Kind) - unsigned FKind : 3; + uint8_t FKind : 3; // Expansion: for hypothetical TCB+types, there could be one Kind for TCB, // then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 19c3f1e0433496d..55d9442a939dae7 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInCon // Warnings and notes related to the function effects system underlying // the nonblocking and nonallocating attributes. def FunctionEffects : DiagGroup<"function-effects">; +def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; // Warnings and notes InstallAPI verification. def InstallAPIViolation : DiagGroup<"installapi-violation">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d60f32674ca3a63..ec02c02d158c891 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning< InGroup>; // Function effects +def warn_func_effect_allocates : Warning< + "'%0' function must not allocate or deallocate memory">, + InGroup; +def note_func_effect_allocates : Note< + "function cannot be inferred '%0' because it allocates/deallocates memory">; +def warn_func_effect_throws_or_catches : Warning< + "'%0' function must not throw or catch exceptions">, + InGroup; +def note_func_effect_throws_or_catches : Note< + "function cannot be inferred '%0' because it throws or catches exceptions">; +def warn_func_effect_has_static_local : Warning< + "'%0' function must not have static locals">, + InGroup; +def note_func_effect_has_static_local : Note< + "function cannot be inferred '%0' because it has a static local">; +def warn_func_effect_uses_thread_local : Warning< + "'%0' function must not use thread-local variables">, + InGroup; +def note_func_effect_uses_thread_local : Note< + "function cannot be inferred '%0' because it uses a thread-local variable">; +def warn_func_effect_calls_objc : Warning< + "'%0' function must not access an ObjC method or property">, + InGroup; +def note_func_effect_calls_objc : Note< + "function cannot be inferred '%0' because it accesses an ObjC method or property">; +def warn_func_effect_calls_func_without_effect : Warning< + "'%0' function must not call non-'%0' function '%1'">, + InGroup; +def warn_func_effect_calls_expr_without_effect : Warning< + "'%0' function must not call non-'%0' expression">, + InGroup; +def note_func_effect_calls_func_without_effect : Note< + "function cannot be inferred '%0' because it calls non-'%0' function '%1'">; +def note_func_effect_call_extern : Note< + "function cannot be inferred '%0' because it has no definition in this translation unit">; +def note_func_effect_call_disallows_inference : Note< + "function does not permit inference of '%0'">; +def note_func_effect_call_virtual : Note< + "virtual method cannot be inferred '%0'">; +def note_func_effect_call_func_ptr : Note< + "function pointer cannot be inferred '%0'">; +def warn_perf_constraint_implies_noexcept : Warning< + "'%0' function should be declared noexcept">, + InGroup; + +// FIXME: It would be nice if we could provide fuller template expansion notes. +def note_func_effect_from_template : Note< + "in template expansion here">; + // spoofing nonblocking/nonallocating def warn_invalid_add_func_effects : Warning< "attribute '%0' should not be added via type conversion">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d638d31e050dc67..e1867348497da81 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -875,6 +875,13 @@ class Sema final : public SemaBase { // ----- function effects --- + /// All functions/lambdas/blocks which have bodies and which have a non-empty + /// FunctionEffectsRef to be verified. + SmallVector DeclsWithEffectsToVerify; + /// The union of all effects present on DeclsWithEffectsToVerify. Conditions + /// are all null. + FunctionEffectSet AllEffectsToVerify; + /// Warn when implicitly changing function effects. void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, SourceLocation Loc); @@ -891,6 +898,12 @@ class Sema final : public SemaBase { SourceLocation NewLoc, SourceLocation OldLoc); + /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. + void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + + /// Unconditionally add a Decl to DeclsWithEfffectsToVerify. + void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + /// Try to parse the conditional expression attached to an effect attribute /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty /// optional on error. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 5dd0ba33f8a9c2e..b975db88dbaae67 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -721,6 +721,10 @@ enum ASTRecordTypes { /// Record code for \#pragma clang unsafe_buffer_usage begin/end PP_UNSAFE_BUFFER_USAGE = 69, + + /// Record code for Sema's vector of functions/blocks with effects to + /// be verified. + DECLS_WITH_EFFECTS_TO_VERIFY = 70, }; /// Record types used within a source manager block. diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h index 76e51ac7ab9792f..1d8985602146ba7 100644 --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -948,6 +948,9 @@ class ASTReader /// Sema tracks these to emit deferred diags. llvm::SmallSetVector DeclsToCheckForDeferredDiags; + /// The IDs of all decls with function effects to be checked. + SmallVector DeclsWithEffectsToVerify; + private: struct ImportedSubmodule { serialization::SubmoduleID ID; diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h index a0e475ec9f862cc..4eaf77e8cb8d996 100644 --- a/clang/include/clang/Serialization/ASTWriter.h +++ b/clang/include/clang/Serialization/ASTWriter.h @@ -592,6 +592,7 @@ class ASTWriter : public ASTDeserializationListener, void WriteMSPointersToMembersPragmaOptions(Sema &SemaRef); void WritePackPragmaOptions(Sema &SemaRef); void WriteFloatControlPragmaOptions(Sema &SemaRef); + void WriteDeclsWithEffectsToVerify(Sema &SemaRef); void WriteModuleFileExtension(Sema &SemaRef, ModuleFileExtensionWriter &Writer); diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 0f604c61fa3af98..3909d5b44a32e7e 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { }; } // namespace +// ============================================================================= + +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 + DeclDisallowsInference, + + CallsDeclWithoutEffect, + CallsExprWithoutEffect, +}; + +// Holds an effect diagnosis, 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. +struct Diagnostic { + FunctionEffect Effect; + DiagnosticID ID = DiagnosticID::None; + SourceLocation Loc; + const Decl *Callee = nullptr; // only valid for Calls* + + Diagnostic() = default; + + Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc, + const Decl *Callee = nullptr) + : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {} +}; + +enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; +enum class CallType { + // 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->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; +} + +/// A mutable set of FunctionEffect, for use in places where any conditions +/// have been resolved or can be ignored. +class EffectSet { + // This implementation optimizes footprint, since we hold one of these for + // every function visited, which, due to inference, can be many more functions + // than have declared effects. + + template struct FixedVector { + SizeT Count = 0; + T Items[Capacity] = {}; + + using value_type = T; + + using iterator = T *; + using const_iterator = const T *; + iterator begin() { return &Items[0]; } + iterator end() { return &Items[Count]; } + const_iterator begin() const { return &Items[0]; } + const_iterator end() const { return &Items[Count]; } + const_iterator cbegin() const { return &Items[0]; } + const_iterator cend() const { return &Items[Count]; } + + void insert(iterator I, const T &Value) { + assert(Count < Capacity); + iterator E = end(); + if (I != E) + std::copy_backward(I, E, E + 1); + *I = Value; + ++Count; + } + + void push_back(const T &Value) { + assert(Count < Capacity); + Items[Count++] = Value; + } + }; + + // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable + // effects, this fixed-size vector with a capacity of 7 is more than + // sufficient and is only 8 bytes. + FixedVector Impl; + +public: + EffectSet() = default; + explicit EffectSet(FunctionEffectsRef FX) { insert(FX); } + + operator ArrayRef() const { + return ArrayRef(Impl.cbegin(), Impl.cend()); + } + + using iterator = const FunctionEffect *; + iterator begin() const { return Impl.cbegin(); } + iterator end() const { return Impl.cend(); } + + void insert(const FunctionEffect &Effect) { + FunctionEffect *Iter = Impl.begin(); + FunctionEffect *End = Impl.end(); + // linear search; lower_bound is overkill for a tiny vector like this + for (; Iter != End; ++Iter) { + if (*Iter == Effect) + return; + if (Effect < *Iter) + break; + } + Impl.insert(Iter, Effect); + } + void insert(const EffectSet &Set) { + for (const FunctionEffect &Item : Set) { + // push_back because set is already sorted + Impl.push_back(Item); + } + } + void insert(FunctionEffectsRef FX) { + for (const FunctionEffectWithCondition &EC : FX) { + assert(EC.Cond.getCondition() == + nullptr); // should be resolved by now, right? + // push_back because set is already sorted + Impl.push_back(EC.Effect); + } + } + bool contains(const FunctionEffect::Kind EK) const { + for (const FunctionEffect &E : Impl) + if (E.kind() == EK) + return true; + return false; + } + + void dump(llvm::raw_ostream &OS) const; + + static EffectSet difference(ArrayRef LHS, + ArrayRef RHS) { + EffectSet Result; + std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), + std::back_inserter(Result.Impl)); + return Result; + } +}; + +LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const { + OS << "Effects{"; + bool First = true; + for (const FunctionEffect &Effect : *this) { + if (!First) + OS << ", "; + else + First = false; + OS << Effect.name(); + } + OS << "}"; +} + +// Transitory, more extended information about a callable, which can be a +// function, block, function pointer, etc. +struct CallableInfo { + // CDecl holds the function's definition, if any. + // FunctionDecl if CallType::Function or Virtual + // BlockDecl if CallType::Block + const Decl *CDecl; + mutable std::optional MaybeName; + SpecialFuncType FuncType = SpecialFuncType::None; + EffectSet Effects; + CallType CType = CallType::Unknown; + + CallableInfo(Sema &SemaRef, const Decl &CD, + SpecialFuncType FT = SpecialFuncType::None) + : CDecl(&CD), FuncType(FT) { + FunctionEffectsRef FXRef; + + if (auto *FD = dyn_cast(CDecl)) { + // Use the function's definition, if any. + if (const FunctionDecl *Def = FD->getDefinition()) + CDecl = FD = Def; + CType = CallType::Function; + if (auto *Method = dyn_cast(FD); + Method && Method->isVirtual()) + CType = CallType::Virtual; + FXRef = FD->getFunctionEffects(); + } else if (auto *BD = dyn_cast(CDecl)) { + CType = CallType::Block; + FXRef = BD->getFunctionEffects(); + } else if (auto *VD = dyn_cast(CDecl)) { + // ValueDecl is function, enum, or variable, so just look at its type. + FXRef = FunctionEffectsRef::get(VD->getType()); + } + Effects = EffectSet(FXRef); + } + + 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(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(CDecl)) + FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), + /*Qualified=*/true); + else if (auto *BD = dyn_cast(CDecl)) + OS << "(block " << BD->getBlockManglingNumber() << ")"; + else if (auto *VD = dyn_cast(CDecl)) + VD->printQualifiedName(OS); + MaybeName = Name; + } + return *MaybeName; + } +}; + +// ---------- +// Map effects to single diagnostics, to hold the first (of potentially many) +// diagnostics pertaining to an effect, per function. +class EffectToDiagnosticMap { + // 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 Diagnostic itself contains a FunctionEffect which is the key. + using ImplVec = llvm::SmallVector; + std::unique_ptr Impl; + +public: + // Insert a new diagnostic if we do not already have one for its effect. + void maybeInsert(const Diagnostic &Diag) { + if (Impl == nullptr) + Impl = std::make_unique(); + auto *Iter = _find(Diag.Effect); + if (Iter != Impl->end() && Iter->Effect == Diag.Effect) + return; + + Impl->insert(Iter, Diag); + } + + const Diagnostic *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. + EffectSet DeclaredVerifiableEffects; + EffectSet FXToInfer; + +private: + // Diagnostics pertaining to the function's explicit effects. + SmallVector DiagnosticsForExplicitFX; + + // Diagnostics pertaining to other, non-explicit, inferrable effects. + EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + + // These unverified direct calls are what keeps the analysis "pending", + // until the callees can be verified. + SmallVector UnverifiedDirectCalls; + +public: + PendingFunctionAnalysis( + Sema &Sem, const CallableInfo &CInfo, + ArrayRef AllInferrableEffectsToVerify) { + DeclaredVerifiableEffects = CInfo.Effects; + + // Check for effects we are not allowed to infer + EffectSet InferrableFX; + + for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { + if (effect.canInferOnFunction(*CInfo.CDecl)) + InferrableFX.insert(effect); + else { + // Add a diagnostic for this effect if a caller were to + // try to infer it. + InferrableEffectToFirstDiagnostic.maybeInsert( + Diagnostic(effect, DiagnosticID::DeclDisallowsInference, + CInfo.CDecl->getLocation())); + } + } + // InferrableFX is now the set of inferrable effects which are not + // prohibited + FXToInfer = EffectSet::difference(InferrableFX, 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) + DiagnosticsForExplicitFX.push_back(NewDiag); + else + InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag); + } + + 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 Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) { + return InferrableEffectToFirstDiagnostic.lookup(effect); + } + + SmallVector &unverifiedCalls() { + assert(!isComplete()); + return UnverifiedDirectCalls; + } + + SmallVector &getDiagnosticsForExplicitFX() { + return DiagnosticsForExplicitFX; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { + OS << "Pending: Declared "; + DeclaredVerifiableEffects.dump(OS); + OS << ", " << DiagnosticsForExplicitFX.size() << " diags; "; + OS << " Infer "; + FXToInfer.dump(OS); + OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags"; + if (!UnverifiedDirectCalls.empty()) { + OS << "; Calls: "; + for (const DirectCall &Call : UnverifiedDirectCalls) { + CallableInfo CI(SemaRef, *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. + EffectSet VerifiedEffects; + +private: + // This is used to generate notes about failed inference. + EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + +public: + // The incoming Pending analysis is consumed (member(s) are moved-from). + CompleteFunctionAnalysis( + ASTContext &Ctx, PendingFunctionAnalysis &Pending, + const EffectSet &DeclaredEffects, + ArrayRef AllInferrableEffectsToVerify) { + VerifiedEffects.insert(DeclaredEffects); + for (const FunctionEffect &effect : AllInferrableEffectsToVerify) + if (Pending.diagnosticForInferrableEffect(effect) == nullptr) + VerifiedEffects.insert(effect); + + InferrableEffectToFirstDiagnostic = + std::move(Pending.InferrableEffectToFirstDiagnostic); + } + + const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) { + return InferrableEffectToFirstDiagnostic.lookup(Effect); + } + + void dump(llvm::raw_ostream &OS) const { + OS << "Complete: Verified "; + VerifiedEffects.dump(OS); + OS << "; Infer "; + OS << InferrableEffectToFirstDiagnostic.size() << " diags\n"; + } +}; + +const Decl *CanonicalFunctionDecl(const Decl *D) { + if (auto *FD = dyn_cast(D)) { + FD = FD->getCanonicalDecl(); + assert(FD != nullptr); + return FD; + } + return D; +} + +// ========== +class Analyzer { + constexpr static int DebugLogLevel = 0; + // -- + Sema &Sem; + + // Subset of Sema.AllEffectsToVerify + EffectSet 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 : protected 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(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(AP)) + return AP.get(); + return nullptr; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) { + OS << "\nAnalysisMap:\n"; + for (const auto &item : *this) { + CallableInfo CI(SemaRef, *item.first); + const auto AP = item.second; + OS << item.first << " " << CI.name(SemaRef) << " : "; + if (AP.isNull()) + OS << "null\n"; + else if (isa(AP)) { + auto *CFA = AP.get(); + OS << CFA << " "; + CFA->dump(OS); + } else if (isa(AP)) { + auto *PFA = AP.get(); + 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 FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) { + const FunctionEffect &Effect = CFE.Effect; + const FunctionEffect::Flags Flags = Effect.flags(); + if (Flags & FunctionEffect::FE_InferrableOnCallees) + AllInferrableEffectsToVerify.insert(Effect); + } + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "AllInferrableEffectsToVerify: "; + AllInferrableEffectsToVerify.dump(llvm::outs()); + llvm::outs() << "\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 = Sem.DeclsWithEffectsToVerify; + std::reverse(VerificationQueue.begin(), VerificationQueue.end()); + + while (!VerificationQueue.empty()) { + const Decl *D = VerificationQueue.back(); + if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { + if (isa(AP)) { + // already done + VerificationQueue.pop_back(); + continue; + } + if (isa(AP)) { + // All children have been traversed; finish analysis. + auto *Pending = AP.get(); + 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(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(Sem, *D); + bool isExternC = false; + + if (const FunctionDecl *FD = dyn_cast(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(D)) { + if (auto *TSI = BD->getSignatureAsWritten()) { + auto *FPT = TSI->getType()->getAs(); + IsNoexcept = FPT->isNothrow() || BD->hasAttr(); + } + } + 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); + + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " "; + FAnalysis.dump(Sem, llvm::outs()); + } + + 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; + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "inserted pending " << PendingPtr << "\n"; + DeclAnalysis.dump(Sem, llvm::outs()); + } + return PendingPtr; + } + + // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis, + // inserted in the container. + void completeAnalysis(const CallableInfo &CInfo, + PendingFunctionAnalysis &Pending) { + if (SmallVector &Diags = + Pending.getDiagnosticsForExplicitFX(); + !Diags.empty()) + emitDiagnostics(Diags, CInfo, Sem); + + CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( + Sem.getASTContext(), Pending, CInfo.Effects, + AllInferrableEffectsToVerify); + DeclAnalysis[CInfo.CDecl] = CompletePtr; + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "inserted complete " << CompletePtr << "\n"; + DeclAnalysis.dump(Sem, llvm::outs()); + } + } + + // 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(Sem, *D); + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : "; + Pending->dump(Sem, llvm::outs()); + llvm::outs() << "\n"; + } + for (const PendingFunctionAnalysis::DirectCall &Call : + Pending->unverifiedCalls()) { + if (Call.Recursed) + continue; + + CallableInfo Callee(Sem, *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.isDirectCall(); + + // Initially, the declared effects; inferred effects will be added. + EffectSet 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; + + if constexpr (DebugLogLevel > 0) { + llvm::outs() << "followCall from " << Caller.name(Sem) << " to " + << Callee.name(Sem) + << "; verifiable: " << Callee.isVerifiable() << "; callee "; + CalleeEffects.dump(llvm::outs()); + llvm::outs() << "\n"; + llvm::outs() << " callee " << Callee.CDecl << " canonical " + << CanonicalFunctionDecl(Callee.CDecl) << " redecls"; + for (Decl *D : Callee.CDecl->redecls()) + llvm::outs() << " " << D; + + llvm::outs() << "\n"; + } + + auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + FunctionEffect::Flags Flags = Effect.flags(); + bool Diagnose = + Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects); + if (Diagnose) { + // If inference is not allowed, or the target is indirect (virtual + // method/function ptr?), generate a diagnostic now. + if (!IsInferencePossible || + !(Flags & FunctionEffect::FE_InferrableOnCallees)) { + if (Callee.FuncType == SpecialFuncType::None) + PFA.checkAddDiagnostic( + Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect, + CallLoc, Callee.CDecl}); + else + PFA.checkAddDiagnostic( + Inferring, {Effect, DiagnosticID::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 determined to be complete. + void emitDiagnostics(SmallVector &Diags, + const CallableInfo &CInfo, Sema &S) { + if (Diags.empty()) + return; + const SourceManager &SM = S.getSourceManager(); + std::sort(Diags.begin(), Diags.end(), + [&SM](const Diagnostic &LHS, const Diagnostic &RHS) { + return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); + }); + + auto checkAddTemplateNote = [&](const Decl *D) { + if (const FunctionDecl *FD = dyn_cast(D)) { + while (FD != nullptr && FD->isTemplateInstantiation()) { + S.Diag(FD->getPointOfInstantiation(), + diag::note_func_effect_from_template); + FD = FD->getTemplateInstantiationPattern(); + } + } + }; + + // Top-level diagnostics are warnings. + for (const Diagnostic &Diag : Diags) { + StringRef effectName = Diag.Effect.name(); + switch (Diag.ID) { + case DiagnosticID::None: + case DiagnosticID::DeclDisallowsInference: // shouldn't happen + // here + llvm_unreachable("Unexpected diagnostic kind"); + break; + case DiagnosticID::AllocatesMemory: + S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::Throws: + case DiagnosticID::Catches: + S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::HasStaticLocal: + S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::AccessesThreadLocal: + S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::CallsObjC: + S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::CallsExprWithoutEffect: + S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + + case DiagnosticID::CallsDeclWithoutEffect: { + CallableInfo CalleeInfo(S, *Diag.Callee); + std::string CalleeName = CalleeInfo.name(S); + + S.Diag(Diag.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 = Diag.Callee; Callee != nullptr;) { + std::optional MaybeNextCallee; + CompleteFunctionAnalysis *Completed = + DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); + if (Completed == nullptr) { + // No result - could be + // - non-inline + // - indirect (virtual or through function pointer) + // - effect has been explicitly disclaimed (e.g. "blocking") + if (CalleeInfo.CType == CallType::Virtual) + S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) + << effectName; + else if (CalleeInfo.CType == CallType::Unknown) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_func_ptr) + << effectName; + else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind())) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_disallows_inference) + << effectName; + else + S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) + << effectName; + + break; + } + const Diagnostic *PtrDiag2 = + Completed->firstDiagnosticForEffect(Diag.Effect); + if (PtrDiag2 == nullptr) + break; + + const Diagnostic &Diag2 = *PtrDiag2; + switch (Diag2.ID) { + case DiagnosticID::None: + llvm_unreachable("Unexpected diagnostic kind"); + break; + case DiagnosticID::DeclDisallowsInference: + S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference) + << effectName; + break; + case DiagnosticID::CallsExprWithoutEffect: + S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) + << effectName; + break; + case DiagnosticID::AllocatesMemory: + S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName; + break; + case DiagnosticID::Throws: + case DiagnosticID::Catches: + S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) + << effectName; + break; + case DiagnosticID::HasStaticLocal: + S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) + << effectName; + break; + case DiagnosticID::AccessesThreadLocal: + S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) + << effectName; + break; + case DiagnosticID::CallsObjC: + S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName; + break; + case DiagnosticID::CallsDeclWithoutEffect: + MaybeNextCallee.emplace(S, *Diag2.Callee); + S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect) + << effectName << MaybeNextCallee->name(S); + break; + } + checkAddTemplateNote(Callee); + Callee = Diag2.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. + // + // Diagnostics are always routed to a PendingFunctionAnalysis, which holds + // all diagnostic output. + // + // Q: Currently we create a new RecursiveASTVisitor for every function + // analysis. Is it so lightweight that this is OK? It would appear so. + struct FunctionBodyASTVisitor + : public RecursiveASTVisitor { + // The meanings of the boolean values returned by the Visit methods can be + // difficult to remember. + constexpr static bool Stop = false; + constexpr static bool Proceed = true; + + 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 itself may have some implicit code paths beyond the + // body: member and base constructors and destructors. Visit these first. + if (const auto *FD = dyn_cast(CurrentCaller.CDecl)) { + if (auto *Ctor = dyn_cast(FD)) { + for (const CXXCtorInitializer *Initer : Ctor->inits()) + if (Expr *Init = Initer->getInit()) + VisitStmt(Init); + } else if (auto *Dtor = dyn_cast(FD)) + followDestructor(dyn_cast(Dtor->getParent()), Dtor); + } + // else could be BlockDecl + + // 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 diagnostic. \p Flag describes + // the construct. + void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D, + SourceLocation Loc, + const Decl *Callee = nullptr) { + // If there are any declared verifiable effects which forbid the construct + // represented by the flag, store just one diagnostic. + for (const FunctionEffect &Effect : + CurrentFunction.DeclaredVerifiableEffects) { + if (Effect.flags() & Flag) { + addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee); + break; + } + } + // For each inferred effect which forbids the construct, store a + // diagnostic, if we don't already have a diagnostic for that effect. + for (const FunctionEffect &Effect : CurrentFunction.FXToInfer) + if (Effect.flags() & Flag) + addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee); + } + + void addDiagnostic(bool Inferring, const FunctionEffect &Effect, + DiagnosticID D, SourceLocation Loc, + const Decl *Callee = nullptr) { + CurrentFunction.checkAddDiagnostic(Inferring, + Diagnostic(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) { + // Currently, built-in functions are always considered safe. + // FIXME: Some are not. + if (const auto *FD = dyn_cast(CI.CDecl); + FD && FD->getBuiltinID() != 0) + return; + + Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, + /*AssertNoFurtherInference=*/false); + } + + void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) { + const QualType CalleeType = CalleeExpr->getType(); + auto *FPT = + CalleeType->getAs(); // null if FunctionType + EffectSet CalleeFX; + if (FPT) + CalleeFX.insert(FPT->getFunctionEffects()); + + auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( + /*direct=*/false, CalleeFX)) + addDiagnostic(Inferring, Effect, DiagnosticID::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()); + + if (const auto *Class = dyn_cast(Rec)) { + for (const CXXBaseSpecifier &Base : Class->bases()) + followTypeDtor(Base.getType()); + + for (const CXXBaseSpecifier &Base : Class->vbases()) + followTypeDtor(Base.getType()); + } + } + + void followTypeDtor(QualType QT) { + 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(Outer.Sem, *Dtor); + followCall(CI, Dtor->getLocation()); + } + } + } + } + + // -- Methods for use of RecursiveASTVisitor -- + + bool shouldVisitImplicitCode() const { return true; } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + DiagnosticID::Throws, Throw->getThrowLoc()); + return Proceed; + } + + bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + DiagnosticID::Catches, Catch->getCatchLoc()); + return Proceed; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, + DiagnosticID::CallsObjC, Msg->getBeginLoc()); + return Proceed; + } + + bool VisitCallExpr(CallExpr *Call) { + if constexpr (DebugLogLevel > 2) { + llvm::errs() << "VisitCallExpr : " + << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr) + << "\n"; + } + + Expr *CalleeExpr = Call->getCallee(); + if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { + CallableInfo CI(Outer.Sem, *Callee); + followCall(CI, Call->getBeginLoc()); + return Proceed; + } + + if (isa(CalleeExpr)) + // just destroying a scalar, fine. + return Proceed; + + // No Decl, just an Expr. Just check based on its type. + checkIndirectCall(Call, CalleeExpr); + + return Proceed; + } + + bool VisitVarDecl(VarDecl *Var) { + if constexpr (DebugLogLevel > 2) { + llvm::errs() << "VisitVarDecl : " + << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr) + << "\n"; + } + + if (Var->isStaticLocal()) + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, + DiagnosticID::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()) { + if (const auto *CxxRec = + dyn_cast(ClsType->getDecl())) { + if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) { + CallableInfo CI(Outer.Sem, *Dtor); + followCall(CI, Var->getLocation()); + } + } + } + } + return Proceed; + } + + 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(Outer.Sem, *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 Proceed; + } + + 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(Outer.Sem, *FD, SpecialFuncType::OperatorDelete); + followCall(CI, Delete->getBeginLoc()); + } + + // It DOES however visit the called destructor + + return Proceed; + } + + bool VisitCXXConstructExpr(CXXConstructExpr *Construct) { + if constexpr (DebugLogLevel > 2) { + llvm::errs() << "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(Outer.Sem, *Ctor); + followCall(CI, Construct->getLocation()); + + return Proceed; + } + + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) { + if (Expr *E = DEI->getExpr()) + TraverseStmt(E); + + return Proceed; + } + + 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. + for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) + if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I, + Lambda->capture_init_begin()[I]) == Stop) + return Stop; + + return Proceed; + } + + bool TraverseBlockExpr(BlockExpr * /*unused*/) { + // TODO: are the capture expressions (ctor call?) safe? + return Proceed; + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const ValueDecl *Val = E->getDecl(); + if (isa(Val)) { + const VarDecl *Var = cast(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, + DiagnosticID::AccessesThreadLocal, + E->getLocation()); + } + } + return Proceed; + } + + // Unevaluated contexts: need to skip + // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641 + + bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) { + return TraverseStmt(Node->getResultExpr()); + } + bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { + return Proceed; + } + + bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; } + + bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; } + + bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; } + + bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; } + }; +}; + +Analyzer::AnalysisMap::~AnalysisMap() { + for (const auto &Item : *this) { + FuncAnalysisPtr AP = Item.second; + if (isa(AP)) + delete AP.get(); + else + delete AP.get(); + } +} + +} // namespace FXAnalysis + +// ============================================================================= + //===----------------------------------------------------------------------===// // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based // warnings on a function, method, or block. @@ -2551,6 +3807,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( SourceLocation())) { CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU); } + + if (S.Context.hasAnyFunctionEffects()) + FXAnalysis::Analyzer{S}.run(*TU); } void clang::sema::AnalysisBasedWarnings::IssueWarnings( diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index bb25a0b3a45ae90..adb4a8f4de68539 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3854,6 +3854,11 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S, OldQTypeForComparison = Context.getFunctionType( OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI); } + if (OldFX.empty()) { + // A redeclaration may add the attribute to a previously seen function + // body which needs to be verified. + maybeAddDeclWithEffects(Old, MergedFX); + } } } } @@ -10897,6 +10902,53 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD, return nullptr; } +// Should only be called when getFunctionEffects() returns a non-empty set. +// 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) { + FunctionEffectSet::Conflicts Errs; + + // 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(FunctionEffectWithCondition(EC.Effect, nullptr), + Errs); + AnyVerifiable = true; + } + assert(Errs.empty() && "effects conflicts should not be possible here"); + + // Record the declaration for later analysis. + if (AnyVerifiable) + DeclsWithEffectsToVerify.push_back(D); +} + bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD, QualType NewT, QualType OldT) { if (!NewD->getLexicalDeclContext()->isDependentContext()) @@ -15609,6 +15661,10 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D, getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation) Diag(FD->getLocation(), diag::warn_function_def_in_objc_container); + if (Context.hasAnyFunctionEffects()) + if (const auto FX = FD->getFunctionEffects(); !FX.empty()) + maybeAddDeclWithEffects(FD, FX); + return D; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 8d24e34520e778d..f9b04f5361f3379 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -16065,6 +16065,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc, BlockScopeInfo *BSI = cast(FunctionScopes.back()); BlockDecl *BD = BSI->TheDecl; + if (Context.hasAnyFunctionEffects()) + if (const auto FX = BD->getFunctionEffects(); !FX.empty()) + maybeAddDeclWithEffects(BD, FX); + if (BSI->HasImplicitReturnType) deduceClosureReturnType(*BSI); diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index 601077e9f3334d7..e3bf582db30279a 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1947,6 +1947,11 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap, ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) { LambdaScopeInfo LSI = *cast(FunctionScopes.back()); ActOnFinishFunctionBody(LSI.CallOperator, Body); + + if (Context.hasAnyFunctionEffects()) + if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty()) + maybeAddDeclWithEffects(LSI.CallOperator, FX); + return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI); } diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 3cb96df12e4da0c..5d81d921d0fffda 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3874,6 +3874,11 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F, FPPragmaOptions.swap(Record); break; + case DECLS_WITH_EFFECTS_TO_VERIFY: + for (unsigned I = 0, N = Record.size(); I != N; /*in loop*/) + DeclsWithEffectsToVerify.push_back(ReadDeclID(F, Record, I)); + break; + case OPENCL_EXTENSIONS: for (unsigned I = 0, E = Record.size(); I != E; ) { auto Name = ReadString(Record, I); @@ -8279,6 +8284,20 @@ void ASTReader::InitializeSema(Sema &S) { NewOverrides.applyOverrides(SemaObj->getLangOpts()); } + if (!DeclsWithEffectsToVerify.empty()) { + for (GlobalDeclID ID : DeclsWithEffectsToVerify) { + Decl *D = GetDecl(ID); + FunctionEffectsRef FX; + if (auto *FD = dyn_cast(D)) + FX = FD->getFunctionEffects(); + else if (auto *BD = dyn_cast(D)) + FX = BD->getFunctionEffects(); + if (!FX.empty()) + SemaObj->addDeclWithEffects(D, FX); + } + DeclsWithEffectsToVerify.clear(); + } + SemaObj->OpenCLFeatures = OpenCLExtensions; UpdateSema(); diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index c78d8943d6d92e5..faf9b9490bafc27 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -4627,6 +4627,17 @@ void ASTWriter::WriteFloatControlPragmaOptions(Sema &SemaRef) { Stream.EmitRecord(FLOAT_CONTROL_PRAGMA_OPTIONS, Record); } +/// Write Sema's collected list of declarations with unverified effects. +void ASTWriter::WriteDeclsWithEffectsToVerify(Sema &SemaRef) { + if (SemaRef.DeclsWithEffectsToVerify.empty()) + return; + RecordData Record; + for (const auto *D : SemaRef.DeclsWithEffectsToVerify) { + AddDeclRef(D, Record); + } + Stream.EmitRecord(DECLS_WITH_EFFECTS_TO_VERIFY, Record); +} + void ASTWriter::WriteModuleFileExtension(Sema &SemaRef, ModuleFileExtensionWriter &Writer) { // Enter the extension block. @@ -5564,6 +5575,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot, } WritePackPragmaOptions(SemaRef); WriteFloatControlPragmaOptions(SemaRef); + WriteDeclsWithEffectsToVerify(SemaRef); // Some simple statistics RecordData::value_type Record[] = { diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp new file mode 100644 index 000000000000000..c248293cf763425 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -0,0 +1,194 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s +// These are in a separate file because errors (e.g. incompatible attributes) currently prevent +// the AnalysisBasedWarnings pass from running at all. + +// This diagnostic is re-enabled and exercised in isolation later in this file. +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" + +// --- CONSTRAINTS --- + +void nl1() [[clang::nonblocking]] +{ + auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} +} + +void nl2() [[clang::nonblocking]] +{ + static int global; // expected-warning {{'nonblocking' function must not have static locals}} +} + +void nl3() [[clang::nonblocking]] +{ + try { + throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + } + catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + } +} + +void nl4_inline() {} +void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + +void nl4() [[clang::nonblocking]] +{ + nl4_inline(); // OK + nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} +} + + +struct HasVirtual { + virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}} +}; + +void nl5() [[clang::nonblocking]] +{ + HasVirtual hv; + hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} +} + +void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +void nl6_transitively_unsafe() +{ + nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} +} + +void nl6() [[clang::nonblocking]] +{ + nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} +} + +thread_local int tl_var{ 42 }; + +bool tl_test() [[clang::nonblocking]] +{ + return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}} +} + +void nl7() +{ + // Make sure we verify blocks + auto blk = ^() [[clang::nonblocking]] { + throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + }; +} + +void nl8() +{ + // Make sure we verify lambdas + auto lambda = []() [[clang::nonblocking]] { + throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + }; +} + +// Make sure template expansions are found and verified. + template + struct Adder { + static T add_explicit(T x, T y) [[clang::nonblocking]] + { + return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + } + static T add_implicit(T x, T y) + { + return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} + } + }; + + struct Stringy { + friend Stringy operator+(const Stringy& x, const Stringy& y) + { + // Do something inferably unsafe + auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}} + return {}; + } + }; + + struct Stringy2 { + friend Stringy2 operator+(const Stringy2& x, const Stringy2& y) + { + // Do something inferably unsafe + throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} + } + }; + +void nl9() [[clang::nonblocking]] +{ + Adder::add_explicit(1, 2); + Adder::add_implicit(1, 2); + + Adder::add_explicit({}, {}); // expected-note {{in template expansion here}} + Adder::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \ + expected-note {{in template expansion here}} +} + +void nl10( + void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}} + void (*fp2)() [[clang::nonblocking]] + ) [[clang::nonblocking]] +{ + fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + fp2(); +} + +// Interactions with nonblocking(false) +void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}} +{ +} +void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}} + +template +struct ComputedNB { + void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}} +}; + +void nl11() [[clang::nonblocking]] +{ + nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + + ComputedNB CNB_true; + CNB_true.method(); + + ComputedNB CNB_false; + CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} +} + +// Verify that when attached to a redeclaration, the attribute successfully attaches. +void nl12() { + static int x; // expected-warning {{'nonblocking' function must not have static locals}} +} +void nl12() [[clang::nonblocking]]; +void nl13() [[clang::nonblocking]] { nl12(); } + +// C++ member function pointers +struct PTMFTester { + typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]]; + + void convert() [[clang::nonblocking]]; + + ConvertFunction mConvertFunc; +}; + +void PTMFTester::convert() [[clang::nonblocking]] +{ + (this->*mConvertFunc)(); +} + +// Block variables +void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] { + blk(); +} + +// References to blocks +void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] +{ + auto &ref = block; + ref(); +} + + +// --- nonblocking implies noexcept --- +#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" + +void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} +{ +} diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp index 644ed754b04daab..90d074d01708f40 100644 --- a/clang/test/Sema/attr-nonblocking-syntax.cpp +++ b/clang/test/Sema/attr-nonblocking-syntax.cpp @@ -3,6 +3,7 @@ // Make sure that the attribute gets parsed and attached to the correct AST elements. #pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // ========================================================================================= // Square brackets, true diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm new file mode 100644 index 000000000000000..aeb8b21f56e4424 --- /dev/null +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s + +#if !__has_attribute(clang_nonblocking) +#error "the 'nonblocking' attribute is not available" +#endif + +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" + +// Objective-C +@interface OCClass +- (void)method; +@end + +void nl14(OCClass *oc) [[clang::nonblocking]] { + [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}} +} +void nl15(OCClass *oc) { + [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}} +} +void nl16(OCClass *oc) [[clang::nonblocking]] { + nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}} +} + From 95b7a00467423e1a0e320a2fe45811739ce4d61e Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sat, 20 Jul 2024 08:57:16 -0700 Subject: [PATCH 02/63] - Sema.h: Move function decls to be in the correct per-source-file sections. - Fix ObjC++ test which was using the attribute's old name. --- clang/include/clang/Sema/Sema.h | 50 +++++++++---------- .../attr-nonblocking-constraints.mm | 4 -- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e1867348497da81..e3259f147ec8841 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -873,8 +873,6 @@ class Sema final : public SemaBase { /// Warn when implicitly casting 0 to nullptr. void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E); - // ----- function effects --- - /// All functions/lambdas/blocks which have bodies and which have a non-empty /// FunctionEffectsRef to be verified. SmallVector DeclsWithEffectsToVerify; @@ -886,30 +884,6 @@ class Sema final : public SemaBase { void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, SourceLocation Loc); - /// Warn and return true if adding an effect to a set would create a conflict. - bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, - const FunctionEffectWithCondition &EC, - SourceLocation NewAttrLoc); - - // Report a failure to merge function effects between declarations due to a - // conflict. - void - diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs, - SourceLocation NewLoc, - SourceLocation OldLoc); - - /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. - void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); - - /// Unconditionally add a Decl to DeclsWithEfffectsToVerify. - void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); - - /// Try to parse the conditional expression attached to an effect attribute - /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty - /// optional on error. - std::optional - ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName); - /// makeUnavailableInSystemHeader - There is an error in the current /// context. If we're still in a system header, and we can plausibly /// make the relevant declaration unavailable instead of erroring, do @@ -4343,6 +4317,24 @@ class Sema final : public SemaBase { // Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check. bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee); + /// Warn and return true if adding a function effect to a set would create a conflict. + bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, + const FunctionEffectWithCondition &EC, + SourceLocation NewAttrLoc); + + // Report a failure to merge function effects between declarations due to a + // conflict. + void + diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs, + SourceLocation NewLoc, + SourceLocation OldLoc); + + /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. + void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + + /// Unconditionally add a Decl to DeclsWithEfffectsToVerify. + void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + private: /// Function or variable declarations to be checked for whether the deferred /// diagnostics should be emitted. @@ -15015,6 +15007,12 @@ class Sema final : public SemaBase { return hasAcceptableDefinition(D, &Hidden, Kind); } + /// Try to parse the conditional expression attached to an effect attribute + /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty + /// optional on error. + std::optional + ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName); + private: /// The implementation of RequireCompleteType bool RequireCompleteTypeImpl(SourceLocation Loc, QualType T, diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index aeb8b21f56e4424..0600062e89c0454 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -1,9 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s -#if !__has_attribute(clang_nonblocking) -#error "the 'nonblocking' attribute is not available" -#endif - #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // Objective-C From 21c780aaeea3deb35c3ffc972eb126e02b48ec7a Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sat, 20 Jul 2024 09:08:16 -0700 Subject: [PATCH 03/63] clang-format --- clang/include/clang/Sema/Sema.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e3259f147ec8841..a42f6af3c70e48f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4317,7 +4317,8 @@ class Sema final : public SemaBase { // Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check. bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee); - /// Warn and return true if adding a function effect to a set would create a conflict. + /// Warn and return true if adding a function effect to a set would create a + /// conflict. bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, const FunctionEffectWithCondition &EC, SourceLocation NewAttrLoc); From ff104137caa083888b51a78da5d439d45db04a1f Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 26 Jul 2024 07:40:33 -0700 Subject: [PATCH 04/63] - Detect ObjC @throw and @catch, diagnose identically to their C++ counterparts. - Tweak test cases. --- clang/lib/Sema/AnalysisBasedWarnings.cpp | 12 ++++++++++++ .../test/Sema/attr-nonblocking-constraints.cpp | 5 +++-- .../SemaObjCXX/attr-nonblocking-constraints.mm | 17 ++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 3909d5b44a32e7e..f26468cfcdad546 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -3475,6 +3475,18 @@ class Analyzer { return Proceed; } + bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + DiagnosticID::Throws, Throw->getThrowLoc()); + return Proceed; + } + + bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + DiagnosticID::Catches, Catch->getAtCatchLoc()); + return Proceed; + } + bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, DiagnosticID::CallsObjC, Msg->getBeginLoc()); diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index c248293cf763425..e50c5b436daafcf 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s // These are in a separate file because errors (e.g. incompatible attributes) currently prevent -// the AnalysisBasedWarnings pass from running at all. +// the FXAnalysis pass from running at all. // This diagnostic is re-enabled and exercised in isolation later in this file. #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" @@ -9,7 +9,8 @@ void nl1() [[clang::nonblocking]] { - auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} + int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} + delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} } void nl2() [[clang::nonblocking]] diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index 0600062e89c0454..ff5873c11c4fe76 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify %s #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" @@ -7,13 +7,20 @@ @interface OCClass - (void)method; @end -void nl14(OCClass *oc) [[clang::nonblocking]] { +void nb1(OCClass *oc) [[clang::nonblocking]] { [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}} } -void nl15(OCClass *oc) { +void nb2(OCClass *oc) { [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}} } -void nl16(OCClass *oc) [[clang::nonblocking]] { - nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}} +void nb3(OCClass *oc) [[clang::nonblocking]] { + nb2(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}} } +void nb4() [[clang::nonblocking]] { + @try { + @throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + } + @catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + } +} From d472964eb11d063b6d0afc433c69f6369d1ae1ae Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sun, 5 May 2024 12:36:53 -0700 Subject: [PATCH 05/63] FunctionEffect constructors: cast Kind to uint8_t Make FunctionEffect just have a Kind instead of a bitfield (from PR #100753) --- clang/include/clang/AST/Type.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 08141f75de8dbc5..509f0d95643b0bd 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4698,26 +4698,25 @@ class FunctionEffect { }; private: - LLVM_PREFERRED_TYPE(Kind) - uint8_t FKind : 3; + Kind FKind; // Expansion: for hypothetical TCB+types, there could be one Kind for TCB, // then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would // be considered for uniqueness. public: - FunctionEffect() : FKind(unsigned(Kind::None)) {} + FunctionEffect() : FKind(Kind::None) {} - explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {} + explicit FunctionEffect(Kind K) : FKind(K) {} /// The kind of the effect. - Kind kind() const { return Kind(FKind); } + Kind kind() const { return FKind; } /// Return the opposite kind, for effects which have opposites. Kind oppositeKind() const; /// For serialization. - uint32_t toOpaqueInt32() const { return FKind; } + uint32_t toOpaqueInt32() const { return uint32_t(FKind); } static FunctionEffect fromOpaqueInt32(uint32_t Value) { return FunctionEffect(Kind(Value)); } From f1142db51dff9794be2f63c2a2aca585760b5620 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 26 Jul 2024 12:48:59 -0700 Subject: [PATCH 06/63] New file: EFfectAnalysis.cpp. Use LLVM_DEBUG for debug logging. Remove unneeded isInline() check in functionIsVerifiable --- clang/include/clang/Sema/Sema.h | 4 + clang/lib/Sema/AnalysisBasedWarnings.cpp | 1269 --------------------- clang/lib/Sema/CMakeLists.txt | 1 + clang/lib/Sema/EffectAnalysis.cpp | 1289 ++++++++++++++++++++++ clang/lib/Sema/Sema.cpp | 3 + 5 files changed, 1297 insertions(+), 1269 deletions(-) create mode 100644 clang/lib/Sema/EffectAnalysis.cpp diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index a42f6af3c70e48f..fae0a2f4f46dee7 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -530,6 +530,10 @@ struct FunctionEffectDifferences : public SmallVector { const FunctionEffectsRef &New); }; +// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move +// more of the effects implementation into that file? +void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU); + /// Sema - This implements semantic analysis and AST building for C. /// \nosubgrouping class Sema final : public SemaBase { diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index f26468cfcdad546..8268117a0addab4 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2399,1272 +2399,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { // ============================================================================= -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 - DeclDisallowsInference, - - CallsDeclWithoutEffect, - CallsExprWithoutEffect, -}; - -// Holds an effect diagnosis, 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. -struct Diagnostic { - FunctionEffect Effect; - DiagnosticID ID = DiagnosticID::None; - SourceLocation Loc; - const Decl *Callee = nullptr; // only valid for Calls* - - Diagnostic() = default; - - Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc, - const Decl *Callee = nullptr) - : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {} -}; - -enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; -enum class CallType { - // 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->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; -} - -/// A mutable set of FunctionEffect, for use in places where any conditions -/// have been resolved or can be ignored. -class EffectSet { - // This implementation optimizes footprint, since we hold one of these for - // every function visited, which, due to inference, can be many more functions - // than have declared effects. - - template struct FixedVector { - SizeT Count = 0; - T Items[Capacity] = {}; - - using value_type = T; - - using iterator = T *; - using const_iterator = const T *; - iterator begin() { return &Items[0]; } - iterator end() { return &Items[Count]; } - const_iterator begin() const { return &Items[0]; } - const_iterator end() const { return &Items[Count]; } - const_iterator cbegin() const { return &Items[0]; } - const_iterator cend() const { return &Items[Count]; } - - void insert(iterator I, const T &Value) { - assert(Count < Capacity); - iterator E = end(); - if (I != E) - std::copy_backward(I, E, E + 1); - *I = Value; - ++Count; - } - - void push_back(const T &Value) { - assert(Count < Capacity); - Items[Count++] = Value; - } - }; - - // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable - // effects, this fixed-size vector with a capacity of 7 is more than - // sufficient and is only 8 bytes. - FixedVector Impl; - -public: - EffectSet() = default; - explicit EffectSet(FunctionEffectsRef FX) { insert(FX); } - - operator ArrayRef() const { - return ArrayRef(Impl.cbegin(), Impl.cend()); - } - - using iterator = const FunctionEffect *; - iterator begin() const { return Impl.cbegin(); } - iterator end() const { return Impl.cend(); } - - void insert(const FunctionEffect &Effect) { - FunctionEffect *Iter = Impl.begin(); - FunctionEffect *End = Impl.end(); - // linear search; lower_bound is overkill for a tiny vector like this - for (; Iter != End; ++Iter) { - if (*Iter == Effect) - return; - if (Effect < *Iter) - break; - } - Impl.insert(Iter, Effect); - } - void insert(const EffectSet &Set) { - for (const FunctionEffect &Item : Set) { - // push_back because set is already sorted - Impl.push_back(Item); - } - } - void insert(FunctionEffectsRef FX) { - for (const FunctionEffectWithCondition &EC : FX) { - assert(EC.Cond.getCondition() == - nullptr); // should be resolved by now, right? - // push_back because set is already sorted - Impl.push_back(EC.Effect); - } - } - bool contains(const FunctionEffect::Kind EK) const { - for (const FunctionEffect &E : Impl) - if (E.kind() == EK) - return true; - return false; - } - - void dump(llvm::raw_ostream &OS) const; - - static EffectSet difference(ArrayRef LHS, - ArrayRef RHS) { - EffectSet Result; - std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), - std::back_inserter(Result.Impl)); - return Result; - } -}; - -LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const { - OS << "Effects{"; - bool First = true; - for (const FunctionEffect &Effect : *this) { - if (!First) - OS << ", "; - else - First = false; - OS << Effect.name(); - } - OS << "}"; -} - -// Transitory, more extended information about a callable, which can be a -// function, block, function pointer, etc. -struct CallableInfo { - // CDecl holds the function's definition, if any. - // FunctionDecl if CallType::Function or Virtual - // BlockDecl if CallType::Block - const Decl *CDecl; - mutable std::optional MaybeName; - SpecialFuncType FuncType = SpecialFuncType::None; - EffectSet Effects; - CallType CType = CallType::Unknown; - - CallableInfo(Sema &SemaRef, const Decl &CD, - SpecialFuncType FT = SpecialFuncType::None) - : CDecl(&CD), FuncType(FT) { - FunctionEffectsRef FXRef; - - if (auto *FD = dyn_cast(CDecl)) { - // Use the function's definition, if any. - if (const FunctionDecl *Def = FD->getDefinition()) - CDecl = FD = Def; - CType = CallType::Function; - if (auto *Method = dyn_cast(FD); - Method && Method->isVirtual()) - CType = CallType::Virtual; - FXRef = FD->getFunctionEffects(); - } else if (auto *BD = dyn_cast(CDecl)) { - CType = CallType::Block; - FXRef = BD->getFunctionEffects(); - } else if (auto *VD = dyn_cast(CDecl)) { - // ValueDecl is function, enum, or variable, so just look at its type. - FXRef = FunctionEffectsRef::get(VD->getType()); - } - Effects = EffectSet(FXRef); - } - - 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(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(CDecl)) - FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), - /*Qualified=*/true); - else if (auto *BD = dyn_cast(CDecl)) - OS << "(block " << BD->getBlockManglingNumber() << ")"; - else if (auto *VD = dyn_cast(CDecl)) - VD->printQualifiedName(OS); - MaybeName = Name; - } - return *MaybeName; - } -}; - -// ---------- -// Map effects to single diagnostics, to hold the first (of potentially many) -// diagnostics pertaining to an effect, per function. -class EffectToDiagnosticMap { - // 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 Diagnostic itself contains a FunctionEffect which is the key. - using ImplVec = llvm::SmallVector; - std::unique_ptr Impl; - -public: - // Insert a new diagnostic if we do not already have one for its effect. - void maybeInsert(const Diagnostic &Diag) { - if (Impl == nullptr) - Impl = std::make_unique(); - auto *Iter = _find(Diag.Effect); - if (Iter != Impl->end() && Iter->Effect == Diag.Effect) - return; - - Impl->insert(Iter, Diag); - } - - const Diagnostic *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. - EffectSet DeclaredVerifiableEffects; - EffectSet FXToInfer; - -private: - // Diagnostics pertaining to the function's explicit effects. - SmallVector DiagnosticsForExplicitFX; - - // Diagnostics pertaining to other, non-explicit, inferrable effects. - EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; - - // These unverified direct calls are what keeps the analysis "pending", - // until the callees can be verified. - SmallVector UnverifiedDirectCalls; - -public: - PendingFunctionAnalysis( - Sema &Sem, const CallableInfo &CInfo, - ArrayRef AllInferrableEffectsToVerify) { - DeclaredVerifiableEffects = CInfo.Effects; - - // Check for effects we are not allowed to infer - EffectSet InferrableFX; - - for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { - if (effect.canInferOnFunction(*CInfo.CDecl)) - InferrableFX.insert(effect); - else { - // Add a diagnostic for this effect if a caller were to - // try to infer it. - InferrableEffectToFirstDiagnostic.maybeInsert( - Diagnostic(effect, DiagnosticID::DeclDisallowsInference, - CInfo.CDecl->getLocation())); - } - } - // InferrableFX is now the set of inferrable effects which are not - // prohibited - FXToInfer = EffectSet::difference(InferrableFX, 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) - DiagnosticsForExplicitFX.push_back(NewDiag); - else - InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag); - } - - 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 Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) { - return InferrableEffectToFirstDiagnostic.lookup(effect); - } - - SmallVector &unverifiedCalls() { - assert(!isComplete()); - return UnverifiedDirectCalls; - } - - SmallVector &getDiagnosticsForExplicitFX() { - return DiagnosticsForExplicitFX; - } - - void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { - OS << "Pending: Declared "; - DeclaredVerifiableEffects.dump(OS); - OS << ", " << DiagnosticsForExplicitFX.size() << " diags; "; - OS << " Infer "; - FXToInfer.dump(OS); - OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags"; - if (!UnverifiedDirectCalls.empty()) { - OS << "; Calls: "; - for (const DirectCall &Call : UnverifiedDirectCalls) { - CallableInfo CI(SemaRef, *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. - EffectSet VerifiedEffects; - -private: - // This is used to generate notes about failed inference. - EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; - -public: - // The incoming Pending analysis is consumed (member(s) are moved-from). - CompleteFunctionAnalysis( - ASTContext &Ctx, PendingFunctionAnalysis &Pending, - const EffectSet &DeclaredEffects, - ArrayRef AllInferrableEffectsToVerify) { - VerifiedEffects.insert(DeclaredEffects); - for (const FunctionEffect &effect : AllInferrableEffectsToVerify) - if (Pending.diagnosticForInferrableEffect(effect) == nullptr) - VerifiedEffects.insert(effect); - - InferrableEffectToFirstDiagnostic = - std::move(Pending.InferrableEffectToFirstDiagnostic); - } - - const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) { - return InferrableEffectToFirstDiagnostic.lookup(Effect); - } - - void dump(llvm::raw_ostream &OS) const { - OS << "Complete: Verified "; - VerifiedEffects.dump(OS); - OS << "; Infer "; - OS << InferrableEffectToFirstDiagnostic.size() << " diags\n"; - } -}; - -const Decl *CanonicalFunctionDecl(const Decl *D) { - if (auto *FD = dyn_cast(D)) { - FD = FD->getCanonicalDecl(); - assert(FD != nullptr); - return FD; - } - return D; -} - -// ========== -class Analyzer { - constexpr static int DebugLogLevel = 0; - // -- - Sema &Sem; - - // Subset of Sema.AllEffectsToVerify - EffectSet 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 : protected 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(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(AP)) - return AP.get(); - return nullptr; - } - - void dump(Sema &SemaRef, llvm::raw_ostream &OS) { - OS << "\nAnalysisMap:\n"; - for (const auto &item : *this) { - CallableInfo CI(SemaRef, *item.first); - const auto AP = item.second; - OS << item.first << " " << CI.name(SemaRef) << " : "; - if (AP.isNull()) - OS << "null\n"; - else if (isa(AP)) { - auto *CFA = AP.get(); - OS << CFA << " "; - CFA->dump(OS); - } else if (isa(AP)) { - auto *PFA = AP.get(); - 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 FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) { - const FunctionEffect &Effect = CFE.Effect; - const FunctionEffect::Flags Flags = Effect.flags(); - if (Flags & FunctionEffect::FE_InferrableOnCallees) - AllInferrableEffectsToVerify.insert(Effect); - } - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "AllInferrableEffectsToVerify: "; - AllInferrableEffectsToVerify.dump(llvm::outs()); - llvm::outs() << "\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 = Sem.DeclsWithEffectsToVerify; - std::reverse(VerificationQueue.begin(), VerificationQueue.end()); - - while (!VerificationQueue.empty()) { - const Decl *D = VerificationQueue.back(); - if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { - if (isa(AP)) { - // already done - VerificationQueue.pop_back(); - continue; - } - if (isa(AP)) { - // All children have been traversed; finish analysis. - auto *Pending = AP.get(); - 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(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(Sem, *D); - bool isExternC = false; - - if (const FunctionDecl *FD = dyn_cast(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(D)) { - if (auto *TSI = BD->getSignatureAsWritten()) { - auto *FPT = TSI->getType()->getAs(); - IsNoexcept = FPT->isNothrow() || BD->hasAttr(); - } - } - 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); - - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " "; - FAnalysis.dump(Sem, llvm::outs()); - } - - 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; - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "inserted pending " << PendingPtr << "\n"; - DeclAnalysis.dump(Sem, llvm::outs()); - } - return PendingPtr; - } - - // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis, - // inserted in the container. - void completeAnalysis(const CallableInfo &CInfo, - PendingFunctionAnalysis &Pending) { - if (SmallVector &Diags = - Pending.getDiagnosticsForExplicitFX(); - !Diags.empty()) - emitDiagnostics(Diags, CInfo, Sem); - - CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( - Sem.getASTContext(), Pending, CInfo.Effects, - AllInferrableEffectsToVerify); - DeclAnalysis[CInfo.CDecl] = CompletePtr; - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "inserted complete " << CompletePtr << "\n"; - DeclAnalysis.dump(Sem, llvm::outs()); - } - } - - // 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(Sem, *D); - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : "; - Pending->dump(Sem, llvm::outs()); - llvm::outs() << "\n"; - } - for (const PendingFunctionAnalysis::DirectCall &Call : - Pending->unverifiedCalls()) { - if (Call.Recursed) - continue; - - CallableInfo Callee(Sem, *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.isDirectCall(); - - // Initially, the declared effects; inferred effects will be added. - EffectSet 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; - - if constexpr (DebugLogLevel > 0) { - llvm::outs() << "followCall from " << Caller.name(Sem) << " to " - << Callee.name(Sem) - << "; verifiable: " << Callee.isVerifiable() << "; callee "; - CalleeEffects.dump(llvm::outs()); - llvm::outs() << "\n"; - llvm::outs() << " callee " << Callee.CDecl << " canonical " - << CanonicalFunctionDecl(Callee.CDecl) << " redecls"; - for (Decl *D : Callee.CDecl->redecls()) - llvm::outs() << " " << D; - - llvm::outs() << "\n"; - } - - auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { - FunctionEffect::Flags Flags = Effect.flags(); - bool Diagnose = - Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects); - if (Diagnose) { - // If inference is not allowed, or the target is indirect (virtual - // method/function ptr?), generate a diagnostic now. - if (!IsInferencePossible || - !(Flags & FunctionEffect::FE_InferrableOnCallees)) { - if (Callee.FuncType == SpecialFuncType::None) - PFA.checkAddDiagnostic( - Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect, - CallLoc, Callee.CDecl}); - else - PFA.checkAddDiagnostic( - Inferring, {Effect, DiagnosticID::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 determined to be complete. - void emitDiagnostics(SmallVector &Diags, - const CallableInfo &CInfo, Sema &S) { - if (Diags.empty()) - return; - const SourceManager &SM = S.getSourceManager(); - std::sort(Diags.begin(), Diags.end(), - [&SM](const Diagnostic &LHS, const Diagnostic &RHS) { - return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); - }); - - auto checkAddTemplateNote = [&](const Decl *D) { - if (const FunctionDecl *FD = dyn_cast(D)) { - while (FD != nullptr && FD->isTemplateInstantiation()) { - S.Diag(FD->getPointOfInstantiation(), - diag::note_func_effect_from_template); - FD = FD->getTemplateInstantiationPattern(); - } - } - }; - - // Top-level diagnostics are warnings. - for (const Diagnostic &Diag : Diags) { - StringRef effectName = Diag.Effect.name(); - switch (Diag.ID) { - case DiagnosticID::None: - case DiagnosticID::DeclDisallowsInference: // shouldn't happen - // here - llvm_unreachable("Unexpected diagnostic kind"); - break; - case DiagnosticID::AllocatesMemory: - S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - case DiagnosticID::Throws: - case DiagnosticID::Catches: - S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) - << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - case DiagnosticID::HasStaticLocal: - S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - case DiagnosticID::AccessesThreadLocal: - S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) - << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - case DiagnosticID::CallsObjC: - S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - case DiagnosticID::CallsExprWithoutEffect: - S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect) - << effectName; - checkAddTemplateNote(CInfo.CDecl); - break; - - case DiagnosticID::CallsDeclWithoutEffect: { - CallableInfo CalleeInfo(S, *Diag.Callee); - std::string CalleeName = CalleeInfo.name(S); - - S.Diag(Diag.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 = Diag.Callee; Callee != nullptr;) { - std::optional MaybeNextCallee; - CompleteFunctionAnalysis *Completed = - DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); - if (Completed == nullptr) { - // No result - could be - // - non-inline - // - indirect (virtual or through function pointer) - // - effect has been explicitly disclaimed (e.g. "blocking") - if (CalleeInfo.CType == CallType::Virtual) - S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) - << effectName; - else if (CalleeInfo.CType == CallType::Unknown) - S.Diag(Callee->getLocation(), - diag::note_func_effect_call_func_ptr) - << effectName; - else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind())) - S.Diag(Callee->getLocation(), - diag::note_func_effect_call_disallows_inference) - << effectName; - else - S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) - << effectName; - - break; - } - const Diagnostic *PtrDiag2 = - Completed->firstDiagnosticForEffect(Diag.Effect); - if (PtrDiag2 == nullptr) - break; - - const Diagnostic &Diag2 = *PtrDiag2; - switch (Diag2.ID) { - case DiagnosticID::None: - llvm_unreachable("Unexpected diagnostic kind"); - break; - case DiagnosticID::DeclDisallowsInference: - S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference) - << effectName; - break; - case DiagnosticID::CallsExprWithoutEffect: - S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) - << effectName; - break; - case DiagnosticID::AllocatesMemory: - S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName; - break; - case DiagnosticID::Throws: - case DiagnosticID::Catches: - S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) - << effectName; - break; - case DiagnosticID::HasStaticLocal: - S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) - << effectName; - break; - case DiagnosticID::AccessesThreadLocal: - S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) - << effectName; - break; - case DiagnosticID::CallsObjC: - S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName; - break; - case DiagnosticID::CallsDeclWithoutEffect: - MaybeNextCallee.emplace(S, *Diag2.Callee); - S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect) - << effectName << MaybeNextCallee->name(S); - break; - } - checkAddTemplateNote(Callee); - Callee = Diag2.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. - // - // Diagnostics are always routed to a PendingFunctionAnalysis, which holds - // all diagnostic output. - // - // Q: Currently we create a new RecursiveASTVisitor for every function - // analysis. Is it so lightweight that this is OK? It would appear so. - struct FunctionBodyASTVisitor - : public RecursiveASTVisitor { - // The meanings of the boolean values returned by the Visit methods can be - // difficult to remember. - constexpr static bool Stop = false; - constexpr static bool Proceed = true; - - 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 itself may have some implicit code paths beyond the - // body: member and base constructors and destructors. Visit these first. - if (const auto *FD = dyn_cast(CurrentCaller.CDecl)) { - if (auto *Ctor = dyn_cast(FD)) { - for (const CXXCtorInitializer *Initer : Ctor->inits()) - if (Expr *Init = Initer->getInit()) - VisitStmt(Init); - } else if (auto *Dtor = dyn_cast(FD)) - followDestructor(dyn_cast(Dtor->getParent()), Dtor); - } - // else could be BlockDecl - - // 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 diagnostic. \p Flag describes - // the construct. - void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D, - SourceLocation Loc, - const Decl *Callee = nullptr) { - // If there are any declared verifiable effects which forbid the construct - // represented by the flag, store just one diagnostic. - for (const FunctionEffect &Effect : - CurrentFunction.DeclaredVerifiableEffects) { - if (Effect.flags() & Flag) { - addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee); - break; - } - } - // For each inferred effect which forbids the construct, store a - // diagnostic, if we don't already have a diagnostic for that effect. - for (const FunctionEffect &Effect : CurrentFunction.FXToInfer) - if (Effect.flags() & Flag) - addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee); - } - - void addDiagnostic(bool Inferring, const FunctionEffect &Effect, - DiagnosticID D, SourceLocation Loc, - const Decl *Callee = nullptr) { - CurrentFunction.checkAddDiagnostic(Inferring, - Diagnostic(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) { - // Currently, built-in functions are always considered safe. - // FIXME: Some are not. - if (const auto *FD = dyn_cast(CI.CDecl); - FD && FD->getBuiltinID() != 0) - return; - - Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, - /*AssertNoFurtherInference=*/false); - } - - void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) { - const QualType CalleeType = CalleeExpr->getType(); - auto *FPT = - CalleeType->getAs(); // null if FunctionType - EffectSet CalleeFX; - if (FPT) - CalleeFX.insert(FPT->getFunctionEffects()); - - auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { - if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( - /*direct=*/false, CalleeFX)) - addDiagnostic(Inferring, Effect, DiagnosticID::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()); - - if (const auto *Class = dyn_cast(Rec)) { - for (const CXXBaseSpecifier &Base : Class->bases()) - followTypeDtor(Base.getType()); - - for (const CXXBaseSpecifier &Base : Class->vbases()) - followTypeDtor(Base.getType()); - } - } - - void followTypeDtor(QualType QT) { - 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(Outer.Sem, *Dtor); - followCall(CI, Dtor->getLocation()); - } - } - } - } - - // -- Methods for use of RecursiveASTVisitor -- - - bool shouldVisitImplicitCode() const { return true; } - - bool shouldWalkTypesOfTypeLocs() const { return false; } - - bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - DiagnosticID::Throws, Throw->getThrowLoc()); - return Proceed; - } - - bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - DiagnosticID::Catches, Catch->getCatchLoc()); - return Proceed; - } - - bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - DiagnosticID::Throws, Throw->getThrowLoc()); - return Proceed; - } - - bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - DiagnosticID::Catches, Catch->getAtCatchLoc()); - return Proceed; - } - - bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, - DiagnosticID::CallsObjC, Msg->getBeginLoc()); - return Proceed; - } - - bool VisitCallExpr(CallExpr *Call) { - if constexpr (DebugLogLevel > 2) { - llvm::errs() << "VisitCallExpr : " - << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr) - << "\n"; - } - - Expr *CalleeExpr = Call->getCallee(); - if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { - CallableInfo CI(Outer.Sem, *Callee); - followCall(CI, Call->getBeginLoc()); - return Proceed; - } - - if (isa(CalleeExpr)) - // just destroying a scalar, fine. - return Proceed; - - // No Decl, just an Expr. Just check based on its type. - checkIndirectCall(Call, CalleeExpr); - - return Proceed; - } - - bool VisitVarDecl(VarDecl *Var) { - if constexpr (DebugLogLevel > 2) { - llvm::errs() << "VisitVarDecl : " - << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr) - << "\n"; - } - - if (Var->isStaticLocal()) - diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, - DiagnosticID::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()) { - if (const auto *CxxRec = - dyn_cast(ClsType->getDecl())) { - if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) { - CallableInfo CI(Outer.Sem, *Dtor); - followCall(CI, Var->getLocation()); - } - } - } - } - return Proceed; - } - - 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(Outer.Sem, *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 Proceed; - } - - 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(Outer.Sem, *FD, SpecialFuncType::OperatorDelete); - followCall(CI, Delete->getBeginLoc()); - } - - // It DOES however visit the called destructor - - return Proceed; - } - - bool VisitCXXConstructExpr(CXXConstructExpr *Construct) { - if constexpr (DebugLogLevel > 2) { - llvm::errs() << "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(Outer.Sem, *Ctor); - followCall(CI, Construct->getLocation()); - - return Proceed; - } - - bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) { - if (Expr *E = DEI->getExpr()) - TraverseStmt(E); - - return Proceed; - } - - 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. - for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) - if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I, - Lambda->capture_init_begin()[I]) == Stop) - return Stop; - - return Proceed; - } - - bool TraverseBlockExpr(BlockExpr * /*unused*/) { - // TODO: are the capture expressions (ctor call?) safe? - return Proceed; - } - - bool VisitDeclRefExpr(const DeclRefExpr *E) { - const ValueDecl *Val = E->getDecl(); - if (isa(Val)) { - const VarDecl *Var = cast(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, - DiagnosticID::AccessesThreadLocal, - E->getLocation()); - } - } - return Proceed; - } - - // Unevaluated contexts: need to skip - // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641 - - bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) { - return TraverseStmt(Node->getResultExpr()); - } - bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { - return Proceed; - } - - bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; } - - bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; } - - bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; } - - bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; } - }; -}; - -Analyzer::AnalysisMap::~AnalysisMap() { - for (const auto &Item : *this) { - FuncAnalysisPtr AP = Item.second; - if (isa(AP)) - delete AP.get(); - else - delete AP.get(); - } -} - -} // namespace FXAnalysis - -// ============================================================================= - //===----------------------------------------------------------------------===// // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based // warnings on a function, method, or block. @@ -3819,9 +2553,6 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( SourceLocation())) { CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU); } - - if (S.Context.hasAnyFunctionEffects()) - FXAnalysis::Analyzer{S}.run(*TU); } void clang::sema::AnalysisBasedWarnings::IssueWarnings( diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 2cee4f5ef6e99c6..ea827323395d745 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -19,6 +19,7 @@ add_clang_library(clangSema CodeCompleteConsumer.cpp DeclSpec.cpp DelayedDiagnostic.cpp + EffectAnalysis.cpp HLSLExternalSemaSource.cpp IdentifierResolver.cpp JumpDiagnostics.cpp diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp new file mode 100644 index 000000000000000..ca1213658f45969 --- /dev/null +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -0,0 +1,1289 @@ +//=== 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/Type.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Sema/SemaInternal.h" + +#define DEBUG_TYPE "fxanalysis" + +using namespace clang; + +namespace { + +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 + DeclDisallowsInference, + + CallsDeclWithoutEffect, + CallsExprWithoutEffect, +}; + +// Holds an effect diagnosis, 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. +struct Diagnostic { + FunctionEffect Effect; + DiagnosticID ID = DiagnosticID::None; + SourceLocation Loc; + const Decl *Callee = nullptr; // only valid for Calls* + + Diagnostic() = default; + + Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc, + const Decl *Callee = nullptr) + : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {} +}; + +enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; +enum class CallType { + // 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(); + if (FPT->isNothrow() || FD->hasAttr()) + return true; + return false; +} + +/// A mutable set of FunctionEffect, for use in places where any conditions +/// have been resolved or can be ignored. +class EffectSet { + // This implementation optimizes footprint, since we hold one of these for + // every function visited, which, due to inference, can be many more functions + // than have declared effects. + + template struct FixedVector { + SizeT Count = 0; + T Items[Capacity] = {}; + + using value_type = T; + + using iterator = T *; + using const_iterator = const T *; + iterator begin() { return &Items[0]; } + iterator end() { return &Items[Count]; } + const_iterator begin() const { return &Items[0]; } + const_iterator end() const { return &Items[Count]; } + const_iterator cbegin() const { return &Items[0]; } + const_iterator cend() const { return &Items[Count]; } + + void insert(iterator I, const T &Value) { + assert(Count < Capacity); + iterator E = end(); + if (I != E) + std::copy_backward(I, E, E + 1); + *I = Value; + ++Count; + } + + void push_back(const T &Value) { + assert(Count < Capacity); + Items[Count++] = Value; + } + }; + + // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable + // effects, this fixed-size vector with a capacity of 7 is more than + // sufficient and is only 8 bytes. + FixedVector Impl; + +public: + EffectSet() = default; + explicit EffectSet(FunctionEffectsRef FX) { insert(FX); } + + operator ArrayRef() const { + return ArrayRef(Impl.cbegin(), Impl.cend()); + } + + using iterator = const FunctionEffect *; + iterator begin() const { return Impl.cbegin(); } + iterator end() const { return Impl.cend(); } + + void insert(const FunctionEffect &Effect) { + FunctionEffect *Iter = Impl.begin(); + FunctionEffect *End = Impl.end(); + // linear search; lower_bound is overkill for a tiny vector like this + for (; Iter != End; ++Iter) { + if (*Iter == Effect) + return; + if (Effect < *Iter) + break; + } + Impl.insert(Iter, Effect); + } + void insert(const EffectSet &Set) { + for (const FunctionEffect &Item : Set) { + // push_back because set is already sorted + Impl.push_back(Item); + } + } + void insert(FunctionEffectsRef FX) { + for (const FunctionEffectWithCondition &EC : FX) { + assert(EC.Cond.getCondition() == + nullptr); // should be resolved by now, right? + // push_back because set is already sorted + Impl.push_back(EC.Effect); + } + } + bool contains(const FunctionEffect::Kind EK) const { + for (const FunctionEffect &E : Impl) + if (E.kind() == EK) + return true; + return false; + } + + void dump(llvm::raw_ostream &OS) const; + + static EffectSet difference(ArrayRef LHS, + ArrayRef RHS) { + EffectSet Result; + std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), + std::back_inserter(Result.Impl)); + return Result; + } +}; + +LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const { + OS << "Effects{"; + bool First = true; + for (const FunctionEffect &Effect : *this) { + if (!First) + OS << ", "; + else + First = false; + OS << Effect.name(); + } + OS << "}"; +} + +// Transitory, more extended information about a callable, which can be a +// function, block, function pointer, etc. +struct CallableInfo { + // CDecl holds the function's definition, if any. + // FunctionDecl if CallType::Function or Virtual + // BlockDecl if CallType::Block + const Decl *CDecl; + mutable std::optional MaybeName; + SpecialFuncType FuncType = SpecialFuncType::None; + EffectSet Effects; + CallType CType = CallType::Unknown; + + CallableInfo(Sema &SemaRef, const Decl &CD, + SpecialFuncType FT = SpecialFuncType::None) + : CDecl(&CD), FuncType(FT) { + FunctionEffectsRef FXRef; + + if (auto *FD = dyn_cast(CDecl)) { + // Use the function's definition, if any. + if (const FunctionDecl *Def = FD->getDefinition()) + CDecl = FD = Def; + CType = CallType::Function; + if (auto *Method = dyn_cast(FD); + Method && Method->isVirtual()) + CType = CallType::Virtual; + FXRef = FD->getFunctionEffects(); + } else if (auto *BD = dyn_cast(CDecl)) { + CType = CallType::Block; + FXRef = BD->getFunctionEffects(); + } else if (auto *VD = dyn_cast(CDecl)) { + // ValueDecl is function, enum, or variable, so just look at its type. + FXRef = FunctionEffectsRef::get(VD->getType()); + } + Effects = EffectSet(FXRef); + } + + bool isDirectCall() const { + return CType == CallType::Function || CType == CallType::Block; + } + + bool isVerifiable() const { + switch (CType) { + case CallType::Unknown: + case CallType::Virtual: + return false; + case CallType::Block: + return true; + case CallType::Function: + return functionIsVerifiable(dyn_cast(CDecl)); + } + llvm_unreachable("undefined CallType"); + } + + /// 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(CDecl)) + FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), + /*Qualified=*/true); + else if (auto *BD = dyn_cast(CDecl)) + OS << "(block " << BD->getBlockManglingNumber() << ")"; + else if (auto *VD = dyn_cast(CDecl)) + VD->printQualifiedName(OS); + MaybeName = Name; + } + return *MaybeName; + } +}; + +// ---------- +// Map effects to single diagnostics, to hold the first (of potentially many) +// diagnostics pertaining to an effect, per function. +class EffectToDiagnosticMap { + // 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 Diagnostic itself contains a FunctionEffect which is the key. + using ImplVec = llvm::SmallVector; + std::unique_ptr Impl; + +public: + // Insert a new diagnostic if we do not already have one for its effect. + void maybeInsert(const Diagnostic &Diag) { + if (Impl == nullptr) + Impl = std::make_unique(); + auto *Iter = _find(Diag.Effect); + if (Iter != Impl->end() && Iter->Effect == Diag.Effect) + return; + + Impl->insert(Iter, Diag); + } + + const Diagnostic *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. + EffectSet DeclaredVerifiableEffects; + EffectSet FXToInfer; + +private: + // Diagnostics pertaining to the function's explicit effects. + SmallVector DiagnosticsForExplicitFX; + + // Diagnostics pertaining to other, non-explicit, inferrable effects. + EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + + // These unverified direct calls are what keeps the analysis "pending", + // until the callees can be verified. + SmallVector UnverifiedDirectCalls; + +public: + PendingFunctionAnalysis( + Sema &Sem, const CallableInfo &CInfo, + ArrayRef AllInferrableEffectsToVerify) { + DeclaredVerifiableEffects = CInfo.Effects; + + // Check for effects we are not allowed to infer + EffectSet InferrableFX; + + for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { + if (effect.canInferOnFunction(*CInfo.CDecl)) + InferrableFX.insert(effect); + else { + // Add a diagnostic for this effect if a caller were to + // try to infer it. + InferrableEffectToFirstDiagnostic.maybeInsert( + Diagnostic(effect, DiagnosticID::DeclDisallowsInference, + CInfo.CDecl->getLocation())); + } + } + // InferrableFX is now the set of inferrable effects which are not + // prohibited + FXToInfer = EffectSet::difference(InferrableFX, 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) + DiagnosticsForExplicitFX.push_back(NewDiag); + else + InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag); + } + + 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 Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) { + return InferrableEffectToFirstDiagnostic.lookup(effect); + } + + SmallVector &unverifiedCalls() { + assert(!isComplete()); + return UnverifiedDirectCalls; + } + + SmallVector &getDiagnosticsForExplicitFX() { + return DiagnosticsForExplicitFX; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { + OS << "Pending: Declared "; + DeclaredVerifiableEffects.dump(OS); + OS << ", " << DiagnosticsForExplicitFX.size() << " diags; "; + OS << " Infer "; + FXToInfer.dump(OS); + OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags"; + if (!UnverifiedDirectCalls.empty()) { + OS << "; Calls: "; + for (const DirectCall &Call : UnverifiedDirectCalls) { + CallableInfo CI(SemaRef, *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. + EffectSet VerifiedEffects; + +private: + // This is used to generate notes about failed inference. + EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + +public: + // The incoming Pending analysis is consumed (member(s) are moved-from). + CompleteFunctionAnalysis( + ASTContext &Ctx, PendingFunctionAnalysis &Pending, + const EffectSet &DeclaredEffects, + ArrayRef AllInferrableEffectsToVerify) { + VerifiedEffects.insert(DeclaredEffects); + for (const FunctionEffect &effect : AllInferrableEffectsToVerify) + if (Pending.diagnosticForInferrableEffect(effect) == nullptr) + VerifiedEffects.insert(effect); + + InferrableEffectToFirstDiagnostic = + std::move(Pending.InferrableEffectToFirstDiagnostic); + } + + const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) { + return InferrableEffectToFirstDiagnostic.lookup(Effect); + } + + void dump(llvm::raw_ostream &OS) const { + OS << "Complete: Verified "; + VerifiedEffects.dump(OS); + OS << "; Infer "; + OS << InferrableEffectToFirstDiagnostic.size() << " diags\n"; + } +}; + +const Decl *CanonicalFunctionDecl(const Decl *D) { + if (auto *FD = dyn_cast(D)) { + FD = FD->getCanonicalDecl(); + assert(FD != nullptr); + return FD; + } + return D; +} + +// ========== +class Analyzer { + Sema &Sem; + + // Subset of Sema.AllEffectsToVerify + EffectSet 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 : protected 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(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(AP)) + return AP.get(); + return nullptr; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) { + OS << "\nAnalysisMap:\n"; + for (const auto &item : *this) { + CallableInfo CI(SemaRef, *item.first); + const auto AP = item.second; + OS << item.first << " " << CI.name(SemaRef) << " : "; + if (AP.isNull()) + OS << "null\n"; + else if (isa(AP)) { + auto *CFA = AP.get(); + OS << CFA << " "; + CFA->dump(OS); + } else if (isa(AP)) { + auto *PFA = AP.get(); + 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 FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) { + const FunctionEffect &Effect = CFE.Effect; + 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 = Sem.DeclsWithEffectsToVerify; + std::reverse(VerificationQueue.begin(), VerificationQueue.end()); + + while (!VerificationQueue.empty()) { + const Decl *D = VerificationQueue.back(); + if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { + if (isa(AP)) { + // already done + VerificationQueue.pop_back(); + continue; + } + if (isa(AP)) { + // All children have been traversed; finish analysis. + auto *Pending = AP.get(); + 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(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(Sem, *D); + bool isExternC = false; + + if (const FunctionDecl *FD = dyn_cast(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(D)) { + if (auto *TSI = BD->getSignatureAsWritten()) { + auto *FPT = TSI->getType()->getAs(); + IsNoexcept = FPT->isNothrow() || BD->hasAttr(); + } + } + 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 &Diags = + Pending.getDiagnosticsForExplicitFX(); + !Diags.empty()) + emitDiagnostics(Diags, 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(Sem, *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(Sem, *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.isDirectCall(); + + // Initially, the declared effects; inferred effects will be added. + EffectSet 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) { + FunctionEffect::Flags Flags = Effect.flags(); + bool Diagnose = + Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects); + if (Diagnose) { + // If inference is not allowed, or the target is indirect (virtual + // method/function ptr?), generate a diagnostic now. + if (!IsInferencePossible || + !(Flags & FunctionEffect::FE_InferrableOnCallees)) { + if (Callee.FuncType == SpecialFuncType::None) + PFA.checkAddDiagnostic( + Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect, + CallLoc, Callee.CDecl}); + else + PFA.checkAddDiagnostic( + Inferring, {Effect, DiagnosticID::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 determined to be complete. + void emitDiagnostics(SmallVector &Diags, + const CallableInfo &CInfo, Sema &S) { + if (Diags.empty()) + return; + const SourceManager &SM = S.getSourceManager(); + std::sort(Diags.begin(), Diags.end(), + [&SM](const Diagnostic &LHS, const Diagnostic &RHS) { + return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); + }); + + auto checkAddTemplateNote = [&](const Decl *D) { + if (const FunctionDecl *FD = dyn_cast(D)) { + while (FD != nullptr && FD->isTemplateInstantiation()) { + S.Diag(FD->getPointOfInstantiation(), + diag::note_func_effect_from_template); + FD = FD->getTemplateInstantiationPattern(); + } + } + }; + + // Top-level diagnostics are warnings. + for (const Diagnostic &Diag : Diags) { + StringRef effectName = Diag.Effect.name(); + switch (Diag.ID) { + case DiagnosticID::None: + case DiagnosticID::DeclDisallowsInference: // shouldn't happen + // here + llvm_unreachable("Unexpected diagnostic kind"); + break; + case DiagnosticID::AllocatesMemory: + S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::Throws: + case DiagnosticID::Catches: + S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::HasStaticLocal: + S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::AccessesThreadLocal: + S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::CallsObjC: + S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + case DiagnosticID::CallsExprWithoutEffect: + S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect) + << effectName; + checkAddTemplateNote(CInfo.CDecl); + break; + + case DiagnosticID::CallsDeclWithoutEffect: { + CallableInfo CalleeInfo(S, *Diag.Callee); + std::string CalleeName = CalleeInfo.name(S); + + S.Diag(Diag.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 = Diag.Callee; Callee != nullptr;) { + std::optional MaybeNextCallee; + CompleteFunctionAnalysis *Completed = + DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); + if (Completed == nullptr) { + // No result - could be + // - non-inline + // - indirect (virtual or through function pointer) + // - effect has been explicitly disclaimed (e.g. "blocking") + if (CalleeInfo.CType == CallType::Virtual) + S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) + << effectName; + else if (CalleeInfo.CType == CallType::Unknown) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_func_ptr) + << effectName; + else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind())) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_disallows_inference) + << effectName; + else + S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) + << effectName; + + break; + } + const Diagnostic *PtrDiag2 = + Completed->firstDiagnosticForEffect(Diag.Effect); + if (PtrDiag2 == nullptr) + break; + + const Diagnostic &Diag2 = *PtrDiag2; + switch (Diag2.ID) { + case DiagnosticID::None: + llvm_unreachable("Unexpected diagnostic kind"); + break; + case DiagnosticID::DeclDisallowsInference: + S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference) + << effectName; + break; + case DiagnosticID::CallsExprWithoutEffect: + S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) + << effectName; + break; + case DiagnosticID::AllocatesMemory: + S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName; + break; + case DiagnosticID::Throws: + case DiagnosticID::Catches: + S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) + << effectName; + break; + case DiagnosticID::HasStaticLocal: + S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) + << effectName; + break; + case DiagnosticID::AccessesThreadLocal: + S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) + << effectName; + break; + case DiagnosticID::CallsObjC: + S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName; + break; + case DiagnosticID::CallsDeclWithoutEffect: + MaybeNextCallee.emplace(S, *Diag2.Callee); + S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect) + << effectName << MaybeNextCallee->name(S); + break; + } + checkAddTemplateNote(Callee); + Callee = Diag2.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. + // + // Diagnostics are always routed to a PendingFunctionAnalysis, which holds + // all diagnostic output. + struct FunctionBodyASTVisitor + : public RecursiveASTVisitor { + + 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 itself may have some implicit code paths beyond the + // body: member and base constructors and destructors. Visit these first. + if (const auto *FD = dyn_cast(CurrentCaller.CDecl)) { + if (auto *Ctor = dyn_cast(FD)) { + for (const CXXCtorInitializer *Initer : Ctor->inits()) + if (Expr *Init = Initer->getInit()) + VisitStmt(Init); + } else if (auto *Dtor = dyn_cast(FD)) + followDestructor(dyn_cast(Dtor->getParent()), Dtor); + } + // else could be BlockDecl + + // 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 diagnostic. \p Flag describes + // the construct. + void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D, + SourceLocation Loc, + const Decl *Callee = nullptr) { + // If there are any declared verifiable effects which forbid the construct + // represented by the flag, store just one diagnostic. + for (const FunctionEffect &Effect : + CurrentFunction.DeclaredVerifiableEffects) { + if (Effect.flags() & Flag) { + addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee); + break; + } + } + // For each inferred effect which forbids the construct, store a + // diagnostic, if we don't already have a diagnostic for that effect. + for (const FunctionEffect &Effect : CurrentFunction.FXToInfer) + if (Effect.flags() & Flag) + addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee); + } + + void addDiagnostic(bool Inferring, const FunctionEffect &Effect, + DiagnosticID D, SourceLocation Loc, + const Decl *Callee = nullptr) { + CurrentFunction.checkAddDiagnostic(Inferring, + Diagnostic(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) { + // Currently, built-in functions are always considered safe. + // FIXME: Some are not. + if (const auto *FD = dyn_cast(CI.CDecl); + FD && FD->getBuiltinID() != 0) + return; + + Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, + /*AssertNoFurtherInference=*/false); + } + + void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) { + const QualType CalleeType = CalleeExpr->getType(); + auto *FPT = + CalleeType->getAs(); // null if FunctionType + EffectSet CalleeFX; + if (FPT) + CalleeFX.insert(FPT->getFunctionEffects()); + + auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( + /*direct=*/false, CalleeFX)) + addDiagnostic(Inferring, Effect, DiagnosticID::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()); + + if (const auto *Class = dyn_cast(Rec)) { + for (const CXXBaseSpecifier &Base : Class->bases()) + followTypeDtor(Base.getType()); + + for (const CXXBaseSpecifier &Base : Class->vbases()) + followTypeDtor(Base.getType()); + } + } + + void followTypeDtor(QualType QT) { + 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(Outer.Sem, *Dtor); + followCall(CI, Dtor->getLocation()); + } + } + } + } + + // -- Methods for use of RecursiveASTVisitor -- + + bool shouldVisitImplicitCode() const { return true; } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + DiagnosticID::Throws, Throw->getThrowLoc()); + return true; + } + + bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + DiagnosticID::Catches, Catch->getCatchLoc()); + return true; + } + + bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + DiagnosticID::Throws, Throw->getThrowLoc()); + return true; + } + + bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + DiagnosticID::Catches, Catch->getAtCatchLoc()); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, + DiagnosticID::CallsObjC, Msg->getBeginLoc()); + 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(Outer.Sem, *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); + + 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, + DiagnosticID::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()) { + if (const auto *CxxRec = + dyn_cast(ClsType->getDecl())) { + if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) { + CallableInfo CI(Outer.Sem, *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(Outer.Sem, *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(Outer.Sem, *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(Outer.Sem, *Ctor); + followCall(CI, Construct->getLocation()); + + return true; + } + + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) { + if (Expr *E = DEI->getExpr()) + TraverseStmt(E); + + 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. + 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(Val)) { + const VarDecl *Var = cast(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, + DiagnosticID::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(AP)) + delete AP.get(); + else + delete AP.get(); + } +} + +} // 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; + Analyzer{S}.run(*TU); +} + +} // namespace clang diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 2e989f0ba6fe450..35ba1da9770c4e8 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1509,6 +1509,9 @@ void Sema::ActOnEndOfTranslationUnit() { AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl()); + if (Context.hasAnyFunctionEffects()) + performEffectAnalysis(*this, Context.getTranslationUnitDecl()); + // Check we've noticed that we're no longer parsing the initializer for every // variable. If we miss cases, then at best we have a performance issue and // at worst a rejects-valid bug. From efe1b93804ed91634bd89d1ea9b64947c5af79d4 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 26 Jul 2024 14:29:42 -0700 Subject: [PATCH 07/63] CallableInfo doesn't need to cache the name. Clean up traversal of constructor initializers and implicit destructors (with test). --- clang/lib/Sema/EffectAnalysis.cpp | 51 ++++++------- .../Sema/attr-nonblocking-constraints.cpp | 71 +++++++++++-------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index ca1213658f45969..862cb17b8bd783f 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -199,7 +199,6 @@ struct CallableInfo { // FunctionDecl if CallType::Function or Virtual // BlockDecl if CallType::Block const Decl *CDecl; - mutable std::optional MaybeName; SpecialFuncType FuncType = SpecialFuncType::None; EffectSet Effects; CallType CType = CallType::Unknown; @@ -247,20 +246,17 @@ struct CallableInfo { /// 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(CDecl)) - FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), - /*Qualified=*/true); - else if (auto *BD = dyn_cast(CDecl)) - OS << "(block " << BD->getBlockManglingNumber() << ")"; - else if (auto *VD = dyn_cast(CDecl)) - VD->printQualifiedName(OS); - MaybeName = Name; - } - return *MaybeName; + std::string Name; + llvm::raw_string_ostream OS(Name); + + if (auto *FD = dyn_cast(CDecl)) + FD->getNameForDiagnostic(OS, Sem.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; } }; @@ -955,17 +951,10 @@ class Analyzer { // -- Entry point -- void run() { - // The target function itself may have some implicit code paths beyond the - // body: member and base constructors and destructors. Visit these first. - if (const auto *FD = dyn_cast(CurrentCaller.CDecl)) { - if (auto *Ctor = dyn_cast(FD)) { - for (const CXXCtorInitializer *Initer : Ctor->inits()) - if (Expr *Init = Initer->getInit()) - VisitStmt(Init); - } else if (auto *Dtor = dyn_cast(FD)) - followDestructor(dyn_cast(Dtor->getParent()), Dtor); - } - // else could be BlockDecl + // 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); // Do an AST traversal of the function/block body TraverseDecl(const_cast(CurrentCaller.CDecl)); @@ -1043,18 +1032,18 @@ class Analyzer { void followDestructor(const CXXRecordDecl *Rec, const CXXDestructorDecl *Dtor) { for (const FieldDecl *Field : Rec->fields()) - followTypeDtor(Field->getType()); + followTypeDtor(Field->getType(), Dtor); if (const auto *Class = dyn_cast(Rec)) { for (const CXXBaseSpecifier &Base : Class->bases()) - followTypeDtor(Base.getType()); + followTypeDtor(Base.getType(), Dtor); for (const CXXBaseSpecifier &Base : Class->vbases()) - followTypeDtor(Base.getType()); + followTypeDtor(Base.getType(), Dtor); } } - void followTypeDtor(QualType QT) { + void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) { const Type *Ty = QT.getTypePtr(); while (Ty->isArrayType()) { const ArrayType *Arr = Ty->getAsArrayTypeUnsafe(); @@ -1066,7 +1055,7 @@ class Analyzer { if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { if (CXXDestructorDecl *Dtor = Class->getDestructor()) { CallableInfo CI(Outer.Sem, *Dtor); - followCall(CI, Dtor->getLocation()); + followCall(CI, OuterDtor->getLocation()); } } } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index e50c5b436daafcf..7c7e322df809648 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -7,18 +7,18 @@ // --- CONSTRAINTS --- -void nl1() [[clang::nonblocking]] +void nb1() [[clang::nonblocking]] { int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} } -void nl2() [[clang::nonblocking]] +void nb2() [[clang::nonblocking]] { static int global; // expected-warning {{'nonblocking' function must not have static locals}} } -void nl3() [[clang::nonblocking]] +void nb3() [[clang::nonblocking]] { try { throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} @@ -27,13 +27,13 @@ void nl3() [[clang::nonblocking]] } } -void nl4_inline() {} -void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +void nb4_inline() {} +void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} -void nl4() [[clang::nonblocking]] +void nb4() [[clang::nonblocking]] { - nl4_inline(); // OK - nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb4_inline(); // OK + nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} } @@ -41,21 +41,21 @@ struct HasVirtual { virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}} }; -void nl5() [[clang::nonblocking]] +void nb5() [[clang::nonblocking]] { HasVirtual hv; hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} } -void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} -void nl6_transitively_unsafe() +void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +void nb6_transitively_unsafe() { - nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} + nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} } -void nl6() [[clang::nonblocking]] +void nb6() [[clang::nonblocking]] { - nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} } thread_local int tl_var{ 42 }; @@ -65,7 +65,7 @@ bool tl_test() [[clang::nonblocking]] return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}} } -void nl7() +void nb7() { // Make sure we verify blocks auto blk = ^() [[clang::nonblocking]] { @@ -73,7 +73,7 @@ void nl7() }; } -void nl8() +void nb8() { // Make sure we verify lambdas auto lambda = []() [[clang::nonblocking]] { @@ -111,7 +111,7 @@ void nl8() } }; -void nl9() [[clang::nonblocking]] +void nb9() [[clang::nonblocking]] { Adder::add_explicit(1, 2); Adder::add_implicit(1, 2); @@ -121,7 +121,7 @@ void nl9() [[clang::nonblocking]] expected-note {{in template expansion here}} } -void nl10( +void nb10( void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}} void (*fp2)() [[clang::nonblocking]] ) [[clang::nonblocking]] @@ -131,20 +131,20 @@ void nl10( } // Interactions with nonblocking(false) -void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}} +void nb11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}} { } -void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}} +void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}} template struct ComputedNB { void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}} }; -void nl11() [[clang::nonblocking]] +void nb11() [[clang::nonblocking]] { - nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} - nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} ComputedNB CNB_true; CNB_true.method(); @@ -154,11 +154,11 @@ void nl11() [[clang::nonblocking]] } // Verify that when attached to a redeclaration, the attribute successfully attaches. -void nl12() { +void nb12() { static int x; // expected-warning {{'nonblocking' function must not have static locals}} } -void nl12() [[clang::nonblocking]]; -void nl13() [[clang::nonblocking]] { nl12(); } +void nb12() [[clang::nonblocking]]; +void nb13() [[clang::nonblocking]] { nb12(); } // C++ member function pointers struct PTMFTester { @@ -175,21 +175,34 @@ void PTMFTester::convert() [[clang::nonblocking]] } // Block variables -void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] { +void nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] { blk(); } // References to blocks -void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] +void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] { auto &ref = block; ref(); } +// Verify traversal of implicit code paths - constructors and destructors. +struct Unsafe { + static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + + Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}} + ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}} +}; + +struct DerivedFromUnsafe : public Unsafe { + DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} + ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}} +}; // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" -void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} +void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} { } From 7ffdbefa56e25f8398b32104c4616690ab076afe Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 1 Aug 2024 18:09:59 -0700 Subject: [PATCH 08/63] Simpler bitmap implementation of FunctionEffectKindSet --- clang/include/clang/AST/Type.h | 75 +++++++++++++++- clang/include/clang/Sema/Sema.h | 6 +- clang/lib/AST/Type.cpp | 17 +++- clang/lib/Sema/EffectAnalysis.cpp | 137 +++++++++++++----------------- clang/lib/Sema/SemaDecl.cpp | 6 +- 5 files changed, 153 insertions(+), 88 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 89a74ff1fb285d9..f4e6c8f04b8be88 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -119,6 +119,7 @@ class Expr; class ExtQualsTypeCommonBase; class FunctionDecl; class FunctionEffectSet; +class FunctionEffectKindSet; class IdentifierInfo; class NamedDecl; class ObjCInterfaceDecl; @@ -4755,7 +4756,7 @@ class FunctionEffect { // diagnostic. Caller should be assumed to have the effect (it may not have it // explicitly when inferring). bool shouldDiagnoseFunctionCall(bool Direct, - ArrayRef CalleeFX) const; + const FunctionEffectKindSet &CalleeFX) const; friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) { return LHS.FKind == RHS.FKind; @@ -4902,6 +4903,78 @@ class FunctionEffectsRef { void dump(llvm::raw_ostream &OS) const; }; +/// A mutable set of FunctionEffect::Kind. +class FunctionEffectKindSet { + // For now this only needs to be a bitmap. + using KindBitsT = uint8_t; + constexpr static size_t EndBitPos = 8; + + KindBitsT KindBits = 0; + + static KindBitsT kindToBit(FunctionEffect::Kind K) { + return 1u << KindBitsT(K); + } + + explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {} + +public: + FunctionEffectKindSet() = default; + explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); } + + class iterator { + const FunctionEffectKindSet *Outer = nullptr; + size_t Idx = 0; + + // If Idx does not reference a set bit, advance it until it does, + // or until it reaches EndBitPos. + void advanceIdx() { + while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx))) + ++Idx; + } + + public: + iterator(); + iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) { + advanceIdx(); + } + bool operator==(const iterator &Other) const { return Idx == Other.Idx; } + bool operator!=(const iterator &Other) const { return Idx != Other.Idx; } + + iterator operator++() { + ++Idx; + advanceIdx(); + return *this; + } + + FunctionEffect operator*() const { + assert(Idx < EndBitPos); + return FunctionEffect(FunctionEffect::Kind(Idx)); + } + }; + + iterator begin() const { return iterator(*this, 0); } + iterator end() const { return iterator(*this, EndBitPos); } + + void insert(const FunctionEffect &Effect) { + KindBits |= kindToBit(Effect.kind()); + } + void insert(FunctionEffectsRef FX) { + for (const FunctionEffect &Item : FX.effects()) + insert(Item); + } + void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; } + + bool contains(const FunctionEffect::Kind EK) const { + return (KindBits & kindToBit(EK)) != 0; + } + void dump(llvm::raw_ostream &OS) const; + + static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS, + const FunctionEffectKindSet &RHS) { + return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits); + } +}; + /// A mutable set of FunctionEffects and possibly conditions attached to them. /// Used to compare and merge effects on declarations. /// diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 49315edcca74642..b166825bef4a5fc 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -530,8 +530,8 @@ struct FunctionEffectDifferences : public SmallVector { const FunctionEffectsRef &New); }; -// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move -// more of the effects implementation into that file? +// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and +// move more of the effects implementation into that file? void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU); /// Sema - This implements semantic analysis and AST building for C. @@ -882,7 +882,7 @@ class Sema final : public SemaBase { SmallVector DeclsWithEffectsToVerify; /// The union of all effects present on DeclsWithEffectsToVerify. Conditions /// are all null. - FunctionEffectSet AllEffectsToVerify; + FunctionEffectKindSet AllEffectsToVerify; /// Warn when implicitly changing function effects. void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index fdaab8e4345936c..6152375eb091aa0 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5163,12 +5163,12 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const { } bool FunctionEffect::shouldDiagnoseFunctionCall( - bool Direct, ArrayRef CalleeFX) const { + bool Direct, const FunctionEffectKindSet &CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { const Kind CallerKind = kind(); - for (const auto &Effect : CalleeFX) { + for (const FunctionEffect &Effect : CalleeFX) { const Kind EK = Effect.kind(); // Does callee have same or stronger constraint? if (EK == CallerKind || @@ -5311,6 +5311,19 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const { FunctionEffectsRef(*this).dump(OS); } +LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const { + OS << "Effects{"; + bool First = true; + for (const auto &Effect : *this) { + if (!First) + OS << ", "; + else + First = false; + OS << Effect.name(); + } + OS << "}"; +} + FunctionEffectsRef FunctionEffectsRef::create(ArrayRef FX, ArrayRef Conds) { diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index 862cb17b8bd783f..83c3a07b2c66c9e 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -11,8 +11,8 @@ //===----------------------------------------------------------------------===// #include "clang/AST/Decl.h" -#include "clang/AST/Type.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/SemaInternal.h" @@ -81,6 +81,7 @@ static bool isNoexcept(const FunctionDecl *FD) { return false; } +#if 0 /// A mutable set of FunctionEffect, for use in places where any conditions /// have been resolved or can be ignored. class EffectSet { @@ -191,6 +192,7 @@ LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const { } OS << "}"; } +#endif // Transitory, more extended information about a callable, which can be a // function, block, function pointer, etc. @@ -200,7 +202,7 @@ struct CallableInfo { // BlockDecl if CallType::Block const Decl *CDecl; SpecialFuncType FuncType = SpecialFuncType::None; - EffectSet Effects; + FunctionEffectKindSet Effects; CallType CType = CallType::Unknown; CallableInfo(Sema &SemaRef, const Decl &CD, @@ -224,7 +226,7 @@ struct CallableInfo { // ValueDecl is function, enum, or variable, so just look at its type. FXRef = FunctionEffectsRef::get(VD->getType()); } - Effects = EffectSet(FXRef); + Effects = FunctionEffectKindSet(FXRef); } bool isDirectCall() const { @@ -251,7 +253,7 @@ struct CallableInfo { if (auto *FD = dyn_cast(CDecl)) FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), - /*Qualified=*/true); + /*Qualified=*/true); else if (auto *BD = dyn_cast(CDecl)) OS << "(block " << BD->getBlockManglingNumber() << ")"; else if (auto *VD = dyn_cast(CDecl)) @@ -328,8 +330,8 @@ class PendingFunctionAnalysis { // We always have two disjoint sets of effects to verify: // 1. Effects declared explicitly by this function. // 2. All other inferrable effects needing verification. - EffectSet DeclaredVerifiableEffects; - EffectSet FXToInfer; + FunctionEffectKindSet DeclaredVerifiableEffects; + FunctionEffectKindSet FXToInfer; private: // Diagnostics pertaining to the function's explicit effects. @@ -345,11 +347,10 @@ class PendingFunctionAnalysis { public: PendingFunctionAnalysis( Sema &Sem, const CallableInfo &CInfo, - ArrayRef AllInferrableEffectsToVerify) { - DeclaredVerifiableEffects = CInfo.Effects; - + const FunctionEffectKindSet &AllInferrableEffectsToVerify) + : DeclaredVerifiableEffects(CInfo.Effects) { // Check for effects we are not allowed to infer - EffectSet InferrableFX; + FunctionEffectKindSet InferrableFX; for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { if (effect.canInferOnFunction(*CInfo.CDecl)) @@ -364,7 +365,8 @@ class PendingFunctionAnalysis { } // InferrableFX is now the set of inferrable effects which are not // prohibited - FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects); + FXToInfer = FunctionEffectKindSet::difference(InferrableFX, + DeclaredVerifiableEffects); } // Hide the way that diagnostics for explicitly required effects vs. inferred @@ -422,7 +424,7 @@ class CompleteFunctionAnalysis { // 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. - EffectSet VerifiedEffects; + FunctionEffectKindSet VerifiedEffects; private: // This is used to generate notes about failed inference. @@ -432,9 +434,9 @@ class CompleteFunctionAnalysis { // The incoming Pending analysis is consumed (member(s) are moved-from). CompleteFunctionAnalysis( ASTContext &Ctx, PendingFunctionAnalysis &Pending, - const EffectSet &DeclaredEffects, - ArrayRef AllInferrableEffectsToVerify) { - VerifiedEffects.insert(DeclaredEffects); + const FunctionEffectKindSet &DeclaredEffects, + const FunctionEffectKindSet &AllInferrableEffectsToVerify) + : VerifiedEffects(DeclaredEffects) { for (const FunctionEffect &effect : AllInferrableEffectsToVerify) if (Pending.diagnosticForInferrableEffect(effect) == nullptr) VerifiedEffects.insert(effect); @@ -469,7 +471,7 @@ class Analyzer { Sema &Sem; // Subset of Sema.AllEffectsToVerify - EffectSet AllInferrableEffectsToVerify; + FunctionEffectKindSet AllInferrableEffectsToVerify; using FuncAnalysisPtr = llvm::PointerUnion; @@ -532,17 +534,14 @@ class Analyzer { 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 FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) { - const FunctionEffect &Effect = CFE.Effect; + 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"; - ); + 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, @@ -640,10 +639,8 @@ class Analyzer { // 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()); - ); + LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " "; + FAnalysis.dump(Sem, llvm::dbgs());); FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo); @@ -656,10 +653,8 @@ class Analyzer { PendingFunctionAnalysis *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis)); DeclAnalysis[D] = PendingPtr; - LLVM_DEBUG( - llvm::dbgs() << "inserted pending " << PendingPtr << "\n"; - DeclAnalysis.dump(Sem, llvm::dbgs()); - ); + LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n"; + DeclAnalysis.dump(Sem, llvm::dbgs());); return PendingPtr; } @@ -676,10 +671,8 @@ class Analyzer { Sem.getASTContext(), Pending, CInfo.Effects, AllInferrableEffectsToVerify); DeclAnalysis[CInfo.CDecl] = CompletePtr; - LLVM_DEBUG( - llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; - DeclAnalysis.dump(Sem, llvm::dbgs()); - ); + LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; + DeclAnalysis.dump(Sem, llvm::dbgs());); } // Called after all direct calls requiring inference have been found -- or @@ -687,11 +680,9 @@ class Analyzer { // the possibility of inference. Deletes Pending. void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) { CallableInfo Caller(Sem, *D); - LLVM_DEBUG( - llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : "; - Pending->dump(Sem, llvm::dbgs()); - llvm::dbgs() << "\n"; - ); + 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) @@ -713,7 +704,7 @@ class Analyzer { const bool DirectCall = Callee.isDirectCall(); // Initially, the declared effects; inferred effects will be added. - EffectSet CalleeEffects = Callee.Effects; + FunctionEffectKindSet CalleeEffects = Callee.Effects; bool IsInferencePossible = DirectCall; @@ -733,19 +724,16 @@ class Analyzer { 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_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"; - ); + llvm::dbgs() << "\n";); auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { FunctionEffect::Flags Flags = Effect.flags(); @@ -1008,7 +996,7 @@ class Analyzer { const QualType CalleeType = CalleeExpr->getType(); auto *FPT = CalleeType->getAs(); // null if FunctionType - EffectSet CalleeFX; + FunctionEffectKindSet CalleeFX; if (FPT) CalleeFX.insert(FPT->getFunctionEffects()); @@ -1098,11 +1086,10 @@ class Analyzer { } bool VisitCallExpr(CallExpr *Call) { - LLVM_DEBUG( - llvm::dbgs() << "VisitCallExpr : " + LLVM_DEBUG(llvm::dbgs() + << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr) - << "\n"; - ); + << "\n";); Expr *CalleeExpr = Call->getCallee(); if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { @@ -1122,11 +1109,10 @@ class Analyzer { } bool VisitVarDecl(VarDecl *Var) { - LLVM_DEBUG( - llvm::dbgs() << "VisitVarDecl : " + LLVM_DEBUG(llvm::dbgs() + << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr) - << "\n"; - ); + << "\n";); if (Var->isStaticLocal()) diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, @@ -1180,12 +1166,10 @@ class Analyzer { } bool VisitCXXConstructExpr(CXXConstructExpr *Construct) { - LLVM_DEBUG( - llvm::dbgs() << "VisitCXXConstructExpr : " - << Construct->getBeginLoc().printToString( - Outer.Sem.SourceMgr) - << "\n"; - ); + 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. @@ -1208,7 +1192,7 @@ class Analyzer { // body. We have to explicitly traverse the captures. for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I, - Lambda->capture_init_begin()[I]); + Lambda->capture_init_begin()[I]); return true; } @@ -1265,14 +1249,13 @@ Analyzer::AnalysisMap::~AnalysisMap() { 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; - Analyzer{S}.run(*TU); +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; + Analyzer{S}.run(*TU); } } // namespace clang diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index b54da058c43c5e5..ef27d42b92ce699 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -10929,8 +10929,6 @@ void Sema::maybeAddDeclWithEffects(const Decl *D, } void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { - FunctionEffectSet::Conflicts Errs; - // 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. @@ -10938,11 +10936,9 @@ void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { bool AnyVerifiable = false; for (const FunctionEffectWithCondition &EC : FX) if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) { - AllEffectsToVerify.insert(FunctionEffectWithCondition(EC.Effect, nullptr), - Errs); + AllEffectsToVerify.insert(EC.Effect); AnyVerifiable = true; } - assert(Errs.empty() && "effects conflicts should not be possible here"); // Record the declaration for later analysis. if (AnyVerifiable) From c39e28e78f882a148914e7c17c60c01e461bea07 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 1 Aug 2024 21:36:11 -0700 Subject: [PATCH 09/63] - function does not permit inference of '%0': include the name of the effect preventing inference. - EffectAnalysis: Rename Diagnostic to Violation to clarify that it is an abstraction which can be reported as either a warning or a note depending on context. --- clang/include/clang/AST/Type.h | 13 +- .../clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/lib/AST/Type.cpp | 21 +- clang/lib/Sema/EffectAnalysis.cpp | 296 +++++++++--------- .../Sema/attr-nonblocking-constraints.cpp | 2 +- 5 files changed, 172 insertions(+), 162 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index f4e6c8f04b8be88..b13ba7d784b3a92 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -118,8 +118,9 @@ class EnumDecl; class Expr; class ExtQualsTypeCommonBase; class FunctionDecl; -class FunctionEffectSet; +class FunctionEffectsRef; class FunctionEffectKindSet; +class FunctionEffectSet; class IdentifierInfo; class NamedDecl; class ObjCInterfaceDecl; @@ -4745,11 +4746,15 @@ class FunctionEffect { /// The description printed in diagnostics, e.g. 'nonblocking'. StringRef name() const; - /// Return true if the effect is allowed to be inferred on the callee, - /// which is either a FunctionDecl or BlockDecl. + /// Determine whether the effect is allowed to be inferred on the callee, + /// which is either a FunctionDecl or BlockDecl. If the returned optional + /// is empty, inference is permitted; otherwise it holds the effect which + /// blocked inference. /// Example: This allows nonblocking(false) to prevent inference for the /// function. - bool canInferOnFunction(const Decl &Callee) const; + std::optional + effectProhibitingInference(const Decl &Callee, + const FunctionEffectsRef &CalleeFX) const; // Return false for success. When true is returned for a direct call, then the // FE_InferrableOnCallees flag may trigger inference rather than an immediate diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 709535dace2cf89..eedc2722a479935 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10969,7 +10969,7 @@ def note_func_effect_calls_func_without_effect : Note< def note_func_effect_call_extern : Note< "function cannot be inferred '%0' because it has no definition in this translation unit">; def note_func_effect_call_disallows_inference : Note< - "function does not permit inference of '%0'">; + "function does not permit inference of '%0' because it is declared '%1'">; def note_func_effect_call_virtual : Note< "virtual method cannot be inferred '%0'">; def note_func_effect_call_func_ptr : Note< diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 6152375eb091aa0..06f6bb03d1d9f11 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5128,35 +5128,30 @@ StringRef FunctionEffect::name() const { llvm_unreachable("unknown effect kind"); } -bool FunctionEffect::canInferOnFunction(const Decl &Callee) const { +std::optional FunctionEffect::effectProhibitingInference( + const Decl &Callee, const FunctionEffectsRef &CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { - FunctionEffectsRef CalleeFX; - if (auto *FD = Callee.getAsFunction()) - CalleeFX = FD->getFunctionEffects(); - else if (auto *BD = dyn_cast(&Callee)) - CalleeFX = BD->getFunctionEffects(); - else - return false; for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) { // nonblocking/nonallocating cannot call allocating. if (CalleeEC.Effect.kind() == Kind::Allocating) - return false; + return CalleeEC.Effect; // nonblocking cannot call blocking. if (kind() == Kind::NonBlocking && CalleeEC.Effect.kind() == Kind::Blocking) - return false; + return CalleeEC.Effect; } - return true; + return std::nullopt; } case Kind::Allocating: case Kind::Blocking: - return false; + assert(0 && "effectProhibitingInference with non-inferable effect kind"); + break; case Kind::None: - assert(0 && "canInferOnFunction with None"); + assert(0 && "effectProhibitingInference with None"); break; } llvm_unreachable("unknown effect kind"); diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index 83c3a07b2c66c9e..a8bbb73faf9fd87 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -22,8 +22,8 @@ using namespace clang; namespace { -enum class DiagnosticID : uint8_t { - None = 0, // sentinel for an empty Diagnostic +enum class ViolationID : uint8_t { + None = 0, // sentinel for an empty Violation Throws, Catches, CallsObjC, @@ -38,20 +38,28 @@ enum class DiagnosticID : uint8_t { CallsExprWithoutEffect, }; -// Holds an effect diagnosis, 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. -struct Diagnostic { +// 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; - DiagnosticID ID = DiagnosticID::None; + FunctionEffect CalleeEffectPreventingInference; // only for certain IDs + ViolationID ID = ViolationID::None; SourceLocation Loc; const Decl *Callee = nullptr; // only valid for Calls* - Diagnostic() = default; + Violation() = default; - Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc, - const Decl *Callee = nullptr) - : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {} + 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 }; @@ -202,13 +210,13 @@ struct CallableInfo { // BlockDecl if CallType::Block const Decl *CDecl; SpecialFuncType FuncType = SpecialFuncType::None; + FunctionEffectsRef DeclEffects; FunctionEffectKindSet Effects; CallType CType = CallType::Unknown; CallableInfo(Sema &SemaRef, const Decl &CD, SpecialFuncType FT = SpecialFuncType::None) : CDecl(&CD), FuncType(FT) { - FunctionEffectsRef FXRef; if (auto *FD = dyn_cast(CDecl)) { // Use the function's definition, if any. @@ -218,15 +226,15 @@ struct CallableInfo { if (auto *Method = dyn_cast(FD); Method && Method->isVirtual()) CType = CallType::Virtual; - FXRef = FD->getFunctionEffects(); + DeclEffects = FD->getFunctionEffects(); } else if (auto *BD = dyn_cast(CDecl)) { CType = CallType::Block; - FXRef = BD->getFunctionEffects(); + DeclEffects = BD->getFunctionEffects(); } else if (auto *VD = dyn_cast(CDecl)) { // ValueDecl is function, enum, or variable, so just look at its type. - FXRef = FunctionEffectsRef::get(VD->getType()); + DeclEffects = FunctionEffectsRef::get(VD->getType()); } - Effects = FunctionEffectKindSet(FXRef); + Effects = FunctionEffectKindSet(DeclEffects); } bool isDirectCall() const { @@ -263,29 +271,29 @@ struct CallableInfo { }; // ---------- -// Map effects to single diagnostics, to hold the first (of potentially many) -// diagnostics pertaining to an effect, per function. -class EffectToDiagnosticMap { +// 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 Diagnostic itself contains a FunctionEffect which is the key. - using ImplVec = llvm::SmallVector; + // Note that Violation itself contains a FunctionEffect which is the key. + using ImplVec = llvm::SmallVector; std::unique_ptr Impl; public: - // Insert a new diagnostic if we do not already have one for its effect. - void maybeInsert(const Diagnostic &Diag) { + // 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(); - auto *Iter = _find(Diag.Effect); - if (Iter != Impl->end() && Iter->Effect == Diag.Effect) + auto *Iter = _find(Viol.Effect); + if (Iter != Impl->end() && Iter->Effect == Viol.Effect) return; - Impl->insert(Iter, Diag); + Impl->insert(Iter, Viol); } - const Diagnostic *lookup(FunctionEffect Key) { + const Violation *lookup(FunctionEffect Key) { if (Impl == nullptr) return nullptr; @@ -334,11 +342,11 @@ class PendingFunctionAnalysis { FunctionEffectKindSet FXToInfer; private: - // Diagnostics pertaining to the function's explicit effects. - SmallVector DiagnosticsForExplicitFX; + // Violations pertaining to the function's explicit effects. + SmallVector ViolationsForExplicitFX; - // Diagnostics pertaining to other, non-explicit, inferrable effects. - EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + // 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. @@ -353,14 +361,16 @@ class PendingFunctionAnalysis { FunctionEffectKindSet InferrableFX; for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { - if (effect.canInferOnFunction(*CInfo.CDecl)) + std::optional ProblemCalleeEffect = + effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects); + if (!ProblemCalleeEffect) InferrableFX.insert(effect); else { - // Add a diagnostic for this effect if a caller were to + // Add a Violation for this effect if a caller were to // try to infer it. - InferrableEffectToFirstDiagnostic.maybeInsert( - Diagnostic(effect, DiagnosticID::DeclDisallowsInference, - CInfo.CDecl->getLocation())); + InferrableEffectToFirstViolation.maybeInsert(Violation( + effect, ViolationID::DeclDisallowsInference, + CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect)); } } // InferrableFX is now the set of inferrable effects which are not @@ -369,13 +379,13 @@ class PendingFunctionAnalysis { DeclaredVerifiableEffects); } - // Hide the way that diagnostics for explicitly required effects vs. inferred + // Hide the way that Violations for explicitly required effects vs. inferred // ones are handled differently. - void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) { + void checkAddViolation(bool Inferring, const Violation &NewViol) { if (!Inferring) - DiagnosticsForExplicitFX.push_back(NewDiag); + ViolationsForExplicitFX.push_back(NewViol); else - InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag); + InferrableEffectToFirstViolation.maybeInsert(NewViol); } void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) { @@ -385,8 +395,8 @@ class PendingFunctionAnalysis { // Analysis is complete when there are no unverified direct calls. bool isComplete() const { return UnverifiedDirectCalls.empty(); } - const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) { - return InferrableEffectToFirstDiagnostic.lookup(effect); + const Violation *violationForInferrableEffect(FunctionEffect effect) { + return InferrableEffectToFirstViolation.lookup(effect); } SmallVector &unverifiedCalls() { @@ -394,17 +404,17 @@ class PendingFunctionAnalysis { return UnverifiedDirectCalls; } - SmallVector &getDiagnosticsForExplicitFX() { - return DiagnosticsForExplicitFX; + SmallVector &getViolationsForExplicitFX() { + return ViolationsForExplicitFX; } void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { OS << "Pending: Declared "; DeclaredVerifiableEffects.dump(OS); - OS << ", " << DiagnosticsForExplicitFX.size() << " diags; "; + OS << ", " << ViolationsForExplicitFX.size() << " violations; "; OS << " Infer "; FXToInfer.dump(OS); - OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags"; + OS << ", " << InferrableEffectToFirstViolation.size() << " violations"; if (!UnverifiedDirectCalls.empty()) { OS << "; Calls: "; for (const DirectCall &Call : UnverifiedDirectCalls) { @@ -428,7 +438,7 @@ class CompleteFunctionAnalysis { private: // This is used to generate notes about failed inference. - EffectToDiagnosticMap InferrableEffectToFirstDiagnostic; + EffectToViolationMap InferrableEffectToFirstViolation; public: // The incoming Pending analysis is consumed (member(s) are moved-from). @@ -438,22 +448,22 @@ class CompleteFunctionAnalysis { const FunctionEffectKindSet &AllInferrableEffectsToVerify) : VerifiedEffects(DeclaredEffects) { for (const FunctionEffect &effect : AllInferrableEffectsToVerify) - if (Pending.diagnosticForInferrableEffect(effect) == nullptr) + if (Pending.violationForInferrableEffect(effect) == nullptr) VerifiedEffects.insert(effect); - InferrableEffectToFirstDiagnostic = - std::move(Pending.InferrableEffectToFirstDiagnostic); + InferrableEffectToFirstViolation = + std::move(Pending.InferrableEffectToFirstViolation); } - const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) { - return InferrableEffectToFirstDiagnostic.lookup(Effect); + 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 << InferrableEffectToFirstDiagnostic.size() << " diags\n"; + OS << InferrableEffectToFirstViolation.size() << " violations\n"; } }; @@ -662,10 +672,9 @@ class Analyzer { // inserted in the container. void completeAnalysis(const CallableInfo &CInfo, PendingFunctionAnalysis &Pending) { - if (SmallVector &Diags = - Pending.getDiagnosticsForExplicitFX(); - !Diags.empty()) - emitDiagnostics(Diags, CInfo, Sem); + if (SmallVector &Viols = Pending.getViolationsForExplicitFX(); + !Viols.empty()) + emitDiagnostics(Viols, CInfo, Sem); CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( Sem.getASTContext(), Pending, CInfo.Effects, @@ -741,16 +750,16 @@ class Analyzer { Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects); if (Diagnose) { // If inference is not allowed, or the target is indirect (virtual - // method/function ptr?), generate a diagnostic now. + // method/function ptr?), generate a Violation now. if (!IsInferencePossible || !(Flags & FunctionEffect::FE_InferrableOnCallees)) { if (Callee.FuncType == SpecialFuncType::None) - PFA.checkAddDiagnostic( - Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect, - CallLoc, Callee.CDecl}); + PFA.checkAddViolation(Inferring, + {Effect, ViolationID::CallsDeclWithoutEffect, + CallLoc, Callee.CDecl}); else - PFA.checkAddDiagnostic( - Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc}); + PFA.checkAddViolation( + Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc}); } else { // Inference is allowed and necessary; defer it. PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc); @@ -766,13 +775,13 @@ class Analyzer { } // Should only be called when determined to be complete. - void emitDiagnostics(SmallVector &Diags, + void emitDiagnostics(SmallVector &Viols, const CallableInfo &CInfo, Sema &S) { - if (Diags.empty()) + if (Viols.empty()) return; const SourceManager &SM = S.getSourceManager(); - std::sort(Diags.begin(), Diags.end(), - [&SM](const Diagnostic &LHS, const Diagnostic &RHS) { + std::sort(Viols.begin(), Viols.end(), + [&SM](const Violation &LHS, const Violation &RHS) { return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); }); @@ -786,55 +795,56 @@ class Analyzer { } }; - // Top-level diagnostics are warnings. - for (const Diagnostic &Diag : Diags) { - StringRef effectName = Diag.Effect.name(); - switch (Diag.ID) { - case DiagnosticID::None: - case DiagnosticID::DeclDisallowsInference: // shouldn't happen - // here - llvm_unreachable("Unexpected diagnostic kind"); + // 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 DiagnosticID::AllocatesMemory: - S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName; + case ViolationID::AllocatesMemory: + S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::Throws: - case DiagnosticID::Catches: - S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) + case ViolationID::Throws: + case ViolationID::Catches: + S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches) << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::HasStaticLocal: - S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName; + case ViolationID::HasStaticLocal: + S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local) + << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::AccessesThreadLocal: - S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) + case ViolationID::AccessesThreadLocal: + S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local) << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::CallsObjC: - S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName; + case ViolationID::CallsObjC: + S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::CallsExprWithoutEffect: - S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect) + case ViolationID::CallsExprWithoutEffect: + S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) << effectName; checkAddTemplateNote(CInfo.CDecl); break; - case DiagnosticID::CallsDeclWithoutEffect: { - CallableInfo CalleeInfo(S, *Diag.Callee); + case ViolationID::CallsDeclWithoutEffect: { + CallableInfo CalleeInfo(S, *Viol1.Callee); std::string CalleeName = CalleeInfo.name(S); - S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect) + 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 = Diag.Callee; Callee != nullptr;) { + for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) { std::optional MaybeNextCallee; CompleteFunctionAnalysis *Completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); @@ -850,61 +860,62 @@ class Analyzer { S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName; - else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind())) + else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind())) S.Diag(Callee->getLocation(), diag::note_func_effect_call_disallows_inference) - << effectName; + << effectName + << FunctionEffect(Viol1.Effect.oppositeKind()).name(); else S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName; break; } - const Diagnostic *PtrDiag2 = - Completed->firstDiagnosticForEffect(Diag.Effect); - if (PtrDiag2 == nullptr) + const Violation *PtrViol2 = + Completed->firstViolationForEffect(Viol1.Effect); + if (PtrViol2 == nullptr) break; - const Diagnostic &Diag2 = *PtrDiag2; - switch (Diag2.ID) { - case DiagnosticID::None: - llvm_unreachable("Unexpected diagnostic kind"); + const Violation &Viol2 = *PtrViol2; + switch (Viol2.ID) { + case ViolationID::None: + llvm_unreachable("Unexpected violation kind"); break; - case DiagnosticID::DeclDisallowsInference: - S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference) - << effectName; + case ViolationID::DeclDisallowsInference: + S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) + << effectName << Viol2.CalleeEffectPreventingInference.name(); break; - case DiagnosticID::CallsExprWithoutEffect: - S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) + case ViolationID::CallsExprWithoutEffect: + S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr) << effectName; break; - case DiagnosticID::AllocatesMemory: - S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName; + case ViolationID::AllocatesMemory: + S.Diag(Viol2.Loc, diag::note_func_effect_allocates) << effectName; break; - case DiagnosticID::Throws: - case DiagnosticID::Catches: - S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) + case ViolationID::Throws: + case ViolationID::Catches: + S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches) << effectName; break; - case DiagnosticID::HasStaticLocal: - S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) + case ViolationID::HasStaticLocal: + S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local) << effectName; break; - case DiagnosticID::AccessesThreadLocal: - S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) + case ViolationID::AccessesThreadLocal: + S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local) << effectName; break; - case DiagnosticID::CallsObjC: - S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName; + case ViolationID::CallsObjC: + S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName; break; - case DiagnosticID::CallsDeclWithoutEffect: - MaybeNextCallee.emplace(S, *Diag2.Callee); - S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect) + case ViolationID::CallsDeclWithoutEffect: + MaybeNextCallee.emplace(S, *Viol2.Callee); + S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) << effectName << MaybeNextCallee->name(S); break; } checkAddTemplateNote(Callee); - Callee = Diag2.Callee; + Callee = Viol2.Callee; if (MaybeNextCallee) { CalleeInfo = *MaybeNextCallee; CalleeName = CalleeInfo.name(S); @@ -922,8 +933,7 @@ class Analyzer { // [2] The function has not explicitly declared an effect in question, and is // being checked for implicit conformance. // - // Diagnostics are always routed to a PendingFunctionAnalysis, which holds - // all diagnostic output. + // Violations are always routed to a PendingFunctionAnalysis. struct FunctionBodyASTVisitor : public RecursiveASTVisitor { @@ -951,32 +961,32 @@ class Analyzer { // -- Methods implementing common logic -- // Handle a language construct forbidden by some effects. Only effects whose - // flags include the specified flag receive a diagnostic. \p Flag describes + // flags include the specified flag receive a violation. \p Flag describes // the construct. - void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D, + void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, ViolationID D, SourceLocation Loc, const Decl *Callee = nullptr) { // If there are any declared verifiable effects which forbid the construct - // represented by the flag, store just one diagnostic. + // represented by the flag, store just one violation.. for (const FunctionEffect &Effect : CurrentFunction.DeclaredVerifiableEffects) { if (Effect.flags() & Flag) { - addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee); + addViolation(/*inferring=*/false, Effect, D, Loc, Callee); break; } } // For each inferred effect which forbids the construct, store a - // diagnostic, if we don't already have a diagnostic for that effect. + // violation, if we don't already have a violation for that effect. for (const FunctionEffect &Effect : CurrentFunction.FXToInfer) if (Effect.flags() & Flag) - addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee); + addViolation(/*inferring=*/true, Effect, D, Loc, Callee); } - void addDiagnostic(bool Inferring, const FunctionEffect &Effect, - DiagnosticID D, SourceLocation Loc, - const Decl *Callee = nullptr) { - CurrentFunction.checkAddDiagnostic(Inferring, - Diagnostic(Effect, D, 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 @@ -1003,8 +1013,8 @@ class Analyzer { auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( /*direct=*/false, CalleeFX)) - addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect, - Call->getBeginLoc()); + addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect, + Call->getBeginLoc()); }; for (const FunctionEffect &Effect : @@ -1057,31 +1067,31 @@ class Analyzer { bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - DiagnosticID::Throws, Throw->getThrowLoc()); + ViolationID::Throws, Throw->getThrowLoc()); return true; } bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - DiagnosticID::Catches, Catch->getCatchLoc()); + ViolationID::Catches, Catch->getCatchLoc()); return true; } bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - DiagnosticID::Throws, Throw->getThrowLoc()); + ViolationID::Throws, Throw->getThrowLoc()); return true; } bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - DiagnosticID::Catches, Catch->getAtCatchLoc()); + ViolationID::Catches, Catch->getAtCatchLoc()); return true; } bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, - DiagnosticID::CallsObjC, Msg->getBeginLoc()); + ViolationID::CallsObjC, Msg->getBeginLoc()); return true; } @@ -1116,7 +1126,7 @@ class Analyzer { if (Var->isStaticLocal()) diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, - DiagnosticID::HasStaticLocal, + ViolationID::HasStaticLocal, Var->getLocation()); const QualType::DestructionKind DK = @@ -1211,7 +1221,7 @@ class Analyzer { // At least on macOS, thread-local variables are initialized on // first access, including a heap allocation. diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars, - DiagnosticID::AccessesThreadLocal, + ViolationID::AccessesThreadLocal, E->getLocation()); } } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 7c7e322df809648..00fcc1714e22cf8 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -138,7 +138,7 @@ void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{fun template struct ComputedNB { - void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}} + void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}} }; void nb11() [[clang::nonblocking]] From 06ca4c53d6de8575abd3b8c9981feb7d2e1cea0e Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 7 Aug 2024 07:26:41 -0700 Subject: [PATCH 10/63] Add tests around lambda traversal and contexts like decltype, sizeof, etc. --- .../Sema/attr-nonblocking-constraints.cpp | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 00fcc1714e22cf8..bad5309dbb543c6 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify %s // These are in a separate file because errors (e.g. incompatible attributes) currently prevent // the FXAnalysis pass from running at all. @@ -81,6 +81,29 @@ void nb8() }; } +void nb8a() [[clang::nonblocking]] +{ + // A blocking lambda shouldn't make the outer function unsafe. + auto unsafeLambda = []() { + throw 42; + }; +} + +void nb8b() [[clang::nonblocking]] +{ + // An unsafe lambda capture makes the outer function unsafe. + auto unsafeCapture = [foo = new int]() { // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} + delete foo; + }; +} + +void nb8c() +{ + // An unsafe lambda capture does not make the lambda unsafe. + auto unsafeCapture = [foo = new int]() [[clang::nonblocking]] { + }; +} + // Make sure template expansions are found and verified. template struct Adder { @@ -200,6 +223,24 @@ struct DerivedFromUnsafe : public Unsafe { ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}} }; +// Contexts where there is no function call, no diagnostic. +bool bad(); + +template +requires requires { bad(); } +void g() [[clang::nonblocking]] {} + +void g() [[clang::nonblocking]] { + decltype(bad()) a; // doesn't generate a call so, OK + [[maybe_unused]] auto b = noexcept(bad()); + [[maybe_unused]] auto c = sizeof(bad()); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wassume" + [[assume(bad())]]; // never evaluated, but maybe still semantically questionable? +#pragma clang diagnostic pop +} + + // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" From 9e45e6f59d6f6fc501181bc535741c415c1b8ead Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 7 Aug 2024 08:06:30 -0700 Subject: [PATCH 11/63] Inline checks preceding maybeAddDeclWithEffects --- clang/include/clang/Sema/Sema.h | 9 +++++++++ clang/lib/Sema/SemaDecl.cpp | 4 +--- clang/lib/Sema/SemaExpr.cpp | 4 +--- clang/lib/Sema/SemaLambda.cpp | 4 +--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index b166825bef4a5fc..e27517f2ac541a4 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4337,6 +4337,15 @@ class Sema final : public SemaBase { SourceLocation NewLoc, SourceLocation OldLoc); + /// Inline checks from the start of maybeAddDeclWithEffects, to + /// minimize performance impact on code not using effects. + template + void maybeAddDeclWithEffects(FuncOrBlockDecl *D) { + if (Context.hasAnyFunctionEffects()) + if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty()) + maybeAddDeclWithEffects(D, FX); + } + /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index b21617a5db39ec8..4d81594673678d2 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15679,9 +15679,7 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D, getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation) Diag(FD->getLocation(), diag::warn_function_def_in_objc_container); - if (Context.hasAnyFunctionEffects()) - if (const auto FX = FD->getFunctionEffects(); !FX.empty()) - maybeAddDeclWithEffects(FD, FX); + maybeAddDeclWithEffects(FD); return D; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 126e924666356dd..2412e84588b763d 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -16091,9 +16091,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc, BlockScopeInfo *BSI = cast(FunctionScopes.back()); BlockDecl *BD = BSI->TheDecl; - if (Context.hasAnyFunctionEffects()) - if (const auto FX = BD->getFunctionEffects(); !FX.empty()) - maybeAddDeclWithEffects(BD, FX); + maybeAddDeclWithEffects(BD); if (BSI->HasImplicitReturnType) deduceClosureReturnType(*BSI); diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index e3bf582db30279a..bb797df28d09ade 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1948,9 +1948,7 @@ ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) { LambdaScopeInfo LSI = *cast(FunctionScopes.back()); ActOnFinishFunctionBody(LSI.CallOperator, Body); - if (Context.hasAnyFunctionEffects()) - if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty()) - maybeAddDeclWithEffects(LSI.CallOperator, FX); + maybeAddDeclWithEffects(LSI.CallOperator); return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI); } From b99f7841ada916df8ed177006856ecfa38402067 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 7 Aug 2024 08:41:49 -0700 Subject: [PATCH 12/63] Remove dead code in EffectAnalysis.cpp. Add comment about AST traversal and lambdas. add tests involving ms-extensions --- clang/lib/Sema/EffectAnalysis.cpp | 125 ++---------------- .../Sema/attr-nonblocking-constraints-ms.cpp | 26 ++++ 2 files changed, 37 insertions(+), 114 deletions(-) create mode 100644 clang/test/Sema/attr-nonblocking-constraints-ms.cpp diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index a8bbb73faf9fd87..d9ec4197b9d4284 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -12,6 +12,7 @@ #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" @@ -89,119 +90,6 @@ static bool isNoexcept(const FunctionDecl *FD) { return false; } -#if 0 -/// A mutable set of FunctionEffect, for use in places where any conditions -/// have been resolved or can be ignored. -class EffectSet { - // This implementation optimizes footprint, since we hold one of these for - // every function visited, which, due to inference, can be many more functions - // than have declared effects. - - template struct FixedVector { - SizeT Count = 0; - T Items[Capacity] = {}; - - using value_type = T; - - using iterator = T *; - using const_iterator = const T *; - iterator begin() { return &Items[0]; } - iterator end() { return &Items[Count]; } - const_iterator begin() const { return &Items[0]; } - const_iterator end() const { return &Items[Count]; } - const_iterator cbegin() const { return &Items[0]; } - const_iterator cend() const { return &Items[Count]; } - - void insert(iterator I, const T &Value) { - assert(Count < Capacity); - iterator E = end(); - if (I != E) - std::copy_backward(I, E, E + 1); - *I = Value; - ++Count; - } - - void push_back(const T &Value) { - assert(Count < Capacity); - Items[Count++] = Value; - } - }; - - // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable - // effects, this fixed-size vector with a capacity of 7 is more than - // sufficient and is only 8 bytes. - FixedVector Impl; - -public: - EffectSet() = default; - explicit EffectSet(FunctionEffectsRef FX) { insert(FX); } - - operator ArrayRef() const { - return ArrayRef(Impl.cbegin(), Impl.cend()); - } - - using iterator = const FunctionEffect *; - iterator begin() const { return Impl.cbegin(); } - iterator end() const { return Impl.cend(); } - - void insert(const FunctionEffect &Effect) { - FunctionEffect *Iter = Impl.begin(); - FunctionEffect *End = Impl.end(); - // linear search; lower_bound is overkill for a tiny vector like this - for (; Iter != End; ++Iter) { - if (*Iter == Effect) - return; - if (Effect < *Iter) - break; - } - Impl.insert(Iter, Effect); - } - void insert(const EffectSet &Set) { - for (const FunctionEffect &Item : Set) { - // push_back because set is already sorted - Impl.push_back(Item); - } - } - void insert(FunctionEffectsRef FX) { - for (const FunctionEffectWithCondition &EC : FX) { - assert(EC.Cond.getCondition() == - nullptr); // should be resolved by now, right? - // push_back because set is already sorted - Impl.push_back(EC.Effect); - } - } - bool contains(const FunctionEffect::Kind EK) const { - for (const FunctionEffect &E : Impl) - if (E.kind() == EK) - return true; - return false; - } - - void dump(llvm::raw_ostream &OS) const; - - static EffectSet difference(ArrayRef LHS, - ArrayRef RHS) { - EffectSet Result; - std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), - std::back_inserter(Result.Impl)); - return Result; - } -}; - -LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const { - OS << "Effects{"; - bool First = true; - for (const FunctionEffect &Effect : *this) { - if (!First) - OS << ", "; - else - First = false; - OS << Effect.name(); - } - OS << "}"; -} -#endif - // Transitory, more extended information about a callable, which can be a // function, block, function pointer, etc. struct CallableInfo { @@ -1095,6 +983,12 @@ class Analyzer { 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 : " @@ -1199,7 +1093,10 @@ class Analyzer { 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. + // 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]); diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp new file mode 100644 index 000000000000000..dbdc39a304ba1b4 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify %s + +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" + +// These need '-fms-extensions' (and maybe '-fdeclspec') +void f1() [[clang::nonblocking]] { + __try {} __except (1) {} // expected-warning {{'nonblocking' function must not throw or catch exceptions}} +} + +struct S { + int get_x(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + __declspec(property(get = get_x)) int x; + + int get_nb() { return 42; } + __declspec(property(get = get_nb)) int nb; + + int get_nb2() [[clang::nonblocking]]; + __declspec(property(get = get_nb2)) int nb2; +}; + +void f2() [[clang::nonblocking]] { + S a; + a.x; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}} + a.nb; + a.nb2; +} From 1b9874f8731ab5227b9698200015a38d9df77f78 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 7 Aug 2024 09:10:40 -0700 Subject: [PATCH 13/63] patch what the bot's clang-format wishes clang-format on my system would do --- clang/lib/Sema/EffectAnalysis.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index d9ec4197b9d4284..80dbb0f4adeb4f2 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -628,7 +628,9 @@ class Analyzer { llvm::dbgs() << " callee " << Callee.CDecl << " canonical " << CanonicalFunctionDecl(Callee.CDecl) << " redecls"; - for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D; + for (Decl *D + : Callee.CDecl->redecls()) llvm::dbgs() + << " " << D; llvm::dbgs() << "\n";); From 8390e691ed71f4a35e4f9bd2e9eeb83ea0f64c31 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 8 Aug 2024 06:23:29 -0700 Subject: [PATCH 14/63] "function cannot be inferred" -> "declaration cannot be inferred" --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/test/Sema/attr-nonblocking-constraints-ms.cpp | 2 +- clang/test/Sema/attr-nonblocking-constraints.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f1ffe4689daeff2..ccec94c353d953c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10975,7 +10975,7 @@ def warn_func_effect_calls_expr_without_effect : Warning< def note_func_effect_calls_func_without_effect : Note< "function cannot be inferred '%0' because it calls non-'%0' function '%1'">; def note_func_effect_call_extern : Note< - "function cannot be inferred '%0' because it has no definition in this translation unit">; + "declaration cannot be inferred '%0' because it has no definition in this translation unit">; def note_func_effect_call_disallows_inference : Note< "function does not permit inference of '%0' because it is declared '%1'">; def note_func_effect_call_virtual : Note< diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp index dbdc39a304ba1b4..d2c25da462c4048 100644 --- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp @@ -8,7 +8,7 @@ void f1() [[clang::nonblocking]] { } struct S { - int get_x(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + int get_x(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} __declspec(property(get = get_x)) int x; int get_nb() { return 42; } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index bad5309dbb543c6..869e56374fe2c30 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -28,7 +28,7 @@ void nb3() [[clang::nonblocking]] } void nb4_inline() {} -void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} void nb4() [[clang::nonblocking]] { @@ -47,7 +47,7 @@ void nb5() [[clang::nonblocking]] hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} } -void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} void nb6_transitively_unsafe() { nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} @@ -211,8 +211,8 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] // Verify traversal of implicit code paths - constructors and destructors. struct Unsafe { - static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} - static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}} ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}} From 7acda8c8891bdb19edd429863fcb8a1fc80ea2fc Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 8 Aug 2024 09:42:03 -0700 Subject: [PATCH 15/63] No need to override VisitCXXDefaultInitExpr. Minor terseness improvements. CallableInfo ctor doesn't need a Sema. FunctionEffect::effectProhibitingInference can receive a FunctionEffectKindSet and eliminate the need for CallableInfo to cache a FunctionEffectsRef. CallType -> CallableType; isDirectCall() -> isCalledDirectly(). --- clang/include/clang/AST/Type.h | 2 +- clang/lib/AST/Type.cpp | 13 ++- clang/lib/Sema/EffectAnalysis.cpp | 141 +++++++++++++++--------------- 3 files changed, 76 insertions(+), 80 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 5d730ce98c1b138..cff10ffdc3a4694 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4766,7 +4766,7 @@ class FunctionEffect { /// function. std::optional effectProhibitingInference(const Decl &Callee, - const FunctionEffectsRef &CalleeFX) const; + const FunctionEffectKindSet &CalleeFX) const; // Return false for success. When true is returned for a direct call, then the // FE_InferrableOnCallees flag may trigger inference rather than an immediate diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index cfc961647d04d5b..4fd4032725f1a99 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5138,18 +5138,17 @@ StringRef FunctionEffect::name() const { } std::optional FunctionEffect::effectProhibitingInference( - const Decl &Callee, const FunctionEffectsRef &CalleeFX) const { + const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { - for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) { + for (const FunctionEffect &Effect : CalleeFX) { // nonblocking/nonallocating cannot call allocating. - if (CalleeEC.Effect.kind() == Kind::Allocating) - return CalleeEC.Effect; + if (Effect.kind() == Kind::Allocating) + return Effect; // nonblocking cannot call blocking. - if (kind() == Kind::NonBlocking && - CalleeEC.Effect.kind() == Kind::Blocking) - return CalleeEC.Effect; + if (kind() == Kind::NonBlocking && Effect.kind() == Kind::Blocking) + return Effect; } return std::nullopt; } diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index 80dbb0f4adeb4f2..799500aa8caff71 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -64,7 +64,7 @@ struct Violation { }; enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; -enum class CallType { +enum class CallableType { // unknown: probably function pointer Unknown, Function, @@ -91,32 +91,39 @@ static bool isNoexcept(const FunctionDecl *FD) { } // Transitory, more extended information about a callable, which can be a -// function, block, function pointer, etc. +// function, block, or function pointer. struct CallableInfo { // CDecl holds the function's definition, if any. - // FunctionDecl if CallType::Function or Virtual - // BlockDecl if CallType::Block + // 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; - FunctionEffectsRef DeclEffects; + + // We inevitably want to know the callable's declared effects, so cache them. FunctionEffectKindSet Effects; - CallType CType = CallType::Unknown; - CallableInfo(Sema &SemaRef, const Decl &CD, - SpecialFuncType FT = SpecialFuncType::None) + 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 = CallType::Function; + CType = CallableType::Function; if (auto *Method = dyn_cast(FD); Method && Method->isVirtual()) - CType = CallType::Virtual; + CType = CallableType::Virtual; DeclEffects = FD->getFunctionEffects(); } else if (auto *BD = dyn_cast(CDecl)) { - CType = CallType::Block; + 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. @@ -125,21 +132,21 @@ struct CallableInfo { Effects = FunctionEffectKindSet(DeclEffects); } - bool isDirectCall() const { - return CType == CallType::Function || CType == CallType::Block; + bool isCalledDirectly() const { + return CType == CallableType::Function || CType == CallableType::Block; } bool isVerifiable() const { switch (CType) { - case CallType::Unknown: - case CallType::Virtual: + case CallableType::Unknown: + case CallableType::Virtual: return false; - case CallType::Block: + case CallableType::Block: return true; - case CallType::Function: + case CallableType::Function: return functionIsVerifiable(dyn_cast(CDecl)); } - llvm_unreachable("undefined CallType"); + llvm_unreachable("undefined CallableType"); } /// Generate a name for logging and diagnostics. @@ -250,7 +257,7 @@ class PendingFunctionAnalysis { for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { std::optional ProblemCalleeEffect = - effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects); + effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects); if (!ProblemCalleeEffect) InferrableFX.insert(effect); else { @@ -306,7 +313,7 @@ class PendingFunctionAnalysis { if (!UnverifiedDirectCalls.empty()) { OS << "; Calls: "; for (const DirectCall &Call : UnverifiedDirectCalls) { - CallableInfo CI(SemaRef, *Call.Callee); + CallableInfo CI(*Call.Callee); OS << " " << CI.name(SemaRef); } } @@ -405,7 +412,7 @@ class Analyzer { void dump(Sema &SemaRef, llvm::raw_ostream &OS) { OS << "\nAnalysisMap:\n"; for (const auto &item : *this) { - CallableInfo CI(SemaRef, *item.first); + CallableInfo CI(*item.first); const auto AP = item.second; OS << item.first << " " << CI.name(SemaRef) << " : "; if (AP.isNull()) @@ -499,7 +506,7 @@ class Analyzer { // 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(Sem, *D); + CallableInfo CInfo(*D); bool isExternC = false; if (const FunctionDecl *FD = dyn_cast(D)) { @@ -576,7 +583,7 @@ class Analyzer { // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without // the possibility of inference. Deletes Pending. void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) { - CallableInfo Caller(Sem, *D); + CallableInfo Caller(*D); LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : "; Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";); @@ -585,7 +592,7 @@ class Analyzer { if (Call.Recursed) continue; - CallableInfo Callee(Sem, *Call.Callee); + CallableInfo Callee(*Call.Callee); followCall(Caller, *Pending, Callee, Call.CallLoc, /*AssertNoFurtherInference=*/true); } @@ -598,21 +605,20 @@ class Analyzer { void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA, const CallableInfo &Callee, SourceLocation CallLoc, bool AssertNoFurtherInference) { - const bool DirectCall = Callee.isDirectCall(); + const bool DirectCall = Callee.isCalledDirectly(); // Initially, the declared effects; inferred effects will be added. FunctionEffectKindSet CalleeEffects = Callee.Effects; bool IsInferencePossible = DirectCall; - if (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); @@ -635,25 +641,23 @@ class Analyzer { llvm::dbgs() << "\n";); auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { - FunctionEffect::Flags Flags = Effect.flags(); - bool Diagnose = - Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects); - if (Diagnose) { - // If inference is not allowed, or the target is indirect (virtual - // method/function ptr?), generate a Violation now. - if (!IsInferencePossible || - !(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); - } + 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); } }; @@ -664,7 +668,8 @@ class Analyzer { check1Effect(Effect, true); } - // Should only be called when determined to be complete. + // Should only be called when function's analysis is determined to be + // complete. void emitDiagnostics(SmallVector &Viols, const CallableInfo &CInfo, Sema &S) { if (Viols.empty()) @@ -725,7 +730,7 @@ class Analyzer { break; case ViolationID::CallsDeclWithoutEffect: { - CallableInfo CalleeInfo(S, *Viol1.Callee); + CallableInfo CalleeInfo(*Viol1.Callee); std::string CalleeName = CalleeInfo.name(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) @@ -743,10 +748,10 @@ class Analyzer { // - non-inline // - indirect (virtual or through function pointer) // - effect has been explicitly disclaimed (e.g. "blocking") - if (CalleeInfo.CType == CallType::Virtual) + if (CalleeInfo.CType == CallableType::Virtual) S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName; - else if (CalleeInfo.CType == CallType::Unknown) + else if (CalleeInfo.CType == CallableType::Unknown) S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName; @@ -799,7 +804,7 @@ class Analyzer { S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName; break; case ViolationID::CallsDeclWithoutEffect: - MaybeNextCallee.emplace(S, *Viol2.Callee); + MaybeNextCallee.emplace(*Viol2.Callee); S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) << effectName << MaybeNextCallee->name(S); break; @@ -853,15 +858,15 @@ class Analyzer { // 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 D, - SourceLocation Loc, + 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, D, Loc, Callee); + addViolation(/*inferring=*/false, Effect, VID, Loc, Callee); break; } } @@ -869,7 +874,7 @@ class Analyzer { // 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, D, Loc, Callee); + addViolation(/*inferring=*/true, Effect, VID, Loc, Callee); } void addViolation(bool Inferring, const FunctionEffect &Effect, @@ -892,8 +897,7 @@ class Analyzer { /*AssertNoFurtherInference=*/false); } - void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) { - const QualType CalleeType = CalleeExpr->getType(); + void checkIndirectCall(CallExpr *Call, QualType CalleeType) { auto *FPT = CalleeType->getAs(); // null if FunctionType FunctionEffectKindSet CalleeFX; @@ -942,7 +946,7 @@ class Analyzer { if (Ty->isRecordType()) { if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { if (CXXDestructorDecl *Dtor = Class->getDestructor()) { - CallableInfo CI(Outer.Sem, *Dtor); + CallableInfo CI(*Dtor); followCall(CI, OuterDtor->getLocation()); } } @@ -999,7 +1003,7 @@ class Analyzer { Expr *CalleeExpr = Call->getCallee(); if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { - CallableInfo CI(Outer.Sem, *Callee); + CallableInfo CI(*Callee); followCall(CI, Call->getBeginLoc()); return true; } @@ -1009,7 +1013,7 @@ class Analyzer { return true; // No Decl, just an Expr. Just check based on its type. - checkIndirectCall(Call, CalleeExpr); + checkIndirectCall(Call, CalleeExpr->getType()); return true; } @@ -1033,7 +1037,7 @@ class Analyzer { if (const auto *CxxRec = dyn_cast(ClsType->getDecl())) { if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) { - CallableInfo CI(Outer.Sem, *Dtor); + CallableInfo CI(*Dtor); followCall(CI, Var->getLocation()); } } @@ -1046,7 +1050,7 @@ class Analyzer { // BUG? It seems incorrect that RecursiveASTVisitor does not // visit the call to operator new. if (FunctionDecl *FD = New->getOperatorNew()) { - CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew); + CallableInfo CI(*FD, SpecialFuncType::OperatorNew); followCall(CI, New->getBeginLoc()); } @@ -1062,7 +1066,7 @@ class Analyzer { // BUG? It seems incorrect that RecursiveASTVisitor does not // visit the call to operator delete. if (FunctionDecl *FD = Delete->getOperatorDelete()) { - CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete); + CallableInfo CI(*FD, SpecialFuncType::OperatorDelete); followCall(CI, Delete->getBeginLoc()); } @@ -1080,19 +1084,12 @@ class Analyzer { // BUG? It seems incorrect that RecursiveASTVisitor does not // visit the call to the constructor. const CXXConstructorDecl *Ctor = Construct->getConstructor(); - CallableInfo CI(Outer.Sem, *Ctor); + CallableInfo CI(*Ctor); followCall(CI, Construct->getLocation()); return true; } - bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) { - if (Expr *E = DEI->getExpr()) - TraverseStmt(E); - - 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 From bffacc582a8c139feddfed26cc0b43dac488bc5c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 13 Aug 2024 13:32:45 -0700 Subject: [PATCH 16/63] Begin a list of unsafe builtin functions, starting with malloc and friends. --- clang/lib/Sema/EffectAnalysis.cpp | 48 +++++++++++++++---- .../Sema/attr-nonblocking-constraints.cpp | 7 +++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index 799500aa8caff71..b59d0786c57dc1e 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -132,6 +132,8 @@ struct CallableInfo { Effects = FunctionEffectKindSet(DeclEffects); } + CallableType type() const { return CType; } + bool isCalledDirectly() const { return CType == CallableType::Function || CType == CallableType::Block; } @@ -745,13 +747,15 @@ class Analyzer { DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); if (Completed == nullptr) { // No result - could be - // - non-inline + // - non-inline and extern // - indirect (virtual or through function pointer) // - effect has been explicitly disclaimed (e.g. "blocking") - if (CalleeInfo.CType == CallableType::Virtual) + + CallableType CType = CalleeInfo.type(); + if (CType == CallableType::Virtual) S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName; - else if (CalleeInfo.CType == CallableType::Unknown) + else if (CType == CallableType::Unknown) S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName; @@ -760,10 +764,13 @@ class Analyzer { diag::note_func_effect_call_disallows_inference) << effectName << FunctionEffect(Viol1.Effect.oppositeKind()).name(); - else + 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 = @@ -887,16 +894,41 @@ class Analyzer { // 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) { - // Currently, built-in functions are always considered safe. - // FIXME: Some are not. if (const auto *FD = dyn_cast(CI.CDecl); - FD && FD->getBuiltinID() != 0) + 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(); // null if FunctionType diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 869e56374fe2c30..540267d2efae4bf 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -209,6 +209,13 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] ref(); } +// Builtin functions +void nb18a() [[clang::nonblocking]] { + __builtin_assume(1); + void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}} + __builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}} +} + // Verify traversal of implicit code paths - constructors and destructors. struct Unsafe { static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} From c718b5ab2de778ab4372615514f994ef43c85d34 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 14 Aug 2024 08:03:00 -0700 Subject: [PATCH 17/63] Apply suggestions from code review Co-authored-by: Sirraide --- clang/lib/Sema/EffectAnalysis.cpp | 61 ++++++++++++------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index b59d0786c57dc1e..ef5a8de628abcc4 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -64,7 +64,7 @@ struct Violation { }; enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; -enum class CallableType { +enum class CallableType : uint8_t { // unknown: probably function pointer Unknown, Function, @@ -296,12 +296,12 @@ class PendingFunctionAnalysis { return InferrableEffectToFirstViolation.lookup(effect); } - SmallVector &unverifiedCalls() { + ArrayRef unverifiedCalls() { assert(!isComplete()); return UnverifiedDirectCalls; } - SmallVector &getViolationsForExplicitFX() { + ArrayRef getViolationsForExplicitFX() { return ViolationsForExplicitFX; } @@ -386,7 +386,7 @@ class Analyzer { // 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 { + class AnalysisMap : llvm::DenseMap { using Base = llvm::DenseMap; public: @@ -460,19 +460,11 @@ class Analyzer { while (!VerificationQueue.empty()) { const Decl *D = VerificationQueue.back(); if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { - if (isa(AP)) { - // already done - VerificationQueue.pop_back(); - continue; - } - if (isa(AP)) { - // All children have been traversed; finish analysis. - auto *Pending = AP.get(); + // All children have been traversed; finish analysis. + if (auto *Pending = AP.dyn_cast()) finishPendingAnalysis(D, Pending); - VerificationQueue.pop_back(); - continue; - } - llvm_unreachable("unexpected DeclAnalysis item"); + VerificationQueue.pop_back(); + continue; } // Not previously visited; begin a new analysis for this Decl. @@ -492,14 +484,12 @@ class Analyzer { VerificationQueue.push_back(Call.Callee); continue; } - if (isa(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"); + + // 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; } } } @@ -569,7 +559,7 @@ class Analyzer { // inserted in the container. void completeAnalysis(const CallableInfo &CInfo, PendingFunctionAnalysis &Pending) { - if (SmallVector &Viols = Pending.getViolationsForExplicitFX(); + if (ArrayRef Viols = Pending.getViolationsForExplicitFX(); !Viols.empty()) emitDiagnostics(Viols, CInfo, Sem); @@ -642,7 +632,7 @@ class Analyzer { llvm::dbgs() << "\n";); - auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects)) return; @@ -672,7 +662,7 @@ class Analyzer { // Should only be called when function's analysis is determined to be // complete. - void emitDiagnostics(SmallVector &Viols, + void emitDiagnostics(ArrayRef Viols, const CallableInfo &CInfo, Sema &S) { if (Viols.empty()) return; @@ -682,7 +672,7 @@ class Analyzer { return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); }); - auto checkAddTemplateNote = [&](const Decl *D) { + auto CheckAddTemplateNote = [&](const Decl *D) { if (const FunctionDecl *FD = dyn_cast(D)) { while (FD != nullptr && FD->isTemplateInstantiation()) { S.Diag(FD->getPointOfInstantiation(), @@ -837,13 +827,13 @@ class Analyzer { // // Violations are always routed to a PendingFunctionAnalysis. struct FunctionBodyASTVisitor - : public RecursiveASTVisitor { + : RecursiveASTVisitor { Analyzer &Outer; PendingFunctionAnalysis &CurrentFunction; CallableInfo &CurrentCaller; - FunctionBodyASTVisitor(Analyzer &outer, + FunctionBodyASTVisitor(Analyzer &Outer, PendingFunctionAnalysis &CurrentFunction, CallableInfo &CurrentCaller) : Outer(outer), CurrentFunction(CurrentFunction), @@ -869,7 +859,7 @@ class Analyzer { 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.. + // represented by the flag, store just one violation. for (const FunctionEffect &Effect : CurrentFunction.DeclaredVerifiableEffects) { if (Effect.flags() & Flag) { @@ -926,7 +916,6 @@ class Analyzer { case Builtin::ID::BIfree: return false; } - llvm_unreachable("above switch is exhaustive"); } void checkIndirectCall(CallExpr *Call, QualType CalleeType) { @@ -936,7 +925,7 @@ class Analyzer { if (FPT) CalleeFX.insert(FPT->getFunctionEffects()); - auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( /*direct=*/false, CalleeFX)) addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect, @@ -1142,10 +1131,8 @@ class Analyzer { bool VisitDeclRefExpr(const DeclRefExpr *E) { const ValueDecl *Val = E->getDecl(); - if (isa(Val)) { - const VarDecl *Var = cast(Val); - VarDecl::TLSKind TLSK = Var->getTLSKind(); - if (TLSK != VarDecl::TLS_None) { + 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, From 8b225f43a7a7ac3fbdcdce7b0f078d4725310928 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 14 Aug 2024 08:05:04 -0700 Subject: [PATCH 18/63] - FunctionEffect and FunctionEffectKindSet are tiny, pass by value wherever possible and obtain values from iterators. - Diagnostics: "local" -> "local variable" - No change needed to AnalysisBasedWarnings - Fixes following review suggestions. --- clang/include/clang/AST/Type.h | 25 ++--- .../clang/Basic/DiagnosticSemaKinds.td | 4 +- clang/lib/AST/Type.cpp | 8 +- clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 - clang/lib/Sema/EffectAnalysis.cpp | 104 +++++++++--------- .../Sema/attr-nonblocking-constraints.cpp | 4 +- 6 files changed, 69 insertions(+), 78 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index cff10ffdc3a4694..e7a0d79447dc4e2 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4766,22 +4766,22 @@ class FunctionEffect { /// function. std::optional effectProhibitingInference(const Decl &Callee, - const FunctionEffectKindSet &CalleeFX) const; + FunctionEffectKindSet CalleeFX) const; // Return false for success. When true is returned for a direct call, then the // FE_InferrableOnCallees flag may trigger inference rather than an immediate // diagnostic. Caller should be assumed to have the effect (it may not have it // explicitly when inferring). bool shouldDiagnoseFunctionCall(bool Direct, - const FunctionEffectKindSet &CalleeFX) const; + FunctionEffectKindSet CalleeFX) const; - friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) { + friend bool operator==(FunctionEffect LHS, FunctionEffect RHS) { return LHS.FKind == RHS.FKind; } - friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) { + friend bool operator!=(FunctionEffect LHS, FunctionEffect RHS) { return !(LHS == RHS); } - friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) { + friend bool operator<(FunctionEffect LHS, FunctionEffect RHS) { return LHS.FKind < RHS.FKind; } }; @@ -4810,8 +4810,7 @@ struct FunctionEffectWithCondition { EffectConditionExpr Cond; FunctionEffectWithCondition() = default; - FunctionEffectWithCondition(const FunctionEffect &E, - const EffectConditionExpr &C) + FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C) : Effect(E), Cond(C) {} /// Return a textual description of the effect, and its condition, if any. @@ -4972,22 +4971,20 @@ class FunctionEffectKindSet { iterator begin() const { return iterator(*this, 0); } iterator end() const { return iterator(*this, EndBitPos); } - void insert(const FunctionEffect &Effect) { - KindBits |= kindToBit(Effect.kind()); - } + void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); } void insert(FunctionEffectsRef FX) { - for (const FunctionEffect &Item : FX.effects()) + for (FunctionEffect Item : FX.effects()) insert(Item); } - void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; } + void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; } bool contains(const FunctionEffect::Kind EK) const { return (KindBits & kindToBit(EK)) != 0; } void dump(llvm::raw_ostream &OS) const; - static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS, - const FunctionEffectKindSet &RHS) { + static FunctionEffectKindSet difference(FunctionEffectKindSet LHS, + FunctionEffectKindSet RHS) { return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits); } }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ccec94c353d953c..7e25afb98d95ff9 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10952,10 +10952,10 @@ def warn_func_effect_throws_or_catches : Warning< def note_func_effect_throws_or_catches : Note< "function cannot be inferred '%0' because it throws or catches exceptions">; def warn_func_effect_has_static_local : Warning< - "'%0' function must not have static locals">, + "'%0' function must not have static local variables">, InGroup; def note_func_effect_has_static_local : Note< - "function cannot be inferred '%0' because it has a static local">; + "function cannot be inferred '%0' because it has a static local variable">; def warn_func_effect_uses_thread_local : Warning< "'%0' function must not use thread-local variables">, InGroup; diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 4fd4032725f1a99..8d36aa15c595729 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5138,11 +5138,11 @@ StringRef FunctionEffect::name() const { } std::optional FunctionEffect::effectProhibitingInference( - const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const { + const Decl &Callee, FunctionEffectKindSet CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { - for (const FunctionEffect &Effect : CalleeFX) { + for (FunctionEffect Effect : CalleeFX) { // nonblocking/nonallocating cannot call allocating. if (Effect.kind() == Kind::Allocating) return Effect; @@ -5166,12 +5166,12 @@ std::optional FunctionEffect::effectProhibitingInference( } bool FunctionEffect::shouldDiagnoseFunctionCall( - bool Direct, const FunctionEffectKindSet &CalleeFX) const { + bool Direct, FunctionEffectKindSet CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { const Kind CallerKind = kind(); - for (const FunctionEffect &Effect : CalleeFX) { + for (FunctionEffect Effect : CalleeFX) { const Kind EK = Effect.kind(); // Does callee have same or stronger constraint? if (EK == CallerKind || diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 8268117a0addab4..0f604c61fa3af98 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2397,8 +2397,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler { }; } // namespace -// ============================================================================= - //===----------------------------------------------------------------------===// // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based // warnings on a function, method, or block. diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index ef5a8de628abcc4..a074aece910c331 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -54,7 +54,7 @@ struct Violation { Violation() = default; - Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc, + Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc, const Decl *Callee = nullptr, const FunctionEffect *CalleeEffect = nullptr) : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) { @@ -204,7 +204,7 @@ class EffectToViolationMap { size_t size() const { return Impl ? Impl->size() : 0; } private: - ImplVec::iterator _find(const FunctionEffect &key) { + ImplVec::iterator _find(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) @@ -250,14 +250,13 @@ class PendingFunctionAnalysis { SmallVector UnverifiedDirectCalls; public: - PendingFunctionAnalysis( - Sema &Sem, const CallableInfo &CInfo, - const FunctionEffectKindSet &AllInferrableEffectsToVerify) + PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo, + FunctionEffectKindSet AllInferrableEffectsToVerify) : DeclaredVerifiableEffects(CInfo.Effects) { // Check for effects we are not allowed to infer FunctionEffectKindSet InferrableFX; - for (const FunctionEffect &effect : AllInferrableEffectsToVerify) { + for (FunctionEffect effect : AllInferrableEffectsToVerify) { std::optional ProblemCalleeEffect = effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects); if (!ProblemCalleeEffect) @@ -296,12 +295,18 @@ class PendingFunctionAnalysis { return InferrableEffectToFirstViolation.lookup(effect); } - ArrayRef unverifiedCalls() { + // Mutable because caller may need to set a DirectCall's Recursing flag. + MutableArrayRef unverifiedCalls() { assert(!isComplete()); return UnverifiedDirectCalls; } - ArrayRef getViolationsForExplicitFX() { + ArrayRef getSortedViolationsForExplicitFX(SourceManager &SM) { + if (!ViolationsForExplicitFX.empty()) + std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(), + [&SM](const Violation &LHS, const Violation &RHS) { + return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); + }); return ViolationsForExplicitFX; } @@ -339,12 +344,11 @@ class CompleteFunctionAnalysis { public: // The incoming Pending analysis is consumed (member(s) are moved-from). - CompleteFunctionAnalysis( - ASTContext &Ctx, PendingFunctionAnalysis &Pending, - const FunctionEffectKindSet &DeclaredEffects, - const FunctionEffectKindSet &AllInferrableEffectsToVerify) + CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending, + FunctionEffectKindSet DeclaredEffects, + FunctionEffectKindSet AllInferrableEffectsToVerify) : VerifiedEffects(DeclaredEffects) { - for (const FunctionEffect &effect : AllInferrableEffectsToVerify) + for (FunctionEffect effect : AllInferrableEffectsToVerify) if (Pending.violationForInferrableEffect(effect) == nullptr) VerifiedEffects.insert(effect); @@ -352,7 +356,7 @@ class CompleteFunctionAnalysis { std::move(Pending.InferrableEffectToFirstViolation); } - const Violation *firstViolationForEffect(const FunctionEffect &Effect) { + const Violation *firstViolationForEffect(FunctionEffect Effect) { return InferrableEffectToFirstViolation.lookup(Effect); } @@ -441,7 +445,7 @@ class Analyzer { 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) { + for (FunctionEffect Effect : Sem.AllEffectsToVerify) { const FunctionEffect::Flags Flags = Effect.flags(); if (Flags & FunctionEffect::FE_InferrableOnCallees) AllInferrableEffectsToVerify.insert(Effect); @@ -487,7 +491,7 @@ class Analyzer { // This indicates recursion (not necessarily direct). For the // purposes of effect analysis, we can just ignore it since - // no effects forbid recursion. + // no effects forbid recursion. assert(isa(AP)); Call.Recursed = true; } @@ -510,7 +514,7 @@ class Analyzer { // 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) { + for (FunctionEffect Effect : CInfo.Effects) { if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow)) continue; @@ -559,7 +563,8 @@ class Analyzer { // inserted in the container. void completeAnalysis(const CallableInfo &CInfo, PendingFunctionAnalysis &Pending) { - if (ArrayRef Viols = Pending.getViolationsForExplicitFX(); + if (ArrayRef Viols = + Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager()); !Viols.empty()) emitDiagnostics(Viols, CInfo, Sem); @@ -632,7 +637,7 @@ class Analyzer { llvm::dbgs() << "\n";); - auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects)) return; @@ -653,26 +658,21 @@ class Analyzer { } }; - for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects) - check1Effect(Effect, false); + for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects) + Check1Effect(Effect, false); - for (const FunctionEffect &Effect : PFA.FXToInfer) - check1Effect(Effect, true); + for (FunctionEffect Effect : PFA.FXToInfer) + Check1Effect(Effect, true); } // Should only be called when function's analysis is determined to be // complete. - void emitDiagnostics(ArrayRef Viols, - const CallableInfo &CInfo, Sema &S) { + void emitDiagnostics(ArrayRef 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) { + auto MaybeAddTemplateNote = [&](const Decl *D) { if (const FunctionDecl *FD = dyn_cast(D)) { while (FD != nullptr && FD->isTemplateInstantiation()) { S.Diag(FD->getPointOfInstantiation(), @@ -693,32 +693,32 @@ class Analyzer { break; case ViolationID::AllocatesMemory: S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::Throws: case ViolationID::Catches: S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::HasStaticLocal: S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::AccessesThreadLocal: S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsObjC: S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) << effectName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsDeclWithoutEffect: { @@ -727,7 +727,7 @@ class Analyzer { S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) << effectName << CalleeName; - checkAddTemplateNote(CInfo.CDecl); + MaybeAddTemplateNote(CInfo.CDecl); // Emit notes explaining the transitive chain of inferences: Why isn't // the callee safe? @@ -806,7 +806,7 @@ class Analyzer { << effectName << MaybeNextCallee->name(S); break; } - checkAddTemplateNote(Callee); + MaybeAddTemplateNote(Callee); Callee = Viol2.Callee; if (MaybeNextCallee) { CalleeInfo = *MaybeNextCallee; @@ -826,8 +826,7 @@ class Analyzer { // being checked for implicit conformance. // // Violations are always routed to a PendingFunctionAnalysis. - struct FunctionBodyASTVisitor - : RecursiveASTVisitor { + struct FunctionBodyASTVisitor : RecursiveASTVisitor { Analyzer &Outer; PendingFunctionAnalysis &CurrentFunction; @@ -836,7 +835,7 @@ class Analyzer { FunctionBodyASTVisitor(Analyzer &Outer, PendingFunctionAnalysis &CurrentFunction, CallableInfo &CurrentCaller) - : Outer(outer), CurrentFunction(CurrentFunction), + : Outer(Outer), CurrentFunction(CurrentFunction), CurrentCaller(CurrentCaller) {} // -- Entry point -- @@ -860,8 +859,7 @@ class Analyzer { 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) { + for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) { if (Effect.flags() & Flag) { addViolation(/*inferring=*/false, Effect, VID, Loc, Callee); break; @@ -869,14 +867,13 @@ class Analyzer { } // 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) + for (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) { + void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D, + SourceLocation Loc, const Decl *Callee = nullptr) { CurrentFunction.checkAddViolation(Inferring, Violation(Effect, D, Loc, Callee)); } @@ -925,19 +922,18 @@ class Analyzer { if (FPT) CalleeFX.insert(FPT->getFunctionEffects()); - auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) { + auto Check1Effect = [&](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 (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) + Check1Effect(Effect, false); - for (const FunctionEffect &Effect : CurrentFunction.FXToInfer) - check1Effect(Effect, true); + for (FunctionEffect Effect : CurrentFunction.FXToInfer) + Check1Effect(Effect, true); } // This destructor's body should be followed by the caller, but here we diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 540267d2efae4bf..02247cb71975cf8 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -15,7 +15,7 @@ void nb1() [[clang::nonblocking]] void nb2() [[clang::nonblocking]] { - static int global; // expected-warning {{'nonblocking' function must not have static locals}} + static int global; // expected-warning {{'nonblocking' function must not have static local variables}} } void nb3() [[clang::nonblocking]] @@ -178,7 +178,7 @@ void nb11() [[clang::nonblocking]] // Verify that when attached to a redeclaration, the attribute successfully attaches. void nb12() { - static int x; // expected-warning {{'nonblocking' function must not have static locals}} + static int x; // expected-warning {{'nonblocking' function must not have static local variables}} } void nb12() [[clang::nonblocking]]; void nb13() [[clang::nonblocking]] { nb12(); } From 2cb4539d5cf5abf7091dd9d6f735b039f86d3a9c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 14 Aug 2024 08:59:53 -0700 Subject: [PATCH 19/63] - Comments begin with capital letters and end with full stops. - Simplify EffectToViolationMap. - FX -> Effects. - getCanonicalDecl() is a method of Decl, no need to wrap it for functions. - Sem -> S. - Clean up comments around RecursiveASTVisitor and implicit calls. --- clang/lib/Sema/EffectAnalysis.cpp | 196 +++++++++++++----------------- 1 file changed, 85 insertions(+), 111 deletions(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index a074aece910c331..dbc429f22c23678 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -17,14 +17,14 @@ #include "clang/Basic/SourceManager.h" #include "clang/Sema/SemaInternal.h" -#define DEBUG_TYPE "fxanalysis" +#define DEBUG_TYPE "effectanalysis" using namespace clang; namespace { enum class ViolationID : uint8_t { - None = 0, // sentinel for an empty Violation + None = 0, // Sentinel for an empty Violation. Throws, Catches, CallsObjC, @@ -32,7 +32,7 @@ enum class ViolationID : uint8_t { HasStaticLocal, AccessesThreadLocal, - // These only apply to callees, where the analysis stops at the Decl + // These only apply to callees, where the analysis stops at the Decl. DeclDisallowsInference, CallsDeclWithoutEffect, @@ -47,10 +47,10 @@ enum class ViolationID : uint8_t { // be inferred as holding that effect. struct Violation { FunctionEffect Effect; - FunctionEffect CalleeEffectPreventingInference; // only for certain IDs + FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs. ViolationID ID = ViolationID::None; SourceLocation Loc; - const Decl *Callee = nullptr; // only valid for Calls* + const Decl *Callee = nullptr; // Only valid for Calls*. Violation() = default; @@ -65,7 +65,7 @@ struct Violation { enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; enum class CallableType : uint8_t { - // unknown: probably function pointer + // Unknown: probably function pointer Unknown, Function, Virtual, @@ -152,12 +152,12 @@ struct CallableInfo { } /// Generate a name for logging and diagnostics. - std::string name(Sema &Sem) const { + std::string name(Sema &S) const { std::string Name; llvm::raw_string_ostream OS(Name); if (auto *FD = dyn_cast(CDecl)) - FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), + FD->getNameForDiagnostic(OS, S.getPrintingPolicy(), /*Qualified=*/true); else if (auto *BD = dyn_cast(CDecl)) OS << "(block " << BD->getBlockManglingNumber() << ")"; @@ -172,7 +172,7 @@ struct CallableInfo { // 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 + // 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. using ImplVec = llvm::SmallVector; @@ -183,35 +183,23 @@ class EffectToViolationMap { void maybeInsert(const Violation &Viol) { if (Impl == nullptr) Impl = std::make_unique(); - auto *Iter = _find(Viol.Effect); - if (Iter != Impl->end() && Iter->Effect == Viol.Effect) + else if (lookup(Viol.Effect) != nullptr) return; - Impl->insert(Iter, Viol); + Impl->push_back(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; + auto *Iter = + std::find_if(Impl->begin(), Impl->end(), + [&](const auto &Item) { return Item.Effect == Key; }); + return Iter != Impl->end() ? &*Iter : nullptr; } size_t size() const { return Impl ? Impl->size() : 0; } - -private: - ImplVec::iterator _find(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; - } }; // ---------- @@ -236,11 +224,11 @@ class PendingFunctionAnalysis { // 1. Effects declared explicitly by this function. // 2. All other inferrable effects needing verification. FunctionEffectKindSet DeclaredVerifiableEffects; - FunctionEffectKindSet FXToInfer; + FunctionEffectKindSet EffectsToInfer; private: // Violations pertaining to the function's explicit effects. - SmallVector ViolationsForExplicitFX; + SmallVector ViolationsForExplicitEffects; // Violations pertaining to other, non-explicit, inferrable effects. EffectToViolationMap InferrableEffectToFirstViolation; @@ -250,17 +238,17 @@ class PendingFunctionAnalysis { SmallVector UnverifiedDirectCalls; public: - PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo, + PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo, FunctionEffectKindSet AllInferrableEffectsToVerify) : DeclaredVerifiableEffects(CInfo.Effects) { // Check for effects we are not allowed to infer - FunctionEffectKindSet InferrableFX; + FunctionEffectKindSet InferrableEffects; for (FunctionEffect effect : AllInferrableEffectsToVerify) { std::optional ProblemCalleeEffect = effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects); if (!ProblemCalleeEffect) - InferrableFX.insert(effect); + InferrableEffects.insert(effect); else { // Add a Violation for this effect if a caller were to // try to infer it. @@ -269,17 +257,17 @@ class PendingFunctionAnalysis { CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect)); } } - // InferrableFX is now the set of inferrable effects which are not + // InferrableEffects is now the set of inferrable effects which are not // prohibited - FXToInfer = FunctionEffectKindSet::difference(InferrableFX, - DeclaredVerifiableEffects); + 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) - ViolationsForExplicitFX.push_back(NewViol); + ViolationsForExplicitEffects.push_back(NewViol); else InferrableEffectToFirstViolation.maybeInsert(NewViol); } @@ -301,21 +289,22 @@ class PendingFunctionAnalysis { return UnverifiedDirectCalls; } - ArrayRef getSortedViolationsForExplicitFX(SourceManager &SM) { - if (!ViolationsForExplicitFX.empty()) - std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(), + ArrayRef getSortedViolationsForExplicitEffects(SourceManager &SM) { + if (!ViolationsForExplicitEffects.empty()) + std::sort(ViolationsForExplicitEffects.begin(), + ViolationsForExplicitEffects.end(), [&SM](const Violation &LHS, const Violation &RHS) { return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); }); - return ViolationsForExplicitFX; + return ViolationsForExplicitEffects; } void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { OS << "Pending: Declared "; DeclaredVerifiableEffects.dump(OS); - OS << ", " << ViolationsForExplicitFX.size() << " violations; "; + OS << ", " << ViolationsForExplicitEffects.size() << " violations; "; OS << " Infer "; - FXToInfer.dump(OS); + EffectsToInfer.dump(OS); OS << ", " << InferrableEffectToFirstViolation.size() << " violations"; if (!UnverifiedDirectCalls.empty()) { OS << "; Calls: "; @@ -368,18 +357,9 @@ class CompleteFunctionAnalysis { } }; -const Decl *CanonicalFunctionDecl(const Decl *D) { - if (auto *FD = dyn_cast(D)) { - FD = FD->getCanonicalDecl(); - assert(FD != nullptr); - return FD; - } - return D; -} - // ========== class Analyzer { - Sema &Sem; + Sema &S; // Subset of Sema.AllEffectsToVerify FunctionEffectKindSet AllInferrableEffectsToVerify; @@ -400,11 +380,11 @@ class Analyzer { // that lookups and insertions are via the canonical Decls. FuncAnalysisPtr lookup(const Decl *Key) const { - return Base::lookup(CanonicalFunctionDecl(Key)); + return Base::lookup(Key->getCanonicalDecl()); } FuncAnalysisPtr &operator[](const Decl *Key) { - return Base::operator[](CanonicalFunctionDecl(Key)); + return Base::operator[](Key->getCanonicalDecl()); } /// Shortcut for the case where we only care about completed analysis. @@ -440,12 +420,12 @@ class Analyzer { AnalysisMap DeclAnalysis; public: - Analyzer(Sema &S) : Sem(S) {} + 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 : Sem.AllEffectsToVerify) { + for (FunctionEffect Effect : S.AllEffectsToVerify) { const FunctionEffect::Flags Flags = Effect.flags(); if (Flags & FunctionEffect::FE_InferrableOnCallees) AllInferrableEffectsToVerify.insert(Effect); @@ -458,15 +438,16 @@ class Analyzer { // 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 = Sem.DeclsWithEffectsToVerify; + SmallVector &VerificationQueue = S.DeclsWithEffectsToVerify; std::reverse(VerificationQueue.begin(), VerificationQueue.end()); while (!VerificationQueue.empty()) { const Decl *D = VerificationQueue.back(); if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { - // All children have been traversed; finish analysis. - if (auto *Pending = AP.dyn_cast()) + if (auto *Pending = AP.dyn_cast()) { + // All children have been traversed; finish analysis. finishPendingAnalysis(D, Pending); + } VerificationQueue.pop_back(); continue; } @@ -474,7 +455,7 @@ class Analyzer { // Not previously visited; begin a new analysis for this Decl. PendingFunctionAnalysis *Pending = verifyDecl(D); if (Pending == nullptr) { - // completed now + // Completed now. VerificationQueue.pop_back(); continue; } @@ -505,15 +486,13 @@ class Analyzer { CallableInfo CInfo(*D); bool isExternC = false; - if (const FunctionDecl *FD = dyn_cast(D)) { - assert(FD->getBuiltinID() == 0); + 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 (Sem.getLangOpts().CPlusPlus && !isExternC) { + if (S.getLangOpts().CPlusPlus && !isExternC) { for (FunctionEffect Effect : CInfo.Effects) { if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow)) continue; @@ -528,8 +507,7 @@ class Analyzer { } } if (!IsNoexcept) - Sem.Diag(D->getBeginLoc(), - diag::warn_perf_constraint_implies_noexcept) + S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept) << Effect.name(); break; } @@ -538,10 +516,10 @@ class Analyzer { // 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); + PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify); - LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " "; - FAnalysis.dump(Sem, llvm::dbgs());); + LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(S) << " "; + FAnalysis.dump(S, llvm::dbgs());); FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo); @@ -555,7 +533,7 @@ class Analyzer { new PendingFunctionAnalysis(std::move(FAnalysis)); DeclAnalysis[D] = PendingPtr; LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n"; - DeclAnalysis.dump(Sem, llvm::dbgs());); + DeclAnalysis.dump(S, llvm::dbgs());); return PendingPtr; } @@ -564,16 +542,16 @@ class Analyzer { void completeAnalysis(const CallableInfo &CInfo, PendingFunctionAnalysis &Pending) { if (ArrayRef Viols = - Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager()); + Pending.getSortedViolationsForExplicitEffects(S.getSourceManager()); !Viols.empty()) - emitDiagnostics(Viols, CInfo, Sem); + emitDiagnostics(Viols, CInfo, S); - CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( - Sem.getASTContext(), Pending, CInfo.Effects, - AllInferrableEffectsToVerify); + CompleteFunctionAnalysis *CompletePtr = + new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects, + AllInferrableEffectsToVerify); DeclAnalysis[CInfo.CDecl] = CompletePtr; LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; - DeclAnalysis.dump(Sem, llvm::dbgs());); + DeclAnalysis.dump(S, llvm::dbgs());); } // Called after all direct calls requiring inference have been found -- or @@ -582,8 +560,8 @@ class Analyzer { 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";); + << "finishPendingAnalysis for " << Caller.name(S) << " : "; + Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";); for (const PendingFunctionAnalysis::DirectCall &Call : Pending->unverifiedCalls()) { if (Call.Recursed) @@ -614,7 +592,7 @@ class Analyzer { 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 + IsInferencePossible = false; // We've already traversed it. } if (AssertNoFurtherInference) { @@ -624,16 +602,13 @@ class Analyzer { if (!Callee.isVerifiable()) IsInferencePossible = false; - LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to " - << Callee.name(Sem) << "; verifiable: " + LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(S) << " to " + << Callee.name(S) << "; 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() << " callee " << Callee.CDecl << " canonical " + << Callee.CDecl->getCanonicalDecl() << " redecls"; + for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D; llvm::dbgs() << "\n";); @@ -661,7 +636,7 @@ class Analyzer { for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects) Check1Effect(Effect, false); - for (FunctionEffect Effect : PFA.FXToInfer) + for (FunctionEffect Effect : PFA.EffectsToInfer) Check1Effect(Effect, true); } @@ -687,8 +662,8 @@ class Analyzer { StringRef effectName = Viol1.Effect.name(); switch (Viol1.ID) { case ViolationID::None: - case ViolationID::DeclDisallowsInference: // shouldn't happen - // here + case ViolationID::DeclDisallowsInference: // Shouldn't happen + // here. llvm_unreachable("Unexpected violation kind"); break; case ViolationID::AllocatesMemory: @@ -867,7 +842,7 @@ class Analyzer { } // 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.FXToInfer) + for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) if (Effect.flags() & Flag) addViolation(/*inferring=*/true, Effect, VID, Loc, Callee); } @@ -895,9 +870,9 @@ class Analyzer { static bool isSafeBuiltinFunction(const FunctionDecl *FD) { unsigned BuiltinID = FD->getBuiltinID(); switch (BuiltinID) { - case 0: // not builtin + case 0: // Not builtin. return false; - default: // not disallowed via cases below + default: // Not disallowed via cases below. return true; // Disallow list @@ -917,14 +892,14 @@ class Analyzer { void checkIndirectCall(CallExpr *Call, QualType CalleeType) { auto *FPT = - CalleeType->getAs(); // null if FunctionType - FunctionEffectKindSet CalleeFX; + CalleeType->getAs(); // Null if FunctionType. + FunctionEffectKindSet CalleeEffects; if (FPT) - CalleeFX.insert(FPT->getFunctionEffects()); + CalleeEffects.insert(FPT->getFunctionEffects()); auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( - /*direct=*/false, CalleeFX)) + /*direct=*/false, CalleeEffects)) addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect, Call->getBeginLoc()); }; @@ -932,7 +907,7 @@ class Analyzer { for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) Check1Effect(Effect, false); - for (FunctionEffect Effect : CurrentFunction.FXToInfer) + for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) Check1Effect(Effect, true); } @@ -1015,7 +990,7 @@ class Analyzer { bool VisitCallExpr(CallExpr *Call) { LLVM_DEBUG(llvm::dbgs() << "VisitCallExpr : " - << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr) + << Call->getBeginLoc().printToString(Outer.S.SourceMgr) << "\n";); Expr *CalleeExpr = Call->getCallee(); @@ -1025,9 +1000,10 @@ class Analyzer { return true; } - if (isa(CalleeExpr)) - // just destroying a scalar, fine. + 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()); @@ -1038,7 +1014,7 @@ class Analyzer { bool VisitVarDecl(VarDecl *Var) { LLVM_DEBUG(llvm::dbgs() << "VisitVarDecl : " - << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr) + << Var->getBeginLoc().printToString(Outer.S.SourceMgr) << "\n";); if (Var->isStaticLocal()) @@ -1047,7 +1023,7 @@ class Analyzer { Var->getLocation()); const QualType::DestructionKind DK = - Var->needsDestruction(Outer.Sem.getASTContext()); + Var->needsDestruction(Outer.S.getASTContext()); if (DK == QualType::DK_cxx_destructor) { QualType QT = Var->getType(); if (const auto *ClsType = QT.getTypePtr()->getAs()) { @@ -1064,8 +1040,7 @@ class Analyzer { } bool VisitCXXNewExpr(CXXNewExpr *New) { - // BUG? It seems incorrect that RecursiveASTVisitor does not - // visit the call to operator new. + // RecursiveASTVisitor does not visit the implicit call to operator new. if (FunctionDecl *FD = New->getOperatorNew()) { CallableInfo CI(*FD, SpecialFuncType::OperatorNew); followCall(CI, New->getBeginLoc()); @@ -1080,8 +1055,8 @@ class Analyzer { } bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) { - // BUG? It seems incorrect that RecursiveASTVisitor does not - // visit the call to operator delete. + // RecursiveASTVisitor does not visit the implicit call to operator + // delete. if (FunctionDecl *FD = Delete->getOperatorDelete()) { CallableInfo CI(*FD, SpecialFuncType::OperatorDelete); followCall(CI, Delete->getBeginLoc()); @@ -1095,11 +1070,11 @@ class Analyzer { bool VisitCXXConstructExpr(CXXConstructExpr *Construct) { LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : " << Construct->getBeginLoc().printToString( - Outer.Sem.SourceMgr) + Outer.S.SourceMgr) << "\n";); - // BUG? It seems incorrect that RecursiveASTVisitor does not - // visit the call to the constructor. + // RecursiveASTVisitor does not visit the implicit call to the + // constructor. const CXXConstructorDecl *Ctor = Construct->getConstructor(); CallableInfo CI(*Ctor); followCall(CI, Construct->getLocation()); @@ -1172,7 +1147,6 @@ 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; From 0e07315aa4b880762f469bed476f55a507a1b793 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 14 Aug 2024 09:55:10 -0700 Subject: [PATCH 20/63] Implement FunctionEffectKindSet with std::bitset. --- clang/include/clang/AST/Type.h | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index e7a0d79447dc4e2..5579db711b59e74 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -48,6 +48,7 @@ #include "llvm/Support/PointerLikeTypeTraits.h" #include "llvm/Support/TrailingObjects.h" #include "llvm/Support/type_traits.h" +#include #include #include #include @@ -4922,43 +4923,44 @@ class FunctionEffectsRef { /// A mutable set of FunctionEffect::Kind. class FunctionEffectKindSet { // For now this only needs to be a bitmap. - using KindBitsT = uint8_t; constexpr static size_t EndBitPos = 8; + using KindBitsT = std::bitset; - KindBitsT KindBits = 0; - - static KindBitsT kindToBit(FunctionEffect::Kind K) { - return 1u << KindBitsT(K); - } + KindBitsT KindBits{}; explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {} + constexpr static size_t kindToPos(FunctionEffect::Kind K) { + return size_t(K); + } + public: FunctionEffectKindSet() = default; explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); } + // Iterates through the bits which are set. class iterator { const FunctionEffectKindSet *Outer = nullptr; size_t Idx = 0; // If Idx does not reference a set bit, advance it until it does, // or until it reaches EndBitPos. - void advanceIdx() { - while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx))) + void advanceToNextSetBit() { + while (Idx < EndBitPos && !Outer->KindBits.test(Idx)) ++Idx; } public: iterator(); iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) { - advanceIdx(); + advanceToNextSetBit(); } bool operator==(const iterator &Other) const { return Idx == Other.Idx; } bool operator!=(const iterator &Other) const { return Idx != Other.Idx; } iterator operator++() { ++Idx; - advanceIdx(); + advanceToNextSetBit(); return *this; } @@ -4971,7 +4973,7 @@ class FunctionEffectKindSet { iterator begin() const { return iterator(*this, 0); } iterator end() const { return iterator(*this, EndBitPos); } - void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); } + void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); } void insert(FunctionEffectsRef FX) { for (FunctionEffect Item : FX.effects()) insert(Item); @@ -4979,7 +4981,7 @@ class FunctionEffectKindSet { void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; } bool contains(const FunctionEffect::Kind EK) const { - return (KindBits & kindToBit(EK)) != 0; + return KindBits.test(kindToPos(EK)); } void dump(llvm::raw_ostream &OS) const; From fddd9d2775e7ee5fa88c9a12888872cafc411ead Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 14 Aug 2024 14:26:02 -0700 Subject: [PATCH 21/63] - Diagnose __builtin_operator_new and delete - More test cases involving function try-block and member initializers. - remove construct which is blowing clang-format's mind --- clang/lib/Sema/EffectAnalysis.cpp | 7 ++- .../Sema/attr-nonblocking-constraints.cpp | 46 ++++++++++++++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp index dbc429f22c23678..c0f679d9ece2062 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/EffectAnalysis.cpp @@ -607,10 +607,7 @@ class Analyzer { << Callee.isVerifiable() << "; callee "; CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n"; llvm::dbgs() << " callee " << Callee.CDecl << " canonical " - << Callee.CDecl->getCanonicalDecl() << " redecls"; - for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D; - - llvm::dbgs() << "\n";); + << Callee.CDecl->getCanonicalDecl() << "\n";); auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects)) @@ -881,6 +878,8 @@ class Analyzer { 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::BIcalloc: case Builtin::ID::BImalloc: case Builtin::ID::BImemalign: diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 02247cb71975cf8..3515f7f4b7d6cc9 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -210,12 +210,54 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] } // Builtin functions -void nb18a() [[clang::nonblocking]] { +void nb19() [[clang::nonblocking]] { __builtin_assume(1); void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}} __builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}} + + void *p2 = __builtin_operator_new(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}} + __builtin_operator_delete(p2); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_delete'}} +} + +// Function try-block +void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} + +void nb20() [[clang::nonblocking]] { + catches(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'catches'}} } +struct S { + int x; + S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} + S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} +}; + +int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + +struct A { + int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} +}; + +struct B { + int y = badi(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}} +}; + +void f() [[clang::nonblocking]] { + S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}} + S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}} + A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}} + B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}} +} + +#if 0 +// FIXME: can we do better with default member initializers? +struct T { + int x = badi(); + T() [[clang::nonblocking]] {} // Warning: this calls bad(). + T(int x) [[clang::nonblocking]] : x(x) {} // This does not. +}; +#endif + // Verify traversal of implicit code paths - constructors and destructors. struct Unsafe { static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} @@ -251,6 +293,6 @@ void g() [[clang::nonblocking]] { // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" -void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} +void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} { } From eb536aba9985c4087d3cd68bd75f4013a8530ff6 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 15 Aug 2024 11:01:13 -0700 Subject: [PATCH 22/63] EffectAnalysis.cpp => SemaFunctionEffects.cpp (minimal first step; more reorg to follow) --- clang/include/clang/Sema/Sema.h | 2 +- clang/lib/Sema/CMakeLists.txt | 2 +- clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} (99%) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index c17cc9ce8bc4435..f23d1aa9c95dfe1 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -530,7 +530,7 @@ struct FunctionEffectDifferences : public SmallVector { const FunctionEffectsRef &New); }; -// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and +// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and // move more of the effects implementation into that file? void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU); diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index ea827323395d745..719c3a9312ec15a 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -19,7 +19,6 @@ add_clang_library(clangSema CodeCompleteConsumer.cpp DeclSpec.cpp DelayedDiagnostic.cpp - EffectAnalysis.cpp HLSLExternalSemaSource.cpp IdentifierResolver.cpp JumpDiagnostics.cpp @@ -56,6 +55,7 @@ add_clang_library(clangSema SemaExprMember.cpp SemaExprObjC.cpp SemaFixItUtils.cpp + SemaFunctionEffects.cpp SemaHLSL.cpp SemaHexagon.cpp SemaInit.cpp diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp similarity index 99% rename from clang/lib/Sema/EffectAnalysis.cpp rename to clang/lib/Sema/SemaFunctionEffects.cpp index c0f679d9ece2062..2144cbf3a72bc6c 100644 --- a/clang/lib/Sema/EffectAnalysis.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1,4 +1,4 @@ -//=== EffectAnalysis.cpp - Sema warnings for function effects -------------===// +//=== SemaFunctionEffects.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. From 15b399fd8b6a8bb9f59ac3367467bb84e20efd0c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 15 Aug 2024 11:28:59 -0700 Subject: [PATCH 23/63] Move most Sema methods involving effects into SemaFunctionEffects.cpp. Two remain elsewhere: - diagnoseFunctionEffectConversion (still in Sema.cpp next to diagnoseNullableToNonnullConversion) - ActOnEffectExpression (still in SemaType.cpp near handleNonBlockingNonAllocatingTypeAttr) --- clang/include/clang/Sema/Sema.h | 90 +++++++++++--------- clang/lib/Sema/Sema.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 99 ---------------------- clang/lib/Sema/SemaFunctionEffects.cpp | 109 +++++++++++++++++++++++-- 4 files changed, 156 insertions(+), 144 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index f23d1aa9c95dfe1..c46b2f6e7e5fa55 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -530,10 +530,6 @@ struct FunctionEffectDifferences : public SmallVector { const FunctionEffectsRef &New); }; -// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and -// move more of the effects implementation into that file? -void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU); - /// Sema - This implements semantic analysis and AST building for C. /// \nosubgrouping class Sema final : public SemaBase { @@ -877,13 +873,6 @@ class Sema final : public SemaBase { /// Warn when implicitly casting 0 to nullptr. void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E); - /// All functions/lambdas/blocks which have bodies and which have a non-empty - /// FunctionEffectsRef to be verified. - SmallVector DeclsWithEffectsToVerify; - /// The union of all effects present on DeclsWithEffectsToVerify. Conditions - /// are all null. - FunctionEffectKindSet AllEffectsToVerify; - /// Warn when implicitly changing function effects. void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, SourceLocation Loc); @@ -4324,34 +4313,6 @@ class Sema final : public SemaBase { // Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check. bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee); - /// Warn and return true if adding a function effect to a set would create a - /// conflict. - bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, - const FunctionEffectWithCondition &EC, - SourceLocation NewAttrLoc); - - // Report a failure to merge function effects between declarations due to a - // conflict. - void - diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs, - SourceLocation NewLoc, - SourceLocation OldLoc); - - /// Inline checks from the start of maybeAddDeclWithEffects, to - /// minimize performance impact on code not using effects. - template - void maybeAddDeclWithEffects(FuncOrBlockDecl *D) { - if (Context.hasAnyFunctionEffects()) - if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty()) - maybeAddDeclWithEffects(D, FX); - } - - /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. - void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); - - /// Unconditionally add a Decl to DeclsWithEfffectsToVerify. - void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); - private: /// Function or variable declarations to be checked for whether the deferred /// diagnostics should be emitted. @@ -15119,6 +15080,57 @@ class Sema final : public SemaBase { bool OrNull); ///@} + + // + // + // ------------------------------------------------------------------------- + // + // + + /// \name Function Effects + /// Implementations are in SemaFunctionEffects.cpp + ///@{ +public: + /// All functions/lambdas/blocks which have bodies and which have a non-empty + /// FunctionEffectsRef to be verified. + SmallVector DeclsWithEffectsToVerify; + + /// The union of all effects present on DeclsWithEffectsToVerify. Conditions + /// are all null. + FunctionEffectKindSet AllEffectsToVerify; + +public: + /// Warn and return true if adding a function effect to a set would create a + /// conflict. + bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, + const FunctionEffectWithCondition &EC, + SourceLocation NewAttrLoc); + + // Report a failure to merge function effects between declarations due to a + // conflict. + void + diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs, + SourceLocation NewLoc, + SourceLocation OldLoc); + + /// Inline checks from the start of maybeAddDeclWithEffects, to + /// minimize performance impact on code not using effects. + template + void maybeAddDeclWithEffects(FuncOrBlockDecl *D) { + if (Context.hasAnyFunctionEffects()) + if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty()) + maybeAddDeclWithEffects(D, FX); + } + + /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify. + void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + + /// Unconditionally add a Decl to DeclsWithEfffectsToVerify. + void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX); + + void performFunctionEffectAnalysis(TranslationUnitDecl *TU); + + ///@} }; DeductionFailureInfo diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 7afc2b5b35d4bab..12f0a0e7bfd753a 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1529,7 +1529,7 @@ void Sema::ActOnEndOfTranslationUnit() { AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl()); if (Context.hasAnyFunctionEffects()) - performEffectAnalysis(*this, Context.getTranslationUnitDecl()); + performFunctionEffectAnalysis(Context.getTranslationUnitDecl()); // Check we've noticed that we're no longer parsing the initializer for every // variable. If we miss cases, then at best we have a performance issue and diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 8b464ab29498fb4..554b35527b22017 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -10919,49 +10919,6 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD, return nullptr; } -// Should only be called when getFunctionEffects() returns a non-empty set. -// 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); -} - bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD, QualType NewT, QualType OldT) { if (!NewD->getLexicalDeclContext()->isDependentContext()) @@ -20331,59 +20288,3 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) { return LangOpts.CUDA && !LangOpts.CUDAIsDevice && CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global; } - -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); - } -} - -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; -} diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 2144cbf3a72bc6c..7624a072d2fca8c 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1,4 +1,4 @@ -//=== SemaFunctionEffects.cpp - Sema warnings for function effects --------===// +//=== 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. @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// This file implements caller/callee analysis for function effects. +// This file implements Sema handling of function effects. // //===----------------------------------------------------------------------===// @@ -1144,12 +1144,111 @@ Analyzer::AnalysisMap::~AnalysisMap() { namespace clang { -void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) { - if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings()) +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); + } +} + +// Should only be called when getFunctionEffects() returns a non-empty set. +// 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{S}.run(*TU); + Analyzer{*this}.run(*TU); } } // namespace clang From dfebc1aa962a524e8767d8ca77b73c7b3840a069 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 07:34:07 -0700 Subject: [PATCH 24/63] Move FunctionEffectDiff/Differences into SemaFunctionEffects.cpp. --- clang/include/clang/Sema/Sema.h | 99 ++++++++-------- clang/lib/Sema/Sema.cpp | 150 ------------------------- clang/lib/Sema/SemaFunctionEffects.cpp | 150 +++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 199 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index c46b2f6e7e5fa55..43f4d72a8fbefc6 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -481,55 +481,6 @@ enum class FunctionEffectMode : uint8_t { Dependent // effect(expr) where expr is dependent. }; -struct FunctionEffectDiff { - enum class Kind { Added, Removed, ConditionMismatch }; - - FunctionEffect::Kind EffectKind; - Kind DiffKind; - FunctionEffectWithCondition Old; // invalid when Added. - FunctionEffectWithCondition New; // invalid when Removed. - - StringRef effectName() const { - if (Old.Effect.kind() != FunctionEffect::Kind::None) - return Old.Effect.name(); - return New.Effect.name(); - } - - /// Describes the result of effects differing between a base class's virtual - /// method and an overriding method in a subclass. - enum class OverrideResult { - NoAction, - Warn, - Merge // Merge missing effect from base to derived. - }; - - /// Return true if adding or removing the effect as part of a type conversion - /// should generate a diagnostic. - bool shouldDiagnoseConversion(QualType SrcType, - const FunctionEffectsRef &SrcFX, - QualType DstType, - const FunctionEffectsRef &DstFX) const; - - /// Return true if adding or removing the effect in a redeclaration should - /// generate a diagnostic. - bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction, - const FunctionEffectsRef &OldFX, - const FunctionDecl &NewFunction, - const FunctionEffectsRef &NewFX) const; - - /// Return true if adding or removing the effect in a C++ virtual method - /// override should generate a diagnostic. - OverrideResult shouldDiagnoseMethodOverride( - const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, - const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const; -}; - -struct FunctionEffectDifferences : public SmallVector { - /// Caller should short-circuit by checking for equality first. - FunctionEffectDifferences(const FunctionEffectsRef &Old, - const FunctionEffectsRef &New); -}; - /// Sema - This implements semantic analysis and AST building for C. /// \nosubgrouping class Sema final : public SemaBase { @@ -568,6 +519,7 @@ class Sema final : public SemaBase { // 30. Constraints and Concepts (SemaConcept.cpp) // 31. Types (SemaType.cpp) // 32. FixIt Helpers (SemaFixItUtils.cpp) + // 33. Function Effects (SemaFunctionEffects.cpp) /// \name Semantic Analysis /// Implementations are in Sema.cpp @@ -15091,6 +15043,55 @@ class Sema final : public SemaBase { /// Implementations are in SemaFunctionEffects.cpp ///@{ public: + struct FunctionEffectDiff { + enum class Kind { Added, Removed, ConditionMismatch }; + + FunctionEffect::Kind EffectKind; + Kind DiffKind; + FunctionEffectWithCondition Old; // invalid when Added. + FunctionEffectWithCondition New; // invalid when Removed. + + StringRef effectName() const { + if (Old.Effect.kind() != FunctionEffect::Kind::None) + return Old.Effect.name(); + return New.Effect.name(); + } + + /// Describes the result of effects differing between a base class's virtual + /// method and an overriding method in a subclass. + enum class OverrideResult { + NoAction, + Warn, + Merge // Merge missing effect from base to derived. + }; + + /// Return true if adding or removing the effect as part of a type + /// conversion should generate a diagnostic. + bool shouldDiagnoseConversion(QualType SrcType, + const FunctionEffectsRef &SrcFX, + QualType DstType, + const FunctionEffectsRef &DstFX) const; + + /// Return true if adding or removing the effect in a redeclaration should + /// generate a diagnostic. + bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction, + const FunctionEffectsRef &OldFX, + const FunctionDecl &NewFunction, + const FunctionEffectsRef &NewFX) const; + + /// Return true if adding or removing the effect in a C++ virtual method + /// override should generate a diagnostic. + OverrideResult shouldDiagnoseMethodOverride( + const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, + const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const; + }; + + struct FunctionEffectDifferences : public SmallVector { + /// Caller should short-circuit by checking for equality first. + FunctionEffectDifferences(const FunctionEffectsRef &Old, + const FunctionEffectsRef &New); + }; + /// All functions/lambdas/blocks which have bodies and which have a non-empty /// FunctionEffectsRef to be verified. SmallVector DeclsWithEffectsToVerify; diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 12f0a0e7bfd753a..5c7460d480db070 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -2783,153 +2783,3 @@ bool Sema::isDeclaratorFunctionLike(Declarator &D) { }); return Result; } - -FunctionEffectDifferences::FunctionEffectDifferences( - 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, {}}); - ++POld; - } else if (cmp > 0) { - // addition - FunctionEffectWithCondition New = *PNew; - push_back(FunctionEffectDiff{ - New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New}); - ++PNew; - } else { - ++POld; - ++PNew; - } - } -} - -bool 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; - } - case FunctionEffect::Kind::Blocking: - case FunctionEffect::Kind::Allocating: - return false; - case FunctionEffect::Kind::None: - break; - } - llvm_unreachable("unknown effect kind"); -} - -bool 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; - } - case FunctionEffect::Kind::Blocking: - case FunctionEffect::Kind::Allocating: - return false; - case FunctionEffect::Kind::None: - break; - } - llvm_unreachable("unknown effect kind"); -} - -FunctionEffectDiff::OverrideResult -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; - } - - case FunctionEffect::Kind::Blocking: - case FunctionEffect::Kind::Allocating: - return OverrideResult::NoAction; - - case FunctionEffect::Kind::None: - break; - } - llvm_unreachable("unknown effect kind"); -} diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 7624a072d2fca8c..526c0bb8d114e61 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1251,4 +1251,154 @@ void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) { Analyzer{*this}.run(*TU); } +Sema::FunctionEffectDifferences::FunctionEffectDifferences( + 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, {}}); + ++POld; + } else if (cmp > 0) { + // addition + FunctionEffectWithCondition New = *PNew; + push_back(FunctionEffectDiff{ + New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, 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; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + case FunctionEffect::Kind::None: + break; + } + 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; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + case FunctionEffect::Kind::None: + break; + } + 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; + } + + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return OverrideResult::NoAction; + + case FunctionEffect::Kind::None: + break; + } + llvm_unreachable("unknown effect kind"); +} + } // namespace clang From 82cb07d0dbd6a0a6bc21bd6d6406c05e05507d09 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 08:09:45 -0700 Subject: [PATCH 25/63] Combine the diagnostics for 5 violations into one using %select{} --- .../clang/Basic/DiagnosticSemaKinds.td | 35 ++------ clang/lib/Sema/SemaFunctionEffects.cpp | 89 ++++++++----------- .../Sema/attr-nonblocking-constraints.cpp | 2 +- 3 files changed, 47 insertions(+), 79 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index be8aa4edb02c1bd..b231207690f6c86 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10945,39 +10945,22 @@ def warn_imp_cast_drops_unaligned : Warning< InGroup>; // Function effects -def warn_func_effect_allocates : Warning< - "'%0' function must not allocate or deallocate memory">, +def warn_func_effect_violation : Warning< + "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|" + "have static local variables|use thread-local variables|access ObjC methods or properties}1">, InGroup; -def note_func_effect_allocates : Note< - "function cannot be inferred '%0' because it allocates/deallocates memory">; -def warn_func_effect_throws_or_catches : Warning< - "'%0' function must not throw or catch exceptions">, - InGroup; -def note_func_effect_throws_or_catches : Note< - "function cannot be inferred '%0' because it throws or catches exceptions">; -def warn_func_effect_has_static_local : Warning< - "'%0' function must not have static local variables">, - InGroup; -def note_func_effect_has_static_local : Note< - "function cannot be inferred '%0' because it has a static local variable">; -def warn_func_effect_uses_thread_local : Warning< - "'%0' function must not use thread-local variables">, - InGroup; -def note_func_effect_uses_thread_local : Note< - "function cannot be inferred '%0' because it uses a thread-local variable">; -def warn_func_effect_calls_objc : Warning< - "'%0' function must not access an ObjC method or property">, - InGroup; -def note_func_effect_calls_objc : Note< - "function cannot be inferred '%0' because it accesses an ObjC method or property">; +def note_func_effect_violation : Note< + "function cannot be inferred '%0' because it %select{allocates or deallocates memory|" + "throws or catches exceptions|has a static local variable|uses a thread-local variable|" + "accesses an ObjC method or property}1">; def warn_func_effect_calls_func_without_effect : Warning< "'%0' function must not call non-'%0' function '%1'">, InGroup; +def note_func_effect_calls_func_without_effect : Note< + "function cannot be inferred '%0' because it calls non-'%0' function '%1'">; def warn_func_effect_calls_expr_without_effect : Warning< "'%0' function must not call non-'%0' expression">, InGroup; -def note_func_effect_calls_func_without_effect : Note< - "function cannot be inferred '%0' because it calls non-'%0' function '%1'">; def note_func_effect_call_extern : Note< "declaration cannot be inferred '%0' because it has no definition in this translation unit">; def note_func_effect_call_disallows_inference : Note< diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 526c0bb8d114e61..de1abf773082f1d 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -25,12 +25,13 @@ namespace { enum class ViolationID : uint8_t { None = 0, // Sentinel for an empty Violation. - Throws, - Catches, - CallsObjC, - AllocatesMemory, - HasStaticLocal, - AccessesThreadLocal, + // These first few map to a %select{} in a diagnostic. + BaseDiagnosticIndex, + AllocatesMemory = BaseDiagnosticIndex, + ThrowsOrCatchesExceptions, + HasStaticLocalVariable, + AccessesThreadLocalVariable, + AccessesObjCMethodOrProperty, // These only apply to callees, where the analysis stops at the Decl. DeclDisallowsInference, @@ -61,6 +62,10 @@ struct Violation { if (CalleeEffect != nullptr) CalleeEffectPreventingInference = *CalleeEffect; } + + unsigned diagnosticSelectIndex() const { + return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex); + } }; enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; @@ -664,27 +669,12 @@ class Analyzer { llvm_unreachable("Unexpected violation kind"); break; case ViolationID::AllocatesMemory: - S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName; - MaybeAddTemplateNote(CInfo.CDecl); - break; - case ViolationID::Throws: - case ViolationID::Catches: - S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches) - << effectName; - MaybeAddTemplateNote(CInfo.CDecl); - break; - case ViolationID::HasStaticLocal: - S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local) - << effectName; - MaybeAddTemplateNote(CInfo.CDecl); - break; - case ViolationID::AccessesThreadLocal: - S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local) - << effectName; - MaybeAddTemplateNote(CInfo.CDecl); - break; - case ViolationID::CallsObjC: - S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName; + case ViolationID::ThrowsOrCatchesExceptions: + case ViolationID::HasStaticLocalVariable: + case ViolationID::AccessesThreadLocalVariable: + case ViolationID::AccessesObjCMethodOrProperty: + S.Diag(Viol1.Loc, diag::warn_func_effect_violation) + << effectName << Viol1.diagnosticSelectIndex(); MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsExprWithoutEffect: @@ -754,23 +744,12 @@ class Analyzer { << 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; + case ViolationID::ThrowsOrCatchesExceptions: + case ViolationID::HasStaticLocalVariable: + case ViolationID::AccessesThreadLocalVariable: + case ViolationID::AccessesObjCMethodOrProperty: + S.Diag(Viol2.Loc, diag::note_func_effect_violation) + << effectName << Viol2.diagnosticSelectIndex(); break; case ViolationID::CallsDeclWithoutEffect: MaybeNextCallee.emplace(*Viol2.Callee); @@ -952,37 +931,43 @@ class Analyzer { bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - ViolationID::Throws, Throw->getThrowLoc()); + ViolationID::ThrowsOrCatchesExceptions, + Throw->getThrowLoc()); return true; } bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - ViolationID::Catches, Catch->getCatchLoc()); + ViolationID::ThrowsOrCatchesExceptions, + Catch->getCatchLoc()); return true; } bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, - ViolationID::Throws, Throw->getThrowLoc()); + ViolationID::ThrowsOrCatchesExceptions, + Throw->getThrowLoc()); return true; } bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - ViolationID::Catches, Catch->getAtCatchLoc()); + ViolationID::ThrowsOrCatchesExceptions, + Catch->getAtCatchLoc()); return true; } bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, - ViolationID::CallsObjC, Msg->getBeginLoc()); + ViolationID::AccessesObjCMethodOrProperty, + Msg->getBeginLoc()); return true; } bool VisitSEHExceptStmt(SEHExceptStmt *Exc) { diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, - ViolationID::Catches, Exc->getExceptLoc()); + ViolationID::ThrowsOrCatchesExceptions, + Exc->getExceptLoc()); return true; } @@ -1018,7 +1003,7 @@ class Analyzer { if (Var->isStaticLocal()) diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, - ViolationID::HasStaticLocal, + ViolationID::HasStaticLocalVariable, Var->getLocation()); const QualType::DestructionKind DK = @@ -1106,7 +1091,7 @@ class Analyzer { // At least on macOS, thread-local variables are initialized on // first access, including a heap allocation. diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars, - ViolationID::AccessesThreadLocal, + ViolationID::AccessesThreadLocalVariable, E->getLocation()); } } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 3515f7f4b7d6cc9..0ff43608ac5a23b 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -121,7 +121,7 @@ void nb8c() friend Stringy operator+(const Stringy& x, const Stringy& y) { // Do something inferably unsafe - auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}} + auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates or deallocates memory}} return {}; } }; From f93ee015a672ba2e3a221bc3d47f464be7f885bb Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 10:35:19 -0700 Subject: [PATCH 26/63] Violation constructor: use optional instead of pointer. --- clang/lib/Sema/SemaFunctionEffects.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index de1abf773082f1d..58615a45e7a2041 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -57,9 +57,9 @@ struct Violation { Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc, const Decl *Callee = nullptr, - const FunctionEffect *CalleeEffect = nullptr) + std::optional CalleeEffect = std::nullopt) : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) { - if (CalleeEffect != nullptr) + if (CalleeEffect) CalleeEffectPreventingInference = *CalleeEffect; } @@ -259,7 +259,7 @@ class PendingFunctionAnalysis { // try to infer it. InferrableEffectToFirstViolation.maybeInsert(Violation( effect, ViolationID::DeclDisallowsInference, - CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect)); + CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect)); } } // InferrableEffects is now the set of inferrable effects which are not @@ -338,7 +338,7 @@ class CompleteFunctionAnalysis { public: // The incoming Pending analysis is consumed (member(s) are moved-from). - CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending, + CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending, FunctionEffectKindSet DeclaredEffects, FunctionEffectKindSet AllInferrableEffectsToVerify) : VerifiedEffects(DeclaredEffects) { @@ -530,7 +530,7 @@ class Analyzer { Visitor.run(); if (FAnalysis.isComplete()) { - completeAnalysis(CInfo, FAnalysis); + completeAnalysis(CInfo, std::move(FAnalysis)); return nullptr; } // Move the pending analysis to the heap and save it in the map. @@ -545,14 +545,14 @@ class Analyzer { // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis, // inserted in the container. void completeAnalysis(const CallableInfo &CInfo, - PendingFunctionAnalysis &Pending) { + PendingFunctionAnalysis &&Pending) { if (ArrayRef Viols = Pending.getSortedViolationsForExplicitEffects(S.getSourceManager()); !Viols.empty()) emitDiagnostics(Viols, CInfo, S); CompleteFunctionAnalysis *CompletePtr = - new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects, + new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects, AllInferrableEffectsToVerify); DeclAnalysis[CInfo.CDecl] = CompletePtr; LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; @@ -576,7 +576,7 @@ class Analyzer { followCall(Caller, *Pending, Callee, Call.CallLoc, /*AssertNoFurtherInference=*/true); } - completeAnalysis(Caller, *Pending); + completeAnalysis(Caller, std::move(*Pending)); delete Pending; } From 6cc0a6275ad3ce1065b28eaaee2aa68d4a634e74 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 10:36:27 -0700 Subject: [PATCH 27/63] clang-format --- clang/lib/Sema/SemaFunctionEffects.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 58615a45e7a2041..f357593668bee19 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -551,9 +551,9 @@ class Analyzer { !Viols.empty()) emitDiagnostics(Viols, CInfo, S); - CompleteFunctionAnalysis *CompletePtr = - new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects, - AllInferrableEffectsToVerify); + 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());); From 69e1ae67a43e36c1cc0b8fb2dd209ba2e364aded Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 11:19:16 -0700 Subject: [PATCH 28/63] emitDiagnostics doesn't need to receive another S --- clang/lib/Sema/SemaFunctionEffects.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index f357593668bee19..2b4d321153d1b9b 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -549,7 +549,7 @@ class Analyzer { if (ArrayRef Viols = Pending.getSortedViolationsForExplicitEffects(S.getSourceManager()); !Viols.empty()) - emitDiagnostics(Viols, CInfo, S); + emitDiagnostics(Viols, CInfo); CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( S.getASTContext(), std::move(Pending), CInfo.Effects, @@ -644,8 +644,7 @@ class Analyzer { // Should only be called when function's analysis is determined to be // complete. - void emitDiagnostics(ArrayRef Viols, const CallableInfo &CInfo, - Sema &S) { + void emitDiagnostics(ArrayRef Viols, const CallableInfo &CInfo) { if (Viols.empty()) return; From 076302ec7091cd46445df014ced97862e0f47e0c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 11:31:10 -0700 Subject: [PATCH 29/63] VisitVarDecl can reuse followTypeDtor(). --- clang/lib/Sema/SemaFunctionEffects.cpp | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 2b4d321153d1b9b..f36bd2c0ec1d9b3 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -892,19 +892,20 @@ class Analyzer { // 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(), Dtor); + followTypeDtor(Field->getType(), DtorLoc); if (const auto *Class = dyn_cast(Rec)) { for (const CXXBaseSpecifier &Base : Class->bases()) - followTypeDtor(Base.getType(), Dtor); + followTypeDtor(Base.getType(), DtorLoc); for (const CXXBaseSpecifier &Base : Class->vbases()) - followTypeDtor(Base.getType(), Dtor); + followTypeDtor(Base.getType(), DtorLoc); } } - void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) { + void followTypeDtor(QualType QT, SourceLocation CallSite) { const Type *Ty = QT.getTypePtr(); while (Ty->isArrayType()) { const ArrayType *Arr = Ty->getAsArrayTypeUnsafe(); @@ -916,7 +917,7 @@ class Analyzer { if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { if (CXXDestructorDecl *Dtor = Class->getDestructor()) { CallableInfo CI(*Dtor); - followCall(CI, OuterDtor->getLocation()); + followCall(CI, CallSite); } } } @@ -1007,18 +1008,8 @@ class Analyzer { const QualType::DestructionKind DK = Var->needsDestruction(Outer.S.getASTContext()); - if (DK == QualType::DK_cxx_destructor) { - QualType QT = Var->getType(); - if (const auto *ClsType = QT.getTypePtr()->getAs()) { - if (const auto *CxxRec = - dyn_cast(ClsType->getDecl())) { - if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) { - CallableInfo CI(*Dtor); - followCall(CI, Var->getLocation()); - } - } - } - } + if (DK == QualType::DK_cxx_destructor) + followTypeDtor(Var->getType(), Var->getLocation()); return true; } From 7b891c630e0e7eb55e27c3eca374f7af83e4ecca Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 15:35:12 -0700 Subject: [PATCH 30/63] Add a test for a delegating initializer. --- clang/test/Sema/attr-nonblocking-constraints.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 0ff43608ac5a23b..f51ca1a855880ef 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -265,10 +265,16 @@ struct Unsafe { Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}} ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}} + + Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + + // Delegating initializer. + Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} }; struct DerivedFromUnsafe : public Unsafe { DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} + DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}} }; From d1fcceb9b2d54674673f1162b0621d12017ab7e6 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 15:35:40 -0700 Subject: [PATCH 31/63] Fix typo in comment for TraverseLambdaExpr. --- clang/lib/Sema/SemaFunctionEffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index f36bd2c0ec1d9b3..f93193a2f93bdbc 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1057,7 +1057,7 @@ class Analyzer { } bool TraverseLambdaExpr(LambdaExpr *Lambda) { - // We override this so as the be able to skip traversal of the lambda's + // 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 From abcf022fc5a1b27a46d9d4a6ab50d897cbdbf489 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 21:37:53 -0700 Subject: [PATCH 32/63] Fix test broken by rewording of diagnostics. --- clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index ff5873c11c4fe76..262d647142d548b 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -8,7 +8,7 @@ - (void)method; @end void nb1(OCClass *oc) [[clang::nonblocking]] { - [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}} + [oc method]; // expected-warning {{'nonblocking' function must not access ObjC methods or properties}} } void nb2(OCClass *oc) { [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}} From 47ebf2767f59dd9a62b63c32b47aef0343a83c7c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Fri, 16 Aug 2024 21:38:32 -0700 Subject: [PATCH 33/63] Diagnostics are now more specific about where a construct was found, specially handling member initializers, and more specifically describing Decls which are constructors, destructors, lambdas and blocks. --- .../clang/Basic/DiagnosticSemaKinds.td | 37 +++-- clang/lib/Sema/SemaFunctionEffects.cpp | 145 ++++++++++++++---- .../Sema/attr-nonblocking-constraints.cpp | 51 +++--- 3 files changed, 161 insertions(+), 72 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3ebc7089a93d7b6..e60088f0703ca36 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10951,29 +10951,38 @@ def warn_imp_cast_drops_unaligned : Warning< // Function effects def warn_func_effect_violation : Warning< - "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|" - "have static local variables|use thread-local variables|access ObjC methods or properties}1">, + "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " + "must not %select{allocate or deallocate memory|throw or catch exceptions|" + "have static local variables|use thread-local variables|access ObjC methods or properties}2">, InGroup; def note_func_effect_violation : Note< - "function cannot be inferred '%0' because it %select{allocates or deallocates memory|" - "throws or catches exceptions|has a static local variable|uses a thread-local variable|" - "accesses an ObjC method or property}1">; + "%select{function|constructor|destructor|lambda|block|member initializer}0 " + "cannot be inferred '%1' because it " + "%select{allocates or deallocates memory|throws or catches exceptions|" + "has a static local variable|uses a thread-local variable|" + "accesses an ObjC method or property}2">; def warn_func_effect_calls_func_without_effect : Warning< - "'%0' function must not call non-'%0' function '%1'">, + "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " + "must not call non-'%0' " + "%select{function|constructor|destructor|lambda|block}2 " + "'%3'">, InGroup; def note_func_effect_calls_func_without_effect : Note< - "function cannot be inferred '%0' because it calls non-'%0' function '%1'">; + "%select{function|constructor|destructor|lambda|block|member initializer}0 " + "cannot be inferred '%1' because it calls non-'%1' " + "%select{function|constructor|destructor|lambda|block}2 " + "'%3'">; def warn_func_effect_calls_expr_without_effect : Warning< - "'%0' function must not call non-'%0' expression">, + "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " + "must not call non-'%0' expression">, InGroup; def note_func_effect_call_extern : Note< "declaration cannot be inferred '%0' because it has no definition in this translation unit">; def note_func_effect_call_disallows_inference : Note< - "function does not permit inference of '%0' because it is declared '%1'">; -def note_func_effect_call_virtual : Note< - "virtual method cannot be inferred '%0'">; -def note_func_effect_call_func_ptr : Note< - "function pointer cannot be inferred '%0'">; + "%select{function|constructor|destructor|lambda|block}0 " + "does not permit inference of '%1' because it is declared '%2'">; +def note_func_effect_call_indirect : Note< + "%select{virtual method|function pointer}0 cannot be inferred '%1'">; def warn_perf_constraint_implies_noexcept : Warning< "'%0' function should be declared noexcept">, InGroup; @@ -10981,6 +10990,8 @@ def warn_perf_constraint_implies_noexcept : Warning< // FIXME: It would be nice if we could provide fuller template expansion notes. def note_func_effect_from_template : Note< "in template expansion here">; +def note_func_effect_in_constructor : Note< + "in%select{| implicit}0 constructor here">; // spoofing nonblocking/nonallocating def warn_invalid_add_func_effects : Warning< diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index f93193a2f93bdbc..acf459ec9555c1e 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" @@ -40,6 +41,13 @@ enum class ViolationID : uint8_t { CallsExprWithoutEffect, }; +// Bits, describing the AST context in which a violation was found. +// If none present, the context was the function body. +using ViolationSite = unsigned; +enum { + VSite_MemberInitializer = 1, +}; + // 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 @@ -48,17 +56,19 @@ enum class ViolationID : uint8_t { // be inferred as holding that effect. struct Violation { FunctionEffect Effect; - FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs. + FunctionEffect + CalleeEffectPreventingInference; // Only for certain IDs; can be None. ViolationID ID = ViolationID::None; + ViolationSite Site; SourceLocation Loc; const Decl *Callee = nullptr; // Only valid for Calls*. Violation() = default; - Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc, - const Decl *Callee = nullptr, + Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS, + SourceLocation Loc, const Decl *Callee = nullptr, std::optional CalleeEffect = std::nullopt) - : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) { + : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) { if (CalleeEffect) CalleeEffectPreventingInference = *CalleeEffect; } @@ -220,9 +230,10 @@ class PendingFunctionAnalysis { // Not all recursive calls are detected, just enough // to break cycles. bool Recursed = false; + ViolationSite VSite; - DirectCall(const Decl *D, SourceLocation CallLoc) - : Callee(D), CallLoc(CallLoc) {} + DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite) + : Callee(D), CallLoc(CallLoc), VSite(VSite) {} }; // We always have two disjoint sets of effects to verify: @@ -246,7 +257,7 @@ class PendingFunctionAnalysis { PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo, FunctionEffectKindSet AllInferrableEffectsToVerify) : DeclaredVerifiableEffects(CInfo.Effects) { - // Check for effects we are not allowed to infer + // Check for effects we are not allowed to infer. FunctionEffectKindSet InferrableEffects; for (FunctionEffect effect : AllInferrableEffectsToVerify) { @@ -258,12 +269,12 @@ class PendingFunctionAnalysis { // Add a Violation for this effect if a caller were to // try to infer it. InferrableEffectToFirstViolation.maybeInsert(Violation( - effect, ViolationID::DeclDisallowsInference, + effect, ViolationID::DeclDisallowsInference, 0, CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect)); } } // InferrableEffects is now the set of inferrable effects which are not - // prohibited + // prohibited. EffectsToInfer = FunctionEffectKindSet::difference( InferrableEffects, DeclaredVerifiableEffects); } @@ -277,8 +288,9 @@ class PendingFunctionAnalysis { InferrableEffectToFirstViolation.maybeInsert(NewViol); } - void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) { - UnverifiedDirectCalls.emplace_back(D, CallLoc); + 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. @@ -574,7 +586,7 @@ class Analyzer { CallableInfo Callee(*Call.Callee); followCall(Caller, *Pending, Callee, Call.CallLoc, - /*AssertNoFurtherInference=*/true); + /*AssertNoFurtherInference=*/true, Call.VSite); } completeAnalysis(Caller, std::move(*Pending)); delete Pending; @@ -584,7 +596,7 @@ class Analyzer { // other AST construct. PFA pertains to the caller. void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA, const CallableInfo &Callee, SourceLocation CallLoc, - bool AssertNoFurtherInference) { + bool AssertNoFurtherInference, ViolationSite VSite) { const bool DirectCall = Callee.isCalledDirectly(); // Initially, the declared effects; inferred effects will be added. @@ -625,13 +637,14 @@ class Analyzer { if (Callee.FuncType == SpecialFuncType::None) PFA.checkAddViolation(Inferring, {Effect, ViolationID::CallsDeclWithoutEffect, - CallLoc, Callee.CDecl}); + VSite, CallLoc, Callee.CDecl}); else PFA.checkAddViolation( - Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc}); + Inferring, + {Effect, ViolationID::AllocatesMemory, VSite, CallLoc}); } else { // Inference is allowed and necessary; defer it. - PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc); + PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite); } }; @@ -658,6 +671,50 @@ class Analyzer { } }; + // For note_func_effect_call_indirect. + enum { Indirect_VirtualMethod, Indirect_FunctionPtr }; + + // Describe a call site or target using an enum mapping to a %select{} + // in a diagnostic. + auto SiteDescIndex = [](const Decl *D, const Violation *V) { + enum { + VS_Function, + VS_Constructor, + VS_Destructor, + VS_Lambda, + VS_Block, + VS_MemberInitializer, + }; + + if (V != nullptr && V->Site == VSite_MemberInitializer) + return VS_MemberInitializer; + if (isa(D)) + return VS_Block; + if (auto *Method = dyn_cast(D)) { + if (isa(D)) + return VS_Constructor; + if (isa(D)) + return VS_Destructor; + const CXXRecordDecl *Rec = Method->getParent(); + if (Rec->isLambda()) + return VS_Lambda; + } + return VS_Function; + }; + + // If a Violation's site is a member initializer, adds a note referring to + // the constructor which invoked it. + auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) { + if (V.Site == VSite_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; + } + }; + // Top-level violations are warnings. for (const Violation &Viol1 : Viols) { StringRef effectName = Viol1.Effect.name(); @@ -673,12 +730,15 @@ class Analyzer { case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol1.Loc, diag::warn_func_effect_violation) - << effectName << Viol1.diagnosticSelectIndex(); + << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) + << Viol1.diagnosticSelectIndex(); + MaybeAddCtorContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) - << effectName; + << effectName << SiteDescIndex(CInfo.CDecl, &Viol1); + MaybeAddCtorContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; @@ -687,7 +747,9 @@ class Analyzer { std::string CalleeName = CalleeInfo.name(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) - << effectName << CalleeName; + << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) + << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName; + MaybeAddCtorContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); // Emit notes explaining the transitive chain of inferences: Why isn't @@ -704,16 +766,17 @@ class Analyzer { CallableType CType = CalleeInfo.type(); if (CType == CallableType::Virtual) - S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) - << effectName; + 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_func_ptr) - << effectName; + 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) - << effectName + << SiteDescIndex(CInfo.CDecl, nullptr) << effectName << FunctionEffect(Viol1.Effect.oppositeKind()).name(); else if (const FunctionDecl *FD = dyn_cast(Callee); FD == nullptr || FD->getBuiltinID() == 0) { @@ -736,11 +799,12 @@ class Analyzer { break; case ViolationID::DeclDisallowsInference: S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) - << effectName << Viol2.CalleeEffectPreventingInference.name(); + << SiteDescIndex(CalleeInfo.CDecl, nullptr) << effectName + << Viol2.CalleeEffectPreventingInference.name(); break; case ViolationID::CallsExprWithoutEffect: - S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr) - << effectName; + S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect) + << Indirect_FunctionPtr << effectName; break; case ViolationID::AllocatesMemory: case ViolationID::ThrowsOrCatchesExceptions: @@ -748,12 +812,16 @@ class Analyzer { case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol2.Loc, diag::note_func_effect_violation) - << effectName << Viol2.diagnosticSelectIndex(); + << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName + << Viol2.diagnosticSelectIndex(); + MaybeAddCtorContext(CalleeInfo.CDecl, Viol2); break; case ViolationID::CallsDeclWithoutEffect: MaybeNextCallee.emplace(*Viol2.Callee); S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) - << effectName << MaybeNextCallee->name(S); + << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName + << SiteDescIndex(Viol2.Callee, nullptr) + << MaybeNextCallee->name(S); break; } MaybeAddTemplateNote(Callee); @@ -777,10 +845,12 @@ class Analyzer { // // Violations are always routed to a PendingFunctionAnalysis. struct FunctionBodyASTVisitor : RecursiveASTVisitor { + using Base = RecursiveASTVisitor; Analyzer &Outer; PendingFunctionAnalysis &CurrentFunction; CallableInfo &CurrentCaller; + ViolationSite VSite = 0; FunctionBodyASTVisitor(Analyzer &Outer, PendingFunctionAnalysis &CurrentFunction, @@ -822,10 +892,10 @@ class Analyzer { addViolation(/*inferring=*/true, Effect, VID, Loc, Callee); } - void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D, + void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID, SourceLocation Loc, const Decl *Callee = nullptr) { - CurrentFunction.checkAddViolation(Inferring, - Violation(Effect, D, Loc, Callee)); + CurrentFunction.checkAddViolation( + Inferring, Violation(Effect, VID, VSite, Loc, Callee)); } // Here we have a call to a Decl, either explicitly via a CallExpr or some @@ -836,7 +906,7 @@ class Analyzer { return; Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, - /*AssertNoFurtherInference=*/false); + /*AssertNoFurtherInference=*/false, VSite); } // FIXME: This is currently specific to the `nonblocking` and @@ -1056,6 +1126,15 @@ class Analyzer { return true; } + bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { + ViolationSite PrevVS = VSite; + if (Init->isAnyMemberInitializer()) + VSite = VSite_MemberInitializer; + bool Result = Base::TraverseConstructorInitializer(Init); + VSite = PrevVS; + return Result; + } + bool TraverseLambdaExpr(LambdaExpr *Lambda) { // 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 diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index f51ca1a855880ef..6caa3d1ad6b226f 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -69,7 +69,7 @@ void nb7() { // Make sure we verify blocks auto blk = ^() [[clang::nonblocking]] { - throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + throw 42; // expected-warning {{'nonblocking' block must not throw or catch exceptions}} }; } @@ -77,7 +77,7 @@ void nb8() { // Make sure we verify lambdas auto lambda = []() [[clang::nonblocking]] { - throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}} }; } @@ -228,54 +228,53 @@ void nb20() [[clang::nonblocking]] { struct S { int x; - S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} - S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} + S(int x) try : x(x) {} catch (...) {} // expected-note {{constructor cannot be inferred 'nonblocking' because it throws or catches exceptions}} + S(double) : x((throw 3, 3)) {} // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}} \ + expected-note {{in constructor here}} }; -int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \ + // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} -struct A { - int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} +struct A { // expected-note {{in implicit constructor here}} + int x = (throw 3, 3); // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}} }; struct B { - int y = badi(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}} + int y = badi(); // expected-note {{member initializer cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}} }; void f() [[clang::nonblocking]] { - S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}} - S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}} - A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}} - B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}} + S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}} + S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}} + A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}} + B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}} } -#if 0 -// FIXME: can we do better with default member initializers? struct T { - int x = badi(); - T() [[clang::nonblocking]] {} // Warning: this calls bad(). - T(int x) [[clang::nonblocking]] : x(x) {} // This does not. + int x = badi(); // expected-warning {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}} + T() [[clang::nonblocking]] {} // expected-note {{in constructor here}} + T(int x) [[clang::nonblocking]] : x(x) {} // OK }; -#endif // Verify traversal of implicit code paths - constructors and destructors. struct Unsafe { - static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} - static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} - Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}} - ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}} + Unsafe() { problem1(); } // expected-note {{constructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}} + ~Unsafe() { problem2(); } // expected-note {{destructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}} Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} // Delegating initializer. - Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} + Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} }; struct DerivedFromUnsafe : public Unsafe { - DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} - DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}} - ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}} + DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} + DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} + ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}} }; // Contexts where there is no function call, no diagnostic. From 6650c1fae8d1023dffb8debf42ea7f4b2b6753d9 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 20 Aug 2024 08:39:01 -0700 Subject: [PATCH 34/63] Two fixes for default arguments: - they are never attributed to the function, only its callers. - while the warning points to the expression in the declaration, add a note pointing to the caller. --- .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/lib/Sema/SemaFunctionEffects.cpp | 70 ++++++++++++++----- .../Sema/attr-nonblocking-constraints.cpp | 10 +++ 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index e60088f0703ca36..ee6072fbb38ca41 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10992,6 +10992,8 @@ def note_func_effect_from_template : Note< "in template expansion here">; def note_func_effect_in_constructor : Note< "in%select{| implicit}0 constructor here">; +def note_in_evaluating_default_argument : Note< + "in evaluating default argument here">; // spoofing nonblocking/nonallocating def warn_invalid_add_func_effects : Warning< diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index acf459ec9555c1e..9166aaa3d1ab7ba 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -41,11 +41,17 @@ enum class ViolationID : uint8_t { CallsExprWithoutEffect, }; -// Bits, describing the AST context in which a violation was found. -// If none present, the context was the function body. -using ViolationSite = unsigned; -enum { - VSite_MemberInitializer = 1, +// Information about the AST context in which a violation was found, so +// that diagnostics can point to the correct source. +struct ViolationSite { + enum class Kind : uint8_t { + Default = 0, // Function body. + MemberInitializer = 1, + DefaultArgExpr = 2 + }; + + Kind VKind = Kind::Default; + CXXDefaultArgExpr *DefaultArgExpr = nullptr; }; // Represents a violation of the rules, potentially for the entire duration of @@ -269,7 +275,7 @@ class PendingFunctionAnalysis { // Add a Violation for this effect if a caller were to // try to infer it. InferrableEffectToFirstViolation.maybeInsert(Violation( - effect, ViolationID::DeclDisallowsInference, 0, + effect, ViolationID::DeclDisallowsInference, ViolationSite{}, CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect)); } } @@ -686,7 +692,8 @@ class Analyzer { VS_MemberInitializer, }; - if (V != nullptr && V->Site == VSite_MemberInitializer) + if (V != nullptr && + V->Site.VKind == ViolationSite::Kind::MemberInitializer) return VS_MemberInitializer; if (isa(D)) return VS_Block; @@ -702,10 +709,10 @@ class Analyzer { return VS_Function; }; - // If a Violation's site is a member initializer, adds a note referring to - // the constructor which invoked it. - auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) { - if (V.Site == VSite_MemberInitializer) { + 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.VKind == ViolationSite::Kind::MemberInitializer) { unsigned ImplicitCtor = 0; if (auto *Ctor = dyn_cast(D); Ctor && Ctor->isImplicit()) @@ -713,6 +720,12 @@ class Analyzer { 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.VKind == ViolationSite::Kind::DefaultArgExpr) + S.Diag(V.Site.DefaultArgExpr->getUsedLocation(), + diag::note_in_evaluating_default_argument); }; // Top-level violations are warnings. @@ -732,13 +745,13 @@ class Analyzer { S.Diag(Viol1.Loc, diag::warn_func_effect_violation) << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) << Viol1.diagnosticSelectIndex(); - MaybeAddCtorContext(CInfo.CDecl, Viol1); + MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) << effectName << SiteDescIndex(CInfo.CDecl, &Viol1); - MaybeAddCtorContext(CInfo.CDecl, Viol1); + MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; @@ -749,7 +762,7 @@ class Analyzer { S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName; - MaybeAddCtorContext(CInfo.CDecl, Viol1); + MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); // Emit notes explaining the transitive chain of inferences: Why isn't @@ -814,7 +827,7 @@ class Analyzer { S.Diag(Viol2.Loc, diag::note_func_effect_violation) << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName << Viol2.diagnosticSelectIndex(); - MaybeAddCtorContext(CalleeInfo.CDecl, Viol2); + MaybeAddSiteContext(CalleeInfo.CDecl, Viol2); break; case ViolationID::CallsDeclWithoutEffect: MaybeNextCallee.emplace(*Viol2.Callee); @@ -850,7 +863,7 @@ class Analyzer { Analyzer &Outer; PendingFunctionAnalysis &CurrentFunction; CallableInfo &CurrentCaller; - ViolationSite VSite = 0; + ViolationSite VSite; FunctionBodyASTVisitor(Analyzer &Outer, PendingFunctionAnalysis &CurrentFunction, @@ -1129,12 +1142,35 @@ class Analyzer { bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { ViolationSite PrevVS = VSite; if (Init->isAnyMemberInitializer()) - VSite = VSite_MemberInitializer; + VSite.VKind = ViolationSite::Kind::MemberInitializer; bool Result = Base::TraverseConstructorInitializer(Init); VSite = PrevVS; return Result; } + bool TraverseCXXDefaultArgExpr(CXXDefaultArgExpr *E) { + LLVM_DEBUG(llvm::dbgs() + << "TraverseCXXDefaultArgExpr : " + << E->getUsedLocation().printToString(Outer.S.SourceMgr) + << "\n";); + + ViolationSite PrevVS = VSite; + if (VSite.VKind == ViolationSite::Kind::Default) + VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr, + .DefaultArgExpr = E}; + + bool Result = Base::TraverseCXXDefaultArgExpr(E); + VSite = PrevVS; + return Result; + } + + bool TraverseParmVarDecl(ParmVarDecl *PV) { + // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid + // incorrectly attributing default argument expressions to this function; + // they are properly attributed to callers, via a CXXDefaultArgExpr. + return Base::TraverseVarDecl(PV); + } + bool TraverseLambdaExpr(LambdaExpr *Lambda) { // 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 diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 6caa3d1ad6b226f..1e90696f00e0be2 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -257,6 +257,16 @@ struct T { T(int x) [[clang::nonblocking]] : x(x) {} // OK }; +// Default arguments +int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + +void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} +} + +void nb21() [[clang::nonblocking]] { + hasDefaultArg(); // expected-note {{in evaluating default argument here}} +} + // Verify traversal of implicit code paths - constructors and destructors. struct Unsafe { static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} From 250b80becb3769f09d2af32434e0322bfa08b83f Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 20 Aug 2024 08:39:28 -0700 Subject: [PATCH 35/63] Type.cpp: remove asserts preceding llvm_unreachable(). --- clang/lib/AST/Type.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 8d36aa15c595729..9c50b6b6087fa4a 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5159,10 +5159,9 @@ std::optional FunctionEffect::effectProhibitingInference( break; case Kind::None: - assert(0 && "effectProhibitingInference with None"); break; } - llvm_unreachable("unknown effect kind"); + llvm_unreachable("unknown effect kind or None"); } bool FunctionEffect::shouldDiagnoseFunctionCall( @@ -5185,10 +5184,9 @@ bool FunctionEffect::shouldDiagnoseFunctionCall( case Kind::Blocking: return false; case Kind::None: - assert(0 && "shouldDiagnoseFunctionCall with None"); break; } - llvm_unreachable("unknown effect kind"); + llvm_unreachable("unknown effect kind or None"); } // ===== From e8bcd9f6962a2b5c2286fccfcd1716b5b180bf47 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 22 Aug 2024 09:28:27 -0700 Subject: [PATCH 36/63] Fix pre-C++20 compile error. Test an unsafe default argument. --- clang/include/clang/AST/Type.h | 1 + clang/lib/Sema/SemaFunctionEffects.cpp | 18 +++++++++--------- .../test/Sema/attr-nonblocking-constraints.cpp | 13 ++++++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 59221dcb85eeba5..49077750744fff3 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4998,6 +4998,7 @@ class FunctionEffectKindSet { } void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; } + bool empty() const { return KindBits.none(); } bool contains(const FunctionEffect::Kind EK) const { return KindBits.test(kindToPos(EK)); } diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 9166aaa3d1ab7ba..9310171a401b0dc 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -37,6 +37,10 @@ enum class ViolationID : uint8_t { // 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, }; @@ -52,6 +56,10 @@ struct ViolationSite { Kind VKind = Kind::Default; CXXDefaultArgExpr *DefaultArgExpr = nullptr; + + ViolationSite() = default; + explicit ViolationSite(CXXDefaultArgExpr *E) + : VKind(Kind::DefaultArgExpr), DefaultArgExpr(E) {} }; // Represents a violation of the rules, potentially for the entire duration of @@ -1156,21 +1164,13 @@ class Analyzer { ViolationSite PrevVS = VSite; if (VSite.VKind == ViolationSite::Kind::Default) - VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr, - .DefaultArgExpr = E}; + VSite = ViolationSite{E}; bool Result = Base::TraverseCXXDefaultArgExpr(E); VSite = PrevVS; return Result; } - bool TraverseParmVarDecl(ParmVarDecl *PV) { - // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid - // incorrectly attributing default argument expressions to this function; - // they are properly attributed to callers, via a CXXDefaultArgExpr. - return Base::TraverseVarDecl(PV); - } - bool TraverseLambdaExpr(LambdaExpr *Lambda) { // 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 diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 1e90696f00e0be2..ca9fe199f5f6685 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -258,13 +258,20 @@ struct T { }; // Default arguments -int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} +int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \ + expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \ + expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} -void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} +void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} \ + expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}} } void nb21() [[clang::nonblocking]] { - hasDefaultArg(); // expected-note {{in evaluating default argument here}} + hasDefaultArg(); // expected-note {{in evaluating default argument here}} \ + expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}} +} + +void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} } // Verify traversal of implicit code paths - constructors and destructors. From ea7f3fc8c0b5269e430916c3f8955c694dbc9af5 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 29 Aug 2024 09:43:54 -0500 Subject: [PATCH 37/63] Review feedback: ViolationSite can use a PointerIntPair. Expand list of known builtins and make the implementation slightly less specific to nonblocking/nonallocating. --- clang/lib/Sema/SemaFunctionEffects.cpp | 140 +++++++++++++++++-------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 9310171a401b0dc..186be26f767d910 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -47,19 +47,27 @@ enum class ViolationID : uint8_t { // Information about the AST context in which a violation was found, so // that diagnostics can point to the correct source. -struct ViolationSite { +class ViolationSite { +public: enum class Kind : uint8_t { Default = 0, // Function body. MemberInitializer = 1, DefaultArgExpr = 2 }; - Kind VKind = Kind::Default; - CXXDefaultArgExpr *DefaultArgExpr = nullptr; +private: + llvm::PointerIntPair Impl; +public: ViolationSite() = default; + explicit ViolationSite(CXXDefaultArgExpr *E) - : VKind(Kind::DefaultArgExpr), DefaultArgExpr(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 @@ -119,6 +127,74 @@ static bool isNoexcept(const FunctionDecl *FD) { return false; } +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::BI__builtin_unwind_init: // ???? + // __builtin_eh_return? + // __builtin_allow_runtime_check + // va_copy + // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf + // scanf family + // coroutine intrinsics? + + 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. + 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: + + 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: + + case Builtin::ID::BIfread: + case Builtin::ID::BIfwrite: + 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 { @@ -701,7 +777,7 @@ class Analyzer { }; if (V != nullptr && - V->Site.VKind == ViolationSite::Kind::MemberInitializer) + V->Site.kind() == ViolationSite::Kind::MemberInitializer) return VS_MemberInitializer; if (isa(D)) return VS_Block; @@ -720,7 +796,7 @@ class Analyzer { 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.VKind == ViolationSite::Kind::MemberInitializer) { + if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) { unsigned ImplicitCtor = 0; if (auto *Ctor = dyn_cast(D); Ctor && Ctor->isImplicit()) @@ -731,8 +807,8 @@ class Analyzer { // 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.VKind == ViolationSite::Kind::DefaultArgExpr) - S.Diag(V.Site.DefaultArgExpr->getUsedLocation(), + else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr) + S.Diag(V.Site.defaultArgExpr()->getUsedLocation(), diag::note_in_evaluating_default_argument); }; @@ -921,43 +997,23 @@ class Analyzer { // 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(CI.CDecl); - FD && isSafeBuiltinFunction(FD)) - return; + 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; + } + } + } Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*AssertNoFurtherInference=*/false, VSite); } - // 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::BI__builtin_operator_delete: - case Builtin::ID::BI__builtin_operator_new: - case Builtin::ID::BIcalloc: - case Builtin::ID::BImalloc: - case Builtin::ID::BImemalign: - case Builtin::ID::BIrealloc: - case Builtin::ID::BIfree: - return false; - } - } - void checkIndirectCall(CallExpr *Call, QualType CalleeType) { auto *FPT = CalleeType->getAs(); // Null if FunctionType. @@ -1150,7 +1206,7 @@ class Analyzer { bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { ViolationSite PrevVS = VSite; if (Init->isAnyMemberInitializer()) - VSite.VKind = ViolationSite::Kind::MemberInitializer; + VSite.setKind(ViolationSite::Kind::MemberInitializer); bool Result = Base::TraverseConstructorInitializer(Init); VSite = PrevVS; return Result; @@ -1163,7 +1219,7 @@ class Analyzer { << "\n";); ViolationSite PrevVS = VSite; - if (VSite.VKind == ViolationSite::Kind::Default) + if (VSite.kind() == ViolationSite::Kind::Default) VSite = ViolationSite{E}; bool Result = Base::TraverseCXXDefaultArgExpr(E); From d1a39e2792c200fa7647451bd7d4a30fc9fdb815 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 29 Aug 2024 10:47:17 -0700 Subject: [PATCH 38/63] Fix: was traversing virtual base class destructors twice, exposed by new test. --- clang/lib/Sema/SemaFunctionEffects.cpp | 6 +----- clang/test/Sema/attr-nonblocking-constraints.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 186be26f767d910..d2e3f1b44ba0cb1 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1043,13 +1043,9 @@ class Analyzer { for (const FieldDecl *Field : Rec->fields()) followTypeDtor(Field->getType(), DtorLoc); - if (const auto *Class = dyn_cast(Rec)) { + if (const auto *Class = dyn_cast(Rec)) for (const CXXBaseSpecifier &Base : Class->bases()) followTypeDtor(Base.getType(), DtorLoc); - - for (const CXXBaseSpecifier &Base : Class->vbases()) - followTypeDtor(Base.getType(), DtorLoc); - } } void followTypeDtor(QualType QT, SourceLocation CallSite) { diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index ca9fe199f5f6685..2890199c6091060 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -294,6 +294,20 @@ struct DerivedFromUnsafe : public Unsafe { ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}} }; +// Virtual inheritance +struct VBase { + int *Ptr; + + VBase() { Ptr = new int; } // expected-note {{constructor cannot be inferred 'nonblocking' because it allocates or deallocates memory}} + virtual ~VBase() { delete Ptr; } // expected-note {{virtual method cannot be inferred 'nonblocking'}} +}; + +struct VDerived : virtual VBase { + VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}} + + ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'VBase::~VBase'}} +}; + // Contexts where there is no function call, no diagnostic. bool bad(); From 75365efc34f8d4255f965940b7a5e3f3aa3fd122 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 29 Aug 2024 11:03:09 -0700 Subject: [PATCH 39/63] More builtin functions. --- clang/lib/Sema/SemaFunctionEffects.cpp | 40 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index d2e3f1b44ba0cb1..423ea54ec56313b 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -63,7 +63,7 @@ class ViolationSite { explicit ViolationSite(CXXDefaultArgExpr *E) : Impl(E, Kind::DefaultArgExpr) {} - + Kind kind() const { return static_cast(Impl.getInt()); } CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); } @@ -127,6 +127,13 @@ static bool isNoexcept(const FunctionDecl *FD) { return false; } +// 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; @@ -149,14 +156,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { case Builtin::ID::BIrealloc: case Builtin::ID::BIfree: - case Builtin::ID::BI__builtin_unwind_init: // ???? - // __builtin_eh_return? - // __builtin_allow_runtime_check - // va_copy - // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf - // scanf family - // coroutine intrinsics? - case Builtin::ID::BIfopen: case Builtin::ID::BIpthread_create: case Builtin::ID::BI_Block_object_dispose: @@ -170,6 +169,7 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { 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: @@ -185,8 +185,29 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { 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; } @@ -194,7 +215,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { return Result; } - // Transitory, more extended information about a callable, which can be a // function, block, or function pointer. struct CallableInfo { From 6aadec09c3e1d2fd6b22b1e3f475433eaf81a31e Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 4 Sep 2024 11:10:21 -0700 Subject: [PATCH 40/63] Apply suggestions from code review Co-authored-by: Erich Keane --- clang/include/clang/AST/Type.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 49077750744fff3..ad3fdf425b43164 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4949,7 +4949,7 @@ class FunctionEffectKindSet { explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {} constexpr static size_t kindToPos(FunctionEffect::Kind K) { - return size_t(K); + return static_assert(K); } public: @@ -4983,7 +4983,7 @@ class FunctionEffectKindSet { } FunctionEffect operator*() const { - assert(Idx < EndBitPos); + assert(Idx < EndBitPos && "Dereference of end iterator"); return FunctionEffect(FunctionEffect::Kind(Idx)); } }; From 9b123a64f1da2ad903defcbe2395ad67779ba628 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 4 Sep 2024 13:26:36 -0700 Subject: [PATCH 41/63] Review feedback: - Add operator<< for FunctionEffect and FunctionEffectWithCondition. - Simplified dump methods for FunctionEffectsRef and FunctionEffectKindSet. - Diagnostics now say "function with 'nonblocking' attribute" (where appropriate). - Other more minor tweaks. --- clang/include/clang/AST/Type.h | 11 ++- .../clang/Basic/DiagnosticSemaKinds.td | 13 ++-- clang/include/clang/Sema/Sema.h | 4 +- clang/include/clang/Serialization/ASTReader.h | 2 +- clang/lib/AST/Type.cpp | 38 ++++----- clang/lib/Sema/SemaFunctionEffects.cpp | 16 ++-- clang/lib/Serialization/ASTReader.cpp | 21 ++--- .../Sema/attr-nonblocking-constraints-ms.cpp | 4 +- .../Sema/attr-nonblocking-constraints.cpp | 78 +++++++++---------- 9 files changed, 94 insertions(+), 93 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index ad3fdf425b43164..e86a628cbc9e7e8 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4777,6 +4777,12 @@ class FunctionEffect { /// The description printed in diagnostics, e.g. 'nonblocking'. StringRef name() const; + friend raw_ostream &operator<<(raw_ostream &OS, + const FunctionEffect &Effect) { + OS << Effect.name(); + return OS; + } + /// Determine whether the effect is allowed to be inferred on the callee, /// which is either a FunctionDecl or BlockDecl. If the returned optional /// is empty, inference is permitted; otherwise it holds the effect which @@ -4834,6 +4840,9 @@ struct FunctionEffectWithCondition { /// Return a textual description of the effect, and its condition, if any. std::string description() const; + + friend raw_ostream &operator<<(raw_ostream &OS, + const FunctionEffectWithCondition &CFE); }; /// Support iteration in parallel through a pair of FunctionEffect and @@ -4949,7 +4958,7 @@ class FunctionEffectKindSet { explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {} constexpr static size_t kindToPos(FunctionEffect::Kind K) { - return static_assert(K); + return static_cast(K); } public: diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ee6072fbb38ca41..efdfd53616cc9de 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10951,7 +10951,8 @@ def warn_imp_cast_drops_unaligned : Warning< // Function effects def warn_func_effect_violation : Warning< - "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " + "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 " + "with '%1' attribute " "must not %select{allocate or deallocate memory|throw or catch exceptions|" "have static local variables|use thread-local variables|access ObjC methods or properties}2">, InGroup; @@ -10962,8 +10963,9 @@ def note_func_effect_violation : Note< "has a static local variable|uses a thread-local variable|" "accesses an ObjC method or property}2">; def warn_func_effect_calls_func_without_effect : Warning< - "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " - "must not call non-'%0' " + "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 " + "with '%1' attribute " + "must not call non-'%1' " "%select{function|constructor|destructor|lambda|block}2 " "'%3'">, InGroup; @@ -10973,7 +10975,8 @@ def note_func_effect_calls_func_without_effect : Note< "%select{function|constructor|destructor|lambda|block}2 " "'%3'">; def warn_func_effect_calls_expr_without_effect : Warning< - "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 " + "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 " + "with '%1' attribute " "must not call non-'%0' expression">, InGroup; def note_func_effect_call_extern : Note< @@ -10984,7 +10987,7 @@ def note_func_effect_call_disallows_inference : Note< def note_func_effect_call_indirect : Note< "%select{virtual method|function pointer}0 cannot be inferred '%1'">; def warn_perf_constraint_implies_noexcept : Warning< - "'%0' function should be declared noexcept">, + "function with '%0' attribute should be declared noexcept">, InGroup; // FIXME: It would be nice if we could provide fuller template expansion notes. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e5f9c3c7dbb09b7..370985f7ce4bbac 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15053,8 +15053,8 @@ class Sema final : public SemaBase { FunctionEffect::Kind EffectKind; Kind DiffKind; - FunctionEffectWithCondition Old; // invalid when Added. - FunctionEffectWithCondition New; // invalid when Removed. + FunctionEffectWithCondition Old; // invalid when Kind is Added. + FunctionEffectWithCondition New; // invalid when Kind is Removed. StringRef effectName() const { if (Old.Effect.kind() != FunctionEffect::Kind::None) diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h index 67032b684f4df03..46369b39cc45593 100644 --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -967,7 +967,7 @@ class ASTReader llvm::SmallSetVector DeclsToCheckForDeferredDiags; /// The IDs of all decls with function effects to be checked. - SmallVector DeclsWithEffectsToVerify; + SmallVector DeclsWithEffectsToVerify; private: struct ImportedSubmodule { diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 9c50b6b6087fa4a..05b5e2c9b494c92 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5290,21 +5290,24 @@ FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS, return Combined; } +namespace clang { + +raw_ostream &operator<<(raw_ostream &OS, + const FunctionEffectWithCondition &CFE) { + OS << CFE.Effect.name(); + if (Expr *E = CFE.Cond.getCondition()) { + OS << '('; + E->dump(); + OS << ')'; + } + return OS; +} + +} // namespace clang + LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const { OS << "Effects{"; - bool First = true; - for (const auto &CFE : *this) { - if (!First) - OS << ", "; - else - First = false; - OS << CFE.Effect.name(); - if (Expr *E = CFE.Cond.getCondition()) { - OS << '('; - E->dump(); - OS << ')'; - } - } + llvm::interleaveComma(*this, OS); OS << "}"; } @@ -5314,14 +5317,7 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const { LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const { OS << "Effects{"; - bool First = true; - for (const auto &Effect : *this) { - if (!First) - OS << ", "; - else - First = false; - OS << Effect.name(); - } + llvm::interleaveComma(*this, OS); OS << "}"; } diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 423ea54ec56313b..c1df87bcacea01c 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -90,10 +90,9 @@ struct Violation { Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS, SourceLocation Loc, const Decl *Callee = nullptr, std::optional CalleeEffect = std::nullopt) - : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) { - if (CalleeEffect) - CalleeEffectPreventingInference = *CalleeEffect; - } + : Effect(Effect), CalleeEffectPreventingInference( + CalleeEffect.value_or(FunctionEffect())), + ID(ID), Site(VS), Loc(Loc), Callee(Callee) {} unsigned diagnosticSelectIndex() const { return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex); @@ -102,7 +101,7 @@ struct Violation { enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; enum class CallableType : uint8_t { - // Unknown: probably function pointer + // Unknown: probably function pointer. Unknown, Function, Virtual, @@ -847,14 +846,14 @@ class Analyzer { case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol1.Loc, diag::warn_func_effect_violation) - << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) + << SiteDescIndex(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) - << effectName << SiteDescIndex(CInfo.CDecl, &Viol1); + << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; @@ -864,7 +863,7 @@ class Analyzer { std::string CalleeName = CalleeInfo.name(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) - << effectName << SiteDescIndex(CInfo.CDecl, &Viol1) + << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); @@ -1362,7 +1361,6 @@ void Sema::diagnoseFunctionEffectMergeConflicts( } } -// Should only be called when getFunctionEffects() returns a non-empty set. // Decl should be a FunctionDecl or BlockDecl. void Sema::maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 306bbc097a7df4c..2a87631a603ab26 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -8397,19 +8397,14 @@ void ASTReader::InitializeSema(Sema &S) { NewOverrides.applyOverrides(SemaObj->getLangOpts()); } - if (!DeclsWithEffectsToVerify.empty()) { - for (GlobalDeclID ID : DeclsWithEffectsToVerify) { - Decl *D = GetDecl(ID); - FunctionEffectsRef FX; - if (auto *FD = dyn_cast(D)) - FX = FD->getFunctionEffects(); - else if (auto *BD = dyn_cast(D)) - FX = BD->getFunctionEffects(); - if (!FX.empty()) - SemaObj->addDeclWithEffects(D, FX); - } - DeclsWithEffectsToVerify.clear(); - } + for (GlobalDeclID ID : DeclsWithEffectsToVerify) { + Decl *D = GetDecl(ID); + if (auto *FD = dyn_cast(D)) + SemaObj->addDeclWithEffects(FD, FD->getFunctionEffects()); + else if (auto *BD = dyn_cast(D)) + SemaObj->addDeclWithEffects(BD, BD->getFunctionEffects()); + } + DeclsWithEffectsToVerify.clear(); SemaObj->OpenCLFeatures = OpenCLExtensions; diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp index d2c25da462c4048..b81d3d475e47fca 100644 --- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp @@ -4,7 +4,7 @@ // These need '-fms-extensions' (and maybe '-fdeclspec') void f1() [[clang::nonblocking]] { - __try {} __except (1) {} // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + __try {} __except (1) {} // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} } struct S { @@ -20,7 +20,7 @@ struct S { void f2() [[clang::nonblocking]] { S a; - a.x; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}} + a.x; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'S::get_x'}} a.nb; a.nb2; } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 2890199c6091060..9ae30a39ff772cc 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -9,21 +9,21 @@ void nb1() [[clang::nonblocking]] { - int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} - delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} + int *pInt = new int; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}} + delete pInt; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}} } void nb2() [[clang::nonblocking]] { - static int global; // expected-warning {{'nonblocking' function must not have static local variables}} + static int global; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}} } void nb3() [[clang::nonblocking]] { try { - throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} } - catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} } } @@ -33,7 +33,7 @@ void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblo void nb4() [[clang::nonblocking]] { nb4_inline(); // OK - nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} } @@ -44,7 +44,7 @@ struct HasVirtual { void nb5() [[clang::nonblocking]] { HasVirtual hv; - hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + hv.unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} } void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} @@ -55,21 +55,21 @@ void nb6_transitively_unsafe() void nb6() [[clang::nonblocking]] { - nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb6_transitively_unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} } thread_local int tl_var{ 42 }; bool tl_test() [[clang::nonblocking]] { - return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}} + return tl_var > 0; // expected-warning {{function with 'nonblocking' attribute must not use thread-local variables}} } void nb7() { // Make sure we verify blocks auto blk = ^() [[clang::nonblocking]] { - throw 42; // expected-warning {{'nonblocking' block must not throw or catch exceptions}} + throw 42; // expected-warning {{block with 'nonblocking' attribute must not throw or catch exceptions}} }; } @@ -77,7 +77,7 @@ void nb8() { // Make sure we verify lambdas auto lambda = []() [[clang::nonblocking]] { - throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}} + throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}} }; } @@ -92,7 +92,7 @@ void nb8a() [[clang::nonblocking]] void nb8b() [[clang::nonblocking]] { // An unsafe lambda capture makes the outer function unsafe. - auto unsafeCapture = [foo = new int]() { // expected-warning {{'nonblocking' function must not allocate or deallocate memory}} + auto unsafeCapture = [foo = new int]() { // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}} delete foo; }; } @@ -109,7 +109,7 @@ void nb8c() struct Adder { static T add_explicit(T x, T y) [[clang::nonblocking]] { - return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + return x + y; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} } static T add_implicit(T x, T y) { @@ -140,7 +140,7 @@ void nb9() [[clang::nonblocking]] Adder::add_implicit(1, 2); Adder::add_explicit({}, {}); // expected-note {{in template expansion here}} - Adder::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \ + Adder::add_implicit({}, {}); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \ expected-note {{in template expansion here}} } @@ -149,7 +149,7 @@ void nb10( void (*fp2)() [[clang::nonblocking]] ) [[clang::nonblocking]] { - fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} fp2(); } @@ -166,19 +166,19 @@ struct ComputedNB { void nb11() [[clang::nonblocking]] { - nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} - nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + nb11_no_inference_1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} + nb11_no_inference_2(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} ComputedNB CNB_true; CNB_true.method(); ComputedNB CNB_false; - CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} + CNB_false.method(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} } // Verify that when attached to a redeclaration, the attribute successfully attaches. void nb12() { - static int x; // expected-warning {{'nonblocking' function must not have static local variables}} + static int x; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}} } void nb12() [[clang::nonblocking]]; void nb13() [[clang::nonblocking]] { nb12(); } @@ -212,18 +212,18 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] // Builtin functions void nb19() [[clang::nonblocking]] { __builtin_assume(1); - void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}} - __builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}} + void *ptr = __builtin_malloc(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_malloc'}} + __builtin_free(ptr); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_free'}} - void *p2 = __builtin_operator_new(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}} - __builtin_operator_delete(p2); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_delete'}} + void *p2 = __builtin_operator_new(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_new'}} + __builtin_operator_delete(p2); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_delete'}} } // Function try-block void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}} void nb20() [[clang::nonblocking]] { - catches(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'catches'}} + catches(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}} } struct S { @@ -245,14 +245,14 @@ struct B { }; void f() [[clang::nonblocking]] { - S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}} - S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}} - A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}} - B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}} + S s1(3); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}} + S s2(3.0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}} + A a; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'A::A'}} + B b; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'B::B'}} } struct T { - int x = badi(); // expected-warning {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}} + int x = badi(); // expected-warning {{member initializer of constructor with 'nonblocking' attribute must not call non-'nonblocking' function 'badi'}} T() [[clang::nonblocking]] {} // expected-note {{in constructor here}} T(int x) [[clang::nonblocking]] : x(x) {} // OK }; @@ -262,16 +262,16 @@ int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonbl expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \ expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} -void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} \ +void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}} \ expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}} } void nb21() [[clang::nonblocking]] { hasDefaultArg(); // expected-note {{in evaluating default argument here}} \ - expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}} + expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}} } -void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} +void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}} } // Verify traversal of implicit code paths - constructors and destructors. @@ -285,13 +285,13 @@ struct Unsafe { Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} // Delegating initializer. - Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} + Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} }; struct DerivedFromUnsafe : public Unsafe { - DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} - DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} - ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}} + DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} + DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} + ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}} }; // Virtual inheritance @@ -303,9 +303,9 @@ struct VBase { }; struct VDerived : virtual VBase { - VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}} + VDerived() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'VBase::VBase'}} - ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'VBase::~VBase'}} + ~VDerived() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'VBase::~VBase'}} }; // Contexts where there is no function call, no diagnostic. @@ -329,6 +329,6 @@ void g() [[clang::nonblocking]] { // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" -void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}} +void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}} { } From ba57bfbde08716c353110516bc0c9b78a7c2c95c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 4 Sep 2024 16:43:35 -0700 Subject: [PATCH 42/63] Fix: Don't try to follow a deleted destructor. (Happens with a std::optional, which contains a union with a deleted destructor. Exposed by a recent fix which wasn't following the complete chain of destructors.) --- clang/lib/Sema/SemaFunctionEffects.cpp | 3 ++- .../Sema/attr-nonblocking-constraints.cpp | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index c1df87bcacea01c..36bc20d52aeeabd 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1077,7 +1077,8 @@ class Analyzer { if (Ty->isRecordType()) { if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { - if (CXXDestructorDecl *Dtor = Class->getDestructor()) { + if (CXXDestructorDecl *Dtor = Class->getDestructor(); + Dtor && !Dtor->isDeleted()) { CallableInfo CI(*Dtor); followCall(CI, CallSite); } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 9ae30a39ff772cc..9838d842abcafc7 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -294,6 +294,29 @@ struct DerivedFromUnsafe : public Unsafe { ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}} }; +// Don't try to follow a deleted destructor, as with std::optional. +struct HasDtor { + ~HasDtor() {} +}; + +template +struct Optional { + union { + char __null_state_; + T __val_; + }; + bool engaged = false; + + ~Optional() { + if (engaged) + __val_.~T(); + } +}; + +void nb_opt() [[clang::nonblocking]] { + Optional x; +} + // Virtual inheritance struct VBase { int *Ptr; From 54feb05e7e697de5a98089693d1a6b3cabb0c37f Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 4 Sep 2024 17:33:42 -0700 Subject: [PATCH 43/63] Fix test broken by rewording of warnings. --- clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index 262d647142d548b..0ff72d3d6d19ded 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -8,19 +8,19 @@ - (void)method; @end void nb1(OCClass *oc) [[clang::nonblocking]] { - [oc method]; // expected-warning {{'nonblocking' function must not access ObjC methods or properties}} + [oc method]; // expected-warning {{function with 'nonblocking' attribute must not access ObjC methods or properties}} } void nb2(OCClass *oc) { [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}} } void nb3(OCClass *oc) [[clang::nonblocking]] { - nb2(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}} + nb2(oc); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'nb2'}} } void nb4() [[clang::nonblocking]] { @try { - @throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + @throw @"foo"; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} } - @catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}} + @catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} } } From 93cb74ea642cc231d551ed715e758fe63bd42707 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 5 Sep 2024 06:55:26 -0700 Subject: [PATCH 44/63] Fix: need to ignore concept requirements. --- clang/lib/Sema/SemaFunctionEffects.cpp | 5 +++++ .../test/Sema/attr-nonblocking-constraints.cpp | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 36bc20d52aeeabd..b65281ca9df2f71 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1289,6 +1289,11 @@ class Analyzer { bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return true; } bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; } + + // Skip concept requirements since they don't generate code. + bool TraverseConceptRequirement(concepts::Requirement *R) { + return true; + } }; }; diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 9838d842abcafc7..2a491a16dc723c2 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -348,6 +348,24 @@ void g() [[clang::nonblocking]] { #pragma clang diagnostic pop } +// Make sure we are skipping concept requirements -- they can trigger an unexpected +// warning involving use of a function pointer (e.g. std::reverse_iterator::operator== +struct HasFoo { int foo() const { return 0; } }; + +template +inline bool compare(const A& a, const B& b) + requires requires { + a.foo(); + } +{ + return a.foo() == b.foo(); +} + +void nb25() [[clang::nonblocking]] { + HasFoo a, b; + compare(a, b); +} + // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" From d583c85fdd19028075b103481d2376c1b2ee6505 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 5 Sep 2024 07:12:22 -0700 Subject: [PATCH 45/63] clang-format --- clang/lib/Sema/SemaFunctionEffects.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index b65281ca9df2f71..cc140386ed26f95 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1291,9 +1291,7 @@ class Analyzer { bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; } // Skip concept requirements since they don't generate code. - bool TraverseConceptRequirement(concepts::Requirement *R) { - return true; - } + bool TraverseConceptRequirement(concepts::Requirement *R) { return true; } }; }; From cda1a9c520f01a9d51f782fb5a589c8f7686cd89 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sat, 7 Sep 2024 06:15:39 -0700 Subject: [PATCH 46/63] Make the FunctionEffects and PerfConstraintImpliesNoexcept diagnostics disabled by default, but enabled with -Wall. --- clang/include/clang/Basic/DiagnosticGroups.td | 13 +++++++------ clang/include/clang/Basic/DiagnosticSemaKinds.td | 16 ++++++++-------- .../Sema/attr-nonblocking-constraints-ms.cpp | 1 + clang/test/Sema/attr-nonblocking-constraints.cpp | 1 + clang/test/Sema/attr-nonblocking-sema.cpp | 2 ++ 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 55d9442a939dae7..5f79d2dad8cfd7a 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1128,12 +1128,18 @@ def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">; // Uniqueness Analysis warnings def Consumed : DiagGroup<"consumed">; +// Warnings and notes related to the function effects system underlying +// the nonblocking and nonallocating attributes. +def FunctionEffects : DiagGroup<"function-effects">; +def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; + // Note that putting warnings in -Wall will not disable them by default. If a // warning should be active _only_ when -Wall is passed in, mark it as // DefaultIgnore in addition to putting it here. def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation, PackedNonPod, - VLACxxExtension]>; + VLACxxExtension, FunctionEffects, + PerfConstraintImpliesNoexcept]>; // Warnings that should be in clang-cl /w4. def : DiagGroup<"CL4", [All, Extra]>; @@ -1554,11 +1560,6 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">; def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">; def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>; -// Warnings and notes related to the function effects system underlying -// the nonblocking and nonallocating attributes. -def FunctionEffects : DiagGroup<"function-effects">; -def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; - // Warnings and notes InstallAPI verification. def InstallAPIViolation : DiagGroup<"installapi-violation">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index efdfd53616cc9de..9f5e8aa5f6dc8bf 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10955,7 +10955,7 @@ def warn_func_effect_violation : Warning< "with '%1' attribute " "must not %select{allocate or deallocate memory|throw or catch exceptions|" "have static local variables|use thread-local variables|access ObjC methods or properties}2">, - InGroup; + InGroup, DefaultIgnore; def note_func_effect_violation : Note< "%select{function|constructor|destructor|lambda|block|member initializer}0 " "cannot be inferred '%1' because it " @@ -10968,7 +10968,7 @@ def warn_func_effect_calls_func_without_effect : Warning< "must not call non-'%1' " "%select{function|constructor|destructor|lambda|block}2 " "'%3'">, - InGroup; + InGroup, DefaultIgnore; def note_func_effect_calls_func_without_effect : Note< "%select{function|constructor|destructor|lambda|block|member initializer}0 " "cannot be inferred '%1' because it calls non-'%1' " @@ -10978,7 +10978,7 @@ def warn_func_effect_calls_expr_without_effect : Warning< "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 " "with '%1' attribute " "must not call non-'%0' expression">, - InGroup; + InGroup, DefaultIgnore; def note_func_effect_call_extern : Note< "declaration cannot be inferred '%0' because it has no definition in this translation unit">; def note_func_effect_call_disallows_inference : Note< @@ -10988,7 +10988,7 @@ def note_func_effect_call_indirect : Note< "%select{virtual method|function pointer}0 cannot be inferred '%1'">; def warn_perf_constraint_implies_noexcept : Warning< "function with '%0' attribute should be declared noexcept">, - InGroup; + InGroup, DefaultIgnore; // FIXME: It would be nice if we could provide fuller template expansion notes. def note_func_effect_from_template : Note< @@ -11001,16 +11001,16 @@ def note_in_evaluating_default_argument : Note< // spoofing nonblocking/nonallocating def warn_invalid_add_func_effects : Warning< "attribute '%0' should not be added via type conversion">, - InGroup; + InGroup, DefaultIgnore; def warn_mismatched_func_effect_override : Warning< "attribute '%0' on overriding function does not match base declaration">, - InGroup; + InGroup, DefaultIgnore; def warn_mismatched_func_effect_redeclaration : Warning< "attribute '%0' on function does not match previous declaration">, - InGroup; + InGroup, DefaultIgnore; def warn_conflicting_func_effects : Warning< "effects conflict when merging declarations; kept '%0', discarded '%1'">, - InGroup; + InGroup, DefaultIgnore; def err_func_with_effects_no_prototype : Error< "'%0' function must have a prototype">; diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp index b81d3d475e47fca..5bf38dceedaed70 100644 --- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify %s +#pragma clang diagnostic warning "-Wfunction-effects" #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // These need '-fms-extensions' (and maybe '-fdeclspec') diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 2a491a16dc723c2..c3bf05169e3247e 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -2,6 +2,7 @@ // These are in a separate file because errors (e.g. incompatible attributes) currently prevent // the FXAnalysis pass from running at all. +#pragma clang diagnostic warning "-Wfunction-effects" // This diagnostic is re-enabled and exercised in isolation later in this file. #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp index 38bf2ac8f8a4cc7..157fba79075fae5 100644 --- a/clang/test/Sema/attr-nonblocking-sema.cpp +++ b/clang/test/Sema/attr-nonblocking-sema.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s // RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s +#pragma clang diagnostic warning "-Wfunction-effects" + #if !__has_attribute(nonblocking) #error "the 'nonblocking' attribute is not available" #endif From 9c971c6e639039833d88446c5cc68064419fbcaa Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Sat, 7 Sep 2024 06:28:02 -0700 Subject: [PATCH 47/63] Add a release note. --- clang/docs/ReleaseNotes.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index ffdd063ec99037a..c8462acff45fa91 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -387,6 +387,14 @@ New features if class of allocation and deallocation function mismatches. `Documentation `__. +- Function effects (the ``nonblocking`` and ``nonallocating`` "performance constraint" attributes) + are now verified. Functions with the attribute are checked for language constructs which allocate + memory or block, such as using exceptions, static local or thread-local variables, or Objective-C + methods or properties. Implicit and explicit function calls are also checked. Called functions + must either have the required attribute, or have inline implementations which can be themselves + verified. The warnings are controlled by ``-Wfunction-effects``, which is now + disabled by default. + Crash and bug fixes ^^^^^^^^^^^^^^^^^^^ From 9bfbe12976b013bcc0c11343b699f1e2aeaed6b0 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Mon, 9 Sep 2024 07:57:53 -0700 Subject: [PATCH 48/63] Fix botched format string in warn_func_effect_calls_expr_without_effect --- clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/test/Sema/attr-nonblocking-constraints.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ec2906ffe086760..c071991e2775397 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10969,7 +10969,7 @@ def note_func_effect_calls_func_without_effect : Note< def warn_func_effect_calls_expr_without_effect : Warning< "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 " "with '%1' attribute " - "must not call non-'%0' expression">, + "must not call non-'%1' expression">, InGroup, DefaultIgnore; def note_func_effect_call_extern : Note< "declaration cannot be inferred '%0' because it has no definition in this translation unit">; diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index c3bf05169e3247e..f1ac518389150bc 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -152,6 +152,9 @@ void nb10( { fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} fp2(); + + // When there's a cast, there's a separate diagnostic. + static_cast(fp1)(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' expression}} } // Interactions with nonblocking(false) From f06909b288dd0cd97e6353cc9c37b33b8a67e4e7 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 17 Sep 2024 09:30:36 -0700 Subject: [PATCH 49/63] Fix test failures introduced by last commit. --- clang/test/Misc/warning-wall.c | 2 ++ clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 1 + 2 files changed, 3 insertions(+) diff --git a/clang/test/Misc/warning-wall.c b/clang/test/Misc/warning-wall.c index 4909ab034ef30aa..b3b4b9ea8e01b5f 100644 --- a/clang/test/Misc/warning-wall.c +++ b/clang/test/Misc/warning-wall.c @@ -108,5 +108,7 @@ CHECK-NEXT: -Wmisleading-indentation CHECK-NEXT: -Wpacked-non-pod CHECK-NEXT: -Wvla-cxx-extension CHECK-NEXT: -Wvla-extension-static-assert +CHECK-NEXT: -Wfunction-effects +CHECK-NEXT: -Wperf-constraint-implies-noexcept CHECK-NOT:-W diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index 0ff72d3d6d19ded..f3fa984ffd9b705 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify %s +#pragma clang diagnostic warning "-Wfunction-effects" #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // Objective-C From adcfd14962afc1a76d8505a6170aa0983a2f4edd Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 18 Sep 2024 07:31:16 -0700 Subject: [PATCH 50/63] If a function is noexcept and noreturn, exempt it from effect analysis. --- clang/lib/Sema/SemaFunctionEffects.cpp | 4 ++++ clang/test/Sema/attr-nonblocking-constraints.cpp | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index cc140386ed26f95..25cf88dec680a62 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1027,6 +1027,10 @@ class Analyzer { return; } } + // If the callee is both noreturn and noexcept, it presumably + // terminates. Ignore it for the purposes of effect analysis. + if (FD->isNoReturn() && isNoexcept(FD)) + return; } Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index f1ac518389150bc..9318a43912b8435 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -370,6 +370,13 @@ void nb25() [[clang::nonblocking]] { compare(a, b); } +// If the callee is both noreturn and noexcept, it presumably terminates. +// Ignore it for the purposes of effect analysis. +[[noreturn]] void abort_wrapper() noexcept; + +void nb26() [[clang::nonblocking]] { + abort_wrapper(); // no diagnostic +} // --- nonblocking implies noexcept --- #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept" From 17320b84b1275f31c1d7c855865a2761fa1d9dbe Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Mon, 23 Sep 2024 11:32:08 -0700 Subject: [PATCH 51/63] Remove the new diagnostics from `-Wall`. Fix a couple of sentences in AttrDocs.td, to track changes in this PR. --- clang/include/clang/Basic/AttrDocs.td | 4 ++-- clang/include/clang/Basic/DiagnosticGroups.td | 13 ++++++------- clang/test/Misc/warning-wall.c | 2 -- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index ef077db298831f2..e31cf01357f506d 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -8367,9 +8367,9 @@ compiler warnings: - A redeclaration of a ``nonblocking`` or ``nonallocating`` function must also be declared with the same attribute (or a stronger one). A redeclaration may add an attribute. -The warnings are controlled by ``-Wfunction-effects``, which is enabled by default. +The warnings are controlled by ``-Wfunction-effects``, which is disabled by default. -In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating`` +The compiler also diagnoses function calls from ``nonblocking`` and ``nonallocating`` functions to other functions which lack the appropriate attribute. }]; } diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 851ad601a0b835d..46972d4244cdb13 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1128,18 +1128,12 @@ def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">; // Uniqueness Analysis warnings def Consumed : DiagGroup<"consumed">; -// Warnings and notes related to the function effects system underlying -// the nonblocking and nonallocating attributes. -def FunctionEffects : DiagGroup<"function-effects">; -def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; - // Note that putting warnings in -Wall will not disable them by default. If a // warning should be active _only_ when -Wall is passed in, mark it as // DefaultIgnore in addition to putting it here. def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation, PackedNonPod, - VLACxxExtension, FunctionEffects, - PerfConstraintImpliesNoexcept]>; + VLACxxExtension]>; // Warnings that should be in clang-cl /w4. def : DiagGroup<"CL4", [All, Extra]>; @@ -1567,6 +1561,11 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container"> def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">; def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>; +// Warnings and notes related to the function effects system underlying +// the nonblocking and nonallocating attributes. +def FunctionEffects : DiagGroup<"function-effects">; +def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; + // Warnings and notes InstallAPI verification. def InstallAPIViolation : DiagGroup<"installapi-violation">; diff --git a/clang/test/Misc/warning-wall.c b/clang/test/Misc/warning-wall.c index b3b4b9ea8e01b5f..4909ab034ef30aa 100644 --- a/clang/test/Misc/warning-wall.c +++ b/clang/test/Misc/warning-wall.c @@ -108,7 +108,5 @@ CHECK-NEXT: -Wmisleading-indentation CHECK-NEXT: -Wpacked-non-pod CHECK-NEXT: -Wvla-cxx-extension CHECK-NEXT: -Wvla-extension-static-assert -CHECK-NEXT: -Wfunction-effects -CHECK-NEXT: -Wperf-constraint-implies-noexcept CHECK-NOT:-W From 909d7ff0a468c80d94315d1a62ebab3bfeafc2e1 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Mon, 23 Sep 2024 15:17:45 -0700 Subject: [PATCH 52/63] Apply suggestions from code review Co-authored-by: Sirraide --- clang/docs/ReleaseNotes.rst | 7 +------ clang/include/clang/Sema/Sema.h | 4 ++-- clang/lib/Sema/SemaFunctionEffects.cpp | 10 ++++------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 1641395eb9a66fd..bd2cd688a9d9de9 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -513,12 +513,7 @@ New features `Documentation `__. - Function effects (the ``nonblocking`` and ``nonallocating`` "performance constraint" attributes) - are now verified. Functions with the attribute are checked for language constructs which allocate - memory or block, such as using exceptions, static local or thread-local variables, or Objective-C - methods or properties. Implicit and explicit function calls are also checked. Called functions - must either have the required attribute, or have inline implementations which can be themselves - verified. The warnings are controlled by ``-Wfunction-effects``, which is now - disabled by default. + are now verified. Crash and bug fixes ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 3854c6f65d039b2..94ae608d205fe8a 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15028,8 +15028,8 @@ class Sema final : public SemaBase { FunctionEffect::Kind EffectKind; Kind DiffKind; - FunctionEffectWithCondition Old; // invalid when Kind is Added. - FunctionEffectWithCondition New; // invalid when Kind is Removed. + FunctionEffectWithCondition Old; // Invalid when 'Kind' is 'Added'. + FunctionEffectWithCondition New; // Invalid when 'Kind' is 'Removed'. StringRef effectName() const { if (Old.Effect.kind() != FunctionEffect::Kind::None) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 25cf88dec680a62..8d7fbacc64a2f95 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -206,7 +206,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { case Builtin::ID::BIvscanf: case Builtin::ID::BIvfscanf: case Builtin::ID::BIvsscanf: - Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking)); break; } @@ -318,7 +317,7 @@ class EffectToViolationMap { return nullptr; auto *Iter = - std::find_if(Impl->begin(), Impl->end(), + llvm::find_if(*Impl, [&](const auto &Item) { return Item.Effect == Key; }); return Iter != Impl->end() ? &*Iter : nullptr; } @@ -417,8 +416,7 @@ class PendingFunctionAnalysis { ArrayRef getSortedViolationsForExplicitEffects(SourceManager &SM) { if (!ViolationsForExplicitEffects.empty()) - std::sort(ViolationsForExplicitEffects.begin(), - ViolationsForExplicitEffects.end(), + llvm::sort(ViolationsForExplicitEffects, [&SM](const Violation &LHS, const Violation &RHS) { return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); }); @@ -527,9 +525,9 @@ class Analyzer { CallableInfo CI(*item.first); const auto AP = item.second; OS << item.first << " " << CI.name(SemaRef) << " : "; - if (AP.isNull()) + if (AP.isNull()) { OS << "null\n"; - else if (isa(AP)) { + } else if (isa(AP)) { auto *CFA = AP.get(); OS << CFA << " "; CFA->dump(OS); From 9367103393d8ffa8535cbc1a1a8f0bb6f57bf626 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Mon, 23 Sep 2024 15:28:59 -0700 Subject: [PATCH 53/63] Review feedback: - Make FunctionEffectKindSet only as large as necessary. - Rename FunctionEffectDifferences -> FunctionEffectDiffVector. - Rename CallableInfo::name() -> getNameForDiagnostic(). - Use ranges with algorithms. --- clang/include/clang/AST/Type.h | 22 +++++++--- clang/include/clang/Sema/Sema.h | 6 +-- clang/lib/Sema/Sema.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 2 +- clang/lib/Sema/SemaDeclCXX.cpp | 2 +- clang/lib/Sema/SemaFunctionEffects.cpp | 43 ++++++++++--------- .../Sema/attr-nonblocking-constraints-ms.cpp | 3 +- .../Sema/attr-nonblocking-constraints.cpp | 3 +- clang/test/Sema/attr-nonblocking-sema.cpp | 8 ++-- .../attr-nonblocking-constraints.mm | 3 +- 10 files changed, 50 insertions(+), 44 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index fe2d0abc922d246..e7bdef68a1ee172 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4719,8 +4719,9 @@ class FunctionEffect { NonBlocking = 1, NonAllocating = 2, Blocking = 3, - Allocating = 4 + Allocating = 4, }; + constexpr static size_t KindMaximum = 4; /// Flags describing some behaviors of the effect. using Flags = unsigned; @@ -4958,20 +4959,23 @@ class FunctionEffectsRef { /// A mutable set of FunctionEffect::Kind. class FunctionEffectKindSet { // For now this only needs to be a bitmap. - constexpr static size_t EndBitPos = 8; + constexpr static size_t EndBitPos = FunctionEffect::KindMaximum; using KindBitsT = std::bitset; KindBitsT KindBits{}; explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {} + // Functions to translate between an effect kind, starting at 1, and a + // position in the bitset. + constexpr static size_t kindToPos(FunctionEffect::Kind K) { - return static_cast(K); + return static_cast(K) - 1; } -public: - FunctionEffectKindSet() = default; - explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); } + constexpr static FunctionEffect::Kind posToKind(size_t Pos) { + return static_cast(Pos + 1); + } // Iterates through the bits which are set. class iterator { @@ -5001,10 +5005,14 @@ class FunctionEffectKindSet { FunctionEffect operator*() const { assert(Idx < EndBitPos && "Dereference of end iterator"); - return FunctionEffect(FunctionEffect::Kind(Idx)); + return FunctionEffect(posToKind(Idx)); } }; +public: + FunctionEffectKindSet() = default; + explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); } + iterator begin() const { return iterator(*this, 0); } iterator end() const { return iterator(*this, EndBitPos); } diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 94ae608d205fe8a..d9d66f551c831e6 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15066,10 +15066,10 @@ class Sema final : public SemaBase { const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const; }; - struct FunctionEffectDifferences : public SmallVector { + struct FunctionEffectDiffVector : public SmallVector { /// Caller should short-circuit by checking for equality first. - FunctionEffectDifferences(const FunctionEffectsRef &Old, - const FunctionEffectsRef &New); + FunctionEffectDiffVector(const FunctionEffectsRef &Old, + const FunctionEffectsRef &New); }; /// All functions/lambdas/blocks which have bodies and which have a non-empty diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 84bf2a7c2922970..23eef1ecb44d95e 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -644,7 +644,7 @@ void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, const auto SrcFX = FunctionEffectsRef::get(SrcType); const auto DstFX = FunctionEffectsRef::get(DstType); if (SrcFX != DstFX) { - for (const auto &Diff : FunctionEffectDifferences(SrcFX, DstFX)) { + for (const auto &Diff : FunctionEffectDiffVector(SrcFX, DstFX)) { if (Diff.shouldDiagnoseConversion(SrcType, SrcFX, DstType, DstFX)) Diag(Loc, diag::warn_invalid_add_func_effects) << Diff.effectName(); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index c13d553ad32126a..2bbc7c91a8de4e0 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3803,7 +3803,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S, const auto OldFX = Old->getFunctionEffects(); const auto NewFX = New->getFunctionEffects(); if (OldFX != NewFX) { - const auto Diffs = FunctionEffectDifferences(OldFX, NewFX); + const auto Diffs = FunctionEffectDiffVector(OldFX, NewFX); for (const auto &Diff : Diffs) { if (Diff.shouldDiagnoseRedeclaration(*Old, OldFX, *New, NewFX)) { Diag(New->getLocation(), diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 6b6fa98bf394ca3..6008f4edc9b6c1c 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18179,7 +18179,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New, if (OldFX != NewFXOrig) { FunctionEffectSet NewFX(NewFXOrig); - const auto Diffs = FunctionEffectDifferences(OldFX, NewFX); + const auto Diffs = FunctionEffectDiffVector(OldFX, NewFX); FunctionEffectSet::Conflicts Errs; for (const auto &Diff : Diffs) { switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) { diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 8d7fbacc64a2f95..fb0069f6aa0e061 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -83,7 +83,8 @@ struct Violation { ViolationID ID = ViolationID::None; ViolationSite Site; SourceLocation Loc; - const Decl *Callee = nullptr; // Only valid for Calls*. + const Decl *Callee = + nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect. Violation() = default; @@ -275,7 +276,7 @@ struct CallableInfo { } /// Generate a name for logging and diagnostics. - std::string name(Sema &S) const { + std::string getNameForDiagnostic(Sema &S) const { std::string Name; llvm::raw_string_ostream OS(Name); @@ -298,6 +299,7 @@ class EffectToViolationMap { // 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; @@ -316,9 +318,8 @@ class EffectToViolationMap { if (Impl == nullptr) return nullptr; - auto *Iter = - llvm::find_if(*Impl, - [&](const auto &Item) { return Item.Effect == Key; }); + auto *Iter = llvm::find_if( + *Impl, [&](const auto &Item) { return Item.Effect == Key; }); return Iter != Impl->end() ? &*Iter : nullptr; } @@ -417,9 +418,9 @@ class PendingFunctionAnalysis { ArrayRef getSortedViolationsForExplicitEffects(SourceManager &SM) { if (!ViolationsForExplicitEffects.empty()) llvm::sort(ViolationsForExplicitEffects, - [&SM](const Violation &LHS, const Violation &RHS) { - return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); - }); + [&SM](const Violation &LHS, const Violation &RHS) { + return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); + }); return ViolationsForExplicitEffects; } @@ -434,7 +435,7 @@ class PendingFunctionAnalysis { OS << "; Calls: "; for (const DirectCall &Call : UnverifiedDirectCalls) { CallableInfo CI(*Call.Callee); - OS << " " << CI.name(SemaRef); + OS << " " << CI.getNameForDiagnostic(SemaRef); } } OS << "\n"; @@ -524,7 +525,7 @@ class Analyzer { for (const auto &item : *this) { CallableInfo CI(*item.first); const auto AP = item.second; - OS << item.first << " " << CI.name(SemaRef) << " : "; + OS << item.first << " " << CI.getNameForDiagnostic(SemaRef) << " : "; if (AP.isNull()) { OS << "null\n"; } else if (isa(AP)) { @@ -642,7 +643,8 @@ class Analyzer { // a fairly trivial move to a heap-allocated object. PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify); - LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(S) << " "; + LLVM_DEBUG(llvm::dbgs() + << "\nVerifying " << CInfo.getNameForDiagnostic(S) << " "; FAnalysis.dump(S, llvm::dbgs());); FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo); @@ -683,8 +685,8 @@ class Analyzer { // the possibility of inference. Deletes Pending. void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) { CallableInfo Caller(*D); - LLVM_DEBUG(llvm::dbgs() - << "finishPendingAnalysis for " << Caller.name(S) << " : "; + LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for " + << Caller.getNameForDiagnostic(S) << " : "; Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";); for (const PendingFunctionAnalysis::DirectCall &Call : Pending->unverifiedCalls()) { @@ -726,9 +728,10 @@ class Analyzer { if (!Callee.isVerifiable()) IsInferencePossible = false; - LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(S) << " to " - << Callee.name(S) << "; verifiable: " - << Callee.isVerifiable() << "; callee "; + 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";); @@ -858,7 +861,7 @@ class Analyzer { case ViolationID::CallsDeclWithoutEffect: { CallableInfo CalleeInfo(*Viol1.Callee); - std::string CalleeName = CalleeInfo.name(S); + std::string CalleeName = CalleeInfo.getNameForDiagnostic(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName @@ -935,14 +938,14 @@ class Analyzer { S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName << SiteDescIndex(Viol2.Callee, nullptr) - << MaybeNextCallee->name(S); + << MaybeNextCallee->getNameForDiagnostic(S); break; } MaybeAddTemplateNote(Callee); Callee = Viol2.Callee; if (MaybeNextCallee) { CalleeInfo = *MaybeNextCallee; - CalleeName = CalleeInfo.name(S); + CalleeName = CalleeInfo.getNameForDiagnostic(S); } } } break; @@ -1417,7 +1420,7 @@ void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) { Analyzer{*this}.run(*TU); } -Sema::FunctionEffectDifferences::FunctionEffectDifferences( +Sema::FunctionEffectDiffVector::FunctionEffectDiffVector( const FunctionEffectsRef &Old, const FunctionEffectsRef &New) { FunctionEffectsRef::iterator POld = Old.begin(); diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp index 5bf38dceedaed70..dcbcef410987f58 100644 --- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp @@ -1,6 +1,5 @@ -// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify %s +// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify -Wfunction-effects %s -#pragma clang diagnostic warning "-Wfunction-effects" #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // These need '-fms-extensions' (and maybe '-fdeclspec') diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 9318a43912b8435..377bb0ef36313a2 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -1,8 +1,7 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify -Wfunction-effects %s // These are in a separate file because errors (e.g. incompatible attributes) currently prevent // the FXAnalysis pass from running at all. -#pragma clang diagnostic warning "-Wfunction-effects" // This diagnostic is re-enabled and exercised in isolation later in this file. #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp index 157fba79075fae5..9056f81f5296b96 100644 --- a/clang/test/Sema/attr-nonblocking-sema.cpp +++ b/clang/test/Sema/attr-nonblocking-sema.cpp @@ -1,7 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s -// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s - -#pragma clang diagnostic warning "-Wfunction-effects" +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify -Wfunction-effects %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 -Wfunction-effects %s #if !__has_attribute(nonblocking) #error "the 'nonblocking' attribute is not available" @@ -79,7 +77,7 @@ void type_conversions() void (*fp_nonallocating)() [[clang::nonallocating]]; fp_nonallocating = nullptr; fp_nonallocating = nonallocating; - fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated; + fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}} } diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm index f3fa984ffd9b705..abd0938ac321af1 100644 --- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -1,6 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify -Wfunction-effects %s -#pragma clang diagnostic warning "-Wfunction-effects" #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" // Objective-C From eb782e00023949bfc884af4cf5f8254145dd96a1 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 24 Sep 2024 07:19:01 -0700 Subject: [PATCH 54/63] - Add constraint tests for plain C. - Prevent crash in isNoexcept if a FunctionDecl doesn't have a prototype. - [[noreturn]] alone (without noexcept) is enough to exclude a callee from diagnostics if plain C. --- clang/lib/Sema/SemaFunctionEffects.cpp | 12 ++++++------ clang/test/Sema/attr-nonblocking-constraints.c | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 clang/test/Sema/attr-nonblocking-constraints.c diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index fb0069f6aa0e061..d6021bb4ebe3e69 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -121,10 +121,8 @@ static bool functionIsVerifiable(const FunctionDecl *FD) { } static bool isNoexcept(const FunctionDecl *FD) { - const auto *FPT = FD->getType()->castAs(); - if (FPT->isNothrow() || FD->hasAttr()) - return true; - return false; + const auto *FPT = FD->getType()->getAs(); + return FPT && (FPT->isNothrow() || FD->hasAttr()); } // This list is probably incomplete. @@ -1028,9 +1026,11 @@ class Analyzer { return; } } - // If the callee is both noreturn and noexcept, it presumably + // If the callee is both `noreturn` and `noexcept`, it presumably // terminates. Ignore it for the purposes of effect analysis. - if (FD->isNoReturn() && isNoexcept(FD)) + // If not C++, `noreturn` alone is sufficient. + if (FD->isNoReturn() && + (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD))) return; } diff --git a/clang/test/Sema/attr-nonblocking-constraints.c b/clang/test/Sema/attr-nonblocking-constraints.c new file mode 100644 index 000000000000000..03e040c1851ffc3 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-constraints.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 -Wfunction-effects %s + +// Tests for a few cases involving C functions without prototypes. + +void hasproto(void) __attribute__((blocking)); // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}} + +// Has no prototype, inferably safe. +void nb1() {} + +// Has no prototype, noreturn. +[[noreturn]] +void aborts(); + +void nb2(void) __attribute__((nonblocking)) { + hasproto(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasproto'}} + nb1(); + aborts(); // no diagnostic because it's noreturn. +} From eccc7cff686d37917f047542b392a9e36af07539 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Tue, 24 Sep 2024 07:37:58 -0700 Subject: [PATCH 55/63] -Wperf-constraint-implies-noexcept is part of -Wall again --- clang/include/clang/Basic/DiagnosticGroups.td | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 46972d4244cdb13..a85768d7ca72d19 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1125,6 +1125,11 @@ def ThreadSafety : DiagGroup<"thread-safety", def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">; def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">; +// Warnings and notes related to the function effects system which underlies +// the nonblocking and nonallocating attributes. +def FunctionEffects : DiagGroup<"function-effects">; +def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; + // Uniqueness Analysis warnings def Consumed : DiagGroup<"consumed">; @@ -1133,7 +1138,7 @@ def Consumed : DiagGroup<"consumed">; // DefaultIgnore in addition to putting it here. def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation, PackedNonPod, - VLACxxExtension]>; + VLACxxExtension, PerfConstraintImpliesNoexcept]>; // Warnings that should be in clang-cl /w4. def : DiagGroup<"CL4", [All, Extra]>; @@ -1561,11 +1566,6 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container"> def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">; def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>; -// Warnings and notes related to the function effects system underlying -// the nonblocking and nonallocating attributes. -def FunctionEffects : DiagGroup<"function-effects">; -def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">; - // Warnings and notes InstallAPI verification. def InstallAPIViolation : DiagGroup<"installapi-violation">; From 220a1bff654285e40984c3c521b57e2668cc0d19 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 25 Sep 2024 15:33:56 -0700 Subject: [PATCH 56/63] Remove FunctionEffect::Kind::None as a sentinel; use optionals instead. --- clang/include/clang/AST/Type.h | 23 +++++++++------------ clang/include/clang/Sema/Sema.h | 12 ++++++----- clang/lib/AST/Type.cpp | 13 ++---------- clang/lib/Sema/SemaDeclCXX.cpp | 2 +- clang/lib/Sema/SemaFunctionEffects.cpp | 28 +++++++++----------------- 5 files changed, 29 insertions(+), 49 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index e7bdef68a1ee172..d7835adf734707f 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4715,13 +4715,13 @@ class FunctionEffect { public: /// Identifies the particular effect. enum class Kind : uint8_t { - None = 0, - NonBlocking = 1, - NonAllocating = 2, - Blocking = 3, - Allocating = 4, + NonBlocking = 0, + NonAllocating = 1, + Blocking = 2, + Allocating = 3, + }; - constexpr static size_t KindMaximum = 4; + constexpr static size_t KindCount = 4; /// Flags describing some behaviors of the effect. using Flags = unsigned; @@ -4747,8 +4747,6 @@ class FunctionEffect { // be considered for uniqueness. public: - FunctionEffect() : FKind(Kind::None) {} - explicit FunctionEffect(Kind K) : FKind(K) {} /// The kind of the effect. @@ -4777,8 +4775,6 @@ class FunctionEffect { case Kind::Blocking: case Kind::Allocating: return 0; - case Kind::None: - break; } llvm_unreachable("unknown effect kind"); } @@ -4843,7 +4839,6 @@ struct FunctionEffectWithCondition { FunctionEffect Effect; EffectConditionExpr Cond; - FunctionEffectWithCondition() = default; FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C) : Effect(E), Cond(C) {} @@ -4959,7 +4954,7 @@ class FunctionEffectsRef { /// A mutable set of FunctionEffect::Kind. class FunctionEffectKindSet { // For now this only needs to be a bitmap. - constexpr static size_t EndBitPos = FunctionEffect::KindMaximum; + constexpr static size_t EndBitPos = FunctionEffect::KindCount; using KindBitsT = std::bitset; KindBitsT KindBits{}; @@ -4970,11 +4965,11 @@ class FunctionEffectKindSet { // position in the bitset. constexpr static size_t kindToPos(FunctionEffect::Kind K) { - return static_cast(K) - 1; + return static_cast(K); } constexpr static FunctionEffect::Kind posToKind(size_t Pos) { - return static_cast(Pos + 1); + return static_cast(Pos); } // Iterates through the bits which are set. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d9d66f551c831e6..30e4fa5d2fd121e 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15028,13 +15028,15 @@ class Sema final : public SemaBase { FunctionEffect::Kind EffectKind; Kind DiffKind; - FunctionEffectWithCondition Old; // Invalid when 'Kind' is 'Added'. - FunctionEffectWithCondition New; // Invalid when 'Kind' is 'Removed'. + std::optional + Old; // Invalid when 'Kind' is 'Added'. + std::optional + New; // Invalid when 'Kind' is 'Removed'. StringRef effectName() const { - if (Old.Effect.kind() != FunctionEffect::Kind::None) - return Old.Effect.name(); - return New.Effect.name(); + if (Old) + return Old.value().Effect.name(); + return New.value().Effect.name(); } /// Describes the result of effects differing between a base class's virtual diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 8542c16a8e10e24..c92dc881dba537d 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5117,8 +5117,6 @@ FunctionEffect::Kind FunctionEffect::oppositeKind() const { return Kind::Allocating; case Kind::Allocating: return Kind::NonAllocating; - case Kind::None: - return Kind::None; } llvm_unreachable("unknown effect kind"); } @@ -5133,8 +5131,6 @@ StringRef FunctionEffect::name() const { return "blocking"; case Kind::Allocating: return "allocating"; - case Kind::None: - return "(none)"; } llvm_unreachable("unknown effect kind"); } @@ -5159,11 +5155,8 @@ std::optional FunctionEffect::effectProhibitingInference( case Kind::Blocking: assert(0 && "effectProhibitingInference with non-inferable effect kind"); break; - - case Kind::None: - break; } - llvm_unreachable("unknown effect kind or None"); + llvm_unreachable("unknown effect kind"); } bool FunctionEffect::shouldDiagnoseFunctionCall( @@ -5185,10 +5178,8 @@ bool FunctionEffect::shouldDiagnoseFunctionCall( case Kind::Allocating: case Kind::Blocking: return false; - case Kind::None: - break; } - llvm_unreachable("unknown effect kind or None"); + llvm_unreachable("unknown effect kind"); } // ===== diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 6008f4edc9b6c1c..9c172c3c1818999 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18192,7 +18192,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New, << Old->getReturnTypeSourceRange(); break; case FunctionEffectDiff::OverrideResult::Merge: { - NewFX.insert(Diff.Old, Errs); + NewFX.insert(Diff.Old.value(), Errs); const auto *NewFT = New->getType()->castAs(); FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo(); EPI.FunctionEffects = FunctionEffectsRef(NewFX); diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index d6021bb4ebe3e69..35e59c542caf774 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -78,7 +78,7 @@ class ViolationSite { // be inferred as holding that effect. struct Violation { FunctionEffect Effect; - FunctionEffect + std::optional CalleeEffectPreventingInference; // Only for certain IDs; can be None. ViolationID ID = ViolationID::None; ViolationSite Site; @@ -86,14 +86,11 @@ struct Violation { const Decl *Callee = nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect. - Violation() = default; - Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS, SourceLocation Loc, const Decl *Callee = nullptr, std::optional CalleeEffect = std::nullopt) - : Effect(Effect), CalleeEffectPreventingInference( - CalleeEffect.value_or(FunctionEffect())), - ID(ID), Site(VS), Loc(Loc), Callee(Callee) {} + : Effect(Effect), CalleeEffectPreventingInference(CalleeEffect), ID(ID), + Site(VS), Loc(Loc), Callee(Callee) {} unsigned diagnosticSelectIndex() const { return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex); @@ -915,7 +912,7 @@ class Analyzer { case ViolationID::DeclDisallowsInference: S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) << SiteDescIndex(CalleeInfo.CDecl, nullptr) << effectName - << Viol2.CalleeEffectPreventingInference.name(); + << Viol2.CalleeEffectPreventingInference->name(); break; case ViolationID::CallsExprWithoutEffect: S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect) @@ -1458,14 +1455,16 @@ Sema::FunctionEffectDiffVector::FunctionEffectDiffVector( if (cmp < 0) { // removal FunctionEffectWithCondition Old = *POld; - push_back(FunctionEffectDiff{ - Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}}); + 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, {}, New}); + push_back(FunctionEffectDiff{New.Effect.kind(), + FunctionEffectDiff::Kind::Added, + std::nullopt, New}); ++PNew; } else { ++POld; @@ -1506,8 +1505,6 @@ bool Sema::FunctionEffectDiff::shouldDiagnoseConversion( case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return false; - case FunctionEffect::Kind::None: - break; } llvm_unreachable("unknown effect kind"); } @@ -1531,8 +1528,6 @@ bool Sema::FunctionEffectDiff::shouldDiagnoseRedeclaration( case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return false; - case FunctionEffect::Kind::None: - break; } llvm_unreachable("unknown effect kind"); } @@ -1563,9 +1558,6 @@ Sema::FunctionEffectDiff::shouldDiagnoseMethodOverride( case FunctionEffect::Kind::Blocking: case FunctionEffect::Kind::Allocating: return OverrideResult::NoAction; - - case FunctionEffect::Kind::None: - break; } llvm_unreachable("unknown effect kind"); } From a8fef93efe629216c4c0847918acccd8260fbe4c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 25 Sep 2024 17:14:52 -0700 Subject: [PATCH 57/63] Ensure that setjmp() is not included in the special treatment of `noreturn` functions. --- clang/lib/Sema/SemaFunctionEffects.cpp | 9 ++++++++- clang/test/Headers/Inputs/include/setjmp.h | 3 +++ clang/test/Sema/attr-nonblocking-constraints.c | 11 ++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 35e59c542caf774..4c87fbf0b6e25e7 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -158,6 +158,8 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { 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: @@ -1016,17 +1018,22 @@ class Analyzer { // Check for a call to a builtin function, whose effects are // handled specially. if (const auto *FD = dyn_cast(CI.CDecl)) { + bool IgnoreIfNoexceptNoreturn = true; 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(). + IgnoreIfNoexceptNoreturn = false; } + // 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() && + if (IgnoreIfNoexceptNoreturn && FD->isNoReturn() && (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD))) return; } diff --git a/clang/test/Headers/Inputs/include/setjmp.h b/clang/test/Headers/Inputs/include/setjmp.h index 3d5e903eff6fcec..c24951e92501f90 100644 --- a/clang/test/Headers/Inputs/include/setjmp.h +++ b/clang/test/Headers/Inputs/include/setjmp.h @@ -5,4 +5,7 @@ typedef struct { int x[42]; } jmp_buf; + __attribute__((noreturn)) +void longjmp(jmp_buf, int); + #endif diff --git a/clang/test/Sema/attr-nonblocking-constraints.c b/clang/test/Sema/attr-nonblocking-constraints.c index 03e040c1851ffc3..e7ab661b3125a2d 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.c +++ b/clang/test/Sema/attr-nonblocking-constraints.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 -Wfunction-effects %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 -Wfunction-effects -internal-isystem %S/../Headers/Inputs/include %s // Tests for a few cases involving C functions without prototypes. @@ -16,3 +16,12 @@ void nb2(void) __attribute__((nonblocking)) { nb1(); aborts(); // no diagnostic because it's noreturn. } + +#include + +void nb3(int x, int y) __attribute__((nonblocking)) { + if (x != y) { + jmp_buf jb; + longjmp(jb, 0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'longjmp'}} + } +} From 092bc169eb5f3aa60f19e8fbf654e75b289ab124 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 25 Sep 2024 17:17:38 -0700 Subject: [PATCH 58/63] Update clang/lib/Sema/SemaFunctionEffects.cpp Co-authored-by: Sirraide --- clang/lib/Sema/SemaFunctionEffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 4c87fbf0b6e25e7..f7f521cb15e73eb 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -79,7 +79,7 @@ class ViolationSite { struct Violation { FunctionEffect Effect; std::optional - CalleeEffectPreventingInference; // Only for certain IDs; can be None. + CalleeEffectPreventingInference; // Only for certain IDs; can be nullopt. ViolationID ID = ViolationID::None; ViolationSite Site; SourceLocation Loc; From 424a74b2ca9f386d6b63279eb9fe2a297e7cb9ec Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 25 Sep 2024 17:27:00 -0700 Subject: [PATCH 59/63] Builtins with effects don't get the noexcept/noreturn special treatment. --- clang/lib/Sema/SemaFunctionEffects.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index f7f521cb15e73eb..9ae2e2931647dc7 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -1018,7 +1018,6 @@ class Analyzer { // Check for a call to a builtin function, whose effects are // handled specially. if (const auto *FD = dyn_cast(CI.CDecl)) { - bool IgnoreIfNoexceptNoreturn = true; if (unsigned BuiltinID = FD->getBuiltinID()) { CI.Effects = getBuiltinFunctionEffects(BuiltinID); if (CI.Effects.empty()) { @@ -1026,16 +1025,16 @@ class Analyzer { return; } // A builtin WITH effects doesn't get any special treatment for - // being noreturn/noexcept, e.g. longjmp(). - IgnoreIfNoexceptNoreturn = false; + // 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; } - - // 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 (IgnoreIfNoexceptNoreturn && FD->isNoReturn() && - (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD))) - return; } Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, From 273871a3633ed4fe87f17738d79a91d1ea6ba621 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 25 Sep 2024 22:13:35 -0700 Subject: [PATCH 60/63] warning-wall test was broken a few commits ago; fix it now. --- clang/test/Misc/warning-wall.c | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/Misc/warning-wall.c b/clang/test/Misc/warning-wall.c index 4909ab034ef30aa..91de843f88c913d 100644 --- a/clang/test/Misc/warning-wall.c +++ b/clang/test/Misc/warning-wall.c @@ -108,5 +108,6 @@ CHECK-NEXT: -Wmisleading-indentation CHECK-NEXT: -Wpacked-non-pod CHECK-NEXT: -Wvla-cxx-extension CHECK-NEXT: -Wvla-extension-static-assert +CHECK-NEXT: -Wperf-constraint-implies-noexcept CHECK-NOT:-W From a77d4e3cbcb0e7afb3b4d8374da8d7a0a602b99c Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 26 Sep 2024 07:57:32 -0700 Subject: [PATCH 61/63] - FunctionEffect::Kind: no need to be specific with enum values. Same for ViolationSite::Kind. - Update comment in ViolationID. - Add an llvm_unreachable() in ASTReader. - Tweak release notes. --- clang/docs/ReleaseNotes.rst | 5 +++-- clang/include/clang/AST/Type.h | 12 ++++++------ clang/lib/Sema/SemaFunctionEffects.cpp | 9 +++++---- clang/lib/Serialization/ASTReader.cpp | 2 ++ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index da88ac4db099b4d..3b70fbe43d9a5f0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -571,8 +571,9 @@ New features if class of allocation and deallocation function mismatches. `Documentation `__. -- Function effects (the ``nonblocking`` and ``nonallocating`` "performance constraint" attributes) - are now verified. +- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint" + attributes, are now verified. For example, functions declared with the ``nonblocking`` attribute + can be analyzed for the use of any language features, or calls to other functions, which may block. Crash and bug fixes ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 767078a144fed68..00cb7e72fc2b1df 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4715,13 +4715,13 @@ class FunctionEffect { public: /// Identifies the particular effect. enum class Kind : uint8_t { - NonBlocking = 0, - NonAllocating = 1, - Blocking = 2, - Allocating = 3, - + NonBlocking, + NonAllocating, + Blocking, + Allocating, + Last = Allocating }; - constexpr static size_t KindCount = 4; + constexpr static size_t KindCount = static_cast(Kind::Last) + 1; /// Flags describing some behaviors of the effect. using Flags = unsigned; diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 9ae2e2931647dc7..e4e7e2c877049d0 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -26,7 +26,8 @@ namespace { enum class ViolationID : uint8_t { None = 0, // Sentinel for an empty Violation. - // These first few map to a %select{} in a diagnostic. + // These first 5 map to a %select{} in one of several FunctionEffects + // diagnostic, e.g. warn_func_effect_violation. BaseDiagnosticIndex, AllocatesMemory = BaseDiagnosticIndex, ThrowsOrCatchesExceptions, @@ -50,9 +51,9 @@ enum class ViolationID : uint8_t { class ViolationSite { public: enum class Kind : uint8_t { - Default = 0, // Function body. - MemberInitializer = 1, - DefaultArgExpr = 2 + Default, // Function body. + MemberInitializer, + DefaultArgExpr }; private: diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 0459e205fb10c08..bb2384d96a8a0c5 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -8424,6 +8424,8 @@ void ASTReader::InitializeSema(Sema &S) { SemaObj->addDeclWithEffects(FD, FD->getFunctionEffects()); else if (auto *BD = dyn_cast(D)) SemaObj->addDeclWithEffects(BD, BD->getFunctionEffects()); + else + llvm_unreachable("unexpected Decl type in DeclsWithEffectsToVerify"); } DeclsWithEffectsToVerify.clear(); From f8d9189f5d5cf4f6f92debd2d59bfc5648f4d572 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Thu, 26 Sep 2024 08:26:04 -0700 Subject: [PATCH 62/63] Tweak release note, fix typo in comment. --- clang/docs/ReleaseNotes.rst | 5 +++-- clang/lib/Sema/SemaFunctionEffects.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 3b70fbe43d9a5f0..6798ead80dcdd8f 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -572,8 +572,9 @@ New features `Documentation `__. - Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint" - attributes, are now verified. For example, functions declared with the ``nonblocking`` attribute - can be analyzed for the use of any language features, or calls to other functions, which may block. + attributes, are now verified. For example, for functions declared with the ``nonblocking`` + attribute, the compiler can generate warnings about the use of any language features, or calls to + other functions, which may block. Crash and bug fixes ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index e4e7e2c877049d0..641cc1ec23b9c6c 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -27,7 +27,7 @@ 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 - // diagnostic, e.g. warn_func_effect_violation. + // diagnostics, e.g. warn_func_effect_violation. BaseDiagnosticIndex, AllocatesMemory = BaseDiagnosticIndex, ThrowsOrCatchesExceptions, From da725c130da43d046891e80a851d05a180c4a6d8 Mon Sep 17 00:00:00 2001 From: Doug Wyatt Date: Wed, 2 Oct 2024 08:15:44 -0700 Subject: [PATCH 63/63] Tweak: "function with 'nonblocking' attribute should be declared noexcept" now says "function/lambda/block" etc. --- .../clang/Basic/DiagnosticSemaKinds.td | 3 +- clang/lib/Sema/SemaFunctionEffects.cpp | 83 ++++++++++--------- .../Sema/attr-nonblocking-constraints.cpp | 1 + 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index afc9a4e57c03334..81f55263c6bea6f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11010,7 +11010,8 @@ def note_func_effect_call_disallows_inference : Note< def note_func_effect_call_indirect : Note< "%select{virtual method|function pointer}0 cannot be inferred '%1'">; def warn_perf_constraint_implies_noexcept : Warning< - "function with '%0' attribute should be declared noexcept">, + "%select{function|constructor|destructor|lambda|block}0 " + "with '%1' attribute should be declared noexcept">, InGroup, DefaultIgnore; // FIXME: It would be nice if we could provide fuller template expansion notes. diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp index 641cc1ec23b9c6c..0fb18d207a50ba7 100644 --- a/clang/lib/Sema/SemaFunctionEffects.cpp +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -631,7 +631,7 @@ class Analyzer { } if (!IsNoexcept) S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept) - << Effect.name(); + << GetCallableDeclKind(D, nullptr) << Effect.name(); break; } } @@ -763,6 +763,40 @@ class Analyzer { 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) { @@ -782,35 +816,6 @@ class Analyzer { // For note_func_effect_call_indirect. enum { Indirect_VirtualMethod, Indirect_FunctionPtr }; - // Describe a call site or target using an enum mapping to a %select{} - // in a diagnostic. - auto SiteDescIndex = [](const Decl *D, const Violation *V) { - enum { - VS_Function, - VS_Constructor, - VS_Destructor, - VS_Lambda, - VS_Block, - VS_MemberInitializer, - }; - - if (V != nullptr && - V->Site.kind() == ViolationSite::Kind::MemberInitializer) - return VS_MemberInitializer; - if (isa(D)) - return VS_Block; - if (auto *Method = dyn_cast(D)) { - if (isa(D)) - return VS_Constructor; - if (isa(D)) - return VS_Destructor; - const CXXRecordDecl *Rec = Method->getParent(); - if (Rec->isLambda()) - return VS_Lambda; - } - return VS_Function; - }; - 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. @@ -845,14 +850,14 @@ class Analyzer { case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol1.Loc, diag::warn_func_effect_violation) - << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName + << 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) - << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName; + << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); break; @@ -862,8 +867,8 @@ class Analyzer { std::string CalleeName = CalleeInfo.getNameForDiagnostic(S); S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) - << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName - << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName; + << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName + << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << CalleeName; MaybeAddSiteContext(CInfo.CDecl, Viol1); MaybeAddTemplateNote(CInfo.CDecl); @@ -891,7 +896,7 @@ class Analyzer { else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind())) S.Diag(Callee->getLocation(), diag::note_func_effect_call_disallows_inference) - << SiteDescIndex(CInfo.CDecl, nullptr) << effectName + << GetCallableDeclKind(CInfo.CDecl, nullptr) << effectName << FunctionEffect(Viol1.Effect.oppositeKind()).name(); else if (const FunctionDecl *FD = dyn_cast(Callee); FD == nullptr || FD->getBuiltinID() == 0) { @@ -914,7 +919,7 @@ class Analyzer { break; case ViolationID::DeclDisallowsInference: S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) - << SiteDescIndex(CalleeInfo.CDecl, nullptr) << effectName + << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << effectName << Viol2.CalleeEffectPreventingInference->name(); break; case ViolationID::CallsExprWithoutEffect: @@ -927,15 +932,15 @@ class Analyzer { case ViolationID::AccessesThreadLocalVariable: case ViolationID::AccessesObjCMethodOrProperty: S.Diag(Viol2.Loc, diag::note_func_effect_violation) - << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName + << 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) - << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName - << SiteDescIndex(Viol2.Callee, nullptr) + << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName + << GetCallableDeclKind(Viol2.Callee, nullptr) << MaybeNextCallee->getNameForDiagnostic(S); break; } diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp index 377bb0ef36313a2..c694860069c9601 100644 --- a/clang/test/Sema/attr-nonblocking-constraints.cpp +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -382,4 +382,5 @@ void nb26() [[clang::nonblocking]] { void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}} { + auto lambda = []() [[clang::nonblocking]] {}; // expected-warning {{lambda with 'nonblocking' attribute should be declared noexcept}} }