https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/99656
>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001 From: Doug Wyatt <dwy...@apple.com> Date: Sun, 5 May 2024 12:36:53 -0700 Subject: [PATCH 1/3] 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 25defea58c2dc..08141f75de8db 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 19c3f1e043349..55d9442a939da 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 d60f32674ca3a..ec02c02d158c8 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<DiagGroup<"unaligned-qualifier-implicit-cast">>; // Function effects +def warn_func_effect_allocates : Warning< + "'%0' function must not allocate or deallocate memory">, + InGroup<FunctionEffects>; +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<FunctionEffects>; +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<FunctionEffects>; +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<FunctionEffects>; +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<FunctionEffects>; +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<FunctionEffects>; +def warn_func_effect_calls_expr_without_effect : Warning< + "'%0' function must not call non-'%0' expression">, + InGroup<FunctionEffects>; +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<PerfConstraintImpliesNoexcept>; + +// 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 d638d31e050dc..e1867348497da 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<const Decl *> 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 5dd0ba33f8a9c..b975db88dbaae 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 76e51ac7ab979..1d8985602146b 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<GlobalDeclID, 4> DeclsToCheckForDeferredDiags; + /// The IDs of all decls with function effects to be checked. + SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify; + private: struct ImportedSubmodule { serialization::SubmoduleID ID; diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h index a0e475ec9f862..4eaf77e8cb8d9 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 0f604c61fa3af..3909d5b44a32e 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 <typename T, typename SizeT, SizeT Capacity> 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<FunctionEffect, uint8_t, 7> Impl; + +public: + EffectSet() = default; + explicit EffectSet(FunctionEffectsRef FX) { insert(FX); } + + operator ArrayRef<FunctionEffect>() 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<FunctionEffect> LHS, + ArrayRef<FunctionEffect> 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<std::string> 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<FunctionDecl>(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<CXXMethodDecl>(FD); + Method && Method->isVirtual()) + CType = CallType::Virtual; + FXRef = FD->getFunctionEffects(); + } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) { + CType = CallType::Block; + FXRef = BD->getFunctionEffects(); + } else if (auto *VD = dyn_cast<ValueDecl>(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<FunctionDecl>(CDecl)); + } + return false; + } + + /// Generate a name for logging and diagnostics. + std::string name(Sema &Sem) const { + if (!MaybeName) { + std::string Name; + llvm::raw_string_ostream OS(Name); + + if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) + FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(), + /*Qualified=*/true); + else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) + OS << "(block " << BD->getBlockManglingNumber() << ")"; + else if (auto *VD = dyn_cast<NamedDecl>(CDecl)) + VD->printQualifiedName(OS); + MaybeName = Name; + } + return *MaybeName; + } +}; + +// ---------- +// Map effects to single diagnostics, 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<Diagnostic, 1>; + std::unique_ptr<ImplVec> 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<ImplVec>(); + 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<Diagnostic, 0> 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<DirectCall, 0> UnverifiedDirectCalls; + +public: + PendingFunctionAnalysis( + Sema &Sem, const CallableInfo &CInfo, + ArrayRef<FunctionEffect> 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<DirectCall, 0> &unverifiedCalls() { + assert(!isComplete()); + return UnverifiedDirectCalls; + } + + SmallVector<Diagnostic, 0> &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<FunctionEffect> 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<FunctionDecl>(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<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>; + + // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger + // than complete state, so use different objects to represent them. + // The state pointers are owned by the container. + class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> { + using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>; + + public: + ~AnalysisMap(); + + // Use non-public inheritance in order to maintain the invariant + // that lookups and insertions are via the canonical Decls. + + FuncAnalysisPtr lookup(const Decl *Key) const { + return Base::lookup(CanonicalFunctionDecl(Key)); + } + + FuncAnalysisPtr &operator[](const Decl *Key) { + return Base::operator[](CanonicalFunctionDecl(Key)); + } + + /// Shortcut for the case where we only care about completed analysis. + CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const { + if (FuncAnalysisPtr AP = lookup(D); + isa_and_nonnull<CompleteFunctionAnalysis *>(AP)) + return AP.get<CompleteFunctionAnalysis *>(); + return nullptr; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) { + OS << "\nAnalysisMap:\n"; + for (const auto &item : *this) { + CallableInfo CI(SemaRef, *item.first); + const auto AP = item.second; + OS << item.first << " " << CI.name(SemaRef) << " : "; + if (AP.isNull()) + OS << "null\n"; + else if (isa<CompleteFunctionAnalysis *>(AP)) { + auto *CFA = AP.get<CompleteFunctionAnalysis *>(); + OS << CFA << " "; + CFA->dump(OS); + } else if (isa<PendingFunctionAnalysis *>(AP)) { + auto *PFA = AP.get<PendingFunctionAnalysis *>(); + OS << PFA << " "; + PFA->dump(SemaRef, OS); + } else + llvm_unreachable("never"); + } + OS << "---\n"; + } + }; + AnalysisMap DeclAnalysis; + +public: + Analyzer(Sema &S) : Sem(S) {} + + void run(const TranslationUnitDecl &TU) { + // Gather all of the effects to be verified to see what operations need to + // be checked, and to see which ones are inferrable. + for (const 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<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify; + std::reverse(VerificationQueue.begin(), VerificationQueue.end()); + + while (!VerificationQueue.empty()) { + const Decl *D = VerificationQueue.back(); + if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { + if (isa<CompleteFunctionAnalysis *>(AP)) { + // already done + VerificationQueue.pop_back(); + continue; + } + if (isa<PendingFunctionAnalysis *>(AP)) { + // All children have been traversed; finish analysis. + auto *Pending = AP.get<PendingFunctionAnalysis *>(); + finishPendingAnalysis(D, Pending); + VerificationQueue.pop_back(); + continue; + } + llvm_unreachable("unexpected DeclAnalysis item"); + } + + // Not previously visited; begin a new analysis for this Decl. + PendingFunctionAnalysis *Pending = verifyDecl(D); + if (Pending == nullptr) { + // completed now + VerificationQueue.pop_back(); + continue; + } + + // Analysis remains pending because there are direct callees to be + // verified first. Push them onto the queue. + for (PendingFunctionAnalysis::DirectCall &Call : + Pending->unverifiedCalls()) { + FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee); + if (AP.isNull()) { + VerificationQueue.push_back(Call.Callee); + continue; + } + if (isa<PendingFunctionAnalysis *>(AP)) { + // This indicates recursion (not necessarily direct). For the + // purposes of effect analysis, we can just ignore it since + // no effects forbid recursion. + Call.Recursed = true; + continue; + } + llvm_unreachable("unexpected DeclAnalysis item"); + } + } + } + +private: + // Verify a single Decl. Return the pending structure if that was the result, + // else null. This method must not recurse. + PendingFunctionAnalysis *verifyDecl(const Decl *D) { + CallableInfo CInfo(Sem, *D); + bool isExternC = false; + + if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { + assert(FD->getBuiltinID() == 0); + isExternC = FD->getCanonicalDecl()->isExternCContext(); + } + + // For C++, with non-extern "C" linkage only - if any of the Decl's declared + // effects forbid throwing (e.g. nonblocking) then the function should also + // be declared noexcept. + if (Sem.getLangOpts().CPlusPlus && !isExternC) { + for (const FunctionEffect &Effect : CInfo.Effects) { + if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow)) + continue; + + bool IsNoexcept = false; + if (auto *FD = D->getAsFunction()) { + IsNoexcept = isNoexcept(FD); + } else if (auto *BD = dyn_cast<BlockDecl>(D)) { + if (auto *TSI = BD->getSignatureAsWritten()) { + auto *FPT = TSI->getType()->getAs<FunctionProtoType>(); + IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>(); + } + } + if (!IsNoexcept) + Sem.Diag(D->getBeginLoc(), + diag::warn_perf_constraint_implies_noexcept) + << Effect.name(); + break; + } + } + + // Build a PendingFunctionAnalysis on the stack. If it turns out to be + // complete, we'll have avoided a heap allocation; if it's incomplete, it's + // a fairly trivial move to a heap-allocated object. + PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify); + + 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<Diagnostic, 0> &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<Diagnostic, 0> &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<FunctionDecl>(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<CallableInfo> 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<FunctionBodyASTVisitor> { + // 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<const FunctionDecl>(CurrentCaller.CDecl)) { + if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) { + for (const CXXCtorInitializer *Initer : Ctor->inits()) + if (Expr *Init = Initer->getInit()) + VisitStmt(Init); + } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) + followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor); + } + // else could be BlockDecl + + // Do an AST traversal of the function/block body + TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl)); + } + + // -- Methods implementing common logic -- + + // Handle a language construct forbidden by some effects. Only effects whose + // flags include the specified flag receive a 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<FunctionDecl>(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<FunctionProtoType>(); // 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<CXXRecordDecl>(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<CXXPseudoDestructorExpr>(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<RecordType>()) { + if (const auto *CxxRec = + dyn_cast<CXXRecordDecl>(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<VarDecl>(Val)) { + const VarDecl *Var = cast<VarDecl>(Val); + VarDecl::TLSKind TLSK = Var->getTLSKind(); + if (TLSK != VarDecl::TLS_None) { + // At least on macOS, thread-local variables are initialized on + // first access, including a heap allocation. + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars, + 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<PendingFunctionAnalysis *>(AP)) + delete AP.get<PendingFunctionAnalysis *>(); + else + delete AP.get<CompleteFunctionAnalysis *>(); + } +} + +} // 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 bb25a0b3a45ae..adb4a8f4de685 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<DeclContext>(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 8d24e34520e77..f9b04f5361f33 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<BlockScopeInfo>(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 601077e9f3334..e3bf582db3027 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<LambdaScopeInfo>(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 3cb96df12e4da..5d81d921d0fff 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<FunctionDecl>(D)) + FX = FD->getFunctionEffects(); + else if (auto *BD = dyn_cast<BlockDecl>(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 c78d8943d6d92..faf9b9490bafc 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 0000000000000..c248293cf7634 --- /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 <typename T> + 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<int>::add_explicit(1, 2); + Adder<int>::add_implicit(1, 2); + + Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}} + Adder<Stringy2>::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 <bool V> +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<true> CNB_true; + CNB_true.method(); + + ComputedNB<false> 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 644ed754b04da..90d074d01708f 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 0000000000000..aeb8b21f56e44 --- /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 <dwy...@apple.com> Date: Sat, 20 Jul 2024 08:57:16 -0700 Subject: [PATCH 2/3] - 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 e1867348497da..e3259f147ec88 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<const Decl *> 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<FunctionEffectMode> - 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<FunctionEffectMode> + 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 aeb8b21f56e44..0600062e89c04 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 <dwy...@apple.com> Date: Sat, 20 Jul 2024 09:08:16 -0700 Subject: [PATCH 3/3] 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 e3259f147ec88..a42f6af3c70e4 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); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits