[clang] nolock/noalloc attributes (PR #84983)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 12 14:06:16 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Doug Wyatt (dougsonos)
<details>
<summary>Changes</summary>
Rough first draft. This is an early PR to solicit comments on the overall approach and a number of outstanding questions. Some of the larger ones, "earlier" in the flow (from parse -> Sema):
- Does the FunctionEffect / FunctionEffectSet abstraction make sense (Type.h)? The idea is that an abstract effect system for functions could easily support things like "TCB with types" or adding "nowait" to the "nolock/noalloc" group of effects.
- FunctionEffect/FunctionEffectSet need to be serialized as part of FunctionProtoType
- Conversions between functions / function pointers with and without the attribute work in C++ but not in C and I'm a bit lost (there's a bunch of debug logging around this e.g. Sema::IsFunctionConversion and Sema::ImpCastExprToType)
---
Patch is 116.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/84983.diff
26 Files Affected:
- (modified) clang/include/clang/AST/Decl.h (+9)
- (modified) clang/include/clang/AST/Type.h (+294-3)
- (modified) clang/include/clang/AST/TypeProperties.td (+5)
- (modified) clang/include/clang/Basic/Attr.td (+23)
- (modified) clang/include/clang/Basic/AttrDocs.td (+17)
- (modified) clang/include/clang/Basic/DiagnosticGroups.td (+4)
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+95)
- (modified) clang/include/clang/Sema/Sema.h (+16)
- (modified) clang/lib/AST/ASTContext.cpp (+26-5)
- (modified) clang/lib/AST/Decl.cpp (+9)
- (modified) clang/lib/AST/Type.cpp (+286)
- (modified) clang/lib/AST/TypePrinter.cpp (+12)
- (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+1237)
- (modified) clang/lib/Sema/Sema.cpp (+23)
- (modified) clang/lib/Sema/SemaDecl.cpp (+82)
- (modified) clang/lib/Sema/SemaDeclAttr.cpp (+28)
- (modified) clang/lib/Sema/SemaDeclCXX.cpp (+21)
- (modified) clang/lib/Sema/SemaExpr.cpp (+4)
- (modified) clang/lib/Sema/SemaExprCXX.cpp (+7-1)
- (modified) clang/lib/Sema/SemaLambda.cpp (+5)
- (modified) clang/lib/Sema/SemaOverload.cpp (+27)
- (modified) clang/lib/Sema/SemaType.cpp (+125-1)
- (added) clang/test/Sema/attr-nolock-wip.cpp (+19)
- (added) clang/test/Sema/attr-nolock.cpp (+78)
- (added) clang/test/Sema/attr-nolock2.cpp (+84)
- (added) clang/test/Sema/attr-nolock3.cpp (+139)
``````````diff
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index a5879591f4c659..0460f30ce8a8b4 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,6 +3008,13 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
+ FunctionEffectSet getFunctionEffects() const {
+ const auto *FPT = getType()->getAs<FunctionProtoType>();
+ if (FPT)
+ return FPT->getFunctionEffects();
+ return {};
+ }
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) {
@@ -4633,6 +4640,8 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
+ FunctionEffectSet getFunctionEffects() const;
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Block; }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1942b0e67f65a3..de21561ce5dcad 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4047,8 +4047,12 @@ class FunctionType : public Type {
LLVM_PREFERRED_TYPE(bool)
unsigned HasArmTypeAttributes : 1;
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned HasFunctionEffects : 1;
+
FunctionTypeExtraBitfields()
- : NumExceptionType(0), HasArmTypeAttributes(false) {}
+ : NumExceptionType(0), HasArmTypeAttributes(false),
+ HasFunctionEffects(false) {}
};
/// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number
@@ -4181,6 +4185,131 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
}
};
+class FunctionEffect;
+class FunctionEffectSet;
+
+// It is the user's responsibility to keep this in set form: elements are
+// ordered and unique.
+// We could hide the mutating methods which are capable of breaking the
+// invariant, but they're needed and safe when used with STL set algorithms.
+class MutableFunctionEffectSet : public SmallVector<const FunctionEffect *, 4> {
+public:
+ using SmallVector::insert;
+ using SmallVector::SmallVector;
+
+ /// Maintains order/uniquenesss.
+ void insert(const FunctionEffect *effect);
+
+ MutableFunctionEffectSet &operator|=(FunctionEffectSet rhs);
+};
+
+class FunctionEffectSet {
+public:
+ // These sets will tend to be very small (1 element), so represent them as
+ // sorted vectors, which are compatible with the STL set algorithms. Using an
+ // array or vector also means the elements are contiguous, keeping iterators
+ // simple.
+
+private:
+ // 'Uniqued' refers to the set itself being uniqued. Storage is allocated
+ // separately. Use ArrayRef for its iterators. Subclass so as to be able to
+ // compare (it seems ArrayRef would silently convert itself to a vector for
+ // comparison?!).
+ class UniquedAndSortedFX : public llvm::ArrayRef<const FunctionEffect *> {
+ public:
+ using Base = llvm::ArrayRef<const FunctionEffect *>;
+
+ UniquedAndSortedFX(Base Array) : Base(Array) {}
+ UniquedAndSortedFX(const FunctionEffect **Ptr, size_t Len)
+ : Base(Ptr, Len) {}
+
+ bool operator<(const UniquedAndSortedFX &rhs) const;
+ };
+
+ // Could have used a TinyPtrVector if it were unique-able.
+ // Empty set has a null Impl.
+ llvm::PointerUnion<const FunctionEffect *, const UniquedAndSortedFX *> Impl;
+
+ explicit FunctionEffectSet(const FunctionEffect *Single) : Impl(Single) {}
+ explicit FunctionEffectSet(const UniquedAndSortedFX *Multi) : Impl(Multi) {}
+
+public:
+ using Differences =
+ SmallVector<std::pair<const FunctionEffect *, bool /*added*/>>;
+
+ FunctionEffectSet() : Impl(nullptr) {}
+
+ void *getOpaqueValue() const { return Impl.getOpaqueValue(); }
+
+ explicit operator bool() const { return !empty(); }
+ bool empty() const {
+ if (Impl.isNull())
+ return true;
+ if (const UniquedAndSortedFX *Vec =
+ dyn_cast_if_present<const UniquedAndSortedFX *>(Impl))
+ return Vec->empty();
+ return false;
+ }
+ size_t size() const {
+ if (empty())
+ return 0;
+ if (isa<const FunctionEffect *>(Impl))
+ return 1;
+ return cast<const UniquedAndSortedFX *>(Impl)->size();
+ }
+
+ using iterator = const FunctionEffect *const *;
+
+ iterator begin() const {
+ if (isa<const FunctionEffect *>(Impl))
+ return Impl.getAddrOfPtr1();
+ return cast<const UniquedAndSortedFX *>(Impl)->begin();
+ }
+
+ iterator end() const {
+ if (isa<const FunctionEffect *>(Impl))
+ return begin() + (Impl.isNull() ? 0 : 1);
+ return cast<const UniquedAndSortedFX *>(Impl)->end();
+ }
+
+ ArrayRef<const FunctionEffect *> items() const { return {begin(), end()}; }
+
+ // Since iterators are non-trivial and sets are very often empty,
+ // encourage short-circuiting loops for the empty set.
+ // void for_each(llvm::function_ref<void(const FunctionEffect*)> func) const;
+
+ bool operator==(const FunctionEffectSet &other) const {
+ return Impl == other.Impl;
+ }
+ bool operator!=(const FunctionEffectSet &other) const {
+ return Impl != other.Impl;
+ }
+ bool operator<(const FunctionEffectSet &other) const;
+
+ void dump(llvm::raw_ostream &OS) const;
+
+ /// Factory functions: return instances with uniqued implementations.
+ static FunctionEffectSet create(const FunctionEffect &single) {
+ return FunctionEffectSet{&single};
+ }
+ static FunctionEffectSet create(llvm::ArrayRef<const FunctionEffect *> items);
+
+ /// Union. Caller should check for incompatible effects.
+ FunctionEffectSet operator|(const FunctionEffectSet &rhs) const;
+ /// Intersection.
+ MutableFunctionEffectSet operator&(const FunctionEffectSet &rhs) const;
+ /// Difference.
+ MutableFunctionEffectSet operator-(const FunctionEffectSet &rhs) const;
+
+ /// Caller should short-circuit by checking for equality first.
+ static Differences differences(const FunctionEffectSet &Old,
+ const FunctionEffectSet &New);
+
+ /// Extract the effects from a Type if it is a BlockType or FunctionProtoType,
+ /// or pointer to one.
+ static FunctionEffectSet get(const Type &TyRef);
+};
+
/// Represents a prototype with parameter type info, e.g.
/// 'int foo(int)' or 'int foo(void)'. 'void' is represented as having no
/// parameters, not as having a single void parameter. Such a type can have
@@ -4195,7 +4324,8 @@ class FunctionProtoType final
FunctionProtoType, QualType, SourceLocation,
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
- Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers> {
+ Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
+ FunctionEffectSet, Qualifiers> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -4226,6 +4356,9 @@ class FunctionProtoType final
// an ExtParameterInfo for each of the parameters. Present if and
// only if hasExtParameterInfos() is true.
//
+ // * Optionally, a FunctionEffectSet. Present if and only if
+ // hasFunctionEffects() is true.
+ //
// * Optionally a Qualifiers object to represent extra qualifiers that can't
// be represented by FunctionTypeBitfields.FastTypeQuals. Present if and only
// if hasExtQualifiers() is true.
@@ -4284,6 +4417,7 @@ class FunctionProtoType final
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
+ FunctionEffectSet FunctionEffects;
ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false),
@@ -4301,7 +4435,8 @@ class FunctionProtoType final
bool requiresFunctionProtoTypeExtraBitfields() const {
return ExceptionSpec.Type == EST_Dynamic ||
- requiresFunctionProtoTypeArmAttributes();
+ requiresFunctionProtoTypeArmAttributes() ||
+ FunctionEffects;
}
bool requiresFunctionProtoTypeArmAttributes() const {
@@ -4349,6 +4484,10 @@ class FunctionProtoType final
return hasExtParameterInfos() ? getNumParams() : 0;
}
+ unsigned numTrailingObjects(OverloadToken<FunctionEffectSet>) const {
+ return hasFunctionEffects();
+ }
+
/// Determine whether there are any argument types that
/// contain an unexpanded parameter pack.
static bool containsAnyUnexpandedParameterPack(const QualType *ArgArray,
@@ -4450,6 +4589,7 @@ class FunctionProtoType final
EPI.RefQualifier = getRefQualifier();
EPI.ExtParameterInfos = getExtParameterInfosOrNull();
EPI.AArch64SMEAttributes = getAArch64SMEAttributes();
+ EPI.FunctionEffects = getFunctionEffects();
return EPI;
}
@@ -4661,6 +4801,18 @@ class FunctionProtoType final
return false;
}
+ bool hasFunctionEffects() const {
+ if (!hasExtraBitfields())
+ return false;
+ return getTrailingObjects<FunctionTypeExtraBitfields>()->HasFunctionEffects;
+ }
+
+ FunctionEffectSet getFunctionEffects() const {
+ if (hasFunctionEffects())
+ return *getTrailingObjects<FunctionEffectSet>();
+ return {};
+ }
+
bool isSugared() const { return false; }
QualType desugar() const { return QualType(this, 0); }
@@ -7754,6 +7906,145 @@ QualType DecayedType::getPointeeType() const {
void FixedPointValueToString(SmallVectorImpl<char> &Str, llvm::APSInt Val,
unsigned Scale);
+// ------------------------------------------------------------------------------
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
+
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+ enum EffectType {
+ kGeneric,
+ kNoLockTrue,
+ kNoAllocTrue,
+ };
+
+ /// Flags describing behaviors of the effect.
+ using Flags = unsigned;
+ enum FlagBit : unsigned {
+ // Some effects require verification, e.g. nolock(true); others might not?
+ // (no example yet)
+ kRequiresVerification = 0x1,
+
+ // Does this effect want to verify all function calls originating in
+ // functions having this effect?
+ kVerifyCalls = 0x2,
+
+ // Can verification inspect callees' implementations? (e.g. nolock: yes,
+ // tcb+types: no)
+ kInferrableOnCallees = 0x4,
+
+ // Language constructs which effects can diagnose as disallowed.
+ kExcludeThrow = 0x8,
+ kExcludeCatch = 0x10,
+ kExcludeObjCMessageSend = 0x20,
+ kExcludeStaticLocalVars = 0x40,
+ kExcludeThreadLocalVars = 0x80
+ };
+
+private:
+ const EffectType Type_;
+ const Flags Flags_;
+ const char *Name;
+
+public:
+ using CalleeDeclOrType =
+ llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
+
+ FunctionEffect(EffectType T, Flags F, const char *Name)
+ : Type_(T), Flags_(F), Name(Name) {}
+ virtual ~FunctionEffect();
+
+ /// The type of the effect.
+ EffectType type() const { return Type_; }
+
+ /// Flags describing behaviors of the effect.
+ Flags flags() const { return Flags_; }
+
+ /// The description printed in diagnostics, e.g. 'nolock'.
+ StringRef name() const { return Name; }
+
+ /// The description used by TypePrinter, e.g. __attribute__((clang_nolock))
+ virtual std::string attribute() const = 0;
+
+ /// Return true if adding or removing the effect as part of a type conversion
+ /// should generate a diagnostic.
+ virtual bool diagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX, QualType NewType,
+ FunctionEffectSet NewFX) const;
+
+ /// Return true if adding or removing the effect in a redeclaration should
+ /// generate a diagnostic.
+ virtual bool diagnoseRedeclaration(bool Adding,
+ const FunctionDecl &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ FunctionEffectSet NewFX) const;
+
+ /// Return true if adding or removing the effect in a C++ virtual method
+ /// override should generate a diagnostic.
+ virtual bool diagnoseMethodOverride(bool Adding,
+ const CXXMethodDecl &OldMethod,
+ FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod,
+ FunctionEffectSet NewFX) const;
+
+ /// Return true if the effect is allowed to be inferred on the specified Decl
+ /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
+ /// kInferrableOnCallees flag set. Example: This allows nolock(false) to
+ /// prevent inference for the function.
+ virtual bool canInferOnDecl(const Decl *Caller,
+ FunctionEffectSet CallerFX) const;
+
+ // Called if kVerifyCalls flag is set; return false for success. When true is
+ // returned for a direct call, then the kInferrableOnCallees 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).
+ virtual bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
+ FunctionEffectSet CallerFX,
+ CalleeDeclOrType Callee,
+ FunctionEffectSet CalleeFX) const;
+};
+
+/// FunctionEffect subclass for nolock and noalloc (whose behaviors are close
+/// to identical).
+class NoLockNoAllocEffect : public FunctionEffect {
+ bool isNoLock() const { return type() == kNoLockTrue; }
+
+public:
+ static const NoLockNoAllocEffect &nolock_instance();
+ static const NoLockNoAllocEffect &noalloc_instance();
+
+ NoLockNoAllocEffect(EffectType Type, const char *Name);
+ ~NoLockNoAllocEffect() override;
+
+ std::string attribute() const override;
+
+ bool diagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX, QualType NewType,
+ FunctionEffectSet NewFX) const override;
+
+ bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ FunctionEffectSet NewFX) const override;
+
+ bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+ FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod,
+ FunctionEffectSet NewFX) const override;
+
+ bool canInferOnDecl(const Decl *Caller,
+ FunctionEffectSet CallerFX) const override;
+
+ bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
+ FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
+ FunctionEffectSet CalleeFX) const override;
+};
+
} // namespace clang
#endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 0ba172a4035fdb..1d5d8f696977e7 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -326,6 +326,10 @@ let Class = FunctionProtoType in {
def : Property<"AArch64SMEAttributes", UInt32> {
let Read = [{ node->getAArch64SMEAttributes() }];
}
+ /* TODO: How to serialize FunctionEffect / FunctionEffectSet?
+ def : Property<"functionEffects", FunctionEffectSet> {
+ let Read = [{ node->getFunctionEffects() }];
+ }*/
def : Creator<[{
auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
@@ -342,6 +346,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
+ //epi.FunctionEffects = functionEffects;
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fd7970d0451acd..4033b9efb86f39 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1402,6 +1402,29 @@ def CXX11NoReturn : InheritableAttr {
let Documentation = [CXX11NoReturnDocs];
}
+def NoLock : DeclOrTypeAttr {
+ let Spellings = [CXX11<"clang", "nolock">,
+ C23<"clang", "nolock">,
+ GNU<"clang_nolock">];
+
+ // Subjects - not needed?
+ //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
+ let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+ let HasCustomParsing = 0;
+ let Documentation = [NoLockNoAllocDocs];
+}
+
+def NoAlloc : DeclOrTypeAttr {
+ let Spellings = [CXX11<"clang", "noalloc">,
+ C23<"clang", "noalloc">,
+ GNU<"clang_noalloc">];
+ // Subjects - not needed?
+ //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
+ let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+ let HasCustomParsing = 0;
+ let Documentation = [NoLockNoAllocDocs];
+}
+
// Similar to CUDA, OpenCL attributes do not receive a [[]] spelling because
// the specification does not expose them with one currently.
def OpenCLKernel : InheritableAttr {
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2c07cd09b0d5b7..bb897c708ebbea 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7973,3 +7973,20 @@ requirement:
}
}];
}
+
+def NoLockNoAllocDocs : Documentation {
+ let Category = DocCatType;
+ let Content = [{
+The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks,
+function pointers, lambdas, and member functions. The attributes identify code
+which must not allocate memory or lock, and the compiler uses the attributes to
+verify these requirements.
+
+Like ``noexcept``, ``nolock`` and ``noalloc`` have an optional argument, a
+compile-time constant boolean expression. By default, the argument is true, so
+``[[clang::nolock(true)]]`` is equivalent to ``[[clang::nolock]]``, and declares
+the function type as never locking.
+
+TODO: how much of the RFC to include here? Make it a separate page?
+ }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3f14167d6b8469..0d6e9f0289f6bc 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1507,3 +1507,7 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>;
+
+// Warnings and notes related to the function effects system underlying
+// the nolock and noalloc attributes.
+def FunctionEffects : DiagGroup<"function-effects">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c54105507753eb..df553a47a8dbe7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
"implicit cast from type %0 to type %1 drops __unaligned qualifier">,
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
+def warn_func_effect_allocates : Warning<
+ "'%0' function '%1' must not allocate or deallocate memory">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_allocates : Note<
+ "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+
+def warn_func_effect_throws_or_catches : Warning<
+ "'%0' function '%1' must not throw or catch exceptions">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_throws_or_catches : Note<
+ "'%1' cannot be inferred '%0' because it throws or catches exceptions">;
+
+def warn_func_effect_has_static_local : Warning<
+ "'%0' function '%1' must not have sta...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/84983
More information about the cfe-commits
mailing list