diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b7d287fdf4cc61..bb759cede22ff9 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -632,6 +632,11 @@ New features if class of allocation and deallocation function mismatches. `Documentation `__. +- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint" + 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/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 7126940058baed..8ff04cf89a6b91 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -49,6 +49,7 @@ #include "llvm/Support/PointerLikeTypeTraits.h" #include "llvm/Support/TrailingObjects.h" #include "llvm/Support/type_traits.h" +#include #include #include #include @@ -119,6 +120,8 @@ class EnumDecl; class Expr; class ExtQualsTypeCommonBase; class FunctionDecl; +class FunctionEffectsRef; +class FunctionEffectKindSet; class FunctionEffectSet; class IdentifierInfo; class NamedDecl; @@ -4712,12 +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, + NonAllocating, + Blocking, + Allocating, + Last = Allocating }; + constexpr static size_t KindCount = static_cast(Kind::Last) + 1; /// Flags describing some behaviors of the effect. using Flags = unsigned; @@ -4743,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. @@ -4773,8 +4775,6 @@ class FunctionEffect { case Kind::Blocking: case Kind::Allocating: return 0; - case Kind::None: - break; } llvm_unreachable("unknown effect kind"); } @@ -4782,26 +4782,36 @@ 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. + 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 + /// 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, + 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, - ArrayRef 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; } }; @@ -4829,13 +4839,14 @@ struct FunctionEffectWithCondition { FunctionEffect Effect; 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. std::string description() const; + + friend raw_ostream &operator<<(raw_ostream &OS, + const FunctionEffectWithCondition &CFE); }; /// Support iteration in parallel through a pair of FunctionEffect and @@ -4940,6 +4951,85 @@ 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. + constexpr static size_t EndBitPos = FunctionEffect::KindCount; + 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); + } + + constexpr static FunctionEffect::Kind posToKind(size_t Pos) { + return static_cast(Pos); + } + + // 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 advanceToNextSetBit() { + while (Idx < EndBitPos && !Outer->KindBits.test(Idx)) + ++Idx; + } + + public: + iterator(); + iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) { + advanceToNextSetBit(); + } + bool operator==(const iterator &Other) const { return Idx == Other.Idx; } + bool operator!=(const iterator &Other) const { return Idx != Other.Idx; } + + iterator operator++() { + ++Idx; + advanceToNextSetBit(); + return *this; + } + + FunctionEffect operator*() const { + assert(Idx < EndBitPos && "Dereference of end iterator"); + 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); } + + void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); } + void insert(FunctionEffectsRef FX) { + for (FunctionEffect Item : FX.effects()) + insert(Item); + } + 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)); + } + void dump(llvm::raw_ostream &OS) const; + + static FunctionEffectKindSet difference(FunctionEffectKindSet LHS, + 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/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 53d88482698f00..b1512e22ee2dd4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -8482,9 +8482,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 7d81bdf827ea0c..41e719d4d57816 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]>; @@ -1566,10 +1571,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">; - // 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 dae357eb2d370e..aa393f2859ed1d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10972,19 +10972,68 @@ def warn_imp_cast_drops_unaligned : Warning< InGroup>; // Function effects +def warn_func_effect_violation : Warning< + "%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, DefaultIgnore; +def note_func_effect_violation : Note< + "%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< + "%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, 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' " + "%select{function|constructor|destructor|lambda|block}2 " + "'%3'">; +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-'%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">; +def note_func_effect_call_disallows_inference : Note< + "%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< + "%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. +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< "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/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 3d9f12d45d646e..bede971ce0191b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -457,55 +457,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 { @@ -546,6 +497,7 @@ class Sema final : public SemaBase { // 32. Constraints and Concepts (SemaConcept.cpp) // 33. Types (SemaType.cpp) // 34. FixIt Helpers (SemaFixItUtils.cpp) + // 35. Function Effects (SemaFunctionEffects.cpp) /// \name Semantic Analysis /// Implementations are in Sema.cpp @@ -851,30 +803,10 @@ class Sema final : public SemaBase { /// Warn when implicitly casting 0 to nullptr. void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E); - // ----- function effects --- - /// Warn when implicitly changing function effects. 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); - - /// 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 @@ -15062,6 +14994,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, @@ -15092,6 +15030,108 @@ class Sema final : public SemaBase { std::string getFixItZeroLiteralForType(QualType T, SourceLocation Loc) const; ///@} + + // + // + // ------------------------------------------------------------------------- + // + // + + /// \name Function Effects + /// Implementations are in SemaFunctionEffects.cpp + ///@{ +public: + struct FunctionEffectDiff { + enum class Kind { Added, Removed, ConditionMismatch }; + + FunctionEffect::Kind EffectKind; + Kind DiffKind; + std::optional + Old; // Invalid when 'Kind' is 'Added'. + std::optional + New; // Invalid when 'Kind' is 'Removed'. + + StringRef effectName() const { + if (Old) + return Old.value().Effect.name(); + return New.value().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 FunctionEffectDiffVector : public SmallVector { + /// Caller should short-circuit by checking for equality first. + FunctionEffectDiffVector(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; + + /// 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/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index bde19a09d6ae07..1af1f4a10db290 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -730,6 +730,10 @@ enum ASTRecordTypes { /// canonical declaration for the lambda class from the same module as /// enclosing function. FUNCTION_DECL_TO_LAMBDAS_MAP = 71, + + /// Record code for Sema's vector of functions/blocks with effects to + /// be verified. + DECLS_WITH_EFFECTS_TO_VERIFY = 72, }; /// 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 c1843218a4b8b1..aa88560b259a3b 100644 --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -984,6 +984,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 e21d41c8673143..198dd01b8d07a0 100644 --- a/clang/include/clang/Serialization/ASTWriter.h +++ b/clang/include/clang/Serialization/ASTWriter.h @@ -604,6 +604,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/AST/Type.cpp b/clang/lib/AST/Type.cpp index c703e43f12a9a6..6f4958801cfe82 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,53 +5131,41 @@ StringRef FunctionEffect::name() const { return "blocking"; case Kind::Allocating: return "allocating"; - case Kind::None: - return "(none)"; } llvm_unreachable("unknown effect kind"); } -bool FunctionEffect::canInferOnFunction(const Decl &Callee) const { +std::optional FunctionEffect::effectProhibitingInference( + const Decl &Callee, FunctionEffectKindSet 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) { + for (FunctionEffect Effect : CalleeFX) { // nonblocking/nonallocating cannot call allocating. - if (CalleeEC.Effect.kind() == Kind::Allocating) - return false; + if (Effect.kind() == Kind::Allocating) + return Effect; // nonblocking cannot call blocking. - if (kind() == Kind::NonBlocking && - CalleeEC.Effect.kind() == Kind::Blocking) - return false; + if (kind() == Kind::NonBlocking && Effect.kind() == Kind::Blocking) + return Effect; } - return true; + return std::nullopt; } case Kind::Allocating: case Kind::Blocking: - return false; - - case Kind::None: - assert(0 && "canInferOnFunction with None"); + assert(0 && "effectProhibitingInference with non-inferable effect kind"); break; } llvm_unreachable("unknown effect kind"); } bool FunctionEffect::shouldDiagnoseFunctionCall( - bool Direct, ArrayRef CalleeFX) const { + bool Direct, FunctionEffectKindSet CalleeFX) const { switch (kind()) { case Kind::NonAllocating: case Kind::NonBlocking: { const Kind CallerKind = kind(); - for (const auto &Effect : CalleeFX) { + for (FunctionEffect Effect : CalleeFX) { const Kind EK = Effect.kind(); // Does callee have same or stronger constraint? if (EK == CallerKind || @@ -5192,9 +5178,6 @@ bool FunctionEffect::shouldDiagnoseFunctionCall( case Kind::Allocating: case Kind::Blocking: return false; - case Kind::None: - assert(0 && "shouldDiagnoseFunctionCall with None"); - break; } llvm_unreachable("unknown effect kind"); } @@ -5300,21 +5283,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 << "}"; } @@ -5322,6 +5308,12 @@ 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{"; + llvm::interleaveComma(*this, OS); + OS << "}"; +} + FunctionEffectsRef FunctionEffectsRef::create(ArrayRef FX, ArrayRef Conds) { diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 2cee4f5ef6e99c..719c3a9312ec15 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -55,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/Sema.cpp b/clang/lib/Sema/Sema.cpp index 4be7dfbc293927..f05760428458b1 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -643,7 +643,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(); } @@ -1529,6 +1529,9 @@ void Sema::ActOnEndOfTranslationUnit() { AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl()); + if (Context.hasAnyFunctionEffects()) + 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 // at worst a rejects-valid bug. @@ -2774,156 +2777,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; - } - llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum"); - 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; - } - llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum"); - 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; - } - llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum"); - - 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/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index add28b370bcfcc..2bf610746bc317 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(), @@ -3839,6 +3839,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); + } } } } @@ -15677,6 +15682,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D, getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation) Diag(FD->getLocation(), diag::warn_function_def_in_objc_container); + maybeAddDeclWithEffects(FD); + return D; } @@ -20303,59 +20310,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/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index d8cdfcf8c6ec05..9cb2ed02a3f764 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18181,7 +18181,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)) { @@ -18194,7 +18194,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/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 2db9d1fc69ed1e..ae7bcedfb28c73 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -16212,6 +16212,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc, BlockScopeInfo *BSI = cast(FunctionScopes.back()); BlockDecl *BD = BSI->TheDecl; + maybeAddDeclWithEffects(BD); + if (BSI->HasImplicitReturnType) deduceClosureReturnType(*BSI); diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp new file mode 100644 index 00000000000000..0fb18d207a50ba --- /dev/null +++ b/clang/lib/Sema/SemaFunctionEffects.cpp @@ -0,0 +1,1577 @@ +//=== SemaFunctionEffects.cpp - Sema handling of function effects ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements Sema handling of function effects. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Sema/SemaInternal.h" + +#define DEBUG_TYPE "effectanalysis" + +using namespace clang; + +namespace { + +enum class ViolationID : uint8_t { + None = 0, // Sentinel for an empty Violation. + // These first 5 map to a %select{} in one of several FunctionEffects + // diagnostics, e.g. warn_func_effect_violation. + BaseDiagnosticIndex, + AllocatesMemory = BaseDiagnosticIndex, + ThrowsOrCatchesExceptions, + HasStaticLocalVariable, + AccessesThreadLocalVariable, + AccessesObjCMethodOrProperty, + + // These only apply to callees, where the analysis stops at the Decl. + DeclDisallowsInference, + + // These both apply to indirect calls. The difference is that sometimes + // we have an actual Decl (generally a variable) which is the function + // pointer being called, and sometimes, typically due to a cast, we only + // have an expression. + CallsDeclWithoutEffect, + CallsExprWithoutEffect, +}; + +// Information about the AST context in which a violation was found, so +// that diagnostics can point to the correct source. +class ViolationSite { +public: + enum class Kind : uint8_t { + Default, // Function body. + MemberInitializer, + DefaultArgExpr + }; + +private: + llvm::PointerIntPair Impl; + +public: + ViolationSite() = default; + + explicit ViolationSite(CXXDefaultArgExpr *E) + : Impl(E, Kind::DefaultArgExpr) {} + + Kind kind() const { return static_cast(Impl.getInt()); } + CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); } + + void setKind(Kind K) { Impl.setPointerAndInt(nullptr, K); } +}; + +// Represents a violation of the rules, potentially for the entire duration of +// the analysis phase, in order to refer to it when explaining why a caller has +// been made unsafe by a callee. Can be transformed into either a Diagnostic +// (warning or a note), depending on whether the violation pertains to a +// function failing to be verifed as holding an effect vs. a function failing to +// be inferred as holding that effect. +struct Violation { + FunctionEffect Effect; + std::optional + CalleeEffectPreventingInference; // Only for certain IDs; can be nullopt. + ViolationID ID = ViolationID::None; + ViolationSite Site; + SourceLocation Loc; + const Decl *Callee = + nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect. + + Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS, + SourceLocation Loc, const Decl *Callee = nullptr, + std::optional CalleeEffect = std::nullopt) + : Effect(Effect), CalleeEffectPreventingInference(CalleeEffect), ID(ID), + Site(VS), Loc(Loc), Callee(Callee) {} + + unsigned diagnosticSelectIndex() const { + return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex); + } +}; + +enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete }; +enum class CallableType : uint8_t { + // Unknown: probably function pointer. + Unknown, + Function, + Virtual, + Block +}; + +// Return whether a function's effects CAN be verified. +// The question of whether it SHOULD be verified is independent. +static bool functionIsVerifiable(const FunctionDecl *FD) { + if (FD->isTrivial()) { + // Otherwise `struct x { int a; };` would have an unverifiable default + // constructor. + return true; + } + return FD->hasBody(); +} + +static bool isNoexcept(const FunctionDecl *FD) { + const auto *FPT = FD->getType()->getAs(); + return FPT && (FPT->isNothrow() || FD->hasAttr()); +} + +// This list is probably incomplete. +// FIXME: Investigate: +// __builtin_eh_return? +// __builtin_allow_runtime_check? +// __builtin_unwind_init and other similar things that sound exception-related. +// va_copy? +// coroutines? +static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) { + FunctionEffectKindSet Result; + + switch (BuiltinID) { + case 0: // Not builtin. + default: // By default, builtins have no known effects. + break; + + // These allocate/deallocate heap memory. + case Builtin::ID::BI__builtin_calloc: + case Builtin::ID::BI__builtin_malloc: + case Builtin::ID::BI__builtin_realloc: + case Builtin::ID::BI__builtin_free: + case Builtin::ID::BI__builtin_operator_delete: + case Builtin::ID::BI__builtin_operator_new: + case Builtin::ID::BIaligned_alloc: + case Builtin::ID::BIcalloc: + case Builtin::ID::BImalloc: + case Builtin::ID::BImemalign: + case Builtin::ID::BIrealloc: + case Builtin::ID::BIfree: + + case Builtin::ID::BIfopen: + case Builtin::ID::BIpthread_create: + case Builtin::ID::BI_Block_object_dispose: + Result.insert(FunctionEffect(FunctionEffect::Kind::Allocating)); + break; + + // These block in some other way than allocating memory. + // longjmp() and friends are presumed unsafe because they are the moral + // equivalent of throwing a C++ exception, which is unsafe. + case Builtin::ID::BIlongjmp: + case Builtin::ID::BI_longjmp: + case Builtin::ID::BIsiglongjmp: + case Builtin::ID::BI__builtin_longjmp: + case Builtin::ID::BIobjc_exception_throw: + + // Objective-C runtime. + case Builtin::ID::BIobjc_msgSend: + case Builtin::ID::BIobjc_msgSend_fpret: + case Builtin::ID::BIobjc_msgSend_fp2ret: + case Builtin::ID::BIobjc_msgSend_stret: + case Builtin::ID::BIobjc_msgSendSuper: + case Builtin::ID::BIobjc_getClass: + case Builtin::ID::BIobjc_getMetaClass: + case Builtin::ID::BIobjc_enumerationMutation: + case Builtin::ID::BIobjc_assign_ivar: + case Builtin::ID::BIobjc_assign_global: + case Builtin::ID::BIobjc_sync_enter: + case Builtin::ID::BIobjc_sync_exit: + case Builtin::ID::BINSLog: + case Builtin::ID::BINSLogv: + + // stdio.h + case Builtin::ID::BIfread: + case Builtin::ID::BIfwrite: + + // stdio.h: printf family. + case Builtin::ID::BIprintf: + case Builtin::ID::BI__builtin_printf: + case Builtin::ID::BIfprintf: + case Builtin::ID::BIsnprintf: + case Builtin::ID::BIsprintf: + case Builtin::ID::BIvprintf: + case Builtin::ID::BIvfprintf: + case Builtin::ID::BIvsnprintf: + case Builtin::ID::BIvsprintf: + + // stdio.h: scanf family. + case Builtin::ID::BIscanf: + case Builtin::ID::BIfscanf: + case Builtin::ID::BIsscanf: + case Builtin::ID::BIvscanf: + case Builtin::ID::BIvfscanf: + case Builtin::ID::BIvsscanf: + Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking)); + break; + } + + return Result; +} + +// Transitory, more extended information about a callable, which can be a +// function, block, or function pointer. +struct CallableInfo { + // CDecl holds the function's definition, if any. + // FunctionDecl if CallableType::Function or Virtual + // BlockDecl if CallableType::Block + const Decl *CDecl; + + // Remember whether the callable is a function, block, virtual method, + // or (presumed) function pointer. + CallableType CType = CallableType::Unknown; + + // Remember whether the callable is an operator new or delete function, + // so that calls to them are reported more meaningfully, as memory + // allocations. + SpecialFuncType FuncType = SpecialFuncType::None; + + // We inevitably want to know the callable's declared effects, so cache them. + FunctionEffectKindSet Effects; + + CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None) + : CDecl(&CD), FuncType(FT) { + FunctionEffectsRef DeclEffects; + if (auto *FD = dyn_cast(CDecl)) { + // Use the function's definition, if any. + if (const FunctionDecl *Def = FD->getDefinition()) + CDecl = FD = Def; + CType = CallableType::Function; + if (auto *Method = dyn_cast(FD); + Method && Method->isVirtual()) + CType = CallableType::Virtual; + DeclEffects = FD->getFunctionEffects(); + } else if (auto *BD = dyn_cast(CDecl)) { + CType = CallableType::Block; + DeclEffects = BD->getFunctionEffects(); + } else if (auto *VD = dyn_cast(CDecl)) { + // ValueDecl is function, enum, or variable, so just look at its type. + DeclEffects = FunctionEffectsRef::get(VD->getType()); + } + Effects = FunctionEffectKindSet(DeclEffects); + } + + CallableType type() const { return CType; } + + bool isCalledDirectly() const { + return CType == CallableType::Function || CType == CallableType::Block; + } + + bool isVerifiable() const { + switch (CType) { + case CallableType::Unknown: + case CallableType::Virtual: + return false; + case CallableType::Block: + return true; + case CallableType::Function: + return functionIsVerifiable(dyn_cast(CDecl)); + } + llvm_unreachable("undefined CallableType"); + } + + /// Generate a name for logging and diagnostics. + std::string getNameForDiagnostic(Sema &S) const { + std::string Name; + llvm::raw_string_ostream OS(Name); + + if (auto *FD = dyn_cast(CDecl)) + FD->getNameForDiagnostic(OS, S.getPrintingPolicy(), + /*Qualified=*/true); + else if (auto *BD = dyn_cast(CDecl)) + OS << "(block " << BD->getBlockManglingNumber() << ")"; + else if (auto *VD = dyn_cast(CDecl)) + VD->printQualifiedName(OS); + return Name; + } +}; + +// ---------- +// Map effects to single Violations, to hold the first (of potentially many) +// violations pertaining to an effect, per function. +class EffectToViolationMap { + // Since we currently only have a tiny number of effects (typically no more + // than 1), use a SmallVector with an inline capacity of 1. Since it + // is often empty, use a unique_ptr to the SmallVector. + // Note that Violation itself contains a FunctionEffect which is the key. + // FIXME: Is there a way to simplify this using existing data structures? + using ImplVec = llvm::SmallVector; + std::unique_ptr Impl; + +public: + // Insert a new Violation if we do not already have one for its effect. + void maybeInsert(const Violation &Viol) { + if (Impl == nullptr) + Impl = std::make_unique(); + else if (lookup(Viol.Effect) != nullptr) + return; + + Impl->push_back(Viol); + } + + const Violation *lookup(FunctionEffect Key) { + if (Impl == nullptr) + return nullptr; + + auto *Iter = llvm::find_if( + *Impl, [&](const auto &Item) { return Item.Effect == Key; }); + return Iter != Impl->end() ? &*Iter : nullptr; + } + + size_t size() const { return Impl ? Impl->size() : 0; } +}; + +// ---------- +// State pertaining to a function whose AST is walked and whose effect analysis +// is dependent on a subsequent analysis of other functions. +class PendingFunctionAnalysis { + friend class CompleteFunctionAnalysis; + +public: + struct DirectCall { + const Decl *Callee; + SourceLocation CallLoc; + // Not all recursive calls are detected, just enough + // to break cycles. + bool Recursed = false; + ViolationSite VSite; + + DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite) + : Callee(D), CallLoc(CallLoc), VSite(VSite) {} + }; + + // We always have two disjoint sets of effects to verify: + // 1. Effects declared explicitly by this function. + // 2. All other inferrable effects needing verification. + FunctionEffectKindSet DeclaredVerifiableEffects; + FunctionEffectKindSet EffectsToInfer; + +private: + // Violations pertaining to the function's explicit effects. + SmallVector ViolationsForExplicitEffects; + + // Violations pertaining to other, non-explicit, inferrable effects. + EffectToViolationMap InferrableEffectToFirstViolation; + + // These unverified direct calls are what keeps the analysis "pending", + // until the callees can be verified. + SmallVector UnverifiedDirectCalls; + +public: + PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo, + FunctionEffectKindSet AllInferrableEffectsToVerify) + : DeclaredVerifiableEffects(CInfo.Effects) { + // Check for effects we are not allowed to infer. + FunctionEffectKindSet InferrableEffects; + + for (FunctionEffect effect : AllInferrableEffectsToVerify) { + std::optional ProblemCalleeEffect = + effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects); + if (!ProblemCalleeEffect) + InferrableEffects.insert(effect); + else { + // Add a Violation for this effect if a caller were to + // try to infer it. + InferrableEffectToFirstViolation.maybeInsert(Violation( + effect, ViolationID::DeclDisallowsInference, ViolationSite{}, + CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect)); + } + } + // InferrableEffects is now the set of inferrable effects which are not + // prohibited. + EffectsToInfer = FunctionEffectKindSet::difference( + InferrableEffects, DeclaredVerifiableEffects); + } + + // Hide the way that Violations for explicitly required effects vs. inferred + // ones are handled differently. + void checkAddViolation(bool Inferring, const Violation &NewViol) { + if (!Inferring) + ViolationsForExplicitEffects.push_back(NewViol); + else + InferrableEffectToFirstViolation.maybeInsert(NewViol); + } + + void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc, + ViolationSite VSite) { + UnverifiedDirectCalls.emplace_back(D, CallLoc, VSite); + } + + // Analysis is complete when there are no unverified direct calls. + bool isComplete() const { return UnverifiedDirectCalls.empty(); } + + const Violation *violationForInferrableEffect(FunctionEffect effect) { + return InferrableEffectToFirstViolation.lookup(effect); + } + + // Mutable because caller may need to set a DirectCall's Recursing flag. + MutableArrayRef unverifiedCalls() { + assert(!isComplete()); + return UnverifiedDirectCalls; + } + + ArrayRef getSortedViolationsForExplicitEffects(SourceManager &SM) { + if (!ViolationsForExplicitEffects.empty()) + llvm::sort(ViolationsForExplicitEffects, + [&SM](const Violation &LHS, const Violation &RHS) { + return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc); + }); + return ViolationsForExplicitEffects; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) const { + OS << "Pending: Declared "; + DeclaredVerifiableEffects.dump(OS); + OS << ", " << ViolationsForExplicitEffects.size() << " violations; "; + OS << " Infer "; + EffectsToInfer.dump(OS); + OS << ", " << InferrableEffectToFirstViolation.size() << " violations"; + if (!UnverifiedDirectCalls.empty()) { + OS << "; Calls: "; + for (const DirectCall &Call : UnverifiedDirectCalls) { + CallableInfo CI(*Call.Callee); + OS << " " << CI.getNameForDiagnostic(SemaRef); + } + } + OS << "\n"; + } +}; + +// ---------- +class CompleteFunctionAnalysis { + // Current size: 2 pointers +public: + // Has effects which are both the declared ones -- not to be inferred -- plus + // ones which have been successfully inferred. These are all considered + // "verified" for the purposes of callers; any issue with verifying declared + // effects has already been reported and is not the problem of any caller. + FunctionEffectKindSet VerifiedEffects; + +private: + // This is used to generate notes about failed inference. + EffectToViolationMap InferrableEffectToFirstViolation; + +public: + // The incoming Pending analysis is consumed (member(s) are moved-from). + CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending, + FunctionEffectKindSet DeclaredEffects, + FunctionEffectKindSet AllInferrableEffectsToVerify) + : VerifiedEffects(DeclaredEffects) { + for (FunctionEffect effect : AllInferrableEffectsToVerify) + if (Pending.violationForInferrableEffect(effect) == nullptr) + VerifiedEffects.insert(effect); + + InferrableEffectToFirstViolation = + std::move(Pending.InferrableEffectToFirstViolation); + } + + const Violation *firstViolationForEffect(FunctionEffect Effect) { + return InferrableEffectToFirstViolation.lookup(Effect); + } + + void dump(llvm::raw_ostream &OS) const { + OS << "Complete: Verified "; + VerifiedEffects.dump(OS); + OS << "; Infer "; + OS << InferrableEffectToFirstViolation.size() << " violations\n"; + } +}; + +// ========== +class Analyzer { + Sema &S; + + // Subset of Sema.AllEffectsToVerify + FunctionEffectKindSet AllInferrableEffectsToVerify; + + using FuncAnalysisPtr = + llvm::PointerUnion; + + // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger + // than complete state, so use different objects to represent them. + // The state pointers are owned by the container. + class AnalysisMap : llvm::DenseMap { + using Base = llvm::DenseMap; + + public: + ~AnalysisMap(); + + // Use non-public inheritance in order to maintain the invariant + // that lookups and insertions are via the canonical Decls. + + FuncAnalysisPtr lookup(const Decl *Key) const { + return Base::lookup(Key->getCanonicalDecl()); + } + + FuncAnalysisPtr &operator[](const Decl *Key) { + return Base::operator[](Key->getCanonicalDecl()); + } + + /// Shortcut for the case where we only care about completed analysis. + CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const { + if (FuncAnalysisPtr AP = lookup(D); + isa_and_nonnull(AP)) + return AP.get(); + return nullptr; + } + + void dump(Sema &SemaRef, llvm::raw_ostream &OS) { + OS << "\nAnalysisMap:\n"; + for (const auto &item : *this) { + CallableInfo CI(*item.first); + const auto AP = item.second; + OS << item.first << " " << CI.getNameForDiagnostic(SemaRef) << " : "; + if (AP.isNull()) { + OS << "null\n"; + } else if (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) : S(S) {} + + void run(const TranslationUnitDecl &TU) { + // Gather all of the effects to be verified to see what operations need to + // be checked, and to see which ones are inferrable. + for (FunctionEffect Effect : S.AllEffectsToVerify) { + const FunctionEffect::Flags Flags = Effect.flags(); + if (Flags & FunctionEffect::FE_InferrableOnCallees) + AllInferrableEffectsToVerify.insert(Effect); + } + LLVM_DEBUG(llvm::dbgs() << "AllInferrableEffectsToVerify: "; + AllInferrableEffectsToVerify.dump(llvm::dbgs()); + llvm::dbgs() << "\n";); + + // We can use DeclsWithEffectsToVerify as a stack for a + // depth-first traversal; there's no need for a second container. But first, + // reverse it, so when working from the end, Decls are verified in the order + // they are declared. + SmallVector &VerificationQueue = S.DeclsWithEffectsToVerify; + std::reverse(VerificationQueue.begin(), VerificationQueue.end()); + + while (!VerificationQueue.empty()) { + const Decl *D = VerificationQueue.back(); + if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) { + if (auto *Pending = AP.dyn_cast()) { + // All children have been traversed; finish analysis. + finishPendingAnalysis(D, Pending); + } + VerificationQueue.pop_back(); + continue; + } + + // Not previously visited; begin a new analysis for this Decl. + PendingFunctionAnalysis *Pending = verifyDecl(D); + if (Pending == nullptr) { + // Completed now. + VerificationQueue.pop_back(); + continue; + } + + // Analysis remains pending because there are direct callees to be + // verified first. Push them onto the queue. + for (PendingFunctionAnalysis::DirectCall &Call : + Pending->unverifiedCalls()) { + FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee); + if (AP.isNull()) { + VerificationQueue.push_back(Call.Callee); + continue; + } + + // This indicates recursion (not necessarily direct). For the + // purposes of effect analysis, we can just ignore it since + // no effects forbid recursion. + assert(isa(AP)); + Call.Recursed = true; + } + } + } + +private: + // Verify a single Decl. Return the pending structure if that was the result, + // else null. This method must not recurse. + PendingFunctionAnalysis *verifyDecl(const Decl *D) { + CallableInfo CInfo(*D); + bool isExternC = false; + + if (const FunctionDecl *FD = dyn_cast(D)) + isExternC = FD->getCanonicalDecl()->isExternCContext(); + + // For C++, with non-extern "C" linkage only - if any of the Decl's declared + // effects forbid throwing (e.g. nonblocking) then the function should also + // be declared noexcept. + if (S.getLangOpts().CPlusPlus && !isExternC) { + for (FunctionEffect Effect : CInfo.Effects) { + if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow)) + continue; + + bool IsNoexcept = false; + if (auto *FD = D->getAsFunction()) { + IsNoexcept = isNoexcept(FD); + } else if (auto *BD = dyn_cast(D)) { + if (auto *TSI = BD->getSignatureAsWritten()) { + auto *FPT = TSI->getType()->getAs(); + IsNoexcept = FPT->isNothrow() || BD->hasAttr(); + } + } + if (!IsNoexcept) + S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept) + << GetCallableDeclKind(D, nullptr) << Effect.name(); + break; + } + } + + // Build a PendingFunctionAnalysis on the stack. If it turns out to be + // complete, we'll have avoided a heap allocation; if it's incomplete, it's + // a fairly trivial move to a heap-allocated object. + PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify); + + LLVM_DEBUG(llvm::dbgs() + << "\nVerifying " << CInfo.getNameForDiagnostic(S) << " "; + FAnalysis.dump(S, llvm::dbgs());); + + FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo); + + Visitor.run(); + if (FAnalysis.isComplete()) { + completeAnalysis(CInfo, std::move(FAnalysis)); + return nullptr; + } + // Move the pending analysis to the heap and save it in the map. + PendingFunctionAnalysis *PendingPtr = + new PendingFunctionAnalysis(std::move(FAnalysis)); + DeclAnalysis[D] = PendingPtr; + LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n"; + DeclAnalysis.dump(S, llvm::dbgs());); + return PendingPtr; + } + + // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis, + // inserted in the container. + void completeAnalysis(const CallableInfo &CInfo, + PendingFunctionAnalysis &&Pending) { + if (ArrayRef Viols = + Pending.getSortedViolationsForExplicitEffects(S.getSourceManager()); + !Viols.empty()) + emitDiagnostics(Viols, CInfo); + + CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis( + S.getASTContext(), std::move(Pending), CInfo.Effects, + AllInferrableEffectsToVerify); + DeclAnalysis[CInfo.CDecl] = CompletePtr; + LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n"; + DeclAnalysis.dump(S, llvm::dbgs());); + } + + // Called after all direct calls requiring inference have been found -- or + // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without + // the possibility of inference. Deletes Pending. + void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) { + CallableInfo Caller(*D); + LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for " + << Caller.getNameForDiagnostic(S) << " : "; + Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";); + for (const PendingFunctionAnalysis::DirectCall &Call : + Pending->unverifiedCalls()) { + if (Call.Recursed) + continue; + + CallableInfo Callee(*Call.Callee); + followCall(Caller, *Pending, Callee, Call.CallLoc, + /*AssertNoFurtherInference=*/true, Call.VSite); + } + completeAnalysis(Caller, std::move(*Pending)); + delete Pending; + } + + // Here we have a call to a Decl, either explicitly via a CallExpr or some + // other AST construct. PFA pertains to the caller. + void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA, + const CallableInfo &Callee, SourceLocation CallLoc, + bool AssertNoFurtherInference, ViolationSite VSite) { + const bool DirectCall = Callee.isCalledDirectly(); + + // Initially, the declared effects; inferred effects will be added. + FunctionEffectKindSet CalleeEffects = Callee.Effects; + + bool IsInferencePossible = DirectCall; + + if (DirectCall) + if (CompleteFunctionAnalysis *CFA = + DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) { + // Combine declared effects with those which may have been inferred. + CalleeEffects.insert(CFA->VerifiedEffects); + IsInferencePossible = false; // We've already traversed it. + } + + if (AssertNoFurtherInference) { + assert(!IsInferencePossible); + } + + if (!Callee.isVerifiable()) + IsInferencePossible = false; + + LLVM_DEBUG(llvm::dbgs() + << "followCall from " << Caller.getNameForDiagnostic(S) + << " to " << Callee.getNameForDiagnostic(S) + << "; verifiable: " << Callee.isVerifiable() << "; callee "; + CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n"; + llvm::dbgs() << " callee " << Callee.CDecl << " canonical " + << Callee.CDecl->getCanonicalDecl() << "\n";); + + auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { + if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects)) + return; + + // If inference is not allowed, or the target is indirect (virtual + // method/function ptr?), generate a Violation now. + if (!IsInferencePossible || + !(Effect.flags() & FunctionEffect::FE_InferrableOnCallees)) { + if (Callee.FuncType == SpecialFuncType::None) + PFA.checkAddViolation(Inferring, + {Effect, ViolationID::CallsDeclWithoutEffect, + VSite, CallLoc, Callee.CDecl}); + else + PFA.checkAddViolation( + Inferring, + {Effect, ViolationID::AllocatesMemory, VSite, CallLoc}); + } else { + // Inference is allowed and necessary; defer it. + PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite); + } + }; + + for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects) + Check1Effect(Effect, false); + + for (FunctionEffect Effect : PFA.EffectsToInfer) + Check1Effect(Effect, true); + } + + // Describe a callable Decl for a diagnostic. + // (Not an enum class because the value is always converted to an integer for + // use in a diagnostic.) + enum CallableDeclKind { + CDK_Function, + CDK_Constructor, + CDK_Destructor, + CDK_Lambda, + CDK_Block, + CDK_MemberInitializer, + }; + + // Describe a call site or target using an enum mapping to a %select{} + // in a diagnostic, e.g. warn_func_effect_violation, + // warn_perf_constraint_implies_noexcept, and others. + static CallableDeclKind GetCallableDeclKind(const Decl *D, + const Violation *V) { + if (V != nullptr && + V->Site.kind() == ViolationSite::Kind::MemberInitializer) + return CDK_MemberInitializer; + if (isa(D)) + return CDK_Block; + if (auto *Method = dyn_cast(D)) { + if (isa(D)) + return CDK_Constructor; + if (isa(D)) + return CDK_Destructor; + const CXXRecordDecl *Rec = Method->getParent(); + if (Rec->isLambda()) + return CDK_Lambda; + } + return CDK_Function; + }; + + // Should only be called when function's analysis is determined to be + // complete. + void emitDiagnostics(ArrayRef Viols, const CallableInfo &CInfo) { + if (Viols.empty()) + return; + + auto MaybeAddTemplateNote = [&](const Decl *D) { + if (const FunctionDecl *FD = dyn_cast(D)) { + while (FD != nullptr && FD->isTemplateInstantiation()) { + S.Diag(FD->getPointOfInstantiation(), + diag::note_func_effect_from_template); + FD = FD->getTemplateInstantiationPattern(); + } + } + }; + + // For note_func_effect_call_indirect. + enum { Indirect_VirtualMethod, Indirect_FunctionPtr }; + + auto MaybeAddSiteContext = [&](const Decl *D, const Violation &V) { + // If a violation site is a member initializer, add a note pointing to + // the constructor which invoked it. + if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) { + unsigned ImplicitCtor = 0; + if (auto *Ctor = dyn_cast(D); + Ctor && Ctor->isImplicit()) + ImplicitCtor = 1; + S.Diag(D->getLocation(), diag::note_func_effect_in_constructor) + << ImplicitCtor; + } + + // If a violation site is a default argument expression, add a note + // pointing to the call site using the default argument. + else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr) + S.Diag(V.Site.defaultArgExpr()->getUsedLocation(), + diag::note_in_evaluating_default_argument); + }; + + // Top-level violations are warnings. + for (const Violation &Viol1 : Viols) { + StringRef effectName = Viol1.Effect.name(); + switch (Viol1.ID) { + case ViolationID::None: + case ViolationID::DeclDisallowsInference: // Shouldn't happen + // here. + llvm_unreachable("Unexpected violation kind"); + break; + case ViolationID::AllocatesMemory: + case ViolationID::ThrowsOrCatchesExceptions: + case ViolationID::HasStaticLocalVariable: + case ViolationID::AccessesThreadLocalVariable: + case ViolationID::AccessesObjCMethodOrProperty: + S.Diag(Viol1.Loc, diag::warn_func_effect_violation) + << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName + << Viol1.diagnosticSelectIndex(); + MaybeAddSiteContext(CInfo.CDecl, Viol1); + MaybeAddTemplateNote(CInfo.CDecl); + break; + case ViolationID::CallsExprWithoutEffect: + S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect) + << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName; + MaybeAddSiteContext(CInfo.CDecl, Viol1); + MaybeAddTemplateNote(CInfo.CDecl); + break; + + case ViolationID::CallsDeclWithoutEffect: { + CallableInfo CalleeInfo(*Viol1.Callee); + std::string CalleeName = CalleeInfo.getNameForDiagnostic(S); + + S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect) + << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName + << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << CalleeName; + MaybeAddSiteContext(CInfo.CDecl, Viol1); + MaybeAddTemplateNote(CInfo.CDecl); + + // Emit notes explaining the transitive chain of inferences: Why isn't + // the callee safe? + for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) { + std::optional MaybeNextCallee; + CompleteFunctionAnalysis *Completed = + DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl); + if (Completed == nullptr) { + // No result - could be + // - non-inline and extern + // - indirect (virtual or through function pointer) + // - effect has been explicitly disclaimed (e.g. "blocking") + + CallableType CType = CalleeInfo.type(); + if (CType == CallableType::Virtual) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_indirect) + << Indirect_VirtualMethod << effectName; + else if (CType == CallableType::Unknown) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_indirect) + << Indirect_FunctionPtr << effectName; + else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind())) + S.Diag(Callee->getLocation(), + diag::note_func_effect_call_disallows_inference) + << GetCallableDeclKind(CInfo.CDecl, nullptr) << effectName + << FunctionEffect(Viol1.Effect.oppositeKind()).name(); + else if (const FunctionDecl *FD = dyn_cast(Callee); + FD == nullptr || FD->getBuiltinID() == 0) { + // A builtin callee generally doesn't have a useful source + // location at which to insert a note. + S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) + << effectName; + } + break; + } + const Violation *PtrViol2 = + Completed->firstViolationForEffect(Viol1.Effect); + if (PtrViol2 == nullptr) + break; + + const Violation &Viol2 = *PtrViol2; + switch (Viol2.ID) { + case ViolationID::None: + llvm_unreachable("Unexpected violation kind"); + break; + case ViolationID::DeclDisallowsInference: + S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference) + << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << effectName + << Viol2.CalleeEffectPreventingInference->name(); + break; + case ViolationID::CallsExprWithoutEffect: + S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect) + << Indirect_FunctionPtr << effectName; + break; + case ViolationID::AllocatesMemory: + case ViolationID::ThrowsOrCatchesExceptions: + case ViolationID::HasStaticLocalVariable: + case ViolationID::AccessesThreadLocalVariable: + case ViolationID::AccessesObjCMethodOrProperty: + S.Diag(Viol2.Loc, diag::note_func_effect_violation) + << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName + << Viol2.diagnosticSelectIndex(); + MaybeAddSiteContext(CalleeInfo.CDecl, Viol2); + break; + case ViolationID::CallsDeclWithoutEffect: + MaybeNextCallee.emplace(*Viol2.Callee); + S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect) + << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName + << GetCallableDeclKind(Viol2.Callee, nullptr) + << MaybeNextCallee->getNameForDiagnostic(S); + break; + } + MaybeAddTemplateNote(Callee); + Callee = Viol2.Callee; + if (MaybeNextCallee) { + CalleeInfo = *MaybeNextCallee; + CalleeName = CalleeInfo.getNameForDiagnostic(S); + } + } + } break; + } + } + } + + // ---------- + // This AST visitor is used to traverse the body of a function during effect + // verification. This happens in 2 situations: + // [1] The function has declared effects which need to be validated. + // [2] The function has not explicitly declared an effect in question, and is + // being checked for implicit conformance. + // + // Violations are always routed to a PendingFunctionAnalysis. + struct FunctionBodyASTVisitor : RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + + Analyzer &Outer; + PendingFunctionAnalysis &CurrentFunction; + CallableInfo &CurrentCaller; + ViolationSite VSite; + + FunctionBodyASTVisitor(Analyzer &Outer, + PendingFunctionAnalysis &CurrentFunction, + CallableInfo &CurrentCaller) + : Outer(Outer), CurrentFunction(CurrentFunction), + CurrentCaller(CurrentCaller) {} + + // -- Entry point -- + void run() { + // The target function may have implicit code paths beyond the + // body: member and base destructors. Visit these first. + if (auto *Dtor = dyn_cast(CurrentCaller.CDecl)) + followDestructor(dyn_cast(Dtor->getParent()), Dtor); + + // Do an AST traversal of the function/block body + TraverseDecl(const_cast(CurrentCaller.CDecl)); + } + + // -- Methods implementing common logic -- + + // Handle a language construct forbidden by some effects. Only effects whose + // flags include the specified flag receive a violation. \p Flag describes + // the construct. + void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, + ViolationID VID, SourceLocation Loc, + const Decl *Callee = nullptr) { + // If there are any declared verifiable effects which forbid the construct + // represented by the flag, store just one violation. + for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) { + if (Effect.flags() & Flag) { + addViolation(/*inferring=*/false, Effect, VID, Loc, Callee); + break; + } + } + // For each inferred effect which forbids the construct, store a + // violation, if we don't already have a violation for that effect. + for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) + if (Effect.flags() & Flag) + addViolation(/*inferring=*/true, Effect, VID, Loc, Callee); + } + + void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID, + SourceLocation Loc, const Decl *Callee = nullptr) { + CurrentFunction.checkAddViolation( + Inferring, Violation(Effect, VID, VSite, Loc, Callee)); + } + + // Here we have a call to a Decl, either explicitly via a CallExpr or some + // other AST construct. CallableInfo pertains to the callee. + void followCall(CallableInfo &CI, SourceLocation CallLoc) { + // Check for a call to a builtin function, whose effects are + // handled specially. + if (const auto *FD = dyn_cast(CI.CDecl)) { + if (unsigned BuiltinID = FD->getBuiltinID()) { + CI.Effects = getBuiltinFunctionEffects(BuiltinID); + if (CI.Effects.empty()) { + // A builtin with no known effects is assumed safe. + return; + } + // A builtin WITH effects doesn't get any special treatment for + // being noreturn/noexcept, e.g. longjmp(), so we skip the check + // below. + } else { + // If the callee is both `noreturn` and `noexcept`, it presumably + // terminates. Ignore it for the purposes of effect analysis. + // If not C++, `noreturn` alone is sufficient. + if (FD->isNoReturn() && + (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD))) + return; + } + } + + Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, + /*AssertNoFurtherInference=*/false, VSite); + } + + void checkIndirectCall(CallExpr *Call, QualType CalleeType) { + auto *FPT = + CalleeType->getAs(); // Null if FunctionType. + FunctionEffectKindSet CalleeEffects; + if (FPT) + CalleeEffects.insert(FPT->getFunctionEffects()); + + auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) { + if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall( + /*direct=*/false, CalleeEffects)) + addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect, + Call->getBeginLoc()); + }; + + for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) + Check1Effect(Effect, false); + + for (FunctionEffect Effect : CurrentFunction.EffectsToInfer) + Check1Effect(Effect, true); + } + + // This destructor's body should be followed by the caller, but here we + // follow the field and base destructors. + void followDestructor(const CXXRecordDecl *Rec, + const CXXDestructorDecl *Dtor) { + SourceLocation DtorLoc = Dtor->getLocation(); + for (const FieldDecl *Field : Rec->fields()) + followTypeDtor(Field->getType(), DtorLoc); + + if (const auto *Class = dyn_cast(Rec)) + for (const CXXBaseSpecifier &Base : Class->bases()) + followTypeDtor(Base.getType(), DtorLoc); + } + + void followTypeDtor(QualType QT, SourceLocation CallSite) { + const Type *Ty = QT.getTypePtr(); + while (Ty->isArrayType()) { + const ArrayType *Arr = Ty->getAsArrayTypeUnsafe(); + QT = Arr->getElementType(); + Ty = QT.getTypePtr(); + } + + if (Ty->isRecordType()) { + if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) { + if (CXXDestructorDecl *Dtor = Class->getDestructor(); + Dtor && !Dtor->isDeleted()) { + CallableInfo CI(*Dtor); + followCall(CI, CallSite); + } + } + } + } + + // -- Methods for use of RecursiveASTVisitor -- + + bool shouldVisitImplicitCode() const { return true; } + + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitCXXThrowExpr(CXXThrowExpr *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + ViolationID::ThrowsOrCatchesExceptions, + Throw->getThrowLoc()); + return true; + } + + bool VisitCXXCatchStmt(CXXCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + ViolationID::ThrowsOrCatchesExceptions, + Catch->getCatchLoc()); + return true; + } + + bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow, + ViolationID::ThrowsOrCatchesExceptions, + Throw->getThrowLoc()); + return true; + } + + bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + ViolationID::ThrowsOrCatchesExceptions, + Catch->getAtCatchLoc()); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend, + ViolationID::AccessesObjCMethodOrProperty, + Msg->getBeginLoc()); + return true; + } + + bool VisitSEHExceptStmt(SEHExceptStmt *Exc) { + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch, + ViolationID::ThrowsOrCatchesExceptions, + Exc->getExceptLoc()); + return true; + } + + bool VisitCallExpr(CallExpr *Call) { + LLVM_DEBUG(llvm::dbgs() + << "VisitCallExpr : " + << Call->getBeginLoc().printToString(Outer.S.SourceMgr) + << "\n";); + + Expr *CalleeExpr = Call->getCallee(); + if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) { + CallableInfo CI(*Callee); + followCall(CI, Call->getBeginLoc()); + return true; + } + + if (isa(CalleeExpr)) { + // Just destroying a scalar, fine. + return true; + } + + // No Decl, just an Expr. Just check based on its type. + checkIndirectCall(Call, CalleeExpr->getType()); + + return true; + } + + bool VisitVarDecl(VarDecl *Var) { + LLVM_DEBUG(llvm::dbgs() + << "VisitVarDecl : " + << Var->getBeginLoc().printToString(Outer.S.SourceMgr) + << "\n";); + + if (Var->isStaticLocal()) + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars, + ViolationID::HasStaticLocalVariable, + Var->getLocation()); + + const QualType::DestructionKind DK = + Var->needsDestruction(Outer.S.getASTContext()); + if (DK == QualType::DK_cxx_destructor) + followTypeDtor(Var->getType(), Var->getLocation()); + return true; + } + + bool VisitCXXNewExpr(CXXNewExpr *New) { + // RecursiveASTVisitor does not visit the implicit call to operator new. + if (FunctionDecl *FD = New->getOperatorNew()) { + CallableInfo CI(*FD, SpecialFuncType::OperatorNew); + followCall(CI, New->getBeginLoc()); + } + + // It's a bit excessive to check operator delete here, since it's + // just a fallback for operator new followed by a failed constructor. + // We could check it via New->getOperatorDelete(). + + // It DOES however visit the called constructor + return true; + } + + bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) { + // RecursiveASTVisitor does not visit the implicit call to operator + // delete. + if (FunctionDecl *FD = Delete->getOperatorDelete()) { + CallableInfo CI(*FD, SpecialFuncType::OperatorDelete); + followCall(CI, Delete->getBeginLoc()); + } + + // It DOES however visit the called destructor + + return true; + } + + bool VisitCXXConstructExpr(CXXConstructExpr *Construct) { + LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : " + << Construct->getBeginLoc().printToString( + Outer.S.SourceMgr) + << "\n";); + + // RecursiveASTVisitor does not visit the implicit call to the + // constructor. + const CXXConstructorDecl *Ctor = Construct->getConstructor(); + CallableInfo CI(*Ctor); + followCall(CI, Construct->getLocation()); + + return true; + } + + bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { + ViolationSite PrevVS = VSite; + if (Init->isAnyMemberInitializer()) + VSite.setKind(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.kind() == ViolationSite::Kind::Default) + VSite = ViolationSite{E}; + + bool Result = Base::TraverseCXXDefaultArgExpr(E); + 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 + // false from shouldVisitLambdaBody()? Because we need to visit a lambda's + // body when we are verifying the lambda itself; we only want to skip it + // in the context of the outer function. + for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) + TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I, + Lambda->capture_init_begin()[I]); + + return true; + } + + bool TraverseBlockExpr(BlockExpr * /*unused*/) { + // TODO: are the capture expressions (ctor call?) safe? + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *E) { + const ValueDecl *Val = E->getDecl(); + if (const auto *Var = dyn_cast(Val)) { + if (Var->getTLSKind() != VarDecl::TLS_None) { + // At least on macOS, thread-local variables are initialized on + // first access, including a heap allocation. + diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars, + ViolationID::AccessesThreadLocalVariable, + E->getLocation()); + } + } + return true; + } + + bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) { + 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; } + + // Skip concept requirements since they don't generate code. + bool TraverseConceptRequirement(concepts::Requirement *R) { 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 { + +bool Sema::diagnoseConflictingFunctionEffect( + const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC, + SourceLocation NewAttrLoc) { + // If the new effect has a condition, we can't detect conflicts until the + // condition is resolved. + if (NewEC.Cond.getCondition() != nullptr) + return false; + + // Diagnose the new attribute as incompatible with a previous one. + auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) { + Diag(NewAttrLoc, diag::err_attributes_are_not_compatible) + << ("'" + NewEC.description() + "'") + << ("'" + PrevEC.description() + "'") << false; + // We don't necessarily have the location of the previous attribute, + // so no note. + return true; + }; + + // Compare against previous attributes. + FunctionEffect::Kind NewKind = NewEC.Effect.kind(); + + for (const FunctionEffectWithCondition &PrevEC : FX) { + // Again, can't check yet when the effect is conditional. + if (PrevEC.Cond.getCondition() != nullptr) + continue; + + FunctionEffect::Kind PrevKind = PrevEC.Effect.kind(); + // Note that we allow PrevKind == NewKind; it's redundant and ignored. + + if (PrevEC.Effect.oppositeKind() == NewKind) + return Incompatible(PrevEC); + + // A new allocating is incompatible with a previous nonblocking. + if (PrevKind == FunctionEffect::Kind::NonBlocking && + NewKind == FunctionEffect::Kind::Allocating) + return Incompatible(PrevEC); + + // A new nonblocking is incompatible with a previous allocating. + if (PrevKind == FunctionEffect::Kind::Allocating && + NewKind == FunctionEffect::Kind::NonBlocking) + return Incompatible(PrevEC); + } + + return false; +} + +void Sema::diagnoseFunctionEffectMergeConflicts( + const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc, + SourceLocation OldLoc) { + for (const FunctionEffectSet::Conflict &Conflict : Errs) { + Diag(NewLoc, diag::warn_conflicting_func_effects) + << Conflict.Kept.description() << Conflict.Rejected.description(); + Diag(OldLoc, diag::note_previous_declaration); + } +} + +// Decl should be a FunctionDecl or BlockDecl. +void Sema::maybeAddDeclWithEffects(const Decl *D, + const FunctionEffectsRef &FX) { + if (!D->hasBody()) { + if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody()) + return; + } + + if (Diags.getIgnoreAllWarnings() || + (Diags.getSuppressSystemWarnings() && + SourceMgr.isInSystemHeader(D->getLocation()))) + return; + + if (hasUncompilableErrorOccurred()) + return; + + // For code in dependent contexts, we'll do this at instantiation time. + // Without this check, we would analyze the function based on placeholder + // template parameters, and potentially generate spurious diagnostics. + if (cast(D)->isDependentContext()) + return; + + addDeclWithEffects(D, FX); +} + +void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) { + // To avoid the possibility of conflict, don't add effects which are + // not FE_InferrableOnCallees and therefore not verified; this removes + // blocking/allocating but keeps nonblocking/nonallocating. + // Also, ignore any conditions when building the list of effects. + bool AnyVerifiable = false; + for (const FunctionEffectWithCondition &EC : FX) + if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) { + AllEffectsToVerify.insert(EC.Effect); + AnyVerifiable = true; + } + + // Record the declaration for later analysis. + if (AnyVerifiable) + DeclsWithEffectsToVerify.push_back(D); +} + +void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) { + if (hasUncompilableErrorOccurred() || Diags.getIgnoreAllWarnings()) + return; + if (TU == nullptr) + return; + Analyzer{*this}.run(*TU); +} + +Sema::FunctionEffectDiffVector::FunctionEffectDiffVector( + const FunctionEffectsRef &Old, const FunctionEffectsRef &New) { + + FunctionEffectsRef::iterator POld = Old.begin(); + FunctionEffectsRef::iterator OldEnd = Old.end(); + FunctionEffectsRef::iterator PNew = New.begin(); + FunctionEffectsRef::iterator NewEnd = New.end(); + + while (true) { + int cmp = 0; + if (POld == OldEnd) { + if (PNew == NewEnd) + break; + cmp = 1; + } else if (PNew == NewEnd) + cmp = -1; + else { + FunctionEffectWithCondition Old = *POld; + FunctionEffectWithCondition New = *PNew; + if (Old.Effect.kind() < New.Effect.kind()) + cmp = -1; + else if (New.Effect.kind() < Old.Effect.kind()) + cmp = 1; + else { + cmp = 0; + if (Old.Cond.getCondition() != New.Cond.getCondition()) { + // FIXME: Cases where the expressions are equivalent but + // don't have the same identity. + push_back(FunctionEffectDiff{ + Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch, + Old, New}); + } + } + } + + if (cmp < 0) { + // removal + FunctionEffectWithCondition Old = *POld; + push_back(FunctionEffectDiff{Old.Effect.kind(), + FunctionEffectDiff::Kind::Removed, Old, + std::nullopt}); + ++POld; + } else if (cmp > 0) { + // addition + FunctionEffectWithCondition New = *PNew; + push_back(FunctionEffectDiff{New.Effect.kind(), + FunctionEffectDiff::Kind::Added, + std::nullopt, New}); + ++PNew; + } else { + ++POld; + ++PNew; + } + } +} + +bool Sema::FunctionEffectDiff::shouldDiagnoseConversion( + QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType, + const FunctionEffectsRef &DstFX) const { + + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + // nonallocating can't be added (spoofed) during a conversion, unless we + // have nonblocking. + if (DiffKind == Kind::Added) { + for (const auto &CFE : SrcFX) { + if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking) + return false; + } + } + [[fallthrough]]; + case FunctionEffect::Kind::NonBlocking: + // nonblocking can't be added (spoofed) during a conversion. + switch (DiffKind) { + case Kind::Added: + return true; + case Kind::Removed: + return false; + case Kind::ConditionMismatch: + // FIXME: Condition mismatches are too coarse right now -- expressions + // which are equivalent but don't have the same identity are detected as + // mismatches. We're going to diagnose those anyhow until expression + // matching is better. + return true; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + } + llvm_unreachable("unknown effect kind"); +} + +bool Sema::FunctionEffectDiff::shouldDiagnoseRedeclaration( + const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX, + const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const { + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + case FunctionEffect::Kind::NonBlocking: + // nonblocking/nonallocating can't be removed in a redeclaration. + switch (DiffKind) { + case Kind::Added: + return false; // No diagnostic. + case Kind::Removed: + return true; // Issue diagnostic. + case Kind::ConditionMismatch: + // All these forms of mismatches are diagnosed. + return true; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + } + llvm_unreachable("unknown effect kind"); +} + +Sema::FunctionEffectDiff::OverrideResult +Sema::FunctionEffectDiff::shouldDiagnoseMethodOverride( + const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, + const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const { + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + case FunctionEffect::Kind::NonBlocking: + switch (DiffKind) { + + // If added on an override, that's fine and not diagnosed. + case Kind::Added: + return OverrideResult::NoAction; + + // If missing from an override (removed), propagate from base to derived. + case Kind::Removed: + return OverrideResult::Merge; + + // If there's a mismatch involving the effect's polarity or condition, + // issue a warning. + case Kind::ConditionMismatch: + return OverrideResult::Warn; + } + + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return OverrideResult::NoAction; + } + llvm_unreachable("unknown effect kind"); +} + +} // namespace clang diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index 15ed8572e60844..c2b35856111f3b 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1951,6 +1951,9 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap, ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) { LambdaScopeInfo LSI = *cast(FunctionScopes.back()); ActOnFinishFunctionBody(LSI.CallOperator, Body); + + maybeAddDeclWithEffects(LSI.CallOperator); + return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI); } diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 0a4251c0e52404..4b599b7dbf3ce8 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3912,6 +3912,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); @@ -8413,6 +8418,17 @@ void ASTReader::InitializeSema(Sema &S) { NewOverrides.applyOverrides(SemaObj->getLangOpts()); } + 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()); + else + llvm_unreachable("unexpected Decl type in DeclsWithEffectsToVerify"); + } + DeclsWithEffectsToVerify.clear(); + SemaObj->OpenCLFeatures = OpenCLExtensions; UpdateSema(); diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index aa9764e25c3233..375ddc90482b27 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -4642,6 +4642,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. @@ -5599,6 +5610,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/Headers/Inputs/include/setjmp.h b/clang/test/Headers/Inputs/include/setjmp.h index 3d5e903eff6fce..c24951e92501f9 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/Misc/warning-wall.c b/clang/test/Misc/warning-wall.c index 4909ab034ef30a..91de843f88c913 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 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 00000000000000..dcbcef410987f5 --- /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 -Wfunction-effects %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 {{function with 'nonblocking' attribute must not throw or catch exceptions}} +} + +struct S { + 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; } + __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 {{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.c b/clang/test/Sema/attr-nonblocking-constraints.c new file mode 100644 index 00000000000000..e7ab661b3125a2 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-constraints.c @@ -0,0 +1,27 @@ +// 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. + +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. +} + +#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'}} + } +} diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp new file mode 100644 index 00000000000000..c694860069c960 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-constraints.cpp @@ -0,0 +1,386 @@ +// 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. + +// This diagnostic is re-enabled and exercised in isolation later in this file. +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" + +// --- CONSTRAINTS --- + +void nb1() [[clang::nonblocking]] +{ + 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 {{function with 'nonblocking' attribute must not have static local variables}} +} + +void nb3() [[clang::nonblocking]] +{ + try { + throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} + } + catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} + } +} + +void nb4_inline() {} +void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + +void nb4() [[clang::nonblocking]] +{ + nb4_inline(); // OK + nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} +} + + +struct HasVirtual { + virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}} +}; + +void nb5() [[clang::nonblocking]] +{ + HasVirtual hv; + 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}} +void nb6_transitively_unsafe() +{ + nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}} +} + +void nb6() [[clang::nonblocking]] +{ + 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 {{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 {{block with 'nonblocking' attribute must not throw or catch exceptions}} + }; +} + +void nb8() +{ + // Make sure we verify lambdas + auto lambda = []() [[clang::nonblocking]] { + throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}} + }; +} + +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 {{function with 'nonblocking' attribute 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 { + static T add_explicit(T x, T y) [[clang::nonblocking]] + { + return x + y; // expected-warning {{function with 'nonblocking' attribute 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 or 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 nb9() [[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 {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \ + expected-note {{in template expansion here}} +} + +void nb10( + void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}} + void (*fp2)() [[clang::nonblocking]] + ) [[clang::nonblocking]] +{ + 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) +void nb11_no_inference_1() [[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' because it is declared 'blocking'}} +}; + +void nb11() [[clang::nonblocking]] +{ + 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 {{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 {{function with 'nonblocking' attribute must not have static local variables}} +} +void nb12() [[clang::nonblocking]]; +void nb13() [[clang::nonblocking]] { nb12(); } + +// 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 nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] { + blk(); +} + +// References to blocks +void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]] +{ + auto &ref = block; + ref(); +} + +// Builtin functions +void nb19() [[clang::nonblocking]] { + __builtin_assume(1); + 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 {{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 {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}} +} + +struct S { + int x; + 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}} \ + // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} + +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 {{member initializer cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}} +}; + +void f() [[clang::nonblocking]] { + 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 {{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 +}; + +// Default arguments +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 {{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 {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}} +} + +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. +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}} + + 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 {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}} +}; + +struct DerivedFromUnsafe : public 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'}} +}; + +// 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; + + 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 {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor '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. +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 +} + +// 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); +} + +// 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" + +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}} +} diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp index 38bf2ac8f8a4cc..9056f81f5296b9 100644 --- a/clang/test/Sema/attr-nonblocking-sema.cpp +++ b/clang/test/Sema/attr-nonblocking-sema.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s -// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s +// 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" @@ -77,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/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp index 644ed754b04daa..90d074d01708f4 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 00000000000000..abd0938ac321af --- /dev/null +++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify -Wfunction-effects %s + +#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept" + +// Objective-C +@interface OCClass +- (void)method; +@end + +void nb1(OCClass *oc) [[clang::nonblocking]] { + [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 {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'nb2'}} +} + +void nb4() [[clang::nonblocking]] { + @try { + @throw @"foo"; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} + } + @catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}} + } +}