[clang] nonblocking/nonallocating attributes (was: nolock/noalloc) (PR #84983)
Doug Wyatt via cfe-commits
cfe-commits at lists.llvm.org
Mon May 27 10:33:31 PDT 2024
https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/84983
>From 0619e30a3a4be724185406fad2413c786e2acff1 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 11 Mar 2024 17:19:01 -0700
Subject: [PATCH 01/71] Initial squash/cleanup
---
clang/include/clang/AST/Decl.h | 9 +
clang/include/clang/AST/Type.h | 297 +++-
clang/include/clang/AST/TypeProperties.td | 5 +
clang/include/clang/Basic/Attr.td | 23 +
clang/include/clang/Basic/AttrDocs.td | 17 +
clang/include/clang/Basic/DiagnosticGroups.td | 4 +
.../clang/Basic/DiagnosticSemaKinds.td | 95 ++
clang/include/clang/Sema/Sema.h | 16 +
clang/lib/AST/ASTContext.cpp | 31 +-
clang/lib/AST/Decl.cpp | 85 +
clang/lib/AST/Type.cpp | 275 +++
clang/lib/AST/TypePrinter.cpp | 14 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1491 +++++++++++++++++
clang/lib/Sema/Sema.cpp | 24 +
clang/lib/Sema/SemaDecl.cpp | 89 +
clang/lib/Sema/SemaDeclAttr.cpp | 28 +
clang/lib/Sema/SemaDeclCXX.cpp | 20 +
clang/lib/Sema/SemaExpr.cpp | 4 +
clang/lib/Sema/SemaExprCXX.cpp | 8 +-
clang/lib/Sema/SemaLambda.cpp | 5 +
clang/lib/Sema/SemaOverload.cpp | 27 +
clang/lib/Sema/SemaType.cpp | 130 +-
clang/test/Sema/attr-nolock.cpp | 78 +
clang/test/Sema/attr-nolock2.cpp | 88 +
clang/test/Sema/attr-nolock3.cpp | 144 ++
25 files changed, 2997 insertions(+), 10 deletions(-)
create mode 100644 clang/test/Sema/attr-nolock.cpp
create mode 100644 clang/test/Sema/attr-nolock2.cpp
create mode 100644 clang/test/Sema/attr-nolock3.cpp
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index a5879591f4c65..0460f30ce8a8b 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 1942b0e67f65a..41c60a6e221d4 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 getFlags() 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 ty, 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 0ba172a4035fd..1d5d8f696977e 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 fd7970d0451ac..78bbe5185741b 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">,
+ C2x<"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">,
+ C2x<"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 2c07cd09b0d5b..bb897c708ebbe 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 3f14167d6b846..0d6e9f0289f6b 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 c54105507753e..5777f7dfbbcad 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 static locals">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_has_static_local : Note<
+ "'%1' cannot be inferred '%0' because it has a static local">;
+
+def warn_func_effect_uses_thread_local : Warning<
+ "'%0' function '%1' must not use thread-local variables">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_uses_thread_local : Note<
+ "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+
+def warn_func_effect_calls_objc : Warning<
+ "'%0' function '%1' must not access an ObjC method or property">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_calls_objc : Note<
+ "'%1' cannot be inferred '%0' because it accesses an ObjC method or property">;
+
+def warn_func_effect_calls_disallowed_func : Warning<
+ "'%0' function '%1' must not call non-'%0' function '%2'">,
+ InGroup<FunctionEffects>;
+
+// UNTESTED
+def warn_func_effect_calls_disallowed_expr : Warning<
+ "'%0' function '%1' must not call non-'%0' expression">,
+ InGroup<FunctionEffects>;
+
+def note_func_effect_calls_disallowed_func : Note<
+ "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+
+def note_func_effect_call_extern : Note<
+ "'%1' cannot be inferred '%0' because it has no definition in this translation unit">;
+
+def note_func_effect_call_not_inferrable : Note<
+ "'%1' does not permit inference of '%0'">;
+
+def note_func_effect_call_virtual : Note<
+ "'%1' cannot be inferred '%0' because it is virtual">;
+
+def note_func_effect_call_func_ptr : Note<
+ "'%1' cannot be inferred '%0' because it is a function pointer">;
+
+// TODO: Not currently being generated
+def warn_perf_annotation_implies_noexcept : Warning<
+ "'%0' function should be declared noexcept">,
+ InGroup<PerfAnnotationImpliesNoexcept>;
+
+// TODO: Not currently being generated
+def warn_func_effect_false_on_type : Warning<
+ "only functions/methods/blocks may be declared %0(false)">,
+ InGroup<FunctionEffects>;
+
+// TODO: can the usual template expansion notes be used?
+def note_func_effect_from_template : Note<
+ "in template expansion here">;
+
+// TODO: Needs to be tested
+def warn_incompatible_func_effects : Warning<
+ "attributes '%0' and '%1' are incompatible">,
+ InGroup<FunctionEffects>;
+
+// spoofing nolock/noalloc
+def warn_invalid_add_func_effects : Warning<
+ "attribute '%0' should not be added via type conversion">,
+ InGroup<FunctionEffects>;
+
+def warn_invalid_remove_func_effects : Warning<
+ "attribute '%0' should not be removed via type conversion">,
+ InGroup<FunctionEffects>;
+
+def warn_mismatched_func_effect_override : Warning<
+ "attribute '%0' on overriding function does not match base version">,
+ InGroup<FunctionEffects>;
+
+def warn_mismatched_func_effect_redeclaration : Warning<
+ "attribute '%0' on function does not match previous declaration">,
+ InGroup<FunctionEffects>;
+
} // end of sema category
let CategoryName = "API Notes Issue" in {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 267c79cc057cb..2772f882a5c09 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -721,6 +721,12 @@ class Sema final {
/// Load weak undeclared identifiers from the external source.
void LoadExternalWeakUndeclaredIdentifiers();
+ /// All functions/lambdas/blocks which have bodies and which have a non-empty
+ /// FunctionEffectSet to be verified.
+ SmallVector<const Decl *> DeclsWithUnverifiedEffects;
+ /// The union of all effects present on DeclsWithUnverifiedEffects.
+ MutableFunctionEffectSet AllEffectsToVerify;
+
/// Determine if VD, which must be a variable or function, is an external
/// symbol that nonetheless can't be referenced from outside this translation
/// unit because its type has no linkage and it's not extern "C".
@@ -940,6 +946,10 @@ class Sema final {
/// Warn when implicitly casting 0 to nullptr.
void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
+ /// Warn when implicitly changing function effects.
+ void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
+ SourceLocation Loc);
+
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
@@ -3132,6 +3142,9 @@ class Sema final {
QualType T, TypeSourceInfo *TSInfo,
StorageClass SC);
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithUnverifiedEffects.
+ void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);
+
// Contexts where using non-trivial C union types can be disallowed. This is
// passed to err_non_trivial_c_union_in_invalid_context.
enum NonTrivialCUnionContext {
@@ -3739,6 +3752,9 @@ class Sema final {
StringRef &Str,
SourceLocation *ArgLocation = nullptr);
+ bool checkBoolExprArgumentAttr(const ParsedAttr &Attr, unsigned ArgNum,
+ bool &Value);
+
/// Determine if type T is a valid subject for a nonnull and similar
/// attributes. By default, we look through references (the behavior used by
/// nonnull), but if the second parameter is true, then we treat a reference
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 5a8fae76a43a4..43fccb117a897 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4502,11 +4502,13 @@ QualType ASTContext::getFunctionTypeInternal(
size_t Size = FunctionProtoType::totalSizeToAlloc<
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
- Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers>(
+ Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
+ FunctionEffectSet, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
EPI.ExtParameterInfos ? NumArgs : 0,
+ EPI.FunctionEffects ? 1 : 0,
EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
@@ -10435,6 +10437,15 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
allRTypes = false;
FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
+ FunctionEffectSet FromFX, ToFX;
+ std::optional<FunctionEffectSet> MergedFX;
+
+ if (lproto) ToFX = lproto->getFunctionEffects();
+ if (rproto) FromFX = rproto->getFunctionEffects();
+ if (ToFX != FromFX) {
+ // We want the intersection of the effects...
+ MergedFX = FunctionEffectSet::create(FromFX & ToFX);
+ }
if (lproto && rproto) { // two C99 style function prototypes
assert((AllowCXX ||
@@ -10487,13 +10498,18 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
allRTypes = false;
}
- if (allLTypes) return lhs;
- if (allRTypes) return rhs;
+ if (!MergedFX) { // effects changed so we can't return either side unaltered
+ if (allLTypes) return lhs;
+ if (allRTypes) return rhs;
+ }
FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
EPI.ExtInfo = einfo;
EPI.ExtParameterInfos =
newParamInfos.empty() ? nullptr : newParamInfos.data();
+ if (MergedFX) {
+ EPI.FunctionEffects = *MergedFX;
+ }
return getFunctionType(retType, types, EPI);
}
@@ -10526,11 +10542,16 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
return {};
}
- if (allLTypes) return lhs;
- if (allRTypes) return rhs;
+ if (!MergedFX) { // effects changed so we can't return either side unaltered
+ if (allLTypes) return lhs;
+ if (allRTypes) return rhs;
+ }
FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
EPI.ExtInfo = einfo;
+ if (MergedFX) {
+ EPI.FunctionEffects = *MergedFX;
+ }
return getFunctionType(retType, proto->getParamTypes(), EPI);
}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 8626f04012f7d..596f4ea2e6717 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3517,6 +3517,61 @@ bool FunctionDecl::isMemberLikeConstrainedFriend() const {
return FriendConstraintRefersToEnclosingTemplate();
}
+static void examine(const Decl& D)
+{
+ PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
+ PP.TerseOutput = 1;
+ D.print(llvm::outs(), PP);
+}
+
+#if TEMP_DISABLE
+// This constant controls the default policy.
+constexpr static bool kDefaultCanInferPerfAnnotation = true;
+
+static bool declCanInferPerfAnnotation(const Decl &D, QualType QT) {
+ // llvm::outs() << "declCanInferPerfAnnotation " << &D << "\n";
+ // examine(D);
+
+ // nolock(false) or noalloc(false) disables inference.
+ if (QT->disallowPerfAnnotationInference()) {
+ // llvm::outs() << " disallowed by QT\n";
+ return false;
+ }
+ if (auto *IA = D.getAttr<PerformanceInferredAttr>()) {
+ // llvm::outs() << " decl has attr " << IA->getCanInfer() << "\n";
+ return IA->getCanInfer();
+ }
+ if (auto *Method = dyn_cast<CXXMethodDecl>(&D)) {
+ auto *Class = Method->getParent();
+ if (auto *IA = Class->getAttr<PerformanceInferredAttr>()) {
+ // llvm::outs() << " class has attr " << IA->getCanInfer() << "\n";
+ return IA->getCanInfer();
+ }
+ }
+
+ // for (const DeclContext *DC = D.getDeclContext(); DC != nullptr; DC = DC->getParent()) {
+ // if (auto *Decl2 = dyn_cast<Decl>(DC)) {
+ // examine(*Decl2);
+ // if (auto *IA = Decl2->getAttr<PerformanceInferredAttr>()) {
+ // llvm::outs() << " decl2 has attr " << IA->getCanInfer() << "\n";
+ // return IA->getCanInfer();
+ // }
+ // }
+ // }
+
+ // llvm::outs() << " result: false\n";
+ return kDefaultCanInferPerfAnnotation;
+}
+
+PerfAnnotation FunctionDecl::getPerfAnnotation() const {
+ return getType()->getPerfAnnotation();
+}
+
+bool FunctionDecl::canInferPerfAnnotation() const {
+ return declCanInferPerfAnnotation(*this, getType());
+}
+#endif // TEMP_DISABLE
+
MultiVersionKind FunctionDecl::getMultiVersionKind() const {
if (hasAttr<TargetAttr>())
return MultiVersionKind::Target;
@@ -5226,6 +5281,36 @@ SourceRange BlockDecl::getSourceRange() const {
return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
}
+FunctionEffectSet BlockDecl::getFunctionEffects() const {
+ if (auto* TSI = getSignatureAsWritten()) {
+ if (auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
+ return FPT->getFunctionEffects();
+ }
+ }
+ return {};
+}
+
+#if TEMP_DISABLE
+PerfAnnotation BlockDecl::getPerfAnnotation() const
+{
+ if (auto* TSI = getSignatureAsWritten()) {
+ return TSI->getType()->getPerfAnnotation();
+ }
+ return PerfAnnotation::None;
+}
+#endif // TEMP_DISABLE
+
+#if 0
+// unused
+bool BlockDecl::canInferPerfAnnotation() const
+{
+ if (auto* TSI = getSignatureAsWritten()) {
+ return declCanInferPerfAnnotation(*this, TSI->getType());
+ }
+ return kDefaultCanInferPerfAnnotation;
+}
+#endif
+
//===----------------------------------------------------------------------===//
// Other Decl Allocation/Deallocation Method Implementations
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 22666184c56cc..ce209be2c3a07 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3583,6 +3583,13 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
auto &EllipsisLoc = *getTrailingObjects<SourceLocation>();
EllipsisLoc = epi.EllipsisLoc;
}
+
+ if (epi.FunctionEffects) {
+ auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
+ ExtraBits.HasFunctionEffects = true;
+
+ *getTrailingObjects<FunctionEffectSet>() = epi.FunctionEffects;
+ }
}
bool FunctionProtoType::hasDependentExceptionSpec() const {
@@ -3666,11 +3673,14 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// Finally we have a trailing return type flag (bool)
// combined with AArch64 SME Attributes, to save space:
// int
+ // Then add the FunctionEffects
//
// There is no ambiguity between the consumed arguments and an empty EH
// spec because of the leading 'bool' which unambiguously indicates
// whether the following bool is the EH spec or part of the arguments.
+ ID.AddPointer(epi.FunctionEffects.getOpaqueValue()); // TODO: Where???
+
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -3684,6 +3694,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
ID.AddInteger(unsigned(epi.Variadic) +
(epi.RefQualifier << 1) +
(epi.ExceptionSpec.Type << 3));
+
ID.Add(epi.TypeQuals);
if (epi.ExceptionSpec.Type == EST_Dynamic) {
for (QualType Ex : epi.ExceptionSpec.Exceptions)
@@ -4912,3 +4923,267 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
getTypeConstraintConcept(), getTypeConstraintArguments());
}
+
+
+FunctionEffect::~FunctionEffect() = default;
+
+bool FunctionEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
+ QualType NewType, FunctionEffectSet NewFX) const
+{
+ return false;
+}
+
+bool FunctionEffect::diagnoseRedeclaration(bool adding,
+ const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
+ const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const { return false; }
+
+bool FunctionEffect::diagnoseMethodOverride(bool adding,
+ const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
+ const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const { return false; }
+
+bool FunctionEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
+{
+ return false;
+}
+
+bool FunctionEffect::diagnoseFunctionCall(bool direct,
+ const Decl* Caller, FunctionEffectSet CallerFX,
+ CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
+{
+ return false;
+}
+
+const NoLockNoAllocEffect& NoLockNoAllocEffect::nolock_instance()
+{
+ static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+ return global;
+}
+
+const NoLockNoAllocEffect& NoLockNoAllocEffect::noalloc_instance()
+{
+ static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
+ return global;
+}
+
+// TODO: Separate flags for noalloc
+NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType ty, const char* name)
+ : FunctionEffect{ ty, kRequiresVerification | kVerifyCalls | kInferrableOnCallees | kExcludeThrow | kExcludeCatch
+ | kExcludeObjCMessageSend | kExcludeStaticLocalVars | kExcludeThreadLocalVars, name }
+{
+}
+
+NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
+
+std::string NoLockNoAllocEffect::attribute() const
+{
+ return std::string{ "__attribute__((clang_" } + name().str() + "))";
+}
+
+bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
+ QualType NewType, FunctionEffectSet NewFX) const
+{
+ // noalloc can't be added (spoofed) during a conversion, unless we have nolock
+ if (adding) {
+ if (!isNoLock()) {
+ for (const auto* effect : OldFX) {
+ if (effect->type() == kNoLockTrue)
+ return false;
+ }
+ }
+ // nolock can't be added (spoofed) during a conversion.
+ return true;
+ }
+ return false;
+}
+
+bool NoLockNoAllocEffect::diagnoseRedeclaration(bool adding,
+ const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
+ const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const
+{
+ // nolock/noalloc can't be removed in a redeclaration
+ // adding -> false, removing -> true (diagnose)
+ return !adding;
+}
+
+bool NoLockNoAllocEffect::diagnoseMethodOverride(bool adding,
+ const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
+ const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const
+{
+ // nolock/noalloc can't be removed from an override
+ return !adding;
+}
+
+bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
+{
+ // Does the Decl have nolock(false) / noalloc(false) ?
+ QualType QT;
+ if (isa<BlockDecl>(Caller)) {
+ const auto* TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+ QT = TSI->getType();
+ } else if (isa<ValueDecl>(Caller)) {
+ QT = cast<ValueDecl>(Caller)->getType();
+ } else {
+ return false;
+ }
+ if (QT->hasAttr(isNoLock() ? attr::Kind::NoLock : attr::Kind::NoAlloc)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct,
+ const Decl* Caller, FunctionEffectSet CallerFX,
+ CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
+{
+ const EffectType callerType = type();
+ for (const auto* effect : CalleeFX) {
+ const EffectType ty = effect->type();
+ if (ty == callerType || (callerType == kNoAllocTrue && ty == kNoLockTrue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// =====
+
+void MutableFunctionEffectSet::insert(const FunctionEffect* effect)
+{
+ auto iter = std::lower_bound(begin(), end(), effect);
+ if (*iter != effect) {
+ insert(iter, effect);
+ }
+}
+
+MutableFunctionEffectSet& MutableFunctionEffectSet::operator|=(FunctionEffectSet rhs)
+{
+ // TODO: For large rhs sets, use set_union or a custom insert-in-place
+ for (const auto* effect : rhs) {
+ insert(effect);
+ }
+ return *this;
+}
+
+// This could be simpler if there were a simple set container that could be queried by
+// ArrayRef but which stored something else. Possibly a DenseMap with void values?
+FunctionEffectSet FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect*> items)
+{
+ if (items.empty()) {
+ return FunctionEffectSet{};
+ }
+ if (items.size() == 1) {
+ return FunctionEffectSet{ items[0] };
+ }
+
+ UniquedAndSortedFX newSet{ items }; // just copies the ArrayRef
+
+ // SmallSet only has contains(), so it provides no way to obtain the uniqued value.
+ static std::set<UniquedAndSortedFX> uniquedFXSets;
+
+ // See if we already have this set.
+ const auto iter = uniquedFXSets.find(newSet);
+ if (iter != uniquedFXSets.end()) {
+ return FunctionEffectSet{ &*iter };
+ }
+
+ // Copy the incoming array to permanent storage.
+ auto* storage = new const FunctionEffect*[items.size()];
+ std::copy(items.begin(), items.end(), storage);
+
+ // Make a new wrapper and insert it into the set.
+ newSet = UniquedAndSortedFX{ storage, items.size() };
+ auto [insiter, good] = uniquedFXSets.insert(newSet);
+ return FunctionEffectSet{ &*insiter };
+}
+
+FunctionEffectSet FunctionEffectSet::operator|(const FunctionEffectSet& rhs) const
+{
+ const FunctionEffectSet& lhs = *this;
+ if (lhs.empty()) {
+ return rhs;
+ }
+ if (rhs.empty()) {
+ return lhs;
+ }
+ // Optimize the case where the two sets are identical
+ if (lhs == rhs) {
+ return lhs;
+ }
+
+ MutableFunctionEffectSet vec;
+ vec.reserve(lhs.size() + rhs.size());
+ std::set_union(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+ // The result of a set operation is an ordered/unique set.
+ return FunctionEffectSet::create(vec);
+}
+
+MutableFunctionEffectSet FunctionEffectSet::operator&(const FunctionEffectSet& rhs) const
+{
+ const FunctionEffectSet& lhs = *this;
+ if (lhs.empty() || rhs.empty()) {
+ return {};
+ }
+
+ MutableFunctionEffectSet vec;
+ std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+ // The result of a set operation is an ordered/unique set.
+ return vec;
+}
+
+// TODO: inline?
+FunctionEffectSet FunctionEffectSet::get(const Type& TyRef)
+{
+ const Type* Ty = &TyRef;
+ if (Ty->isPointerType())
+ Ty = Ty->getPointeeType().getTypePtr();
+ if (const auto* FPT = Ty->getAs<FunctionProtoType>())
+ return FPT->getFunctionEffects();
+ return {};
+}
+
+FunctionEffectSet::Differences FunctionEffectSet::differences(
+ const FunctionEffectSet& Old, const FunctionEffectSet& New)
+{
+ // TODO: Could be a one-pass algorithm.
+ Differences result;
+ for (const auto* effect : (New - Old)) {
+ result.emplace_back(effect, true);
+ }
+ for (const auto* effect : (Old - New)) {
+ result.emplace_back(effect, false);
+ }
+ return result;
+}
+
+MutableFunctionEffectSet FunctionEffectSet::operator-(const FunctionEffectSet& rhs) const
+{
+ const FunctionEffectSet& lhs = *this;
+ MutableFunctionEffectSet result;
+
+ std::set_difference(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(result));
+ return result;
+}
+
+bool FunctionEffectSet::operator<(const FunctionEffectSet& rhs) const
+{
+ const FunctionEffectSet& lhs = *this;
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+bool FunctionEffectSet::UniquedAndSortedFX::operator<(const UniquedAndSortedFX& rhs) const
+{
+ return this < &rhs;
+}
+
+void FunctionEffectSet::dump(llvm::raw_ostream &OS) const
+{
+ OS << "FX{";
+ bool first = true;
+ for (const auto* effect : *this) {
+ if (!first) OS << ", ";
+ else first = false;
+ OS << effect->name();
+ }
+ OS << "}";
+}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 7dcc4348f8e03..560f2faeb9e50 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -984,8 +984,15 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
OS << " &&";
break;
}
+
T->printExceptionSpecification(OS, Policy);
+ if (auto effects = T->getFunctionEffects()) {
+ for (const auto* effect : effects) {
+ OS << " " << effect->attribute();
+ }
+ }
+
if (T->hasTrailingReturn()) {
OS << " -> ";
print(T->getReturnType(), OS, StringRef());
@@ -1904,6 +1911,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
+
case attr::PreserveMost:
OS << "preserve_most";
break;
@@ -1930,6 +1938,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
// Nothing to print for this attribute.
case attr::HLSLParamModifier:
break;
+ case attr::NoLock:
+ OS << "clang_nolock(false)";
+ break;
+ case attr::NoAlloc:
+ OS << "clang_noalloc(false)";
+ break;
}
OS << "))";
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 6992ba9ad9a75..a0d813fa4d3d1 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2380,6 +2380,1494 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
};
} // namespace
+// =============================================================================
+
+#define FX_ANALYZER_VERIFY_DECL_LIST 1
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+ None = 0, // sentinel for an empty Diagnostic
+ Throws,
+ Catches,
+ CallsObjC,
+ AllocatesMemory,
+ HasStaticLocal,
+ AccessesThreadLocal,
+
+ // These only apply to callees, where the analysis stops at the Decl
+ // DeclExternWithoutConstraint, // TODO: not used?
+ DeclWithoutConstraintOrInference,
+ //DeclVirtualWithoutConstraint,
+ //DeclFuncPtrWithoutConstraint,
+
+ CallsUnsafeDecl,
+ CallsDisallowedExpr,
+};
+
+struct Diagnostic {
+ const FunctionEffect* Effect = nullptr;
+ const Decl* Callee = nullptr; // only valid for Calls*
+ SourceLocation Loc{};
+ DiagnosticID ID{ DiagnosticID::None };
+
+ Diagnostic() = default;
+
+ Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc, const Decl* Callee = nullptr)
+ : Effect{ Effect }, Callee{ Callee }, Loc{ Loc }, ID{ ID }
+ {
+ }
+};
+
+enum class SpecialFuncType : uint8_t {
+ None, OperatorNew, OperatorDelete
+};
+enum class CallType {
+ Unknown, Function, Virtual, Block
+ // unknown: probably function pointer
+};
+
+// Return whether the function CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl* FD)
+{
+ if (!(FD->hasBody() || FD->isInlined())) {
+ // externally defined; we couldn't verify if we wanted to.
+ return false;
+ }
+ if (FD->isTrivial()) {
+ // Otherwise `struct x { int a; };` would have an unverifiable default
+ // constructor.
+ return true;
+ }
+ return true;
+}
+
+// Transitory, more extended information about a callable, which can be a function,
+// block, function pointer...
+struct CallableInfo {
+ const Decl* CDecl;
+ mutable std::optional<std::string> MaybeName; // mutable because built on demand in const method
+ SpecialFuncType FuncType{ SpecialFuncType::None };
+ FunctionEffectSet Effects;
+ CallType CType{ CallType::Unknown };
+ // bool IsAllowListed{ false };
+
+ CallableInfo(const Decl& CD, SpecialFuncType FT = SpecialFuncType::None)
+ : CDecl{ &CD }, FuncType{ FT }
+ {
+ //llvm::errs() << "CallableInfo " << name() << "\n";
+
+ if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+ assert(FD->getCanonicalDecl() == FD);
+ // Use the function's definition, if any.
+ if (auto* Def = FD->getDefinition()) {
+ CDecl = FD = Def;
+ }
+ CType = CallType::Function;
+ if (auto* Method = dyn_cast<CXXMethodDecl>(FD)) {
+ if (Method->isVirtual()) {
+ CType = CallType::Virtual;
+ }
+ }
+ Effects = FD->getFunctionEffects();
+
+ // TODO: Generalize via noreturn??? but that would cover exceptions too.
+ // if (name() == "__assert_rtn") {
+ // // big hack because it's hard to get the attribute to stick on it
+ // // through a redeclaration, not sure why.
+ // IsAllowListed = true;
+ // }
+ } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+ CType = CallType::Block;
+ Effects = BD->getFunctionEffects();
+ } else if (auto* VD = dyn_cast<ValueDecl>(CDecl)) {
+ // ValueDecl is function, enum, or variable, so just look at the type.
+ Effects = FunctionEffectSet::get(*VD->getType());
+ }
+ }
+
+ bool isDirectCall() const {
+ return CType == CallType::Function || CType == CallType::Block;
+ }
+
+ bool isVerifiable() const
+ {
+ switch (CType) {
+ case CallType::Unknown:
+ case CallType::Virtual:
+ break;
+ case CallType::Block:
+ return true;
+ case CallType::Function:
+ return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+ }
+ return false;
+ }
+
+ /// Generate a name for logging.
+ std::string name(Sema& sema) const
+ {
+ if (!MaybeName) {
+ std::string Name;
+ llvm::raw_string_ostream OS(Name);
+
+ if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+ FD->getNameForDiagnostic(OS, sema.getPrintingPolicy(),
+ /*Qualified=*/true);
+ } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+ OS << "(block " << BD->getBlockManglingNumber() << ")";
+ } else if (auto* VD = dyn_cast<NamedDecl>(CDecl)) {
+ VD->printQualifiedName(OS);
+ }
+ MaybeName = Name;
+ }
+ return *MaybeName;
+ }
+};
+
+// ----------
+// Map effects to single diagnostics.
+class EffectToDiagnosticMap {
+ // Since we currently only have a tiny number of effects (typically no more than 1),
+ // use a sorted SmallVector.
+ using Element = std::pair<const FunctionEffect*, Diagnostic>;
+ using ImplVec = llvm::SmallVector<Element>;
+ std::unique_ptr<ImplVec> Impl;
+public:
+ Diagnostic& getOrInsertDefault(const FunctionEffect* key)
+ {
+ if (Impl == nullptr) {
+ Impl = std::make_unique<llvm::SmallVector<Element>>();
+ auto& item = Impl->emplace_back();
+ item.first = key;
+ return item.second;
+ }
+ Element elem{ key, {} };
+ auto iter = _find(elem);
+ if (iter != Impl->end() && iter->first == key) {
+ return iter->second;
+ }
+ iter = Impl->insert(iter, elem);
+ return iter->second;
+ }
+
+ const Diagnostic* lookup(const FunctionEffect* key)
+ {
+ if (Impl == nullptr) {
+ return nullptr;
+ }
+ Element elem{ key, {} };
+ auto iter = _find(elem);
+ if (iter != Impl->end() && iter->first == key) {
+ return &iter->second;
+ }
+ return nullptr;
+ }
+
+ size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+ ImplVec::iterator _find(const Element& elem)
+ {
+ return std::lower_bound(Impl->begin(), Impl->end(), elem, [](const Element& lhs, const Element& rhs) {
+ return lhs.first < rhs.first;
+ });
+ }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked. Since there are potentially a large
+// number of these objects, it needs care about size.
+class PendingFunctionAnalysis {
+ // Current size: 5 pointers
+ friend class CompleteFunctionAnalysis;
+
+ struct DirectCall {
+ const Decl* Callee;
+ SourceLocation CallLoc;
+ };
+
+public:
+ // We always have two disjoint sets of effects to verify:
+ // 1. Effects declared explicitly by this function.
+ // 2. All other inferrable effects needing verification.
+ FunctionEffectSet DeclaredVerifiableEffects;
+ FunctionEffectSet FXToInfer;
+
+private:
+ // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr to optimize
+ // size for the case of 0 diagnostics.
+ std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
+
+ // Potential diagnostics pertaining to other, non-explicit, inferrable effects.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+ std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
+
+public:
+ PendingFunctionAnalysis(const CallableInfo& cinfo, FunctionEffectSet AllInferrableEffectsToVerify)
+ {
+ MutableFunctionEffectSet fx;
+ for (const auto* effect : cinfo.Effects) {
+ if (effect->getFlags() & FunctionEffect::kRequiresVerification) {
+ fx.insert(effect);
+ }
+ }
+ DeclaredVerifiableEffects = FunctionEffectSet::create(fx);
+
+ // Check for effects we are not allowed to infer
+ fx.clear();
+ for (const auto* effect : AllInferrableEffectsToVerify) {
+ if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
+ fx.insert(effect);
+ } else {
+ // Add a diagnostic for this effect if a caller were to
+ // try to infer it.
+ auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
+ diag = Diagnostic{ effect, DiagnosticID::DeclWithoutConstraintOrInference,
+ cinfo.CDecl->getLocation() };
+ }
+ }
+ // fx is now the set of inferrable effects which are not prohibited
+ FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) - DeclaredVerifiableEffects);
+ }
+
+ void checkAddDiagnostic(bool inferring, const Diagnostic& NewDiag)
+ {
+ if (!inferring) {
+ if (DiagnosticsForExplicitFX == nullptr) {
+ DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
+ }
+ DiagnosticsForExplicitFX->push_back(NewDiag);
+ } else {
+ auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
+ if (diag.ID == DiagnosticID::None) {
+ diag = NewDiag;
+ }
+ }
+ }
+
+ void addUnverifiedDirectCall(const Decl* D, SourceLocation CallLoc)
+ {
+ if (UnverifiedDirectCalls == nullptr) {
+ UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
+ }
+ UnverifiedDirectCalls->emplace_back(DirectCall{ D, CallLoc });
+ }
+
+ // Analysis is complete when there are no unverified direct calls.
+ bool isComplete() const
+ {
+ return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
+ }
+
+ const Diagnostic* diagnosticForInferrableEffect(const FunctionEffect* effect)
+ {
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ const SmallVector<DirectCall>& unverifiedCalls() const
+ {
+ assert(!isComplete());
+ return *UnverifiedDirectCalls;
+ }
+
+ SmallVector<Diagnostic>* getDiagnosticsForExplicitFX() const
+ {
+ return DiagnosticsForExplicitFX.get();
+ }
+
+ void dump(llvm::raw_ostream& OS) const
+ {
+ OS << "Pending: Declared ";
+ DeclaredVerifiableEffects.dump(OS);
+ OS << ", " << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0) << " diags; ";
+ OS << " Infer ";
+ FXToInfer.dump(OS);
+ OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ }
+};
+
+// ----------
+class CompleteFunctionAnalysis {
+ // Current size: 2 pointers
+public:
+ // Has effects which are both the declared ones -- not to be inferred -- plus ones which
+ // have been successfully inferred. These are all considered "verified" for the purposes
+ // of callers; any issue with verifying declared effects has already been reported and
+ // is not the problem of any caller.
+ FunctionEffectSet VerifiedEffects;
+
+private:
+ // This is used to generate notes about failed inference.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+ CompleteFunctionAnalysis(PendingFunctionAnalysis& pending, FunctionEffectSet funcFX, FunctionEffectSet AllInferrableEffectsToVerify)
+ {
+ MutableFunctionEffectSet verified;
+ verified |= funcFX;
+ for (const auto* effect : AllInferrableEffectsToVerify) {
+ if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
+ verified.insert(effect);
+ }
+ }
+ VerifiedEffects = FunctionEffectSet::create(verified);
+
+ InferrableEffectToFirstDiagnostic = std::move(pending.InferrableEffectToFirstDiagnostic);
+ }
+
+ const Diagnostic* firstDiagnosticForEffect(const FunctionEffect* effect)
+ {
+ // TODO: is this correct?
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ void dump(llvm::raw_ostream& OS) const
+ {
+ OS << "Complete: Verified ";
+ VerifiedEffects.dump(OS);
+ OS << "; Infer ";
+ OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ }
+};
+
+/*
+ TODO: nolock and noalloc imply noexcept
+ if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
+ if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
+ != clang::CT_Cannot) {
+ S.Diag(Callable->getBeginLoc(),
+ diag::warn_perf_annotation_implies_noexcept)
+ << getPerfAnnotationSpelling(CInfo.PerfAnnot);
+ }
+ }
+*/
+
+// ==========
+class Analyzer {
+ constexpr static int kDebugLogLevel = 3;
+
+ // --
+ Sema& mSema;
+
+ // used from Sema:
+ // SmallVector<const Decl *> DeclsWithUnverifiedEffects
+
+ // Subset of Sema.AllEffectsToVerify
+ FunctionEffectSet AllInferrableEffectsToVerify;
+
+ using FuncAnalysisPtr = llvm::PointerUnion<PendingFunctionAnalysis*, CompleteFunctionAnalysis*>;
+
+ // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+ // than complete state, so use different objects to represent them.
+ // The state pointers are owned by the container.
+ struct AnalysisMap : public llvm::DenseMap<const Decl*, FuncAnalysisPtr> {
+
+ ~AnalysisMap();
+
+ // use lookup()
+
+ CompleteFunctionAnalysis* completedAnalysisForDecl(const Decl* D) const
+ {
+ if (auto AP = lookup(D)) {
+ if (isa<CompleteFunctionAnalysis*>(AP)) {
+ return AP.get<CompleteFunctionAnalysis*>();
+ }
+ }
+ return nullptr;
+ }
+
+ void dump(Sema& S, llvm::raw_ostream& OS)
+ {
+ OS << "AnalysisMap:\n";
+ for (const auto& item : *this) {
+ CallableInfo CI{ *item.first };
+ const auto AP = item.second;
+ OS << item.first << " " << CI.name(S) << " : ";
+ if (AP.isNull()) {
+ OS << "null\n";
+ } else if (isa<CompleteFunctionAnalysis*>(AP)) {
+ auto* CFA = AP.get<CompleteFunctionAnalysis*>();
+ OS << CFA << " ";
+ CFA->dump(OS);
+ } else if (isa<PendingFunctionAnalysis*>(AP)) {
+ auto* PFA = AP.get<PendingFunctionAnalysis*>();
+ OS << PFA << " ";
+ PFA->dump(OS);
+ } else llvm_unreachable("never");
+ }
+ }
+ };
+ AnalysisMap DeclAnalysis;
+
+public:
+ Analyzer(Sema& S)
+ : mSema{ S }
+ {
+ }
+
+ void run(const TranslationUnitDecl& TU)
+ {
+#if FX_ANALYZER_VERIFY_DECL_LIST
+ verifyRootDecls(TU);
+#endif
+ // Gather all of the effects to be verified to see what operations need to be checked,
+ // and to see which ones are inferrable.
+ {
+ MutableFunctionEffectSet inferrableEffects;
+ for (const FunctionEffect* effect : mSema.AllEffectsToVerify) {
+ const auto Flags = effect->getFlags();
+ if (Flags & FunctionEffect::kInferrableOnCallees) {
+ inferrableEffects.insert(effect);
+ }
+ }
+ AllInferrableEffectsToVerify = FunctionEffectSet::create(inferrableEffects);
+ llvm::outs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::outs());
+ llvm::outs() << "\n";
+ }
+
+ SmallVector<const Decl*>& verifyQueue = mSema.DeclsWithUnverifiedEffects;
+
+ // It's useful to use DeclsWithUnverifiedEffects as a stack for a
+ // depth-first traversal rather than have a secondary container. But first,
+ // reverse it, so Decls are verified in the order they are declared.
+ std::reverse(verifyQueue.begin(), verifyQueue.end());
+
+ while (!verifyQueue.empty()) {
+ const Decl* D = verifyQueue.back();
+ if (auto AP = DeclAnalysis.lookup(D)) {
+ if (isa<CompleteFunctionAnalysis*>(AP)) {
+ // already done
+ verifyQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis*>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto* pending = AP.get<PendingFunctionAnalysis*>();
+ finishPendingAnalysis(D, pending);
+ verifyQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("shouldn't happen");
+ }
+
+ auto* pending = verifyDecl(D);
+ if (pending == nullptr) {
+ // completed now
+ verifyQueue.pop_back();
+ continue;
+ }
+
+ for (const auto& call : pending->unverifiedCalls()) {
+ // This lookup could be optimized out if the results could have been saved
+ // from followCall when we traversed the caller's AST. It would however
+ // make the check for recursion more complex.
+ auto AP = DeclAnalysis.lookup(call.Callee);
+ if (AP.isNull()) {
+ verifyQueue.push_back(call.Callee);
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis*>(AP)) {
+ // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+ __builtin_trap();
+ }
+ llvm_unreachable("shouldn't happen");
+ }
+ }
+ }
+
+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)
+ {
+ const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
+ if (FD != nullptr) {
+ // Currently, built-in functions are always considered safe.
+ if (FD->getBuiltinID() != 0) {
+ return nullptr;
+ }
+ // If it doesn't have a body, then we have to rely on the declaration.
+
+#warning FIXME
+/* if (!functionIsVerifiable(mSema, FD)) {
+ // const PerfAnnotation PA = FD->getPerfAnnotation();
+ // if (PA != PerfAnnotation::NoLock) {
+ // Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
+ // }
+ return;
+ }*/
+ }
+ CallableInfo CInfo{ *D };
+
+ // 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{ CInfo, AllInferrableEffectsToVerify };
+
+ llvm::outs() << "\nVerifying " << CInfo.name(mSema) << " ";
+ FAnalysis.dump(llvm::outs());
+
+ FunctionBodyASTVisitor Visitor{ *this, FAnalysis, CInfo };
+
+ Visitor.run();
+ if (FAnalysis.isComplete()) {
+ completeAnalysis(CInfo, FAnalysis);
+ return nullptr;
+ }
+ // Copy the pending analysis to the heap and save it in the map.
+ auto* pendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
+ DeclAnalysis[D] = pendingPtr;
+ llvm::outs() << "inserted pending " << pendingPtr << "\n";
+ DeclAnalysis.dump(mSema, llvm::outs());
+ return pendingPtr;
+ }
+
+ // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis and inserted
+ // in the container.
+ void completeAnalysis(const CallableInfo& CInfo, PendingFunctionAnalysis& pending)
+ {
+ if (auto* diags = pending.getDiagnosticsForExplicitFX()) {
+ emitDiagnostics(*diags, CInfo, mSema);
+ }
+ auto* completePtr = new CompleteFunctionAnalysis(pending,
+ CInfo.Effects, AllInferrableEffectsToVerify);
+ DeclAnalysis[CInfo.CDecl] = completePtr;
+ llvm::outs() << "inserted complete " << completePtr << "\n";
+ DeclAnalysis.dump(mSema, llvm::outs());
+ }
+
+ // Called after all direct calls requiring inference have been found -- or not.
+ // Generally replicates FunctionBodyASTVisitor::followCall() but without the
+ // possibility of inference.
+ void finishPendingAnalysis(const Decl* D, PendingFunctionAnalysis* pending)
+ {
+ CallableInfo Caller{ *D };
+ for (const auto& call : pending->unverifiedCalls()) {
+ CallableInfo Callee{ *call.Callee };
+ followCall(Caller, *pending, Callee, call.CallLoc, /*assertNoFurtherInference=*/true);
+ }
+ completeAnalysis(Caller, *pending);
+ delete pending;
+ llvm::outs() << "destroyed pending " << pending << "\n";
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. CallableInfo pertains to the callee.
+ void followCall(const CallableInfo& Caller, PendingFunctionAnalysis &PFA,
+ const CallableInfo& Callee, SourceLocation CallLoc,
+ bool assertNoFurtherInference)
+ {
+ const bool DirectCall = Callee.isDirectCall();
+ FunctionEffectSet CalleeEffects = Callee.Effects;
+ bool isInferencePossible = DirectCall;
+
+ if (DirectCall) {
+ if (auto* CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+ CalleeEffects = CFA->VerifiedEffects;
+ isInferencePossible = false; // we've already traversed it
+ }
+ }
+ if (assertNoFurtherInference) {
+ assert(!isInferencePossible);
+ }
+ if (!Callee.isVerifiable()) {
+ isInferencePossible = false;
+ }
+ llvm::outs() << "followCall from " << Caller.name(mSema) << " to " << Callee.name(mSema)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::outs());
+ llvm::outs() << "\n";
+ puts("");
+
+ auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
+ const auto flags = effect->getFlags();
+ if (flags & FunctionEffect::kVerifyCalls) {
+ const bool diagnose = effect->diagnoseFunctionCall(DirectCall, Caller.CDecl, Caller.Effects,
+ Callee.CDecl, CalleeEffects);
+ if (diagnose) {
+ // If inference is not allowed, or the target is indirect (virtual method/function ptr?),
+ // generate a diagnostic now.
+ if (!isInferencePossible || !(flags & FunctionEffect::kInferrableOnCallees)) {
+ if (Callee.FuncType == SpecialFuncType::None) {
+ PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl });
+ } else {
+ PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::AllocatesMemory, CallLoc });
+ }
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ }
+ }
+ }
+ };
+
+ for (auto* effect : PFA.DeclaredVerifiableEffects) {
+ check1Effect(effect, false);
+ }
+
+ for (auto* effect : PFA.FXToInfer) {
+ check1Effect(effect, true);
+ }
+ }
+
+ // Should only be called when determined to be complete.
+ void emitDiagnostics(SmallVector<Diagnostic>& Diags, const CallableInfo& CInfo, Sema& S)
+ {
+#define UNTESTED __builtin_trap();
+#define TESTED
+ const SourceManager& SM = S.getSourceManager();
+ std::sort(Diags.begin(), Diags.end(), [&SM](const Diagnostic& lhs, const Diagnostic& rhs) {
+ return SM.isBeforeInTranslationUnit(lhs.Loc, rhs.Loc);
+ });
+
+ const auto TopFuncName = CInfo.name(S);
+
+ // TODO: Can we get better template instantiation notes?
+ auto checkAddTemplateNote = [&](const Decl* D) {
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ while (FD != nullptr && FD->isTemplateInstantiation()) {
+ S.Diag(FD->getPointOfInstantiation(), diag::note_func_effect_from_template);
+ FD = FD->getTemplateInstantiationPattern();
+ }
+ }
+ };
+
+ // Top-level diagnostics are warnings.
+ for (const auto& Diag : Diags) {
+ StringRef effectName = Diag.Effect->name();
+ switch (Diag.ID) {
+ case DiagnosticID::None:
+ //case DiagnosticID::DeclExternWithoutConstraint:
+ case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen here
+ //case DiagnosticID::DeclVirtualWithoutConstraint:
+ //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ TESTED
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ TESTED
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ TESTED
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ TESTED
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ TESTED
+ break;
+ case DiagnosticID::CallsDisallowedExpr:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr) << effectName << TopFuncName;
+ checkAddTemplateNote(CInfo.CDecl);
+ UNTESTED
+ break;
+
+ case DiagnosticID::CallsUnsafeDecl:
+ {
+ CallableInfo CalleeInfo{ *Diag.Callee };
+ auto CalleeName = CalleeInfo.name(S);
+
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func) << effectName << TopFuncName << CalleeName;
+ checkAddTemplateNote(CInfo.CDecl);
+
+ // Emit notes explaining the transitive chain of inferences: Why isn't the callee safe?
+ for (const auto* Callee = Diag.Callee; Callee != nullptr; ) {
+ std::optional<CallableInfo> MaybeNextCallee;
+ auto* completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+ if (completed == nullptr) {
+ // No result - could be
+ // - non-inline
+ // - virtual
+ if (CalleeInfo.CType == CallType::Virtual) {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName << CalleeName;
+ TESTED
+ } else if (CalleeInfo.CType == CallType::Unknown) {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
+ TESTED
+ } else {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName << CalleeName;
+ TESTED
+ }
+ break;
+ }
+ const auto* pDiag2 = completed->firstDiagnosticForEffect(Diag.Effect);
+ if (pDiag2 == nullptr) {
+ break;
+ }
+
+ const auto& Diag2 = *pDiag2;
+ switch (Diag2.ID) {
+ case DiagnosticID::None:
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ // case DiagnosticID::DeclExternWithoutConstraint:
+ // S.Diag(Diag2.Loc, diag::note_func_effect_call_extern) << effectName << CalleeName;
+ // break;
+ case DiagnosticID::DeclWithoutConstraintOrInference:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable) << effectName << CalleeName;
+ TESTED
+ break;
+ // case DiagnosticID::DeclVirtualWithoutConstraint:
+ // S.Diag(Diag2.Loc, diag::note_func_effect_call_virtual) << effectName << CalleeName;
+ // break;
+ //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+ case DiagnosticID::CallsDisallowedExpr:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName << CalleeName;
+ TESTED
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) << effectName << CalleeName;
+ TESTED
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::CallsUnsafeDecl:
+ MaybeNextCallee.emplace(*Diag2.Callee);
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func) << effectName << CalleeName << MaybeNextCallee->name(S);
+ TESTED
+ break;
+ }
+ checkAddTemplateNote(Callee);
+ Callee = Diag2.Callee;
+ if (MaybeNextCallee) {
+ CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(S);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // ----------
+ // This AST visitor is used to traverse the body of a function during effect verification.
+ // This happens in 2 distinct 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.
+ // When we are verifying explicit conformance [1] we should generate all diagnostics.
+ // When we are inferring conformance [2] we will need to save enough diagnostics
+ // to provide a note to explain why inference failed; just the first violation per
+ // FunctionEffect.
+ //
+ // Populates a provided FunctionAnalysis object.
+ //
+ // TODO: Currently we create a new RecursiveASTVisitor for every function analysis.
+ // Is it so lightweight that this is OK? It would appear so.
+ struct FunctionBodyASTVisitor : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ constexpr static bool Stop = false;
+ constexpr static bool Proceed = true;
+
+ Analyzer &Outer;
+ PendingFunctionAnalysis &CurrentFunction;
+ CallableInfo& CurrentCaller;
+
+ FunctionBodyASTVisitor(Analyzer &outer, PendingFunctionAnalysis &CurrentFunction,
+ CallableInfo& CurrentCaller)
+ : Outer{ outer }, CurrentFunction{ CurrentFunction }, CurrentCaller{ CurrentCaller }
+ {
+ }
+
+ // -- Entry point --
+ void run()
+ {
+ // The target function itself may have some implicit code paths beyond the body:
+ // member and base constructors and destructors.
+ if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
+ if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+ for (const CXXCtorInitializer* Initer : Ctor->inits()) {
+ if (Expr* Init = Initer->getInit()) {
+ VisitStmt(Init);
+ }
+ }
+ } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+ followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+ } else if (!FD->isTrivial() && FD->isDefaulted()) {
+ // needed? maybe not
+ }
+ }
+ // else could be BlockDecl
+
+ // Do an AST traversal of the function/block body
+ TraverseDecl(const_cast<Decl*>(CurrentCaller.CDecl));
+ }
+
+ // -- Methods implementing common logic --
+
+ // Only effects whose flags include the specified flag receive a potential diagnostic.
+ // TODO: Consider bypasses for these loops by precomputing the flags we care about?
+ void addDiagnostic(FunctionEffect::FlagBit Flag, DiagnosticID D, SourceLocation Loc,
+ const Decl* Callee = nullptr)
+ {
+ // If there are ANY declared verifiable effects holding the flag, store just one diagnostic.
+ for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+ if (effect->getFlags() & Flag) {
+ addDiagnosticInner(/*inferring=*/false, effect, D, Loc, Callee);
+ break;
+ }
+ }
+ // For each inferred-but-not-verifiable effect holding the flag, store a diagnostic,
+ // if we don't already have a diagnostic for that effect.
+ for (auto* effect : CurrentFunction.FXToInfer) {
+ if (effect->getFlags() & Flag) {
+ addDiagnosticInner(/*inferring=*/true, effect, D, Loc, Callee);
+ }
+ }
+ }
+
+ void addDiagnosticInner(bool inferring, const FunctionEffect* effect, DiagnosticID D,
+ SourceLocation Loc, const Decl* Callee = nullptr)
+ {
+ Diagnostic NewDiag{ effect, D, Loc, Callee };
+ CurrentFunction.checkAddDiagnostic(inferring, NewDiag);
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. CallableInfo pertains to the callee.
+ void followCall(const CallableInfo& CI, SourceLocation CallLoc)
+ {
+ Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*assertNoFurtherInference=*/false);
+ }
+
+ void checkIndirectCall(CallExpr* Call, Expr* CalleeExpr)
+ {
+ const auto CalleeType = CalleeExpr->getType();
+ auto *FPT = CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+
+ auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
+ if (effect->getFlags() & FunctionEffect::kVerifyCalls) {
+ if (FPT == nullptr || effect->diagnoseFunctionCall(/*direct=*/false,
+ CurrentCaller.CDecl, CurrentCaller.Effects, FPT, FPT->getFunctionEffects())) {
+ addDiagnosticInner(inferring, effect, DiagnosticID::CallsDisallowedExpr, Call->getBeginLoc());
+ }
+ }
+ };
+
+ for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+ check1Effect(effect, false);
+ }
+
+ for (auto* effect : CurrentFunction.FXToInfer) {
+ check1Effect(effect, true);
+ }
+ }
+
+ // This destructor's body should be followed by the caller, but here we follow
+ // the field and base destructors.
+ void followDestructor(const CXXRecordDecl* Rec, const CXXDestructorDecl* Dtor)
+ {
+ for (const FieldDecl* Field : Rec->fields()) {
+ followTypeDtor(Field->getType());
+ }
+
+ if (const auto* Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ for (const CXXBaseSpecifier& Base : Class->bases()) {
+ followTypeDtor(Base.getType());
+ }
+ for (const CXXBaseSpecifier& Base : Class->vbases()) {
+ followTypeDtor(Base.getType());
+ }
+ }
+ }
+
+ void followTypeDtor(QualType QT)
+ {
+ const Type* Ty = QT.getTypePtr();
+ while (Ty->isArrayType()) {
+ const ArrayType* Arr = Ty->getAsArrayTypeUnsafe();
+ QT = Arr->getElementType();
+ Ty = QT.getTypePtr();
+ }
+
+ if (Ty->isRecordType()) {
+ if (const CXXRecordDecl* Class = Ty->getAsCXXRecordDecl()) {
+ if (auto* Dtor = Class->getDestructor()) {
+ CallableInfo CI{ *Dtor };
+ followCall(CI, Dtor->getLocation());
+ }
+ }
+ return;
+ }
+ if (Ty->isScalarType() || Ty->isReferenceType() || Ty->isAtomicType()) {
+ return;
+ }
+
+ llvm::errs() << "warning: " << QT << ": unknown special functions\n";
+ }
+
+ // -- Methods for use of RecursiveASTVisitor --
+
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+ bool VisitCXXThrowExpr(CXXThrowExpr* Throw)
+ {
+ addDiagnostic(FunctionEffect::kExcludeThrow, DiagnosticID::Throws, Throw->getThrowLoc());
+ return Proceed;
+ }
+
+ bool VisitCXXCatchStmt(CXXCatchStmt* Catch)
+ {
+ addDiagnostic(FunctionEffect::kExcludeCatch, DiagnosticID::Catches, Catch->getCatchLoc());
+ return Proceed;
+ }
+
+ bool VisitObjCMessageExpr(ObjCMessageExpr* Msg)
+ {
+ addDiagnostic(FunctionEffect::kExcludeObjCMessageSend, DiagnosticID::CallsObjC, Msg->getBeginLoc());
+ return Proceed;
+ }
+
+ bool VisitCallExpr(CallExpr* Call)
+ {
+ if constexpr (kDebugLogLevel > 2) {
+ llvm::errs() << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ }
+
+ /*if ((AllFlagsOfFXToVerify & FunctionEffect::kVerifyCalls) == 0u) {
+ return Proceed;
+ }*/
+
+ Expr* CalleeExpr = Call->getCallee();
+ if (const Decl* Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+ CallableInfo CI{ *Callee };
+ followCall(CI, Call->getBeginLoc());
+ return Proceed;
+ }
+
+ if (isa<CXXPseudoDestructorExpr>(CalleeExpr)) {
+ // just destroying a scalar, fine.
+ return Proceed;
+ }
+
+ checkIndirectCall(Call, CalleeExpr);
+
+ // No Decl, just an Expr. It could be a cast or something; we could dig
+ // into it and look for a DeclRefExpr. But for now it should suffice
+ // to look at the type of the Expr. Well, unless we're in a template :-/
+
+ //llvm::errs() << "CalleeExpr ";
+ //CalleeExpr->dumpColor();
+ //llvm::errs() << Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ": warning: null callee\n";
+
+ return Proceed;
+ }
+
+ bool VisitVarDecl(VarDecl* Var)
+ {
+ if constexpr (kDebugLogLevel > 2) {
+ llvm::errs() << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ }
+
+ if (Var->isStaticLocal()) {
+ addDiagnostic(FunctionEffect::kExcludeStaticLocalVars, DiagnosticID::HasStaticLocal, Var->getLocation());
+ }
+
+ const QualType::DestructionKind DK = Var->needsDestruction(Outer.mSema.getASTContext());
+ if (DK == QualType::DK_cxx_destructor) {
+ QualType QT = Var->getType();
+ if (const auto* ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+ if (const auto* CxxRec = dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+ if (const auto* Dtor = CxxRec->getDestructor()) {
+ CallableInfo CI{ *Dtor };
+ followCall(CI, Var->getLocation());
+ }
+ }
+ }
+ }
+ return Proceed;
+ }
+
+ bool VisitCXXNewExpr(CXXNewExpr* New)
+ {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator new.
+ if (auto* 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 Proceed;
+ }
+
+ bool VisitCXXDeleteExpr(CXXDeleteExpr* Delete)
+ {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator delete.
+ if (auto* FD = Delete->getOperatorDelete()) {
+ CallableInfo CI{ *FD, SpecialFuncType::OperatorDelete };
+ followCall(CI, Delete->getBeginLoc());
+ }
+
+ // It DOES however visit the called destructor
+
+ return Proceed;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr* Construct)
+ {
+ if constexpr (kDebugLogLevel > 2) {
+ llvm::errs() << "VisitCXXConstructExpr : " <<
+ Construct->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ }
+
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to the constructor.
+ const CXXConstructorDecl* Ctor = Construct->getConstructor();
+ CallableInfo CI{ *Ctor };
+ followCall(CI, Construct->getLocation());
+
+ return Proceed;
+ }
+
+ bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr* DEI)
+ {
+ if (auto* Expr = DEI->getExpr()) {
+ TraverseStmt(Expr);
+ }
+ return Proceed;
+ }
+
+ bool TraverseLambdaExpr(LambdaExpr* Lambda)
+ {
+ // We override this so as the be able to skip traversal of the lambda's
+ // body. We have to explicitly traverse the captures.
+ for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) {
+ if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+ Lambda->capture_init_begin()[I]) == Stop) return Stop;
+ }
+
+ return Proceed;
+ }
+
+ bool TraverseBlockExpr(BlockExpr* /*unused*/)
+ {
+ // TODO: are the capture expressions (ctor call?) safe?
+ return Proceed;
+ }
+
+ bool VisitDeclRefExpr(const DeclRefExpr *E)
+ {
+ const ValueDecl* Val = E->getDecl();
+ if (isa<VarDecl>(Val)) {
+ const VarDecl* Var = cast<VarDecl>(Val);
+ const auto TLSK = Var->getTLSKind();
+ if (TLSK != VarDecl::TLS_None) {
+ // At least on macOS, thread-local variables are initialized on
+ // first access.
+ addDiagnostic(FunctionEffect::kExcludeThreadLocalVars,
+ DiagnosticID::AccessesThreadLocal, E->getLocation());
+ }
+ }
+ return Proceed;
+ }
+
+
+ // Unevaluated contexts: need to skip
+ // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
+
+ // bool TraverseTypeLoc(TypeLoc /*unused*/)
+ // {
+ // // This is a big blunt hammer so that we don't reach __invoke()'s call to declval().
+ // // Is it correct?
+ // return Proceed;
+ // }
+
+ bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+ return TraverseStmt(Node->getResultExpr());
+ }
+ bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { return Proceed; }
+
+ bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
+
+ bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
+
+ bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
+
+ bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
+ };
+
+#if FX_ANALYZER_VERIFY_DECL_LIST
+ // Sema has accumulated DeclsWithUnverifiedEffects. As a debug check, do our
+ // own AST traversal and see what we find.
+
+ using MatchFinder = ast_matchers::MatchFinder;
+ static constexpr StringRef Tag_Callable = "Callable";
+
+ // -----
+ // Called for every callable in the translation unit.
+ struct CallableFinderCallback : MatchFinder::MatchCallback {
+ Sema& mSema;
+
+ CallableFinderCallback(Sema& S) : mSema{ S } {}
+
+ void run(const MatchFinder::MatchResult &Result) override {
+ if (auto* Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
+ if (const auto FX = functionEffectsForDecl(Callable)) {
+ // Reuse this filtering method in Sema
+ mSema.CheckAddCallableWithEffects(Callable, FX);
+ }
+ }
+ }
+
+ static FunctionEffectSet functionEffectsForDecl(const Decl *D) {
+ if (auto* FD = D->getAsFunction()) {
+ return FD->getFunctionEffects();
+ }
+ if (auto* BD = dyn_cast<BlockDecl>(D)) {
+ return BD->getFunctionEffects();
+ }
+ return {};
+ }
+
+ static void get(Sema &S,const TranslationUnitDecl& TU)
+ {
+ MatchFinder CallableFinder;
+ CallableFinderCallback Callback{ S };
+
+ using namespace clang::ast_matchers;
+
+ CallableFinder.addMatcher(
+ decl(forEachDescendant(decl(anyOf(
+ functionDecl(hasBody(anything())).bind(Tag_Callable),
+ //objcMethodDecl(isDefinition()).bind(Tag_Callable), // no, always unsafe
+ blockDecl().bind(Tag_Callable))))),
+ &Callback);
+ // Matching LambdaExpr this way [a] doesn't seem to work (need to check for Stmt?)
+ // and [b] doesn't seem necessary, since the anonymous function is reached via the above.
+ // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))), &Callback);
+
+ CallableFinder.match(TU, TU.getASTContext());
+ }
+ };
+
+ void verifyRootDecls(const TranslationUnitDecl& TU) const
+ {
+ // If this weren't debug code, it would be good to find a way to move/swap
+ // instead of copying.
+ SmallVector<const Decl *> decls = mSema.DeclsWithUnverifiedEffects;
+ mSema.DeclsWithUnverifiedEffects.clear();
+
+ CallableFinderCallback::get(mSema, TU);
+
+ /*if (decls.size() != mSema.DeclsWithUnverifiedEffects.size())*/ {
+ llvm::errs() << "\nFXAnalysis: mSema gathered " << decls.size()
+ << " Decls; second AST pass found " << mSema.DeclsWithUnverifiedEffects.size() << "\n";
+ }
+ }
+#endif
+
+};
+
+Analyzer::AnalysisMap::~AnalysisMap()
+{
+ for (const auto& item : *this) {
+ auto ptr = item.second;
+ if (isa<PendingFunctionAnalysis*>(ptr)) {
+ delete ptr.get<PendingFunctionAnalysis*>();
+ } else {
+ delete ptr.get<CompleteFunctionAnalysis*>();
+ }
+ }
+}
+
+} // namespace FXAnalysis
+
+#if TEMP_DISABLED
+#if 0
+ // OLD OLD OLD from followCall
+ if (CI.PerfAnnot != PerfAnnotation::None) {
+ // Even if the callable turns out to be unsafe, if it is declared safe,
+ // don't create a chain reaction by propagating the error to its callers.
+ if constexpr (kDebugLogLevel > 1) {
+ llvm::errs() << Outer.mDebugIndent << "followCall: safe: " << int(CI.CType) << " " << CI.name() << "\n";
+ }
+
+ if (CI.isVerifiable()) {
+ // The top-level search for callables isn't finding all template instantiations?
+ // Since we have a function body here, possibly verify now.
+ Outer.determineCallableSafety(CI, /*EmitDiags=*/true);
+ } else {
+ Outer.markWithoutFollowing(CI, DiagnosticID::None);
+ }
+
+ return;
+ }
+
+ if constexpr (kDebugLogLevel > 1) {
+ llvm::errs() << Outer.mDebugIndent << "followCall type " << int(CI.CType) << " " << CI.name() << "\n";
+ }
+
+ if (!CI.isDirectCall()) {
+ // Function pointer or virtual method
+ Outer.markWithoutFollowing(CI, CI.CType == CallType::Virtual ?
+ DiagnosticID::DeclVirtualWithoutConstraint : DiagnosticID::DeclFuncPtrWithoutConstraint);
+ return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+ }
+
+ // Here we have a direct call. Are we allowed to infer? If not, emit a diagnostic.
+ if (!CI.isInferrable()) {
+ Outer.markWithoutFollowing(CI, DiagnosticID::DeclWithoutConstraintOrInference);
+ return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+ }
+
+ const DeclInfo& DI = Outer.determineCallableSafety(CI);
+ if (DI.hasDiagnostic()) {
+ if (CI.FuncType == SpecialFuncType::None) {
+ return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+ }
+ return addDiagnostic(DiagnosticID::AllocatesMemory, CallLoc);
+ }
+#endif
+
+
+struct PerfConstraintAnalyzer {
+ // Move all no_locks functions (both explicit and implicitly via call chain)
+ // into a separate code segment?
+ //constexpr static bool kCreateNoLocksSection = true;
+
+ // If non-null, the unsafe callable.
+ using FollowResult = const FunctionDecl*;
+
+ using MatchFinder = ast_matchers::MatchFinder;
+
+
+ // Information recorded about every visited function/block.
+ struct DeclInfo {
+ explicit DeclInfo(PerfAnnotation PA) {} //: DeclPerfAnnot{ PA } {}
+
+ bool hasDiagnostic() const { return Diag.ID != DiagnosticID::None; }
+ const Diagnostic& diagnostic() const { assert(hasDiagnostic()); return Diag; }
+ void setDiagnostic(const Diagnostic& e)
+ {
+ Diag = e;
+ }
+
+ // Scanning: prevent recursion
+ bool isScanning() const { return Scanning; }
+ void setScanning(bool b) { Scanning = b; }
+
+ Diagnostic Diag;
+
+ // This reflects the declared annotation, not the inferred one.
+ //PerfAnnotation DeclPerfAnnot{};
+
+ // Used to prevent infinite recursion.
+ bool Scanning = false;
+ };
+
+ // This contains an entry for every visited callable. Caution: determineCallableSafety
+ // assumes that an iterator remains valid across calls which may mutate the container.
+ using InferenceMap = std::map<const Decl*, DeclInfo>;
+
+ // --
+
+
+ // -----
+
+ Sema& S;
+ InferenceMap mInferenceMap;
+ std::string mDebugIndent;
+
+ // -----
+
+ PerfConstraintAnalyzer(Sema& S)
+ : S{ S }
+ {
+ }
+
+ // Variant of determineCallableSafety where we already know we can't follow
+ // because it is virtual or a function pointer etc. Or, we don't need to follow
+ // it because we know it's OK but we want to have it in the inference table.
+ void markWithoutFollowing(const CallableInfo& CInfo, DiagnosticID Diag)
+ {
+ auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
+ if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
+ return;
+ }
+ iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
+ if (Diag != DiagnosticID::None) {
+ DeclInfo& Result = iter->second;
+ Result.setDiagnostic({ Diag, CInfo.CDecl->getLocation(), nullptr });
+ }
+ }
+
+ void maybeDiagnose(DeclInfo& Result, const Diagnostic& Diag)
+ {
+ Result.setDiagnostic(Diag);
+ }
+
+ // Returns a populated map entry for the given CallableInfo's Decl.
+ // Caller should take special care to avoid recursion when Scanning is true.
+ // EmitDiags should only be true when called from the top-level traversal
+ // of all attributed functions in the TranslationUnit.
+ const DeclInfo& determineCallableSafety(const CallableInfo& CInfo, bool EmitDiags = false)
+ {
+ // If we have already visited this callable, return the previous result.
+ auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
+ if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
+ return iter->second;
+ }
+
+ if constexpr (kDebugLogLevel) {
+ llvm::errs() << mDebugIndent << "determineCallableSafety -> " << CInfo.name() << "\n";
+ }
+
+ iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
+ DeclInfo& Result = iter->second;
+
+ std::optional<PerfConstraintASTVisitor> MaybeVisitor; // built on demand
+ auto getVisitor = [&]() -> PerfConstraintASTVisitor& {
+ if (!MaybeVisitor) {
+ MaybeVisitor.emplace(*this, /*StopOnFirstDiag=*/!EmitDiags);
+ }
+ return *MaybeVisitor;
+ };
+
+ const FunctionDecl *FD = dyn_cast<FunctionDecl>(CInfo.CDecl);
+ if (FD != nullptr) {
+ // Currently, built-in functions are always considered safe.
+ if (FD->getBuiltinID() != 0) {
+ return Result;
+ }
+ // If it doesn't have a body, then we have to rely on the declaration.
+ if (!functionIsVerifiable(S, FD)) {
+ const PerfAnnotation PA = FD->getPerfAnnotation();
+ if (PA != PerfAnnotation::NoLock) {
+ Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
+ }
+ return Result;
+ }
+
+ if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+ for (const CXXCtorInitializer* Initer : Ctor->inits()) {
+ if (Expr* Init = Initer->getInit()) {
+ Result.setScanning(true);
+ getVisitor().VisitStmt(Init);
+ }
+ }
+ } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+ if (auto* UnsafeCallee = followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor)) {
+ //const auto PA = UnsafeCallee->getType()->isNoLock() ? PerfAnnotation::True : PerfAnnotation::False;
+ getVisitor().addDiagnostic(DiagnosticID::CallsUnsafeDecl, FD->getLocation(), UnsafeCallee);
+ }
+ } else if (!FD->isTrivial() && FD->isDefaulted()) {
+ // needed? maybe not
+ }
+ }
+
+ if constexpr (kDebugLogLevel) {
+ mDebugIndent += " ";
+ }
+
+ Result.setScanning(true);
+
+ auto& Visitor = getVisitor();
+ Visitor.TraverseDecl(const_cast<Decl*>(CInfo.CDecl));
+
+ const auto& Diagnostics = Visitor.Diagnostics;
+
+ Result.setScanning(false);
+
+ if constexpr (kDebugLogLevel) {
+ mDebugIndent = mDebugIndent.substr(0, mDebugIndent.size() - 4);
+ llvm::errs() << mDebugIndent << "determineCallableSafety <- " << CInfo.name() << " : " << Diagnostics.size() << " violations\n";
+ //EmitDiags = true; // TEMPORARY
+ }
+
+ if (!Diagnostics.empty()) {
+ if (EmitDiags) {
+ emitDiagnostics(Diagnostics, CInfo);
+ }
+ Result.setDiagnostic(Diagnostics.front());
+ }
+
+ return Result;
+ }
+
+ void emitDiagnostics(const std::vector<Diagnostic>& Diags,
+ const CallableInfo& CInfo)
+ {
+ }
+
+ // Top-level entry point. Search the entire TU for functions to verify.
+ // Ways to optimize:
+ // - are the diagnostics enabled? if not, bail
+ // - did Sema see any tagged functions with bodies? if not, bail
+ void run(const TranslationUnitDecl& TU)
+ {
+
+ /*if constexpr (kCreateNoLocksSection) {
+ createNoLocksSection();
+ }*/
+ }
+
+ /*void createNoLocksSection()
+ {
+ const char* kSectionName = "__TEXT,__nolock,regular,pure_instructions";
+ if (auto err = S.isValidSectionSpecifier(kSectionName)) {
+ // real diagnostic?
+ llvm::errs() << "error: invalid section name " << kSectionName << " (" << err << ")\n";
+ return;
+ }
+
+ auto& MapEntry = S.getASTContext().SemaAnalysisGeneratedSections[kSectionName];
+ MapEntry.reserve(mInferenceMap.size());
+ for (auto& item : mInferenceMap) {
+ MapEntry.emplace_back(item.first);
+ if constexpr (kDebugLogLevel > 1) {
+ if (auto* F = dyn_cast<FunctionDecl>(item.first)) {
+ CallableInfo CI{ *F, S };
+ llvm::errs() << "nolock: " << CI.name() << "\n";
+ }
+ }
+ }
+ }*/
+};
+#endif // TEMP_DISABLED
+
+// =============================================================================
+
+
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
@@ -2534,6 +4022,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
SourceLocation())) {
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
}
+
+ // TODO: skip this if the warning isn't enabled.
+ FXAnalysis::Analyzer{ S }.run(*TU);
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 720d5fd5f0428..6022e4a838e8a 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -585,6 +585,27 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
Diag(Loc, diag::warn_nullability_lost) << SrcType << DstType;
}
+// Generate diagnostics when adding or removing effects in a type conversion.
+void Sema::diagnoseFunctionEffectConversion(QualType DstType,
+ QualType SrcType,
+ SourceLocation Loc)
+{
+ llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> " << DstType << "\n";
+ const auto SrcFX = FunctionEffectSet::get(*SrcType);
+ const auto DstFX = FunctionEffectSet::get(*DstType);
+ if (SrcFX != DstFX) {
+ const auto diffs = FunctionEffectSet::differences(SrcFX, DstFX);
+ for (const auto& item : diffs) {
+ const FunctionEffect* effect = item.first;
+ const bool adding = item.second;
+ if (effect->diagnoseConversion(adding,
+ SrcType, SrcFX, DstType, DstFX)) {
+ Diag(Loc, adding ? diag::warn_invalid_add_func_effects : diag::warn_invalid_remove_func_effects) << effect->name();
+ }
+ }
+ }
+}
+
void Sema::diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E) {
// nullptr only exists from C++11 on, so don't warn on its absence earlier.
if (!getLangOpts().CPlusPlus11)
@@ -661,6 +682,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
diagnoseZeroToNullptrConversion(Kind, E);
+ if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+ diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
+ }
QualType ExprTy = Context.getCanonicalType(E->getType());
QualType TypeTy = Context.getCanonicalType(Ty);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1f4a041e88dff..77fdffef6382e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3922,6 +3922,40 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
return true;
}
+ const auto OldFX = Old->getFunctionEffects();
+ const auto NewFX = New->getFunctionEffects();
+ if (OldFX != NewFX) {
+ const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ for (const auto& item : diffs) {
+ const FunctionEffect* effect = item.first;
+ const bool adding = item.second;
+ if (effect->diagnoseRedeclaration(adding, *Old, OldFX, *New, NewFX)) {
+ Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << effect->name();
+ Diag(Old->getLocation(), diag::note_previous_declaration);
+ }
+ }
+
+ const auto MergedFX = OldFX | NewFX;
+
+ // Having diagnosed any problems, prevent further errors by applying the merged set of effects
+ // to both declarations.
+ auto applyMergedFX = [&](FunctionDecl* FD) {
+ const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
+ FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+ EPI.FunctionEffects = MergedFX;
+ QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+ FPT->getParamTypes(), EPI);
+
+ FD->setType(ModQT);
+ };
+
+ applyMergedFX(Old);
+ applyMergedFX(New);
+
+ OldQType = Old->getType();
+ NewQType = New->getType();
+ }
+
if (getLangOpts().CPlusPlus) {
OldQType = Context.getCanonicalType(Old->getType());
NewQType = Context.getCanonicalType(New->getType());
@@ -11100,6 +11134,49 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
return nullptr;
}
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
+{
+ if (!D->hasBody()) {
+ if (const auto *FD = D->getAsFunction()) {
+ if (!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
+ // (??? This was copied from something else in AnalysisBasedWarnings ???)
+ if (cast<DeclContext>(D)->isDependentContext()) {
+ return;
+ }
+
+ // Filter out declarations that the FunctionEffect analysis should skip
+ // and not verify. (??? Is this the optimal order in which to test ???)
+ bool effectsNeedVerification = false;
+ for (const auto *Effect : FX) {
+ if (Effect->getFlags() & FunctionEffect::kRequiresVerification) {
+ AllEffectsToVerify.insert(Effect);
+ effectsNeedVerification = true;
+ }
+ }
+ if (!effectsNeedVerification) {
+ return;
+ }
+
+ // Record the declaration for later analysis.
+ DeclsWithUnverifiedEffects.push_back(D);
+}
+
/// Determines if we can perform a correct type check for \p D as a
/// redeclaration of \p PrevDecl. If not, we can generally still perform a
/// best-effort check.
@@ -15712,6 +15789,12 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
else
FD = cast<FunctionDecl>(D);
+ auto *Canon = FD->getCanonicalDecl();
+
+ // llvm::outs() << "** ActOnStartOfFunctionDef " << FD->getName() <<
+ // " " << FD << " " << Canon << " " << FD->getType() << "\n";
+ // getNameForDiagnostic
+
// Do not push if it is a lambda because one is already pushed when building
// the lambda in ActOnStartOfLambdaDefinition().
if (!isLambdaCallOperator(FD))
@@ -15912,6 +15995,12 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
+ const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
+ llvm::outs() << "^^ " << FX.size() << " effects\n";
+ if (FX) {
+ CheckAddCallableWithEffects(FD, FX);
+ }
+
return D;
}
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c00120b59d396..12f7869441c9b 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -395,6 +395,34 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
}
+/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
+/// integer (boolean) expression. If not, emit an error and return false.
+bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
+ bool &Value)
+{
+ if (AL.isInvalid()) {
+ return false;
+ }
+ Expr* ArgExpr = AL.getArgAsExpr(ArgNum);
+ SourceLocation errorLoc{ AL.getLoc() };
+
+ if (AL.isArgIdent(ArgNum)) {
+ IdentifierLoc * IL = AL.getArgAsIdent(ArgNum);
+ errorLoc = IL->Loc;
+ } else if (ArgExpr != nullptr) {
+ auto maybeVal = ArgExpr->getIntegerConstantExpr(Context, &errorLoc);
+ if (maybeVal) {
+ Value = maybeVal->getBoolValue();
+ return true;
+ }
+ }
+
+ AL.setInvalid();
+ Diag(errorLoc, diag::err_attribute_argument_n_type)
+ << AL << ArgNum << AANT_ArgumentConstantExpr;
+ return false;
+}
+
/// Applies the given attribute to the Decl without performing any
/// additional semantic checking.
template <typename AttrType>
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index e258a4f7c8941..dac5d5f3e5c1e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18321,6 +18321,26 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
return true;
}
+ // Virtual overrides must have the same or stronger performance annotation.
+ const auto OldFX = Old->getFunctionEffects();
+ const auto NewFX = New->getFunctionEffects();
+
+ if (OldFX != NewFX) {
+ const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ bool AnyDiags = false;
+
+ for (const auto& item : diffs) {
+ const FunctionEffect* effect = item.first;
+ const bool adding = item.second;
+ if (effect->diagnoseMethodOverride(adding, *Old, OldFX, *New, NewFX)) {
+ Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << effect->name();
+ Diag(Old->getLocation(), diag::note_overridden_virtual_function);
+ AnyDiags = true;
+ }
+ }
+ if (AnyDiags) return true;
+ }
+
CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
// If the calling conventions match, everything is fine
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 93f82e68ab644..643f8e7f44488 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17126,6 +17126,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
BlockDecl *BD = BSI->TheDecl;
+ if (const auto FX = BD->getFunctionEffects()) {
+ CheckAddCallableWithEffects(BD, FX);
+ }
+
if (BSI->HasImplicitReturnType)
deduceClosureReturnType(*BSI);
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index c34a40fa7c81a..50007d4e1de11 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4945,10 +4945,16 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
// If this conversion sequence succeeded and involved implicitly converting a
// _Nullable type to a _Nonnull one, complain.
- if (!isCast(CCK))
+ if (!isCast(CCK)) {
diagnoseNullableToNonnullConversion(ToType, InitialFromType,
From->getBeginLoc());
+ // TODO: This generates a redundant diagnostic for:
+ // void (^nl_block0)() NOLOCK = ^(){};
+ // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/))
+ // diagnoseFunctionEffectConversion(ToType, InitialFromType,
+ // From->getBeginLoc());
+ }
return From;
}
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 5b95bae567b72..0e1a0f8dfdbab 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1888,6 +1888,11 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+ if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
+ CheckAddCallableWithEffects(LSI.CallOperator, FX);
+ }
+
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index b0c693f078efe..8f1563e788442 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1786,6 +1786,9 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
/// type.
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
+
+ llvm::outs() << "IsFunctionConversion " << FromType << " " << ToType << "\n";
+
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
@@ -1866,6 +1869,30 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
FromFn = QT->getAs<FunctionType>();
Changed = true;
}
+
+#if 1
+ // NOTE (TEMP): this works for C++ to allow dropping effects.
+ // For plain C, however, this creates an error when dropping effects!
+
+ // Transparently add/drop effects; here we are concerned with
+ // language rules/canonicalization. Adding/dropping effects is a warning.
+ auto FromFX = FromFPT->getFunctionEffects();
+ auto ToFX = ToFPT->getFunctionEffects();
+ if (FromFX != ToFX) {
+ llvm::outs() << "IsFunctionConversion effects change " << FromType << " -> " << ToType << "\n";
+
+ //const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
+ // TODO: diagnose conflicts
+
+ FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
+ ExtInfo.FunctionEffects = ToFX;
+ QualType QT = Context.getFunctionType(FromFPT->getReturnType(),
+ FromFPT->getParamTypes(), ExtInfo);
+ FromFn = QT->getAs<FunctionType>();
+ llvm::outs() << " produced " << QT << "\n";
+ Changed = true;
+ }
+#endif
}
if (!Changed)
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3148299f6467a..2bc6b5b3880a8 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -144,6 +144,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
#define FUNCTION_TYPE_ATTRS_CASELIST \
case ParsedAttr::AT_NSReturnsRetained: \
case ParsedAttr::AT_NoReturn: \
+ case ParsedAttr::AT_NoLock: \
+ case ParsedAttr::AT_NoAlloc: \
case ParsedAttr::AT_Regparm: \
case ParsedAttr::AT_CmseNSCall: \
case ParsedAttr::AT_ArmStreaming: \
@@ -209,10 +211,18 @@ namespace {
/// validating that noderef was used on a pointer or array.
bool parsedNoDeref;
+ // Flags to diagnose illegal permutations of nolock(cond) and noalloc(cond).
+ // Manual logic for finding previous attributes would be more complex,
+ // unless we transoformed nolock/noalloc(false) into distinct separate
+ // attributes from the ones which are parsed.
+ unsigned char parsedNolock : 2;
+ unsigned char parsedNoalloc : 2;
+
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
: sema(sema), declarator(declarator),
- chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false) {}
+ chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
+ parsedNolock(0), parsedNoalloc(0) {}
Sema &getSema() const {
return sema;
@@ -339,6 +349,11 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
+ void setParsedNolock(unsigned char v) { parsedNolock = v; }
+ unsigned char getParsedNolock() const { return parsedNolock; }
+ void setParsedNoalloc(unsigned char v) { parsedNoalloc = v; }
+ unsigned char getParsedNoalloc() const { return parsedNoalloc; }
+
~TypeProcessingState() {
if (savedAttrs.empty())
return;
@@ -7926,6 +7941,114 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
+static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
+ ParsedAttr &attr, QualType &type, FunctionTypeUnwrapper& unwrapped)
+{
+ // Values of nolockState / noallocState
+ enum {
+ kNotSeen = 0,
+ kSeenFalse = 1,
+ kSeenTrue = 2
+ };
+
+ const bool isNoLock = attr.getKind() == ParsedAttr::AT_NoLock;
+ Sema &S = state.getSema();
+
+ // Delay if this is not a function type.
+ if (!unwrapped.isFunctionType())
+ return false;
+
+ // Require FunctionProtoType
+ auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
+ if (FPT == nullptr) {
+ // TODO: special diagnostic?
+ return false;
+ }
+
+ // Parse the conditional expression, if any
+ bool Cond = true; // default
+ if (attr.getNumArgs() > 0) {
+ if (!S.checkBoolExprArgumentAttr(attr, 0, Cond)) {
+ attr.setInvalid();
+ return false;
+ }
+ }
+
+ FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+
+ auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
+ Sema &S = state.getSema();
+ S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
+ << attrTrue << attrFalse
+ << false;
+ // we don't necessarily have the location of the previous attribute,
+ // so no note.
+ attr.setInvalid();
+ return true;
+ };
+
+ // check nolock(true) against nolock(false), and same for noalloc
+ const unsigned newState = Cond ? kSeenTrue : kSeenFalse;
+ const unsigned oppositeNewState = Cond ? kSeenFalse : kSeenTrue;
+ if (isNoLock) {
+ if (state.getParsedNolock() == oppositeNewState) {
+ return incompatible("nolock(true)", "nolock(false)");
+ }
+ // also check nolock(true) against noalloc(false)
+ if (Cond && state.getParsedNoalloc() == kSeenFalse) {
+ return incompatible("nolock(true)", "noalloc(false)");
+ }
+ state.setParsedNolock(newState);
+ } else {
+ if (state.getParsedNoalloc() == oppositeNewState) {
+ return incompatible("noalloc(true)", "noalloc(false)");
+ }
+ // also check nolock(true) against noalloc(false)
+ if (state.getParsedNolock() == kSeenTrue) {
+ if (!Cond) {
+ return incompatible("nolock(true)", "noalloc(false)");
+ }
+ // Ignore noalloc(true) since we already have nolock(true).
+ return true;
+ }
+ state.setParsedNoalloc(newState);
+ }
+
+ if (!Cond) {
+ // nolock(false) and noalloc(false) are represented as sugar, with AttributedType
+ Attr *A = nullptr;
+ if (isNoLock) {
+ A = NoLockAttr::Create(S.Context, false);
+ } else {
+ A = NoAllocAttr::Create(S.Context, false);
+ }
+ type = state.getAttributedType(A, type, type);
+ return true;
+ }
+
+ const FunctionEffect* Effect = nullptr;
+ if (isNoLock) {
+ Effect = &NoLockNoAllocEffect::nolock_instance();
+ } else {
+ Effect = &NoLockNoAllocEffect::noalloc_instance();
+ }
+
+ MutableFunctionEffectSet newEffectSet{ Effect };
+ if (EPI.FunctionEffects) {
+ // Preserve all previous effects - except noalloc, when we are adding nolock
+ for (const auto* effect : EPI.FunctionEffects) {
+ if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
+ newEffectSet.insert(effect);
+ }
+ }
+
+ EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
+ QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
+ FPT->getParamTypes(), EPI);
+ type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
+ return true;
+}
+
static bool checkMutualExclusion(TypeProcessingState &state,
const FunctionProtoType::ExtProtoInfo &EPI,
ParsedAttr &Attr,
@@ -8239,6 +8362,11 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
+ if (attr.getKind() == ParsedAttr::AT_NoLock
+ || attr.getKind() == ParsedAttr::AT_NoAlloc) {
+ return handleNoLockNoAllocTypeAttr(state, attr, type, unwrapped);
+ }
+
// Delay if the type didn't work out to a function.
if (!unwrapped.isFunctionType()) return false;
diff --git a/clang/test/Sema/attr-nolock.cpp b/clang/test/Sema/attr-nolock.cpp
new file mode 100644
index 0000000000000..3f9938a2e5460
--- /dev/null
+++ b/clang/test/Sema/attr-nolock.cpp
@@ -0,0 +1,78 @@
+// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
+// expected-no-diagnostics
+
+// Make sure that the attribute gets parsed and attached to the correct AST elements.
+// Update 1 Mar 2024
+
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+// =========================================================================================
+// Square brackets, true
+
+#define NOLOCK [[clang::nolock]]
+
+// On the type of the FunctionDecl
+void nl_function() NOLOCK;
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() NOLOCK;
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+
+// Check alternate attribute type and placement
+__attribute__((clang_nolock)) void (*nl_func_b)(void);
+// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nolock))'
+
+// On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() NOLOCK);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nolock))'
+
+// As an AttributedType within the nested types of a typedef
+typedef void (*nl_fp_type)() NOLOCK;
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nolock))'
+using nl_fp_talias = void (*)() NOLOCK;
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nolock))'
+
+// From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nolock))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nolock))'
+
+// On type of a FieldDecl
+struct Struct {
+ void (*nl_func_field)() NOLOCK;
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nolock))'
+};
+
+// noalloc should be subsumed into nolock
+void nl1() [[clang::nolock]] [[clang::noalloc]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nolock))'
+
+void nl2() [[clang::noalloc]] [[clang::nolock]];
+// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nolock))'
+
+// --- Blocks ---
+
+// On the type of the VarDecl holding a BlockDecl
+void (^nl_block1)() NOLOCK = ^() NOLOCK {};
+// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nolock))'
+
+int (^nl_block2)() NOLOCK = ^() NOLOCK { return 0; };
+// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nolock))'
+
+// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
+static void blockCaller() { nl_block1(); }
+// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
+
+// $$$ TODO: There are still some loose ends in all the methods of the lambda
+auto nl_lambda = []() NOLOCK {};
+
+// =========================================================================================
+// Square brackets, false
+
+void nl_func_false() [[clang::nolock(false)]];
+// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
+
+// TODO: Duplicate the above for noalloc
+// TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
new file mode 100644
index 0000000000000..9c494dcdd581d
--- /dev/null
+++ b/clang/test/Sema/attr-nolock2.cpp
@@ -0,0 +1,88 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c2x %s
+
+// TODO: There's a problem with diagnosing type conversions in plain C.
+
+#pragma clang diagnostic error "-Wstrict-prototypes"
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+#if 1 // TEMP_DISABLE
+
+// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
+// Check invalid combinations of nolock/noalloc attributes
+void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+void nl_true_false_2(void) [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+
+void na_true_false_1(void) [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+void na_true_false_2(void) [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+
+void nl_true_na_true_1(void) [[clang::nolock]] [[clang::noalloc]];
+void nl_true_na_true_2(void) [[clang::noalloc]] [[clang::nolock]];
+
+void nl_true_na_false_1(void) [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+void nl_true_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+
+void nl_false_na_true_1(void) [[clang::nolock(false)]] [[clang::noalloc]];
+void nl_false_na_true_2(void) [[clang::noalloc]] [[clang::nolock(false)]];
+
+void nl_false_na_false_1(void) [[clang::nolock(false)]] [[clang::noalloc(false)]];
+void nl_false_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock(false)]];
+
+// --- TYPE CONVERSIONS ---
+
+void unannotated(void);
+void nolock(void) [[clang::nolock]];
+void noalloc(void) [[clang::noalloc]];
+void type_conversions(void)
+{
+ // It's fine to remove a performance constraint.
+ void (*fp_plain)(void);
+
+ fp_plain = unannotated;
+ fp_plain = nolock;
+ fp_plain = noalloc;
+
+ // Adding/spoofing nolock is unsafe.
+ void (*fp_nolock)(void) [[clang::nolock]];
+ fp_nolock = nolock;
+ fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+ fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+ // Adding/spoofing noalloc is unsafe.
+ void (*fp_noalloc)(void) [[clang::noalloc]];
+ fp_noalloc = noalloc;
+ fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+ fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
+}
+
+// --- VIRTUAL METHODS ---
+#ifdef __cplusplus
+struct Base {
+ virtual void f1();
+ virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
+ virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+};
+
+struct Derived : public Base {
+ void f1() [[clang::nolock]] override;
+ void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
+ void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+};
+#endif // __cplusplus
+
+// --- REDECLARATIONS ---
+
+int f2(void);
+// redeclaration with a stronger constraint is OK.
+int f2(void) [[clang::nolock]]; // expected-note {{previous declaration is here}}
+int f2(void) { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
+int f3(void);
+// redeclaration with a stronger constraint is OK.
+int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
+int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+
+#endif // TEMP_DISABLE
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock3.cpp
new file mode 100644
index 0000000000000..cfe52da16447c
--- /dev/null
+++ b/clang/test/Sema/attr-nolock3.cpp
@@ -0,0 +1,144 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+// --- CONSTRAINTS ---
+#if 0 // TEMP_DISABLE
+
+void nl1() [[clang::nolock]]
+{
+ auto* pInt = new int; // expected-warning {{'nolock' function 'nl1' must not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nolock]]
+{
+ static int global; // expected-warning {{'nolock' function 'nl2' must not have static locals}}
+}
+
+void nl3() [[clang::nolock]]
+{
+ try {
+ throw 42; // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+ }
+ catch (...) { // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+ }
+}
+
+void nl4_inline() {}
+void nl4_not_inline(); // expected-note {{'nl4_not_inline' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+
+void nl4() [[clang::nolock]]
+{
+ nl4_inline(); // OK
+ nl4_not_inline(); // expected-warning {{'nolock' function 'nl4' must not call non-'nolock' function 'nl4_not_inline'}}
+}
+
+
+struct HasVirtual {
+ virtual void unsafe(); // expected-note {{'HasVirtual::unsafe' cannot be inferred 'nolock' because it is virtual}}
+};
+
+void nl5() [[clang::nolock]]
+{
+ HasVirtual hv;
+ hv.unsafe(); // expected-warning {{'nolock' function 'nl5' must not call non-'nolock' function 'HasVirtual::unsafe'}}
+}
+
+void nl6_unsafe(); // expected-note {{'nl6_unsafe' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl6_transitively_unsafe()
+{
+ nl6_unsafe(); // expected-note {{'nl6_transitively_unsafe' cannot be inferred 'nolock' because it calls non-'nolock' function 'nl6_unsafe'}}
+}
+
+void nl6() [[clang::nolock]]
+{
+ nl6_transitively_unsafe(); // expected-warning {{'nolock' function 'nl6' must not call non-'nolock' function 'nl6_transitively_unsafe'}}
+}
+
+thread_local int tl_var{ 42 };
+
+bool tl_test() [[clang::nolock]]
+{
+ return tl_var > 0; // expected-warning {{'nolock' function 'tl_test' must not use thread-local variables}}
+}
+
+void nl7()
+{
+ // Make sure we verify blocks
+ auto blk = ^() [[clang::nolock]] {
+ throw 42; // expected-warning {{'nolock' function '(block 0)' must not throw or catch exceptions}}
+ };
+}
+
+void nl8()
+{
+ // Make sure we verify lambdas
+ auto lambda = []() [[clang::nolock]] {
+ throw 42; // expected-warning {{'nolock' function 'nl8()::(anonymous class)::operator()' must not throw or catch exceptions}}
+ };
+}
+
+// Make sure template expansions are found and verified.
+ template <typename T>
+ struct Adder {
+ static T add_explicit(T x, T y) [[clang::nolock]]
+ {
+ return x + y; // expected-warning {{'nolock' function 'Adder<Stringy>::add_explicit' must not call non-'nolock' function 'operator+'}}
+ }
+ static T add_implicit(T x, T y)
+ {
+ return x + y; // expected-note {{'Adder<Stringy2>::add_implicit' cannot be inferred 'nolock' because it calls non-'nolock' function 'operator+'}}
+ }
+ };
+
+ struct Stringy {
+ friend Stringy operator+(const Stringy& x, const Stringy& y)
+ {
+ // Do something inferably unsafe
+ auto* z = new char[42]; // expected-note {{'operator+' cannot be inferred 'nolock' because it allocates/deallocates memory}}
+ return {};
+ }
+ };
+
+ struct Stringy2 {
+ friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
+ {
+ // Do something inferably unsafe
+ throw 42; // expected-note {{'operator+' cannot be inferred 'nolock' because it throws or catches exceptions}}
+ }
+ };
+
+void nl9() [[clang::nolock]]
+{
+ Adder<int>::add_explicit(1, 2);
+ Adder<int>::add_implicit(1, 2);
+
+ Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
+ Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function 'nl9' must not call non-'nolock' function 'Adder<Stringy2>::add_implicit'}} \
+ expected-note {{in template expansion here}}
+}
+
+void nl10(
+ void (*fp1)(), // expected-note {{'fp1' cannot be inferred 'nolock' because it is a function pointer}}
+ void (*fp2)() [[clang::nolock]]
+ ) [[clang::nolock]]
+{
+ fp1(); // expected-warning {{'nolock' function 'nl10' must not call non-'nolock' function 'fp1'}}
+ fp2();
+}
+
+#endif // TEMP_DISABLE
+
+// --- PLAYGROUND ---
+
+void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
+{
+}
+
+void nl11() [[clang::nolock]]
+{
+ nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
+}
+
>From 2a1b0d2d99c9bc4595dfc98f250bf43d23a28093 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 13:22:58 -0700
Subject: [PATCH 02/71] Cleanup, mostly code style
---
clang/include/clang/AST/Type.h | 24 +-
clang/include/clang/Basic/Attr.td | 4 +-
.../clang/Basic/DiagnosticSemaKinds.td | 6 +-
clang/lib/AST/Decl.cpp | 80 +-
clang/lib/AST/Type.cpp | 287 ++--
clang/lib/AST/TypePrinter.cpp | 8 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1316 +++++++----------
clang/lib/Sema/Sema.cpp | 23 +-
clang/lib/Sema/SemaDecl.cpp | 31 +-
clang/lib/Sema/SemaDeclAttr.cpp | 16 +-
clang/lib/Sema/SemaDeclCXX.cpp | 17 +-
clang/lib/Sema/SemaType.cpp | 44 +-
clang/test/Sema/attr-nolock-wip.cpp | 19 +
clang/test/Sema/attr-nolock2.cpp | 4 -
clang/test/Sema/attr-nolock3.cpp | 7 +-
15 files changed, 782 insertions(+), 1104 deletions(-)
create mode 100644 clang/test/Sema/attr-nolock-wip.cpp
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 41c60a6e221d4..de21561ce5dca 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4221,7 +4221,7 @@ class FunctionEffectSet {
UniquedAndSortedFX(Base Array) : Base(Array) {}
UniquedAndSortedFX(const FunctionEffect **Ptr, size_t Len)
- : Base(ptr, len) {}
+ : Base(Ptr, Len) {}
bool operator<(const UniquedAndSortedFX &rhs) const;
};
@@ -7955,14 +7955,14 @@ class FunctionEffect {
llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
FunctionEffect(EffectType T, Flags F, const char *Name)
- : Type_{T}, Flags_{F}, Name{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 getFlags() const { return Flags_; }
+ Flags flags() const { return Flags_; }
/// The description printed in diagnostics, e.g. 'nolock'.
StringRef name() const { return Name; }
@@ -7972,13 +7972,13 @@ class FunctionEffect {
/// 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,
+ 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,
+ virtual bool diagnoseRedeclaration(bool Adding,
const FunctionDecl &OldFunction,
FunctionEffectSet OldFX,
const FunctionDecl &NewFunction,
@@ -7986,7 +7986,7 @@ class FunctionEffect {
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
- virtual bool diagnoseMethodOverride(bool adding,
+ virtual bool diagnoseMethodOverride(bool Adding,
const CXXMethodDecl &OldMethod,
FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod,
@@ -8003,7 +8003,7 @@ class FunctionEffect {
// 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,
+ virtual bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
FunctionEffectSet CallerFX,
CalleeDeclOrType Callee,
FunctionEffectSet CalleeFX) const;
@@ -8018,21 +8018,21 @@ class NoLockNoAllocEffect : public FunctionEffect {
static const NoLockNoAllocEffect &nolock_instance();
static const NoLockNoAllocEffect &noalloc_instance();
- NoLockNoAllocEffect(EffectType ty, const char *name);
+ NoLockNoAllocEffect(EffectType Type, const char *Name);
~NoLockNoAllocEffect() override;
std::string attribute() const override;
- bool diagnoseConversion(bool adding, QualType OldType,
+ bool diagnoseConversion(bool Adding, QualType OldType,
FunctionEffectSet OldFX, QualType NewType,
FunctionEffectSet NewFX) const override;
- bool diagnoseRedeclaration(bool adding, const FunctionDecl &OldFunction,
+ bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
FunctionEffectSet OldFX,
const FunctionDecl &NewFunction,
FunctionEffectSet NewFX) const override;
- bool diagnoseMethodOverride(bool adding, const CXXMethodDecl &OldMethod,
+ bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod,
FunctionEffectSet NewFX) const override;
@@ -8040,7 +8040,7 @@ class NoLockNoAllocEffect : public FunctionEffect {
bool canInferOnDecl(const Decl *Caller,
FunctionEffectSet CallerFX) const override;
- bool diagnoseFunctionCall(bool direct, const Decl *Caller,
+ bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
FunctionEffectSet CalleeFX) const override;
};
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 78bbe5185741b..4033b9efb86f3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1404,7 +1404,7 @@ def CXX11NoReturn : InheritableAttr {
def NoLock : DeclOrTypeAttr {
let Spellings = [CXX11<"clang", "nolock">,
- C2x<"clang", "nolock">,
+ C23<"clang", "nolock">,
GNU<"clang_nolock">];
// Subjects - not needed?
@@ -1416,7 +1416,7 @@ def NoLock : DeclOrTypeAttr {
def NoAlloc : DeclOrTypeAttr {
let Spellings = [CXX11<"clang", "noalloc">,
- C2x<"clang", "noalloc">,
+ C23<"clang", "noalloc">,
GNU<"clang_noalloc">];
// Subjects - not needed?
//let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5777f7dfbbcad..df553a47a8dbe 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10838,9 +10838,9 @@ def note_func_effect_call_func_ptr : Note<
"'%1' cannot be inferred '%0' because it is a function pointer">;
// TODO: Not currently being generated
-def warn_perf_annotation_implies_noexcept : Warning<
- "'%0' function should be declared noexcept">,
- InGroup<PerfAnnotationImpliesNoexcept>;
+// def warn_perf_annotation_implies_noexcept : Warning<
+// "'%0' function should be declared noexcept">,
+// InGroup<PerfAnnotationImpliesNoexcept>;
// TODO: Not currently being generated
def warn_func_effect_false_on_type : Warning<
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 596f4ea2e6717..502c978d5b78e 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3517,61 +3517,6 @@ bool FunctionDecl::isMemberLikeConstrainedFriend() const {
return FriendConstraintRefersToEnclosingTemplate();
}
-static void examine(const Decl& D)
-{
- PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
- PP.TerseOutput = 1;
- D.print(llvm::outs(), PP);
-}
-
-#if TEMP_DISABLE
-// This constant controls the default policy.
-constexpr static bool kDefaultCanInferPerfAnnotation = true;
-
-static bool declCanInferPerfAnnotation(const Decl &D, QualType QT) {
- // llvm::outs() << "declCanInferPerfAnnotation " << &D << "\n";
- // examine(D);
-
- // nolock(false) or noalloc(false) disables inference.
- if (QT->disallowPerfAnnotationInference()) {
- // llvm::outs() << " disallowed by QT\n";
- return false;
- }
- if (auto *IA = D.getAttr<PerformanceInferredAttr>()) {
- // llvm::outs() << " decl has attr " << IA->getCanInfer() << "\n";
- return IA->getCanInfer();
- }
- if (auto *Method = dyn_cast<CXXMethodDecl>(&D)) {
- auto *Class = Method->getParent();
- if (auto *IA = Class->getAttr<PerformanceInferredAttr>()) {
- // llvm::outs() << " class has attr " << IA->getCanInfer() << "\n";
- return IA->getCanInfer();
- }
- }
-
- // for (const DeclContext *DC = D.getDeclContext(); DC != nullptr; DC = DC->getParent()) {
- // if (auto *Decl2 = dyn_cast<Decl>(DC)) {
- // examine(*Decl2);
- // if (auto *IA = Decl2->getAttr<PerformanceInferredAttr>()) {
- // llvm::outs() << " decl2 has attr " << IA->getCanInfer() << "\n";
- // return IA->getCanInfer();
- // }
- // }
- // }
-
- // llvm::outs() << " result: false\n";
- return kDefaultCanInferPerfAnnotation;
-}
-
-PerfAnnotation FunctionDecl::getPerfAnnotation() const {
- return getType()->getPerfAnnotation();
-}
-
-bool FunctionDecl::canInferPerfAnnotation() const {
- return declCanInferPerfAnnotation(*this, getType());
-}
-#endif // TEMP_DISABLE
-
MultiVersionKind FunctionDecl::getMultiVersionKind() const {
if (hasAttr<TargetAttr>())
return MultiVersionKind::Target;
@@ -5282,35 +5227,14 @@ SourceRange BlockDecl::getSourceRange() const {
}
FunctionEffectSet BlockDecl::getFunctionEffects() const {
- if (auto* TSI = getSignatureAsWritten()) {
- if (auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
+ if (auto *TSI = getSignatureAsWritten()) {
+ if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
}
}
return {};
}
-#if TEMP_DISABLE
-PerfAnnotation BlockDecl::getPerfAnnotation() const
-{
- if (auto* TSI = getSignatureAsWritten()) {
- return TSI->getType()->getPerfAnnotation();
- }
- return PerfAnnotation::None;
-}
-#endif // TEMP_DISABLE
-
-#if 0
-// unused
-bool BlockDecl::canInferPerfAnnotation() const
-{
- if (auto* TSI = getSignatureAsWritten()) {
- return declCanInferPerfAnnotation(*this, TSI->getType());
- }
- return kDefaultCanInferPerfAnnotation;
-}
-#endif
-
//===----------------------------------------------------------------------===//
// Other Decl Allocation/Deallocation Method Implementations
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index ce209be2c3a07..998360db3c5dd 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3694,7 +3694,6 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
ID.AddInteger(unsigned(epi.Variadic) +
(epi.RefQualifier << 1) +
(epi.ExceptionSpec.Type << 3));
-
ID.Add(epi.TypeQuals);
if (epi.ExceptionSpec.Type == EST_Dynamic) {
for (QualType Ex : epi.ExceptionSpec.Exceptions)
@@ -4924,69 +4923,77 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
-
FunctionEffect::~FunctionEffect() = default;
-bool FunctionEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
- QualType NewType, FunctionEffectSet NewFX) const
-{
+bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX,
+ QualType NewType,
+ FunctionEffectSet NewFX) const {
return false;
}
-bool FunctionEffect::diagnoseRedeclaration(bool adding,
- const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
- const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const { return false; }
+bool FunctionEffect::diagnoseRedeclaration(bool Adding,
+ const FunctionDecl &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ FunctionEffectSet NewFX) const {
+ return false;
+}
-bool FunctionEffect::diagnoseMethodOverride(bool adding,
- const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
- const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const { return false; }
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+ const CXXMethodDecl &OldMethod,
+ FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod,
+ FunctionEffectSet NewFX) const {
+ return false;
+}
-bool FunctionEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
-{
+bool FunctionEffect::canInferOnDecl(const Decl *Caller,
+ FunctionEffectSet CallerFX) const {
return false;
}
-bool FunctionEffect::diagnoseFunctionCall(bool direct,
- const Decl* Caller, FunctionEffectSet CallerFX,
- CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
-{
+bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
+ FunctionEffectSet CallerFX,
+ CalleeDeclOrType Callee,
+ FunctionEffectSet CalleeFX) const {
return false;
}
-const NoLockNoAllocEffect& NoLockNoAllocEffect::nolock_instance()
-{
+const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
return global;
}
-const NoLockNoAllocEffect& NoLockNoAllocEffect::noalloc_instance()
-{
+const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
return global;
}
// TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType ty, const char* name)
- : FunctionEffect{ ty, kRequiresVerification | kVerifyCalls | kInferrableOnCallees | kExcludeThrow | kExcludeCatch
- | kExcludeObjCMessageSend | kExcludeStaticLocalVars | kExcludeThreadLocalVars, name }
-{
-}
+NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType Ty, const char *Name)
+ : FunctionEffect(Ty,
+ kRequiresVerification | kVerifyCalls |
+ kInferrableOnCallees | kExcludeThrow | kExcludeCatch |
+ kExcludeObjCMessageSend | kExcludeStaticLocalVars |
+ kExcludeThreadLocalVars,
+ Name) {}
NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
-std::string NoLockNoAllocEffect::attribute() const
-{
- return std::string{ "__attribute__((clang_" } + name().str() + "))";
+std::string NoLockNoAllocEffect::attribute() const {
+ return std::string{"__attribute__((clang_"} + name().str() + "))";
}
-bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
- QualType NewType, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType,
+ FunctionEffectSet OldFX,
+ QualType NewType,
+ FunctionEffectSet NewFX) const {
// noalloc can't be added (spoofed) during a conversion, unless we have nolock
if (adding) {
if (!isNoLock()) {
- for (const auto* effect : OldFX) {
- if (effect->type() == kNoLockTrue)
+ for (const auto *Effect : OldFX) {
+ if (Effect->type() == kNoLockTrue)
return false;
}
}
@@ -4996,29 +5003,29 @@ bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, Func
return false;
}
-bool NoLockNoAllocEffect::diagnoseRedeclaration(bool adding,
- const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
- const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseRedeclaration(bool Adding,
+ const FunctionDecl &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ FunctionEffectSet NewFX) const {
// nolock/noalloc can't be removed in a redeclaration
// adding -> false, removing -> true (diagnose)
- return !adding;
+ return !Adding;
}
-bool NoLockNoAllocEffect::diagnoseMethodOverride(bool adding,
- const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
- const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseMethodOverride(
+ bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
// nolock/noalloc can't be removed from an override
- return !adding;
+ return !Adding;
}
-bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
-{
+bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
+ FunctionEffectSet CallerFX) const {
// Does the Decl have nolock(false) / noalloc(false) ?
QualType QT;
if (isa<BlockDecl>(Caller)) {
- const auto* TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+ const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
QT = TSI->getType();
} else if (isa<ValueDecl>(Caller)) {
QT = cast<ValueDecl>(Caller)->getType();
@@ -5032,14 +5039,15 @@ bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet C
return true;
}
-bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct,
- const Decl* Caller, FunctionEffectSet CallerFX,
- CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
-{
- const EffectType callerType = type();
- for (const auto* effect : CalleeFX) {
- const EffectType ty = effect->type();
- if (ty == callerType || (callerType == kNoAllocTrue && ty == kNoLockTrue)) {
+// TODO: Notice that we don't care about some of the parameters. Is the
+// interface overly general?
+bool NoLockNoAllocEffect::diagnoseFunctionCall(
+ bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
+ CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
+ const EffectType CallerType = type();
+ for (const auto *Effect : CalleeFX) {
+ const EffectType ET = Effect->type();
+ if (ET == CallerType || (CallerType == kNoAllocTrue && ET == kNoLockTrue)) {
return false;
}
}
@@ -5048,142 +5056,145 @@ bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct,
// =====
-void MutableFunctionEffectSet::insert(const FunctionEffect* effect)
-{
- auto iter = std::lower_bound(begin(), end(), effect);
- if (*iter != effect) {
- insert(iter, effect);
+void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
+ auto Iter = std::lower_bound(begin(), end(), Effect);
+ if (*Iter != Effect) {
+ insert(Iter, Effect);
}
}
-MutableFunctionEffectSet& MutableFunctionEffectSet::operator|=(FunctionEffectSet rhs)
-{
- // TODO: For large rhs sets, use set_union or a custom insert-in-place
- for (const auto* effect : rhs) {
- insert(effect);
+MutableFunctionEffectSet &
+MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
+ // TODO: For large RHS sets, use set_union or a custom insert-in-place
+ for (const auto *Effect : RHS) {
+ insert(Effect);
}
return *this;
}
-// This could be simpler if there were a simple set container that could be queried by
-// ArrayRef but which stored something else. Possibly a DenseMap with void values?
-FunctionEffectSet FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect*> items)
-{
- if (items.empty()) {
+// This could be simpler if there were a simple set container that could be
+// queried by ArrayRef but which stored something else. Possibly a DenseMap with
+// void values?
+FunctionEffectSet
+FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
+ if (Items.empty()) {
return FunctionEffectSet{};
}
- if (items.size() == 1) {
- return FunctionEffectSet{ items[0] };
+ if (Items.size() == 1) {
+ return FunctionEffectSet{Items[0]};
}
- UniquedAndSortedFX newSet{ items }; // just copies the ArrayRef
+ UniquedAndSortedFX NewSet(Items); // just copies the ArrayRef
- // SmallSet only has contains(), so it provides no way to obtain the uniqued value.
+ // SmallSet only has contains(), so it provides no way to obtain the uniqued
+ // value.
static std::set<UniquedAndSortedFX> uniquedFXSets;
// See if we already have this set.
- const auto iter = uniquedFXSets.find(newSet);
- if (iter != uniquedFXSets.end()) {
- return FunctionEffectSet{ &*iter };
+ const auto Iter = uniquedFXSets.find(NewSet);
+ if (Iter != uniquedFXSets.end()) {
+ return FunctionEffectSet{&*Iter};
}
// Copy the incoming array to permanent storage.
- auto* storage = new const FunctionEffect*[items.size()];
- std::copy(items.begin(), items.end(), storage);
+ auto *Storage = new const FunctionEffect *[Items.size()];
+ std::copy(Items.begin(), Items.end(), Storage);
// Make a new wrapper and insert it into the set.
- newSet = UniquedAndSortedFX{ storage, items.size() };
- auto [insiter, good] = uniquedFXSets.insert(newSet);
- return FunctionEffectSet{ &*insiter };
+ NewSet = UniquedAndSortedFX(Storage, Items.size());
+ auto [InsIter, _] = uniquedFXSets.insert(NewSet);
+ return FunctionEffectSet(&*InsIter);
}
-FunctionEffectSet FunctionEffectSet::operator|(const FunctionEffectSet& rhs) const
-{
- const FunctionEffectSet& lhs = *this;
- if (lhs.empty()) {
- return rhs;
- }
- if (rhs.empty()) {
- return lhs;
- }
+FunctionEffectSet
+FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
+ // Optimize for one of the two sets being empty
+ const FunctionEffectSet &LHS = *this;
+ if (LHS.empty())
+ return RHS;
+ if (RHS.empty())
+ return LHS;
+
// Optimize the case where the two sets are identical
- if (lhs == rhs) {
- return lhs;
- }
+ if (LHS == RHS)
+ return LHS;
- MutableFunctionEffectSet vec;
- vec.reserve(lhs.size() + rhs.size());
- std::set_union(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+ MutableFunctionEffectSet Vec;
+ Vec.reserve(LHS.size() + RHS.size());
+ std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Vec));
// The result of a set operation is an ordered/unique set.
- return FunctionEffectSet::create(vec);
+ return FunctionEffectSet::create(Vec);
}
-MutableFunctionEffectSet FunctionEffectSet::operator&(const FunctionEffectSet& rhs) const
-{
- const FunctionEffectSet& lhs = *this;
- if (lhs.empty() || rhs.empty()) {
+MutableFunctionEffectSet
+FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ if (LHS.empty() || RHS.empty()) {
return {};
}
- MutableFunctionEffectSet vec;
- std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+ MutableFunctionEffectSet Vec;
+ std::set_intersection(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Vec));
// The result of a set operation is an ordered/unique set.
- return vec;
+ return Vec;
}
// TODO: inline?
-FunctionEffectSet FunctionEffectSet::get(const Type& TyRef)
-{
- const Type* Ty = &TyRef;
+FunctionEffectSet FunctionEffectSet::get(const Type &TyRef) {
+ const Type *Ty = &TyRef;
if (Ty->isPointerType())
Ty = Ty->getPointeeType().getTypePtr();
- if (const auto* FPT = Ty->getAs<FunctionProtoType>())
+ if (const auto *FPT = Ty->getAs<FunctionProtoType>())
return FPT->getFunctionEffects();
return {};
}
-FunctionEffectSet::Differences FunctionEffectSet::differences(
- const FunctionEffectSet& Old, const FunctionEffectSet& New)
-{
+FunctionEffectSet::Differences
+FunctionEffectSet::differences(const FunctionEffectSet &Old,
+ const FunctionEffectSet &New) {
// TODO: Could be a one-pass algorithm.
- Differences result;
- for (const auto* effect : (New - Old)) {
- result.emplace_back(effect, true);
+ Differences Result;
+ for (const auto *Effect : (New - Old)) {
+ Result.emplace_back(Effect, true);
}
- for (const auto* effect : (Old - New)) {
- result.emplace_back(effect, false);
+ for (const auto *Effect : (Old - New)) {
+ Result.emplace_back(Effect, false);
}
- return result;
+ return Result;
}
-MutableFunctionEffectSet FunctionEffectSet::operator-(const FunctionEffectSet& rhs) const
-{
- const FunctionEffectSet& lhs = *this;
- MutableFunctionEffectSet result;
+MutableFunctionEffectSet
+FunctionEffectSet::operator-(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ MutableFunctionEffectSet Result;
- std::set_difference(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(result));
- return result;
+ std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Result));
+ return Result;
}
-bool FunctionEffectSet::operator<(const FunctionEffectSet& rhs) const
-{
- const FunctionEffectSet& lhs = *this;
- return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ return std::lexicographical_compare(LHS.begin(), LHS.end(), RHS.begin(),
+ RHS.end());
}
-bool FunctionEffectSet::UniquedAndSortedFX::operator<(const UniquedAndSortedFX& rhs) const
-{
- return this < &rhs;
+bool FunctionEffectSet::UniquedAndSortedFX::operator<(
+ const UniquedAndSortedFX &RHS) const {
+ return this < &RHS;
}
-void FunctionEffectSet::dump(llvm::raw_ostream &OS) const
-{
+void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
OS << "FX{";
- bool first = true;
- for (const auto* effect : *this) {
- if (!first) OS << ", ";
- else first = false;
- OS << effect->name();
+ bool First = true;
+ for (const auto *Effect : *this) {
+ if (!First)
+ OS << ", ";
+ else
+ First = false;
+ OS << Effect->name();
}
OS << "}";
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 560f2faeb9e50..252a5dfbc952b 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -984,12 +984,11 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
OS << " &&";
break;
}
-
T->printExceptionSpecification(OS, Policy);
- if (auto effects = T->getFunctionEffects()) {
- for (const auto* effect : effects) {
- OS << " " << effect->attribute();
+ if (const FunctionEffectSet FX = T->getFunctionEffects()) {
+ for (const auto *Effect : FX) {
+ OS << " " << Effect->attribute();
}
}
@@ -1911,7 +1910,6 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
case attr::IntelOclBicc: OS << "inteloclbicc"; break;
-
case attr::PreserveMost:
OS << "preserve_most";
break;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index a0d813fa4d3d1..69417b10049e9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -27,6 +27,8 @@
#include "clang/AST/StmtObjC.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
#include "clang/Analysis/Analyses/Consumed.h"
@@ -2382,6 +2384,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
// =============================================================================
+// Temporary debugging option
#define FX_ANALYZER_VERIFY_DECL_LIST 1
namespace FXAnalysis {
@@ -2396,41 +2399,37 @@ enum class DiagnosticID : uint8_t {
AccessesThreadLocal,
// These only apply to callees, where the analysis stops at the Decl
- // DeclExternWithoutConstraint, // TODO: not used?
DeclWithoutConstraintOrInference,
- //DeclVirtualWithoutConstraint,
- //DeclFuncPtrWithoutConstraint,
CallsUnsafeDecl,
CallsDisallowedExpr,
};
struct Diagnostic {
- const FunctionEffect* Effect = nullptr;
- const Decl* Callee = nullptr; // only valid for Calls*
- SourceLocation Loc{};
- DiagnosticID ID{ DiagnosticID::None };
+ const FunctionEffect *Effect = nullptr;
+ const Decl *Callee = nullptr; // only valid for Calls*
+ SourceLocation Loc;
+ DiagnosticID ID = DiagnosticID::None;
Diagnostic() = default;
- Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc, const Decl* Callee = nullptr)
- : Effect{ Effect }, Callee{ Callee }, Loc{ Loc }, ID{ ID }
- {
- }
+ Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+ : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
};
-enum class SpecialFuncType : uint8_t {
- None, OperatorNew, OperatorDelete
-};
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
enum class CallType {
- Unknown, Function, Virtual, Block
+ Unknown,
+ Function,
+ Virtual,
+ Block
// unknown: probably function pointer
};
// Return whether the function CAN be verified.
// The question of whether it SHOULD be verified is independent.
-static bool functionIsVerifiable(const FunctionDecl* FD)
-{
+static bool functionIsVerifiable(const FunctionDecl *FD) {
if (!(FD->hasBody() || FD->isInlined())) {
// externally defined; we couldn't verify if we wanted to.
return false;
@@ -2443,45 +2442,37 @@ static bool functionIsVerifiable(const FunctionDecl* FD)
return true;
}
-// Transitory, more extended information about a callable, which can be a function,
-// block, function pointer...
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer...
struct CallableInfo {
- const Decl* CDecl;
- mutable std::optional<std::string> MaybeName; // mutable because built on demand in const method
- SpecialFuncType FuncType{ SpecialFuncType::None };
+ const Decl *CDecl;
+ mutable std::optional<std::string>
+ MaybeName; // mutable because built on demand in const method
+ SpecialFuncType FuncType = SpecialFuncType::None;
FunctionEffectSet Effects;
- CallType CType{ CallType::Unknown };
- // bool IsAllowListed{ false };
+ CallType CType = CallType::Unknown;
- CallableInfo(const Decl& CD, SpecialFuncType FT = SpecialFuncType::None)
- : CDecl{ &CD }, FuncType{ FT }
- {
- //llvm::errs() << "CallableInfo " << name() << "\n";
+ CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+ : CDecl(&CD), FuncType(FT) {
+ // llvm::errs() << "CallableInfo " << name() << "\n";
- if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
assert(FD->getCanonicalDecl() == FD);
// Use the function's definition, if any.
- if (auto* Def = FD->getDefinition()) {
+ if (auto *Def = FD->getDefinition()) {
CDecl = FD = Def;
}
CType = CallType::Function;
- if (auto* Method = dyn_cast<CXXMethodDecl>(FD)) {
+ if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
if (Method->isVirtual()) {
CType = CallType::Virtual;
}
}
Effects = FD->getFunctionEffects();
-
- // TODO: Generalize via noreturn??? but that would cover exceptions too.
- // if (name() == "__assert_rtn") {
- // // big hack because it's hard to get the attribute to stick on it
- // // through a redeclaration, not sure why.
- // IsAllowListed = true;
- // }
- } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
Effects = BD->getFunctionEffects();
- } else if (auto* VD = dyn_cast<ValueDecl>(CDecl)) {
+ } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at the type.
Effects = FunctionEffectSet::get(*VD->getType());
}
@@ -2491,8 +2482,7 @@ struct CallableInfo {
return CType == CallType::Function || CType == CallType::Block;
}
- bool isVerifiable() const
- {
+ bool isVerifiable() const {
switch (CType) {
case CallType::Unknown:
case CallType::Virtual:
@@ -2505,19 +2495,18 @@ struct CallableInfo {
return false;
}
- /// Generate a name for logging.
- std::string name(Sema& sema) const
- {
+ /// Generate a name for logging and diagnostics.
+ std::string name(Sema &Sem) const {
if (!MaybeName) {
std::string Name;
llvm::raw_string_ostream OS(Name);
- if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
- FD->getNameForDiagnostic(OS, sema.getPrintingPolicy(),
- /*Qualified=*/true);
- } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+ FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
OS << "(block " << BD->getBlockManglingNumber() << ")";
- } else if (auto* VD = dyn_cast<NamedDecl>(CDecl)) {
+ } else if (auto *VD = dyn_cast<NamedDecl>(CDecl)) {
VD->printQualifiedName(OS);
}
MaybeName = Name;
@@ -2529,35 +2518,34 @@ struct CallableInfo {
// ----------
// Map effects to single diagnostics.
class EffectToDiagnosticMap {
- // Since we currently only have a tiny number of effects (typically no more than 1),
- // use a sorted SmallVector.
- using Element = std::pair<const FunctionEffect*, Diagnostic>;
+ // Since we currently only have a tiny number of effects (typically no more
+ // than 1), use a sorted SmallVector.
+ using Element = std::pair<const FunctionEffect *, Diagnostic>;
using ImplVec = llvm::SmallVector<Element>;
std::unique_ptr<ImplVec> Impl;
+
public:
- Diagnostic& getOrInsertDefault(const FunctionEffect* key)
- {
+ Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
if (Impl == nullptr) {
Impl = std::make_unique<llvm::SmallVector<Element>>();
- auto& item = Impl->emplace_back();
- item.first = key;
- return item.second;
+ auto &Item = Impl->emplace_back();
+ Item.first = Key;
+ return Item.second;
}
- Element elem{ key, {} };
- auto iter = _find(elem);
- if (iter != Impl->end() && iter->first == key) {
- return iter->second;
+ Element Elem(Key, {});
+ auto Iter = _find(Elem);
+ if (Iter != Impl->end() && Iter->first == Key) {
+ return Iter->second;
}
- iter = Impl->insert(iter, elem);
- return iter->second;
+ Iter = Impl->insert(Iter, Elem);
+ return Iter->second;
}
- const Diagnostic* lookup(const FunctionEffect* key)
- {
+ const Diagnostic *lookup(const FunctionEffect *key) {
if (Impl == nullptr) {
return nullptr;
}
- Element elem{ key, {} };
+ Element elem(key, {});
auto iter = _find(elem);
if (iter != Impl->end() && iter->first == key) {
return &iter->second;
@@ -2568,23 +2556,23 @@ class EffectToDiagnosticMap {
size_t size() const { return Impl ? Impl->size() : 0; }
private:
- ImplVec::iterator _find(const Element& elem)
- {
- return std::lower_bound(Impl->begin(), Impl->end(), elem, [](const Element& lhs, const Element& rhs) {
- return lhs.first < rhs.first;
- });
+ ImplVec::iterator _find(const Element &elem) {
+ return std::lower_bound(Impl->begin(), Impl->end(), elem,
+ [](const Element &lhs, const Element &rhs) {
+ return lhs.first < rhs.first;
+ });
}
};
// ----------
-// State pertaining to a function whose AST is walked. Since there are potentially a large
-// number of these objects, it needs care about size.
+// State pertaining to a function whose AST is walked. Since there are
+// potentially a large number of these objects, it needs care about size.
class PendingFunctionAnalysis {
// Current size: 5 pointers
friend class CompleteFunctionAnalysis;
struct DirectCall {
- const Decl* Callee;
+ const Decl *Callee;
SourceLocation CallLoc;
};
@@ -2596,21 +2584,22 @@ class PendingFunctionAnalysis {
FunctionEffectSet FXToInfer;
private:
- // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr to optimize
- // size for the case of 0 diagnostics.
+ // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr
+ // to optimize size for the case of 0 diagnostics.
std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
- // Potential diagnostics pertaining to other, non-explicit, inferrable effects.
+ // Potential diagnostics pertaining to other, non-explicit, inferrable
+ // effects.
EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(const CallableInfo& cinfo, FunctionEffectSet AllInferrableEffectsToVerify)
- {
+ PendingFunctionAnalysis(const CallableInfo &cinfo,
+ FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet fx;
- for (const auto* effect : cinfo.Effects) {
- if (effect->getFlags() & FunctionEffect::kRequiresVerification) {
+ for (const auto *effect : cinfo.Effects) {
+ if (effect->flags() & FunctionEffect::kRequiresVerification) {
fx.insert(effect);
}
}
@@ -2618,71 +2607,73 @@ class PendingFunctionAnalysis {
// Check for effects we are not allowed to infer
fx.clear();
- for (const auto* effect : AllInferrableEffectsToVerify) {
+ for (const auto *effect : AllInferrableEffectsToVerify) {
if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
fx.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
// try to infer it.
- auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
- diag = Diagnostic{ effect, DiagnosticID::DeclWithoutConstraintOrInference,
- cinfo.CDecl->getLocation() };
+ auto &diag =
+ InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
+ diag =
+ Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
+ cinfo.CDecl->getLocation());
}
}
// fx is now the set of inferrable effects which are not prohibited
- FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) - DeclaredVerifiableEffects);
+ FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) -
+ DeclaredVerifiableEffects);
}
- void checkAddDiagnostic(bool inferring, const Diagnostic& NewDiag)
- {
- if (!inferring) {
+ // Hide the way that diagnostics for explicitly required effects vs. inferred
+ // ones are handled differently.
+ void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+ if (!Inferring) {
if (DiagnosticsForExplicitFX == nullptr) {
DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
}
DiagnosticsForExplicitFX->push_back(NewDiag);
} else {
- auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
- if (diag.ID == DiagnosticID::None) {
- diag = NewDiag;
+ auto &Diag =
+ InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
+ if (Diag.ID == DiagnosticID::None) {
+ Diag = NewDiag;
}
}
}
- void addUnverifiedDirectCall(const Decl* D, SourceLocation CallLoc)
- {
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
if (UnverifiedDirectCalls == nullptr) {
UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
}
- UnverifiedDirectCalls->emplace_back(DirectCall{ D, CallLoc });
+ UnverifiedDirectCalls->emplace_back(DirectCall{D, CallLoc});
}
// Analysis is complete when there are no unverified direct calls.
- bool isComplete() const
- {
+ bool isComplete() const {
return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
}
- const Diagnostic* diagnosticForInferrableEffect(const FunctionEffect* effect)
- {
+ const Diagnostic *
+ diagnosticForInferrableEffect(const FunctionEffect *effect) {
return InferrableEffectToFirstDiagnostic.lookup(effect);
}
- const SmallVector<DirectCall>& unverifiedCalls() const
- {
+ const SmallVector<DirectCall> &unverifiedCalls() const {
assert(!isComplete());
return *UnverifiedDirectCalls;
}
- SmallVector<Diagnostic>* getDiagnosticsForExplicitFX() const
- {
+ SmallVector<Diagnostic> *getDiagnosticsForExplicitFX() const {
return DiagnosticsForExplicitFX.get();
}
- void dump(llvm::raw_ostream& OS) const
- {
+ void dump(llvm::raw_ostream &OS) const {
OS << "Pending: Declared ";
DeclaredVerifiableEffects.dump(OS);
- OS << ", " << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0) << " diags; ";
+ OS << ", "
+ << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0)
+ << " diags; ";
OS << " Infer ";
FXToInfer.dump(OS);
OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
@@ -2693,10 +2684,10 @@ class PendingFunctionAnalysis {
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.
+ // Has effects which are both the declared ones -- not to be inferred -- plus
+ // ones which have been successfully inferred. These are all considered
+ // "verified" for the purposes of callers; any issue with verifying declared
+ // effects has already been reported and is not the problem of any caller.
FunctionEffectSet VerifiedEffects;
private:
@@ -2704,28 +2695,28 @@ class CompleteFunctionAnalysis {
EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
public:
- CompleteFunctionAnalysis(PendingFunctionAnalysis& pending, FunctionEffectSet funcFX, FunctionEffectSet AllInferrableEffectsToVerify)
- {
+ CompleteFunctionAnalysis(PendingFunctionAnalysis &pending,
+ FunctionEffectSet funcFX,
+ FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet verified;
verified |= funcFX;
- for (const auto* effect : AllInferrableEffectsToVerify) {
+ for (const auto *effect : AllInferrableEffectsToVerify) {
if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
verified.insert(effect);
}
}
VerifiedEffects = FunctionEffectSet::create(verified);
- InferrableEffectToFirstDiagnostic = std::move(pending.InferrableEffectToFirstDiagnostic);
+ InferrableEffectToFirstDiagnostic =
+ std::move(pending.InferrableEffectToFirstDiagnostic);
}
- const Diagnostic* firstDiagnosticForEffect(const FunctionEffect* effect)
- {
+ const Diagnostic *firstDiagnosticForEffect(const FunctionEffect *effect) {
// TODO: is this correct?
return InferrableEffectToFirstDiagnostic.lookup(effect);
}
- void dump(llvm::raw_ostream& OS) const
- {
+ void dump(llvm::raw_ostream &OS) const {
OS << "Complete: Verified ";
VerifiedEffects.dump(OS);
OS << "; Infer ";
@@ -2734,11 +2725,11 @@ class CompleteFunctionAnalysis {
};
/*
- TODO: nolock and noalloc imply noexcept
+ TODO: nolock and noalloc imply noexcept
if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
!= clang::CT_Cannot) {
- S.Diag(Callable->getBeginLoc(),
+ S.Diag(Callable->getBeginLoc(),
diag::warn_perf_annotation_implies_noexcept)
<< getPerfAnnotationSpelling(CInfo.PerfAnnot);
}
@@ -2750,7 +2741,7 @@ class Analyzer {
constexpr static int kDebugLogLevel = 3;
// --
- Sema& mSema;
+ Sema &Sem;
// used from Sema:
// SmallVector<const Decl *> DeclsWithUnverifiedEffects
@@ -2758,78 +2749,76 @@ class Analyzer {
// Subset of Sema.AllEffectsToVerify
FunctionEffectSet AllInferrableEffectsToVerify;
- using FuncAnalysisPtr = llvm::PointerUnion<PendingFunctionAnalysis*, CompleteFunctionAnalysis*>;
+ using FuncAnalysisPtr =
+ llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
- // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+ // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
// than complete state, so use different objects to represent them.
// The state pointers are owned by the container.
- struct AnalysisMap : public llvm::DenseMap<const Decl*, FuncAnalysisPtr> {
-
+ struct AnalysisMap : public llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+
~AnalysisMap();
// use lookup()
- CompleteFunctionAnalysis* completedAnalysisForDecl(const Decl* D) const
- {
+ /// Shortcut for the case where we only care about completed analysis.
+ CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
if (auto AP = lookup(D)) {
- if (isa<CompleteFunctionAnalysis*>(AP)) {
- return AP.get<CompleteFunctionAnalysis*>();
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ return AP.get<CompleteFunctionAnalysis *>();
}
}
return nullptr;
}
- void dump(Sema& S, llvm::raw_ostream& OS)
- {
+ void dump(Sema &S, llvm::raw_ostream &OS) {
OS << "AnalysisMap:\n";
- for (const auto& item : *this) {
- CallableInfo CI{ *item.first };
+ for (const auto &item : *this) {
+ CallableInfo CI(*item.first);
const auto AP = item.second;
OS << item.first << " " << CI.name(S) << " : ";
if (AP.isNull()) {
OS << "null\n";
- } else if (isa<CompleteFunctionAnalysis*>(AP)) {
- auto* CFA = AP.get<CompleteFunctionAnalysis*>();
+ } else if (isa<CompleteFunctionAnalysis *>(AP)) {
+ auto *CFA = AP.get<CompleteFunctionAnalysis *>();
OS << CFA << " ";
CFA->dump(OS);
- } else if (isa<PendingFunctionAnalysis*>(AP)) {
- auto* PFA = AP.get<PendingFunctionAnalysis*>();
+ } else if (isa<PendingFunctionAnalysis *>(AP)) {
+ auto *PFA = AP.get<PendingFunctionAnalysis *>();
OS << PFA << " ";
PFA->dump(OS);
- } else llvm_unreachable("never");
+ } else
+ llvm_unreachable("never");
}
}
};
AnalysisMap DeclAnalysis;
public:
- Analyzer(Sema& S)
- : mSema{ S }
- {
- }
+ Analyzer(Sema &S) : Sem(S) {}
- void run(const TranslationUnitDecl& TU)
- {
+ void run(const TranslationUnitDecl &TU) {
#if FX_ANALYZER_VERIFY_DECL_LIST
verifyRootDecls(TU);
#endif
- // Gather all of the effects to be verified to see what operations need to be checked,
- // and to see which ones are inferrable.
+ // Gather all of the effects to be verified to see what operations need to
+ // be checked, and to see which ones are inferrable.
{
MutableFunctionEffectSet inferrableEffects;
- for (const FunctionEffect* effect : mSema.AllEffectsToVerify) {
- const auto Flags = effect->getFlags();
+ for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
+ const auto Flags = effect->flags();
if (Flags & FunctionEffect::kInferrableOnCallees) {
inferrableEffects.insert(effect);
}
}
- AllInferrableEffectsToVerify = FunctionEffectSet::create(inferrableEffects);
+ AllInferrableEffectsToVerify =
+ FunctionEffectSet::create(inferrableEffects);
llvm::outs() << "AllInferrableEffectsToVerify: ";
AllInferrableEffectsToVerify.dump(llvm::outs());
llvm::outs() << "\n";
}
- SmallVector<const Decl*>& verifyQueue = mSema.DeclsWithUnverifiedEffects;
+ SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
// It's useful to use DeclsWithUnverifiedEffects as a stack for a
// depth-first traversal rather than have a secondary container. But first,
@@ -2837,40 +2826,40 @@ class Analyzer {
std::reverse(verifyQueue.begin(), verifyQueue.end());
while (!verifyQueue.empty()) {
- const Decl* D = verifyQueue.back();
+ const Decl *D = verifyQueue.back();
if (auto AP = DeclAnalysis.lookup(D)) {
- if (isa<CompleteFunctionAnalysis*>(AP)) {
- // already done
- verifyQueue.pop_back();
- continue;
- }
- if (isa<PendingFunctionAnalysis*>(AP)) {
- // All children have been traversed; finish analysis.
- auto* pending = AP.get<PendingFunctionAnalysis*>();
- finishPendingAnalysis(D, pending);
- verifyQueue.pop_back();
- continue;
- }
- llvm_unreachable("shouldn't happen");
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ // already done
+ verifyQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto *pending = AP.get<PendingFunctionAnalysis *>();
+ finishPendingAnalysis(D, pending);
+ verifyQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("shouldn't happen");
}
- auto* pending = verifyDecl(D);
- if (pending == nullptr) {
+ auto *Pending = verifyDecl(D);
+ if (Pending == nullptr) {
// completed now
verifyQueue.pop_back();
continue;
}
- for (const auto& call : pending->unverifiedCalls()) {
- // This lookup could be optimized out if the results could have been saved
- // from followCall when we traversed the caller's AST. It would however
- // make the check for recursion more complex.
- auto AP = DeclAnalysis.lookup(call.Callee);
+ for (const auto &Call : Pending->unverifiedCalls()) {
+ // This lookup could be optimized out if the results could have been
+ // saved from followCall when we traversed the caller's AST. It would
+ // however make the check for recursion more complex.
+ auto AP = DeclAnalysis.lookup(Call.Callee);
if (AP.isNull()) {
- verifyQueue.push_back(call.Callee);
+ verifyQueue.push_back(Call.Callee);
continue;
}
- if (isa<PendingFunctionAnalysis*>(AP)) {
+ if (isa<PendingFunctionAnalysis *>(AP)) {
// $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
__builtin_trap();
}
@@ -2880,38 +2869,28 @@ class Analyzer {
}
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)
- {
+ // 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) {
+ // TODO: Is this in the right place?
const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
if (FD != nullptr) {
// Currently, built-in functions are always considered safe.
if (FD->getBuiltinID() != 0) {
return nullptr;
}
- // If it doesn't have a body, then we have to rely on the declaration.
-
-#warning FIXME
-/* if (!functionIsVerifiable(mSema, FD)) {
- // const PerfAnnotation PA = FD->getPerfAnnotation();
- // if (PA != PerfAnnotation::NoLock) {
- // Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
- // }
- return;
- }*/
}
- CallableInfo CInfo{ *D };
+ CallableInfo CInfo(*D);
- // 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{ CInfo, AllInferrableEffectsToVerify };
+ // 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(CInfo, AllInferrableEffectsToVerify);
- llvm::outs() << "\nVerifying " << CInfo.name(mSema) << " ";
+ llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
FAnalysis.dump(llvm::outs());
- FunctionBodyASTVisitor Visitor{ *this, FAnalysis, CInfo };
+ FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
Visitor.run();
if (FAnalysis.isComplete()) {
@@ -2919,83 +2898,88 @@ class Analyzer {
return nullptr;
}
// Copy the pending analysis to the heap and save it in the map.
- auto* pendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
- DeclAnalysis[D] = pendingPtr;
- llvm::outs() << "inserted pending " << pendingPtr << "\n";
- DeclAnalysis.dump(mSema, llvm::outs());
- return pendingPtr;
- }
-
- // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis and inserted
- // in the container.
- void completeAnalysis(const CallableInfo& CInfo, PendingFunctionAnalysis& pending)
- {
- if (auto* diags = pending.getDiagnosticsForExplicitFX()) {
- emitDiagnostics(*diags, CInfo, mSema);
- }
- auto* completePtr = new CompleteFunctionAnalysis(pending,
- CInfo.Effects, AllInferrableEffectsToVerify);
- DeclAnalysis[CInfo.CDecl] = completePtr;
- llvm::outs() << "inserted complete " << completePtr << "\n";
- DeclAnalysis.dump(mSema, llvm::outs());
- }
-
- // Called after all direct calls requiring inference have been found -- or not.
- // Generally replicates FunctionBodyASTVisitor::followCall() but without the
- // possibility of inference.
- void finishPendingAnalysis(const Decl* D, PendingFunctionAnalysis* pending)
- {
- CallableInfo Caller{ *D };
- for (const auto& call : pending->unverifiedCalls()) {
- CallableInfo Callee{ *call.Callee };
- followCall(Caller, *pending, Callee, call.CallLoc, /*assertNoFurtherInference=*/true);
- }
- completeAnalysis(Caller, *pending);
- delete pending;
- llvm::outs() << "destroyed pending " << pending << "\n";
+ auto *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
+ DeclAnalysis[D] = PendingPtr;
+ llvm::outs() << "inserted pending " << PendingPtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ return PendingPtr;
+ }
+
+ // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis
+ // and inserted in the container.
+ void completeAnalysis(const CallableInfo &CInfo,
+ PendingFunctionAnalysis &Pending) {
+ if (auto *Diags = Pending.getDiagnosticsForExplicitFX()) {
+ emitDiagnostics(*Diags, CInfo, Sem);
+ }
+ auto *CompletePtr = new CompleteFunctionAnalysis(
+ Pending, CInfo.Effects, AllInferrableEffectsToVerify);
+ DeclAnalysis[CInfo.CDecl] = CompletePtr;
+ llvm::outs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ }
+
+ // Called after all direct calls requiring inference have been found -- or
+ // not. Generally replicates FunctionBodyASTVisitor::followCall() but without
+ // the possibility of inference.
+ void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+ CallableInfo Caller(*D);
+ for (const auto &Call : Pending->unverifiedCalls()) {
+ CallableInfo Callee(*Call.Callee);
+ followCall(Caller, *Pending, Callee, Call.CallLoc,
+ /*AssertNoFurtherInference=*/true);
+ }
+ completeAnalysis(Caller, *Pending);
+ delete Pending;
+ llvm::outs() << "destroyed pending " << Pending << "\n";
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
// other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo& Caller, PendingFunctionAnalysis &PFA,
- const CallableInfo& Callee, SourceLocation CallLoc,
- bool assertNoFurtherInference)
- {
+ void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
+ const CallableInfo &Callee, SourceLocation CallLoc,
+ bool AssertNoFurtherInference) {
const bool DirectCall = Callee.isDirectCall();
FunctionEffectSet CalleeEffects = Callee.Effects;
- bool isInferencePossible = DirectCall;
+ bool IsInferencePossible = DirectCall;
if (DirectCall) {
- if (auto* CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+ if (auto *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
CalleeEffects = CFA->VerifiedEffects;
- isInferencePossible = false; // we've already traversed it
+ IsInferencePossible = false; // we've already traversed it
}
}
- if (assertNoFurtherInference) {
- assert(!isInferencePossible);
+ if (AssertNoFurtherInference) {
+ assert(!IsInferencePossible);
}
if (!Callee.isVerifiable()) {
- isInferencePossible = false;
+ IsInferencePossible = false;
}
- llvm::outs() << "followCall from " << Caller.name(mSema) << " to " << Callee.name(mSema)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+ << Callee.name(Sem)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
CalleeEffects.dump(llvm::outs());
llvm::outs() << "\n";
puts("");
- auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
- const auto flags = effect->getFlags();
- if (flags & FunctionEffect::kVerifyCalls) {
- const bool diagnose = effect->diagnoseFunctionCall(DirectCall, Caller.CDecl, Caller.Effects,
- Callee.CDecl, CalleeEffects);
+ auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
+ const auto Flags = Effect->flags();
+ if (Flags & FunctionEffect::kVerifyCalls) {
+ const bool diagnose = Effect->diagnoseFunctionCall(
+ DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
+ CalleeEffects);
if (diagnose) {
- // If inference is not allowed, or the target is indirect (virtual method/function ptr?),
- // generate a diagnostic now.
- if (!isInferencePossible || !(flags & FunctionEffect::kInferrableOnCallees)) {
+ // If inference is not allowed, or the target is indirect (virtual
+ // method/function ptr?), generate a diagnostic now.
+ if (!IsInferencePossible ||
+ !(Flags & FunctionEffect::kInferrableOnCallees)) {
if (Callee.FuncType == SpecialFuncType::None) {
- PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl });
+ PFA.checkAddDiagnostic(Inferring,
+ {Effect, DiagnosticID::CallsUnsafeDecl,
+ CallLoc, Callee.CDecl});
} else {
- PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::AllocatesMemory, CallLoc });
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
}
} else {
// Inference is allowed and necessary; defer it.
@@ -3005,215 +2989,225 @@ class Analyzer {
}
};
- for (auto* effect : PFA.DeclaredVerifiableEffects) {
- check1Effect(effect, false);
+ for (auto *Effect : PFA.DeclaredVerifiableEffects) {
+ check1Effect(Effect, false);
}
- for (auto* effect : PFA.FXToInfer) {
- check1Effect(effect, true);
+ for (auto *Effect : PFA.FXToInfer) {
+ check1Effect(Effect, true);
}
}
// Should only be called when determined to be complete.
- void emitDiagnostics(SmallVector<Diagnostic>& Diags, const CallableInfo& CInfo, Sema& S)
- {
+ void emitDiagnostics(SmallVector<Diagnostic> &Diags,
+ const CallableInfo &CInfo, Sema &S) {
#define UNTESTED __builtin_trap();
#define TESTED
- const SourceManager& SM = S.getSourceManager();
- std::sort(Diags.begin(), Diags.end(), [&SM](const Diagnostic& lhs, const Diagnostic& rhs) {
- return SM.isBeforeInTranslationUnit(lhs.Loc, rhs.Loc);
- });
+ const SourceManager &SM = S.getSourceManager();
+ std::sort(Diags.begin(), Diags.end(),
+ [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+ return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+ });
const auto TopFuncName = CInfo.name(S);
// TODO: Can we get better template instantiation notes?
- auto checkAddTemplateNote = [&](const Decl* D) {
+ auto checkAddTemplateNote = [&](const Decl *D) {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
while (FD != nullptr && FD->isTemplateInstantiation()) {
- S.Diag(FD->getPointOfInstantiation(), diag::note_func_effect_from_template);
+ S.Diag(FD->getPointOfInstantiation(),
+ diag::note_func_effect_from_template);
FD = FD->getTemplateInstantiationPattern();
}
}
};
// Top-level diagnostics are warnings.
- for (const auto& Diag : Diags) {
+ for (const auto &Diag : Diags) {
StringRef effectName = Diag.Effect->name();
switch (Diag.ID) {
case DiagnosticID::None:
- //case DiagnosticID::DeclExternWithoutConstraint:
- case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen here
- //case DiagnosticID::DeclVirtualWithoutConstraint:
- //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+ case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen
+ // here
llvm_unreachable("Unexpected diagnostic kind");
break;
case DiagnosticID::AllocatesMemory:
- S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
+ TESTED
break;
case DiagnosticID::Throws:
case DiagnosticID::Catches:
- S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
+ TESTED
break;
case DiagnosticID::HasStaticLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
+ TESTED
break;
case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
+ TESTED
break;
case DiagnosticID::CallsObjC:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
+ TESTED
break;
case DiagnosticID::CallsDisallowedExpr:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr) << effectName << TopFuncName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
+ << effectName << TopFuncName;
checkAddTemplateNote(CInfo.CDecl);
- UNTESTED
+ UNTESTED
break;
- case DiagnosticID::CallsUnsafeDecl:
- {
- CallableInfo CalleeInfo{ *Diag.Callee };
- auto CalleeName = CalleeInfo.name(S);
-
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func) << effectName << TopFuncName << CalleeName;
- checkAddTemplateNote(CInfo.CDecl);
-
- // Emit notes explaining the transitive chain of inferences: Why isn't the callee safe?
- for (const auto* Callee = Diag.Callee; Callee != nullptr; ) {
- std::optional<CallableInfo> MaybeNextCallee;
- auto* completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
- if (completed == nullptr) {
- // No result - could be
- // - non-inline
- // - virtual
- if (CalleeInfo.CType == CallType::Virtual) {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName << CalleeName;
- TESTED
- } else if (CalleeInfo.CType == CallType::Unknown) {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
- TESTED
- } else {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName << CalleeName;
- TESTED
- }
- break;
- }
- const auto* pDiag2 = completed->firstDiagnosticForEffect(Diag.Effect);
- if (pDiag2 == nullptr) {
- break;
- }
+ case DiagnosticID::CallsUnsafeDecl: {
+ CallableInfo CalleeInfo{*Diag.Callee};
+ auto CalleeName = CalleeInfo.name(S);
- const auto& Diag2 = *pDiag2;
- switch (Diag2.ID) {
- case DiagnosticID::None:
- llvm_unreachable("Unexpected diagnostic kind");
- break;
- // case DiagnosticID::DeclExternWithoutConstraint:
- // S.Diag(Diag2.Loc, diag::note_func_effect_call_extern) << effectName << CalleeName;
- // break;
- case DiagnosticID::DeclWithoutConstraintOrInference:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable) << effectName << CalleeName;
- TESTED
- break;
- // case DiagnosticID::DeclVirtualWithoutConstraint:
- // S.Diag(Diag2.Loc, diag::note_func_effect_call_virtual) << effectName << CalleeName;
- // break;
- //case DiagnosticID::DeclFuncPtrWithoutConstraint:
- case DiagnosticID::CallsDisallowedExpr:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
- UNTESTED
- break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName << CalleeName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
+ << effectName << TopFuncName << CalleeName;
+ checkAddTemplateNote(CInfo.CDecl);
+
+ // Emit notes explaining the transitive chain of inferences: Why isn't
+ // the callee safe?
+ for (const auto *Callee = Diag.Callee; Callee != nullptr;) {
+ std::optional<CallableInfo> MaybeNextCallee;
+ auto *Completed =
+ DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+ if (Completed == nullptr) {
+ // No result - could be
+ // - non-inline
+ // - virtual
+ if (CalleeInfo.CType == CallType::Virtual) {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
+ << effectName << CalleeName;
TESTED
- break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) << effectName << CalleeName;
+ } else if (CalleeInfo.CType == CallType::Unknown) {
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_func_ptr)
+ << effectName << CalleeName;
TESTED
- break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) << effectName << CalleeName;
- UNTESTED
- break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) << effectName << CalleeName;
- UNTESTED
- break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName << CalleeName;
- UNTESTED
- break;
- case DiagnosticID::CallsUnsafeDecl:
- MaybeNextCallee.emplace(*Diag2.Callee);
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func) << effectName << CalleeName << MaybeNextCallee->name(S);
+ } else {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+ << effectName << CalleeName;
TESTED
- break;
- }
- checkAddTemplateNote(Callee);
- Callee = Diag2.Callee;
- if (MaybeNextCallee) {
- CalleeInfo = *MaybeNextCallee;
- CalleeName = CalleeInfo.name(S);
}
+ break;
+ }
+ const auto *pDiag2 = Completed->firstDiagnosticForEffect(Diag.Effect);
+ if (pDiag2 == nullptr) {
+ break;
+ }
+
+ const auto &Diag2 = *pDiag2;
+ switch (Diag2.ID) {
+ case DiagnosticID::None:
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::DeclWithoutConstraintOrInference:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
+ << effectName << CalleeName;
+ TESTED
+ break;
+ case DiagnosticID::CallsDisallowedExpr:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+ << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
+ << effectName << CalleeName;
+ TESTED
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+ << effectName << CalleeName;
+ TESTED
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+ << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+ << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
+ << effectName << CalleeName;
+ UNTESTED
+ break;
+ case DiagnosticID::CallsUnsafeDecl:
+ MaybeNextCallee.emplace(*Diag2.Callee);
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
+ << effectName << CalleeName << MaybeNextCallee->name(S);
+ TESTED
+ break;
+ }
+ checkAddTemplateNote(Callee);
+ Callee = Diag2.Callee;
+ if (MaybeNextCallee) {
+ CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(S);
}
}
- break;
+ } break;
}
}
}
// ----------
- // This AST visitor is used to traverse the body of a function during effect verification.
- // This happens in 2 distinct 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.
- // When we are verifying explicit conformance [1] we should generate all diagnostics.
- // When we are inferring conformance [2] we will need to save enough diagnostics
- // to provide a note to explain why inference failed; just the first violation per
- // FunctionEffect.
+ // 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.
//
- // Populates a provided FunctionAnalysis object.
+ // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
+ // all diagnostic output.
//
- // TODO: Currently we create a new RecursiveASTVisitor for every function analysis.
- // Is it so lightweight that this is OK? It would appear so.
- struct FunctionBodyASTVisitor : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ // TODO: Currently we create a new RecursiveASTVisitor for every function
+ // analysis. Is it so lightweight that this is OK? It would appear so.
+ struct FunctionBodyASTVisitor
+ : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
constexpr static bool Stop = false;
constexpr static bool Proceed = true;
-
+
Analyzer &Outer;
PendingFunctionAnalysis &CurrentFunction;
- CallableInfo& CurrentCaller;
+ CallableInfo &CurrentCaller;
- FunctionBodyASTVisitor(Analyzer &outer, PendingFunctionAnalysis &CurrentFunction,
- CallableInfo& CurrentCaller)
- : Outer{ outer }, CurrentFunction{ CurrentFunction }, CurrentCaller{ CurrentCaller }
- {
- }
+ FunctionBodyASTVisitor(Analyzer &outer,
+ PendingFunctionAnalysis &CurrentFunction,
+ CallableInfo &CurrentCaller)
+ : Outer(outer), CurrentFunction(CurrentFunction),
+ CurrentCaller(CurrentCaller) {}
// -- Entry point --
void run()
{
- // The target function itself may have some implicit code paths beyond the body:
- // member and base constructors and destructors.
+ // The target function itself may have some implicit code paths beyond the
+ // body: member and base constructors and destructors. Visit these first.
if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
- if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
- for (const CXXCtorInitializer* Initer : Ctor->inits()) {
- if (Expr* Init = Initer->getInit()) {
+ if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+ for (const CXXCtorInitializer *Initer : Ctor->inits()) {
+ if (Expr *Init = Initer->getInit()) {
VisitStmt(Init);
}
}
- } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+ } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
} else if (!FD->isTrivial() && FD->isDefaulted()) {
// needed? maybe not
@@ -3222,100 +3216,105 @@ class Analyzer {
// else could be BlockDecl
// Do an AST traversal of the function/block body
- TraverseDecl(const_cast<Decl*>(CurrentCaller.CDecl));
+ TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
}
// -- Methods implementing common logic --
- // Only effects whose flags include the specified flag receive a potential diagnostic.
- // TODO: Consider bypasses for these loops by precomputing the flags we care about?
- void addDiagnostic(FunctionEffect::FlagBit Flag, DiagnosticID D, SourceLocation Loc,
- const Decl* Callee = nullptr)
+ // Handle a language construct forbidden by some effects. Only effects whose
+ // flags include the specified flag receive a diagnostic. \p Flag describes
+ // the construct.
+ void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+ SourceLocation Loc, const Decl *Callee = nullptr)
{
- // If there are ANY declared verifiable effects holding the flag, store just one diagnostic.
- for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
- if (effect->getFlags() & Flag) {
- addDiagnosticInner(/*inferring=*/false, effect, D, Loc, Callee);
+ // If there are ANY declared verifiable effects holding the flag, store
+ // just one diagnostic.
+ for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
+ if (Effect->flags() & Flag) {
+ addDiagnosticInner(/*inferring=*/false, Effect, D, Loc, Callee);
break;
}
}
- // For each inferred-but-not-verifiable effect holding the flag, store a diagnostic,
- // if we don't already have a diagnostic for that effect.
- for (auto* effect : CurrentFunction.FXToInfer) {
- if (effect->getFlags() & Flag) {
- addDiagnosticInner(/*inferring=*/true, effect, D, Loc, Callee);
+ // For each inferred-but-not-verifiable effect holding the flag, store a
+ // diagnostic, if we don't already have a diagnostic for that effect.
+ for (auto *Effect : CurrentFunction.FXToInfer) {
+ if (Effect->flags() & Flag) {
+ addDiagnosticInner(/*inferring=*/true, Effect, D, Loc, Callee);
}
}
}
- void addDiagnosticInner(bool inferring, const FunctionEffect* effect, DiagnosticID D,
- SourceLocation Loc, const Decl* Callee = nullptr)
- {
- Diagnostic NewDiag{ effect, D, Loc, Callee };
- CurrentFunction.checkAddDiagnostic(inferring, NewDiag);
+ void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
+ DiagnosticID D, SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ CurrentFunction.checkAddDiagnostic(Inferring,
+ Diagnostic(Effect, D, Loc, Callee));
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
// other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo& CI, SourceLocation CallLoc)
- {
- Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*assertNoFurtherInference=*/false);
+ void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+ Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+ /*AssertNoFurtherInference=*/false);
}
- void checkIndirectCall(CallExpr* Call, Expr* CalleeExpr)
- {
+ void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
const auto CalleeType = CalleeExpr->getType();
- auto *FPT = CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
-
- auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
- if (effect->getFlags() & FunctionEffect::kVerifyCalls) {
- if (FPT == nullptr || effect->diagnoseFunctionCall(/*direct=*/false,
- CurrentCaller.CDecl, CurrentCaller.Effects, FPT, FPT->getFunctionEffects())) {
- addDiagnosticInner(inferring, effect, DiagnosticID::CallsDisallowedExpr, Call->getBeginLoc());
+ auto *FPT =
+ CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+
+ auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
+ if (effect->flags() & FunctionEffect::kVerifyCalls) {
+ if (FPT == nullptr ||
+ effect->diagnoseFunctionCall(
+ /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
+ FPT, FPT->getFunctionEffects())) {
+ addDiagnosticInner(inferring, effect,
+ DiagnosticID::CallsDisallowedExpr,
+ Call->getBeginLoc());
}
}
};
- for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+ for (auto *effect : CurrentFunction.DeclaredVerifiableEffects) {
check1Effect(effect, false);
}
- for (auto* effect : CurrentFunction.FXToInfer) {
+ for (auto *effect : CurrentFunction.FXToInfer) {
check1Effect(effect, true);
}
}
- // This destructor's body should be followed by the caller, but here we follow
- // the field and base destructors.
- void followDestructor(const CXXRecordDecl* Rec, const CXXDestructorDecl* Dtor)
- {
- for (const FieldDecl* Field : Rec->fields()) {
+ // This destructor's body should be followed by the caller, but here we
+ // follow the field and base destructors.
+ void followDestructor(const CXXRecordDecl *Rec,
+ const CXXDestructorDecl *Dtor) {
+ for (const FieldDecl *Field : Rec->fields()) {
followTypeDtor(Field->getType());
}
- if (const auto* Class = dyn_cast<CXXRecordDecl>(Rec)) {
- for (const CXXBaseSpecifier& Base : Class->bases()) {
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ for (const CXXBaseSpecifier &Base : Class->bases()) {
followTypeDtor(Base.getType());
}
- for (const CXXBaseSpecifier& Base : Class->vbases()) {
+ for (const CXXBaseSpecifier &Base : Class->vbases()) {
followTypeDtor(Base.getType());
}
}
}
- void followTypeDtor(QualType QT)
- {
- const Type* Ty = QT.getTypePtr();
+ void followTypeDtor(QualType QT) {
+ const Type *Ty = QT.getTypePtr();
while (Ty->isArrayType()) {
- const ArrayType* Arr = Ty->getAsArrayTypeUnsafe();
+ const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
QT = Arr->getElementType();
Ty = QT.getTypePtr();
}
if (Ty->isRecordType()) {
- if (const CXXRecordDecl* Class = Ty->getAsCXXRecordDecl()) {
- if (auto* Dtor = Class->getDestructor()) {
- CallableInfo CI{ *Dtor };
+ if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
+ if (auto *Dtor = Class->getDestructor()) {
+ CallableInfo CI{*Dtor};
followCall(CI, Dtor->getLocation());
}
}
@@ -3334,37 +3333,34 @@ class Analyzer {
bool shouldWalkTypesOfTypeLocs() const { return false; }
- bool VisitCXXThrowExpr(CXXThrowExpr* Throw)
- {
- addDiagnostic(FunctionEffect::kExcludeThrow, DiagnosticID::Throws, Throw->getThrowLoc());
+ bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow, DiagnosticID::Throws,
+ Throw->getThrowLoc());
return Proceed;
}
- bool VisitCXXCatchStmt(CXXCatchStmt* Catch)
- {
- addDiagnostic(FunctionEffect::kExcludeCatch, DiagnosticID::Catches, Catch->getCatchLoc());
+ bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch, DiagnosticID::Catches,
+ Catch->getCatchLoc());
return Proceed;
}
- bool VisitObjCMessageExpr(ObjCMessageExpr* Msg)
- {
- addDiagnostic(FunctionEffect::kExcludeObjCMessageSend, DiagnosticID::CallsObjC, Msg->getBeginLoc());
+ bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeObjCMessageSend,
+ DiagnosticID::CallsObjC, Msg->getBeginLoc());
return Proceed;
}
- bool VisitCallExpr(CallExpr* Call)
- {
+ bool VisitCallExpr(CallExpr *Call) {
if constexpr (kDebugLogLevel > 2) {
- llvm::errs() << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ llvm::errs() << "VisitCallExpr : "
+ << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
}
- /*if ((AllFlagsOfFXToVerify & FunctionEffect::kVerifyCalls) == 0u) {
- return Proceed;
- }*/
-
- Expr* CalleeExpr = Call->getCallee();
- if (const Decl* Callee = CalleeExpr->getReferencedDeclOfCallee()) {
- CallableInfo CI{ *Callee };
+ Expr *CalleeExpr = Call->getCallee();
+ if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+ CallableInfo CI(*Callee);
followCall(CI, Call->getBeginLoc());
return Proceed;
}
@@ -3380,30 +3376,36 @@ class Analyzer {
// into it and look for a DeclRefExpr. But for now it should suffice
// to look at the type of the Expr. Well, unless we're in a template :-/
- //llvm::errs() << "CalleeExpr ";
- //CalleeExpr->dumpColor();
- //llvm::errs() << Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ": warning: null callee\n";
+ // llvm::errs() << "CalleeExpr ";
+ // CalleeExpr->dumpColor();
+ // llvm::errs() <<
+ // Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ":
+ // warning: null callee\n";
return Proceed;
}
- bool VisitVarDecl(VarDecl* Var)
- {
+ bool VisitVarDecl(VarDecl *Var) {
if constexpr (kDebugLogLevel > 2) {
- llvm::errs() << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ llvm::errs() << "VisitVarDecl : "
+ << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
}
if (Var->isStaticLocal()) {
- addDiagnostic(FunctionEffect::kExcludeStaticLocalVars, DiagnosticID::HasStaticLocal, Var->getLocation());
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
+ DiagnosticID::HasStaticLocal, Var->getLocation());
}
- const QualType::DestructionKind DK = Var->needsDestruction(Outer.mSema.getASTContext());
+ const QualType::DestructionKind DK =
+ Var->needsDestruction(Outer.Sem.getASTContext());
if (DK == QualType::DK_cxx_destructor) {
QualType QT = Var->getType();
- if (const auto* ClsType = QT.getTypePtr()->getAs<RecordType>()) {
- if (const auto* CxxRec = dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
- if (const auto* Dtor = CxxRec->getDestructor()) {
- CallableInfo CI{ *Dtor };
+ if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+ if (const auto *CxxRec =
+ dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+ if (const auto *Dtor = CxxRec->getDestructor()) {
+ CallableInfo CI(*Dtor);
followCall(CI, Var->getLocation());
}
}
@@ -3412,12 +3414,11 @@ class Analyzer {
return Proceed;
}
- bool VisitCXXNewExpr(CXXNewExpr* New)
- {
+ bool VisitCXXNewExpr(CXXNewExpr *New) {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator new.
- if (auto* FD = New->getOperatorNew()) {
- CallableInfo CI{ *FD, SpecialFuncType::OperatorNew };
+ if (auto *FD = New->getOperatorNew()) {
+ CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
followCall(CI, New->getBeginLoc());
}
@@ -3429,12 +3430,11 @@ class Analyzer {
return Proceed;
}
- bool VisitCXXDeleteExpr(CXXDeleteExpr* Delete)
- {
+ bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator delete.
- if (auto* FD = Delete->getOperatorDelete()) {
- CallableInfo CI{ *FD, SpecialFuncType::OperatorDelete };
+ if (auto *FD = Delete->getOperatorDelete()) {
+ CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
followCall(CI, Delete->getBeginLoc());
}
@@ -3443,71 +3443,69 @@ class Analyzer {
return Proceed;
}
- bool VisitCXXConstructExpr(CXXConstructExpr* Construct)
- {
+ bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
if constexpr (kDebugLogLevel > 2) {
- llvm::errs() << "VisitCXXConstructExpr : " <<
- Construct->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+ llvm::errs() << "VisitCXXConstructExpr : "
+ << Construct->getBeginLoc().printToString(
+ Outer.Sem.SourceMgr)
+ << "\n";
}
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to the constructor.
- const CXXConstructorDecl* Ctor = Construct->getConstructor();
- CallableInfo CI{ *Ctor };
+ const CXXConstructorDecl *Ctor = Construct->getConstructor();
+ CallableInfo CI(*Ctor);
followCall(CI, Construct->getLocation());
return Proceed;
}
- bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr* DEI)
- {
- if (auto* Expr = DEI->getExpr()) {
+ bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+ if (auto *Expr = DEI->getExpr()) {
TraverseStmt(Expr);
}
return Proceed;
}
- bool TraverseLambdaExpr(LambdaExpr* Lambda)
- {
+ bool TraverseLambdaExpr(LambdaExpr *Lambda) {
// We override this so as the be able to skip traversal of the lambda's
// body. We have to explicitly traverse the captures.
for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) {
if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
- Lambda->capture_init_begin()[I]) == Stop) return Stop;
+ Lambda->capture_init_begin()[I]) == Stop)
+ return Stop;
}
return Proceed;
}
- bool TraverseBlockExpr(BlockExpr* /*unused*/)
- {
+ bool TraverseBlockExpr(BlockExpr * /*unused*/) {
// TODO: are the capture expressions (ctor call?) safe?
return Proceed;
}
- bool VisitDeclRefExpr(const DeclRefExpr *E)
- {
- const ValueDecl* Val = E->getDecl();
+ bool VisitDeclRefExpr(const DeclRefExpr *E) {
+ const ValueDecl *Val = E->getDecl();
if (isa<VarDecl>(Val)) {
- const VarDecl* Var = cast<VarDecl>(Val);
+ const VarDecl *Var = cast<VarDecl>(Val);
const auto TLSK = Var->getTLSKind();
if (TLSK != VarDecl::TLS_None) {
// At least on macOS, thread-local variables are initialized on
// first access.
- addDiagnostic(FunctionEffect::kExcludeThreadLocalVars,
- DiagnosticID::AccessesThreadLocal, E->getLocation());
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
+ DiagnosticID::AccessesThreadLocal, E->getLocation());
}
}
return Proceed;
}
-
// Unevaluated contexts: need to skip
// see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
// bool TraverseTypeLoc(TypeLoc /*unused*/)
// {
- // // This is a big blunt hammer so that we don't reach __invoke()'s call to declval().
+ // // This is a big blunt hammer so that we don't reach __invoke()'s call
+ // to declval().
// // Is it correct?
// return Proceed;
// }
@@ -3515,7 +3513,9 @@ class Analyzer {
bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
return TraverseStmt(Node->getResultExpr());
}
- bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { return Proceed; }
+ bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+ return Proceed;
+ }
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
@@ -3536,338 +3536,84 @@ class Analyzer {
// -----
// Called for every callable in the translation unit.
struct CallableFinderCallback : MatchFinder::MatchCallback {
- Sema& mSema;
+ Sema &Sem;
- CallableFinderCallback(Sema& S) : mSema{ S } {}
+ CallableFinderCallback(Sema &S) : Sem(S) {}
void run(const MatchFinder::MatchResult &Result) override {
- if (auto* Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
+ if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
if (const auto FX = functionEffectsForDecl(Callable)) {
// Reuse this filtering method in Sema
- mSema.CheckAddCallableWithEffects(Callable, FX);
+ Sem.CheckAddCallableWithEffects(Callable, FX);
}
}
}
static FunctionEffectSet functionEffectsForDecl(const Decl *D) {
- if (auto* FD = D->getAsFunction()) {
+ if (auto *FD = D->getAsFunction()) {
return FD->getFunctionEffects();
}
- if (auto* BD = dyn_cast<BlockDecl>(D)) {
- return BD->getFunctionEffects();
+ if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ return BD->getFunctionEffects();
}
return {};
}
- static void get(Sema &S,const TranslationUnitDecl& TU)
- {
+ static void get(Sema &S, const TranslationUnitDecl &TU) {
MatchFinder CallableFinder;
- CallableFinderCallback Callback{ S };
+ CallableFinderCallback Callback(S);
using namespace clang::ast_matchers;
CallableFinder.addMatcher(
- decl(forEachDescendant(decl(anyOf(
- functionDecl(hasBody(anything())).bind(Tag_Callable),
- //objcMethodDecl(isDefinition()).bind(Tag_Callable), // no, always unsafe
- blockDecl().bind(Tag_Callable))))),
+ decl(forEachDescendant(
+ decl(anyOf(functionDecl(hasBody(anything())).bind(Tag_Callable),
+ // objcMethodDecl(isDefinition()).bind(Tag_Callable),
+ // // no, always unsafe
+ blockDecl().bind(Tag_Callable))))),
&Callback);
- // Matching LambdaExpr this way [a] doesn't seem to work (need to check for Stmt?)
- // and [b] doesn't seem necessary, since the anonymous function is reached via the above.
- // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))), &Callback);
+ // Matching LambdaExpr this way [a] doesn't seem to work (need to check
+ // for Stmt?) and [b] doesn't seem necessary, since the anonymous function
+ // is reached via the above.
+ // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))),
+ // &Callback);
CallableFinder.match(TU, TU.getASTContext());
}
};
- void verifyRootDecls(const TranslationUnitDecl& TU) const
- {
+ void verifyRootDecls(const TranslationUnitDecl &TU) const {
// If this weren't debug code, it would be good to find a way to move/swap
// instead of copying.
- SmallVector<const Decl *> decls = mSema.DeclsWithUnverifiedEffects;
- mSema.DeclsWithUnverifiedEffects.clear();
+ SmallVector<const Decl *> decls = Sem.DeclsWithUnverifiedEffects;
+ Sem.DeclsWithUnverifiedEffects.clear();
- CallableFinderCallback::get(mSema, TU);
+ CallableFinderCallback::get(Sem, TU);
- /*if (decls.size() != mSema.DeclsWithUnverifiedEffects.size())*/ {
- llvm::errs() << "\nFXAnalysis: mSema gathered " << decls.size()
- << " Decls; second AST pass found " << mSema.DeclsWithUnverifiedEffects.size() << "\n";
+ /*if (decls.size() != Sem.DeclsWithUnverifiedEffects.size())*/ {
+ llvm::errs() << "\nFXAnalysis: Sem gathered " << decls.size()
+ << " Decls; second AST pass found "
+ << Sem.DeclsWithUnverifiedEffects.size() << "\n";
}
}
#endif
-
};
-Analyzer::AnalysisMap::~AnalysisMap()
-{
- for (const auto& item : *this) {
- auto ptr = item.second;
- if (isa<PendingFunctionAnalysis*>(ptr)) {
- delete ptr.get<PendingFunctionAnalysis*>();
+Analyzer::AnalysisMap::~AnalysisMap() {
+ for (const auto &Item : *this) {
+ auto AP = Item.second;
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ delete AP.get<PendingFunctionAnalysis *>();
} else {
- delete ptr.get<CompleteFunctionAnalysis*>();
+ delete AP.get<CompleteFunctionAnalysis *>();
}
}
}
} // namespace FXAnalysis
-#if TEMP_DISABLED
-#if 0
- // OLD OLD OLD from followCall
- if (CI.PerfAnnot != PerfAnnotation::None) {
- // Even if the callable turns out to be unsafe, if it is declared safe,
- // don't create a chain reaction by propagating the error to its callers.
- if constexpr (kDebugLogLevel > 1) {
- llvm::errs() << Outer.mDebugIndent << "followCall: safe: " << int(CI.CType) << " " << CI.name() << "\n";
- }
-
- if (CI.isVerifiable()) {
- // The top-level search for callables isn't finding all template instantiations?
- // Since we have a function body here, possibly verify now.
- Outer.determineCallableSafety(CI, /*EmitDiags=*/true);
- } else {
- Outer.markWithoutFollowing(CI, DiagnosticID::None);
- }
-
- return;
- }
-
- if constexpr (kDebugLogLevel > 1) {
- llvm::errs() << Outer.mDebugIndent << "followCall type " << int(CI.CType) << " " << CI.name() << "\n";
- }
-
- if (!CI.isDirectCall()) {
- // Function pointer or virtual method
- Outer.markWithoutFollowing(CI, CI.CType == CallType::Virtual ?
- DiagnosticID::DeclVirtualWithoutConstraint : DiagnosticID::DeclFuncPtrWithoutConstraint);
- return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
- }
-
- // Here we have a direct call. Are we allowed to infer? If not, emit a diagnostic.
- if (!CI.isInferrable()) {
- Outer.markWithoutFollowing(CI, DiagnosticID::DeclWithoutConstraintOrInference);
- return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
- }
-
- const DeclInfo& DI = Outer.determineCallableSafety(CI);
- if (DI.hasDiagnostic()) {
- if (CI.FuncType == SpecialFuncType::None) {
- return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
- }
- return addDiagnostic(DiagnosticID::AllocatesMemory, CallLoc);
- }
-#endif
-
-
-struct PerfConstraintAnalyzer {
- // Move all no_locks functions (both explicit and implicitly via call chain)
- // into a separate code segment?
- //constexpr static bool kCreateNoLocksSection = true;
-
- // If non-null, the unsafe callable.
- using FollowResult = const FunctionDecl*;
-
- using MatchFinder = ast_matchers::MatchFinder;
-
-
- // Information recorded about every visited function/block.
- struct DeclInfo {
- explicit DeclInfo(PerfAnnotation PA) {} //: DeclPerfAnnot{ PA } {}
-
- bool hasDiagnostic() const { return Diag.ID != DiagnosticID::None; }
- const Diagnostic& diagnostic() const { assert(hasDiagnostic()); return Diag; }
- void setDiagnostic(const Diagnostic& e)
- {
- Diag = e;
- }
-
- // Scanning: prevent recursion
- bool isScanning() const { return Scanning; }
- void setScanning(bool b) { Scanning = b; }
-
- Diagnostic Diag;
-
- // This reflects the declared annotation, not the inferred one.
- //PerfAnnotation DeclPerfAnnot{};
-
- // Used to prevent infinite recursion.
- bool Scanning = false;
- };
-
- // This contains an entry for every visited callable. Caution: determineCallableSafety
- // assumes that an iterator remains valid across calls which may mutate the container.
- using InferenceMap = std::map<const Decl*, DeclInfo>;
-
- // --
-
-
- // -----
-
- Sema& S;
- InferenceMap mInferenceMap;
- std::string mDebugIndent;
-
- // -----
-
- PerfConstraintAnalyzer(Sema& S)
- : S{ S }
- {
- }
-
- // Variant of determineCallableSafety where we already know we can't follow
- // because it is virtual or a function pointer etc. Or, we don't need to follow
- // it because we know it's OK but we want to have it in the inference table.
- void markWithoutFollowing(const CallableInfo& CInfo, DiagnosticID Diag)
- {
- auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
- if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
- return;
- }
- iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
- if (Diag != DiagnosticID::None) {
- DeclInfo& Result = iter->second;
- Result.setDiagnostic({ Diag, CInfo.CDecl->getLocation(), nullptr });
- }
- }
-
- void maybeDiagnose(DeclInfo& Result, const Diagnostic& Diag)
- {
- Result.setDiagnostic(Diag);
- }
-
- // Returns a populated map entry for the given CallableInfo's Decl.
- // Caller should take special care to avoid recursion when Scanning is true.
- // EmitDiags should only be true when called from the top-level traversal
- // of all attributed functions in the TranslationUnit.
- const DeclInfo& determineCallableSafety(const CallableInfo& CInfo, bool EmitDiags = false)
- {
- // If we have already visited this callable, return the previous result.
- auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
- if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
- return iter->second;
- }
-
- if constexpr (kDebugLogLevel) {
- llvm::errs() << mDebugIndent << "determineCallableSafety -> " << CInfo.name() << "\n";
- }
-
- iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
- DeclInfo& Result = iter->second;
-
- std::optional<PerfConstraintASTVisitor> MaybeVisitor; // built on demand
- auto getVisitor = [&]() -> PerfConstraintASTVisitor& {
- if (!MaybeVisitor) {
- MaybeVisitor.emplace(*this, /*StopOnFirstDiag=*/!EmitDiags);
- }
- return *MaybeVisitor;
- };
-
- const FunctionDecl *FD = dyn_cast<FunctionDecl>(CInfo.CDecl);
- if (FD != nullptr) {
- // Currently, built-in functions are always considered safe.
- if (FD->getBuiltinID() != 0) {
- return Result;
- }
- // If it doesn't have a body, then we have to rely on the declaration.
- if (!functionIsVerifiable(S, FD)) {
- const PerfAnnotation PA = FD->getPerfAnnotation();
- if (PA != PerfAnnotation::NoLock) {
- Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
- }
- return Result;
- }
-
- if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
- for (const CXXCtorInitializer* Initer : Ctor->inits()) {
- if (Expr* Init = Initer->getInit()) {
- Result.setScanning(true);
- getVisitor().VisitStmt(Init);
- }
- }
- } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
- if (auto* UnsafeCallee = followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor)) {
- //const auto PA = UnsafeCallee->getType()->isNoLock() ? PerfAnnotation::True : PerfAnnotation::False;
- getVisitor().addDiagnostic(DiagnosticID::CallsUnsafeDecl, FD->getLocation(), UnsafeCallee);
- }
- } else if (!FD->isTrivial() && FD->isDefaulted()) {
- // needed? maybe not
- }
- }
-
- if constexpr (kDebugLogLevel) {
- mDebugIndent += " ";
- }
-
- Result.setScanning(true);
-
- auto& Visitor = getVisitor();
- Visitor.TraverseDecl(const_cast<Decl*>(CInfo.CDecl));
-
- const auto& Diagnostics = Visitor.Diagnostics;
-
- Result.setScanning(false);
-
- if constexpr (kDebugLogLevel) {
- mDebugIndent = mDebugIndent.substr(0, mDebugIndent.size() - 4);
- llvm::errs() << mDebugIndent << "determineCallableSafety <- " << CInfo.name() << " : " << Diagnostics.size() << " violations\n";
- //EmitDiags = true; // TEMPORARY
- }
-
- if (!Diagnostics.empty()) {
- if (EmitDiags) {
- emitDiagnostics(Diagnostics, CInfo);
- }
- Result.setDiagnostic(Diagnostics.front());
- }
-
- return Result;
- }
-
- void emitDiagnostics(const std::vector<Diagnostic>& Diags,
- const CallableInfo& CInfo)
- {
- }
-
- // Top-level entry point. Search the entire TU for functions to verify.
- // Ways to optimize:
- // - are the diagnostics enabled? if not, bail
- // - did Sema see any tagged functions with bodies? if not, bail
- void run(const TranslationUnitDecl& TU)
- {
-
- /*if constexpr (kCreateNoLocksSection) {
- createNoLocksSection();
- }*/
- }
-
- /*void createNoLocksSection()
- {
- const char* kSectionName = "__TEXT,__nolock,regular,pure_instructions";
- if (auto err = S.isValidSectionSpecifier(kSectionName)) {
- // real diagnostic?
- llvm::errs() << "error: invalid section name " << kSectionName << " (" << err << ")\n";
- return;
- }
-
- auto& MapEntry = S.getASTContext().SemaAnalysisGeneratedSections[kSectionName];
- MapEntry.reserve(mInferenceMap.size());
- for (auto& item : mInferenceMap) {
- MapEntry.emplace_back(item.first);
- if constexpr (kDebugLogLevel > 1) {
- if (auto* F = dyn_cast<FunctionDecl>(item.first)) {
- CallableInfo CI{ *F, S };
- llvm::errs() << "nolock: " << CI.name() << "\n";
- }
- }
- }
- }*/
-};
-#endif // TEMP_DISABLED
-
// =============================================================================
-
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 6022e4a838e8a..39d9eeac43429 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -586,21 +586,20 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
}
// Generate diagnostics when adding or removing effects in a type conversion.
-void Sema::diagnoseFunctionEffectConversion(QualType DstType,
- QualType SrcType,
- SourceLocation Loc)
-{
- llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> " << DstType << "\n";
+void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
+ SourceLocation Loc) {
+ llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> "
+ << DstType << "\n";
const auto SrcFX = FunctionEffectSet::get(*SrcType);
const auto DstFX = FunctionEffectSet::get(*DstType);
if (SrcFX != DstFX) {
- const auto diffs = FunctionEffectSet::differences(SrcFX, DstFX);
- for (const auto& item : diffs) {
- const FunctionEffect* effect = item.first;
- const bool adding = item.second;
- if (effect->diagnoseConversion(adding,
- SrcType, SrcFX, DstType, DstFX)) {
- Diag(Loc, adding ? diag::warn_invalid_add_func_effects : diag::warn_invalid_remove_func_effects) << effect->name();
+ for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
+ const FunctionEffect *Effect = Item.first;
+ const bool Adding = Item.second;
+ if (Effect->diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
+ Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
+ : diag::warn_invalid_remove_func_effects)
+ << Effect->name();
}
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 77fdffef6382e..f437db5661b00 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3925,12 +3925,12 @@ 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 = FunctionEffectSet::differences(OldFX, NewFX);
- for (const auto& item : diffs) {
- const FunctionEffect* effect = item.first;
- const bool adding = item.second;
- if (effect->diagnoseRedeclaration(adding, *Old, OldFX, *New, NewFX)) {
- Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << effect->name();
+ const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ for (const auto& Item : Diffs) {
+ const FunctionEffect* Effect = Item.first;
+ const bool Adding = Item.second;
+ if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+ Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << Effect->name();
Diag(Old->getLocation(), diag::note_previous_declaration);
}
}
@@ -11162,14 +11162,14 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
// Filter out declarations that the FunctionEffect analysis should skip
// and not verify. (??? Is this the optimal order in which to test ???)
- bool effectsNeedVerification = false;
+ bool FXNeedVerification = false;
for (const auto *Effect : FX) {
- if (Effect->getFlags() & FunctionEffect::kRequiresVerification) {
+ if (Effect->flags() & FunctionEffect::kRequiresVerification) {
AllEffectsToVerify.insert(Effect);
- effectsNeedVerification = true;
+ FXNeedVerification = true;
}
}
- if (!effectsNeedVerification) {
+ if (!FXNeedVerification) {
return;
}
@@ -15789,12 +15789,6 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
else
FD = cast<FunctionDecl>(D);
- auto *Canon = FD->getCanonicalDecl();
-
- // llvm::outs() << "** ActOnStartOfFunctionDef " << FD->getName() <<
- // " " << FD << " " << Canon << " " << FD->getType() << "\n";
- // getNameForDiagnostic
-
// Do not push if it is a lambda because one is already pushed when building
// the lambda in ActOnStartOfLambdaDefinition().
if (!isLambdaCallOperator(FD))
@@ -15995,9 +15989,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
- const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
- llvm::outs() << "^^ " << FX.size() << " effects\n";
- if (FX) {
+ // TODO: does this really need to be getCanonicalDecl()?
+ if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
CheckAddCallableWithEffects(FD, FX);
}
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 12f7869441c9b..fd27ccd5c1dd1 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -403,22 +403,22 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
if (AL.isInvalid()) {
return false;
}
- Expr* ArgExpr = AL.getArgAsExpr(ArgNum);
- SourceLocation errorLoc{ AL.getLoc() };
+ Expr *ArgExpr = AL.getArgAsExpr(ArgNum);
+ SourceLocation ErrorLoc(AL.getLoc());
if (AL.isArgIdent(ArgNum)) {
- IdentifierLoc * IL = AL.getArgAsIdent(ArgNum);
- errorLoc = IL->Loc;
+ IdentifierLoc *IL = AL.getArgAsIdent(ArgNum);
+ ErrorLoc = IL->Loc;
} else if (ArgExpr != nullptr) {
- auto maybeVal = ArgExpr->getIntegerConstantExpr(Context, &errorLoc);
- if (maybeVal) {
- Value = maybeVal->getBoolValue();
+ if (const std::optional<llvm::APSInt> MaybeVal =
+ ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+ Value = MaybeVal->getBoolValue();
return true;
}
}
AL.setInvalid();
- Diag(errorLoc, diag::err_attribute_argument_n_type)
+ Diag(ErrorLoc, diag::err_attribute_argument_n_type)
<< AL << ArgNum << AANT_ArgumentConstantExpr;
return false;
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index dac5d5f3e5c1e..7533d3f9054a2 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18321,24 +18321,25 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
return true;
}
- // Virtual overrides must have the same or stronger performance annotation.
+ // Virtual overrides: check for matching effects.
const auto OldFX = Old->getFunctionEffects();
const auto NewFX = New->getFunctionEffects();
if (OldFX != NewFX) {
- const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
bool AnyDiags = false;
- for (const auto& item : diffs) {
- const FunctionEffect* effect = item.first;
- const bool adding = item.second;
- if (effect->diagnoseMethodOverride(adding, *Old, OldFX, *New, NewFX)) {
- Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << effect->name();
+ for (const auto& Item : Diffs) {
+ const FunctionEffect* Effect = Item.first;
+ const bool Adding = Item.second;
+ if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+ Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << Effect->name();
Diag(Old->getLocation(), diag::note_overridden_virtual_function);
AnyDiags = true;
}
}
- if (AnyDiags) return true;
+ if (AnyDiags)
+ return true;
}
CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 2bc6b5b3880a8..c4f87be129fdc 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -213,7 +213,7 @@ namespace {
// Flags to diagnose illegal permutations of nolock(cond) and noalloc(cond).
// Manual logic for finding previous attributes would be more complex,
- // unless we transoformed nolock/noalloc(false) into distinct separate
+ // unless we transformed nolock/noalloc(false) into distinct separate
// attributes from the ones which are parsed.
unsigned char parsedNolock : 2;
unsigned char parsedNoalloc : 2;
@@ -7942,16 +7942,12 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
}
static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
- ParsedAttr &attr, QualType &type, FunctionTypeUnwrapper& unwrapped)
-{
+ ParsedAttr &PAttr, QualType &type,
+ FunctionTypeUnwrapper &unwrapped) {
// Values of nolockState / noallocState
- enum {
- kNotSeen = 0,
- kSeenFalse = 1,
- kSeenTrue = 2
- };
+ enum { kNotSeen = 0, kSeenFalse = 1, kSeenTrue = 2 };
- const bool isNoLock = attr.getKind() == ParsedAttr::AT_NoLock;
+ const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
Sema &S = state.getSema();
// Delay if this is not a function type.
@@ -7967,9 +7963,9 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
// Parse the conditional expression, if any
bool Cond = true; // default
- if (attr.getNumArgs() > 0) {
- if (!S.checkBoolExprArgumentAttr(attr, 0, Cond)) {
- attr.setInvalid();
+ if (PAttr.getNumArgs() > 0) {
+ if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
+ PAttr.setInvalid();
return false;
}
}
@@ -7978,12 +7974,11 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
Sema &S = state.getSema();
- S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
- << attrTrue << attrFalse
- << false;
+ S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
+ << attrTrue << attrFalse << false;
// we don't necessarily have the location of the previous attribute,
// so no note.
- attr.setInvalid();
+ PAttr.setInvalid();
return true;
};
@@ -8013,9 +8008,10 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
}
state.setParsedNoalloc(newState);
}
-
+
if (!Cond) {
- // nolock(false) and noalloc(false) are represented as sugar, with AttributedType
+ // nolock(false) and noalloc(false) are represented as sugar, with
+ // AttributedType
Attr *A = nullptr;
if (isNoLock) {
A = NoLockAttr::Create(S.Context, false);
@@ -8026,17 +8022,17 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
return true;
}
- const FunctionEffect* Effect = nullptr;
+ const FunctionEffect *Effect = nullptr;
if (isNoLock) {
Effect = &NoLockNoAllocEffect::nolock_instance();
} else {
Effect = &NoLockNoAllocEffect::noalloc_instance();
}
- MutableFunctionEffectSet newEffectSet{ Effect };
+ MutableFunctionEffectSet newEffectSet{Effect};
if (EPI.FunctionEffects) {
// Preserve all previous effects - except noalloc, when we are adding nolock
- for (const auto* effect : EPI.FunctionEffects) {
+ for (const auto *effect : EPI.FunctionEffects) {
if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
newEffectSet.insert(effect);
}
@@ -8044,7 +8040,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
- FPT->getParamTypes(), EPI);
+ FPT->getParamTypes(), EPI);
type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
return true;
}
@@ -8362,8 +8358,8 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
- if (attr.getKind() == ParsedAttr::AT_NoLock
- || attr.getKind() == ParsedAttr::AT_NoAlloc) {
+ if (attr.getKind() == ParsedAttr::AT_NoLock ||
+ attr.getKind() == ParsedAttr::AT_NoAlloc) {
return handleNoLockNoAllocTypeAttr(state, attr, type, unwrapped);
}
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
new file mode 100644
index 0000000000000..a63750f5ca18e
--- /dev/null
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+void unannotated(void);
+void nolock(void) [[clang::nolock]];
+void noalloc(void) [[clang::noalloc]];
+void type_conversions(void)
+{
+ // It's fine to remove a performance constraint.
+ void (*fp_plain)(void);
+
+ fp_plain = unannotated;
+ fp_plain = nolock;
+ fp_plain = noalloc;
+}
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
index 9c494dcdd581d..d2a35453b08bb 100644
--- a/clang/test/Sema/attr-nolock2.cpp
+++ b/clang/test/Sema/attr-nolock2.cpp
@@ -9,8 +9,6 @@
#error "the 'nolock' attribute is not available"
#endif
-#if 1 // TEMP_DISABLE
-
// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
// Check invalid combinations of nolock/noalloc attributes
void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
@@ -84,5 +82,3 @@ int f3(void);
// redeclaration with a stronger constraint is OK.
int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
-
-#endif // TEMP_DISABLE
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock3.cpp
index cfe52da16447c..65d610f796f87 100644
--- a/clang/test/Sema/attr-nolock3.cpp
+++ b/clang/test/Sema/attr-nolock3.cpp
@@ -5,7 +5,6 @@
#endif
// --- CONSTRAINTS ---
-#if 0 // TEMP_DISABLE
void nl1() [[clang::nolock]]
{
@@ -129,10 +128,7 @@ void nl10(
fp2();
}
-#endif // TEMP_DISABLE
-
-// --- PLAYGROUND ---
-
+// Interactions with nolock(false)
void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
{
}
@@ -141,4 +137,3 @@ void nl11() [[clang::nolock]]
{
nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
}
-
>From 8aaeeb27ef50557ed56839db7956c1b6e69ba318 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 14:36:23 -0700
Subject: [PATCH 03/71] squash me
---
clang/lib/AST/Type.cpp | 4 ++--
clang/lib/Sema/Sema.cpp | 3 ++-
clang/lib/Sema/SemaOverload.cpp | 5 +++--
clang/test/Sema/attr-nolock-wip.cpp | 11 +++++++++--
4 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 998360db3c5dd..8cc4bc42210f9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4985,12 +4985,12 @@ std::string NoLockNoAllocEffect::attribute() const {
return std::string{"__attribute__((clang_"} + name().str() + "))";
}
-bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType,
+bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
FunctionEffectSet OldFX,
QualType NewType,
FunctionEffectSet NewFX) const {
// noalloc can't be added (spoofed) during a conversion, unless we have nolock
- if (adding) {
+ if (Adding) {
if (!isNoLock()) {
for (const auto *Effect : OldFX) {
if (Effect->type() == kNoLockTrue)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 39d9eeac43429..d3b9b048230f9 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,7 +588,7 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> "
+ llvm::outs() << "diagnoseFunctionEffectConversion: " << SrcType << " -> "
<< DstType << "\n";
const auto SrcFX = FunctionEffectSet::get(*SrcType);
const auto DstFX = FunctionEffectSet::get(*DstType);
@@ -682,6 +682,7 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
diagnoseZeroToNullptrConversion(Kind, E);
if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+ llvm::outs() << "Sema::ImpCastExprToType\n";
diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 8f1563e788442..80a2e6a6456d0 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,7 +1787,7 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
- llvm::outs() << "IsFunctionConversion " << FromType << " " << ToType << "\n";
+ llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType << "\n";
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
@@ -1827,6 +1827,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
if (TyClass != Type::FunctionProto && TyClass != Type::FunctionNoProto)
return false;
}
+ llvm::outs() << " didn't exit early\n";
const auto *FromFn = cast<FunctionType>(CanFrom);
FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
@@ -1879,7 +1880,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
auto FromFX = FromFPT->getFunctionEffects();
auto ToFX = ToFPT->getFunctionEffects();
if (FromFX != ToFX) {
- llvm::outs() << "IsFunctionConversion effects change " << FromType << " -> " << ToType << "\n";
+ llvm::outs() << " effects change: " << FromType << " -> " << ToType << "\n";
//const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
// TODO: diagnose conflicts
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index a63750f5ca18e..c30d0f42e37ae 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -8,12 +8,19 @@
void unannotated(void);
void nolock(void) [[clang::nolock]];
void noalloc(void) [[clang::noalloc]];
+
+
+void callthis(void (*fp)(void));
+
+
void type_conversions(void)
{
+// callthis(nolock);
+
// It's fine to remove a performance constraint.
void (*fp_plain)(void);
- fp_plain = unannotated;
+// fp_plain = unannotated;
fp_plain = nolock;
- fp_plain = noalloc;
+// fp_plain = noalloc;
}
>From eb204dff79817667c65f09bbd38ece245f50b99e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 14:41:16 -0700
Subject: [PATCH 04/71] apply the in-tree clang-format (which seems subtly
different from my system's local one)
---
clang/include/clang/AST/Type.h | 11 +++++-----
clang/lib/AST/ASTContext.cpp | 23 ++++++++++++--------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 27 ++++++++++++------------
clang/lib/Sema/Sema.cpp | 3 ++-
clang/lib/Sema/SemaDecl.cpp | 23 ++++++++++----------
clang/lib/Sema/SemaDeclAttr.cpp | 7 +++---
clang/lib/Sema/SemaDeclCXX.cpp | 7 +++---
clang/lib/Sema/SemaExprCXX.cpp | 3 ++-
clang/lib/Sema/SemaOverload.cpp | 10 +++++----
9 files changed, 62 insertions(+), 52 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index de21561ce5dca..779c72ab63648 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4360,8 +4360,8 @@ class FunctionProtoType final
// 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.
+ // be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
+ // only if hasExtQualifiers() is true.
//
// The optional FunctionTypeExtraBitfields has to be before the data
// related to the exception specification since it contains the number
@@ -4435,8 +4435,7 @@ class FunctionProtoType final
bool requiresFunctionProtoTypeExtraBitfields() const {
return ExceptionSpec.Type == EST_Dynamic ||
- requiresFunctionProtoTypeArmAttributes() ||
- FunctionEffects;
+ requiresFunctionProtoTypeArmAttributes() || FunctionEffects;
}
bool requiresFunctionProtoTypeArmAttributes() const {
@@ -7984,7 +7983,7 @@ class FunctionEffect {
const FunctionDecl &NewFunction,
FunctionEffectSet NewFX) const;
- /// Return true if adding or removing the effect in a C++ virtual method
+ /// 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,
@@ -7994,7 +7993,7 @@ class FunctionEffect {
/// 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
+ /// kInferrableOnCallees flag set. Example: This allows nolock(false) to
/// prevent inference for the function.
virtual bool canInferOnDecl(const Decl *Caller,
FunctionEffectSet CallerFX) const;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 43fccb117a897..68f2ac3e1329f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4502,13 +4502,12 @@ QualType ASTContext::getFunctionTypeInternal(
size_t Size = FunctionProtoType::totalSizeToAlloc<
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
- Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
+ Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
FunctionEffectSet, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
- EPI.ExtParameterInfos ? NumArgs : 0,
- EPI.FunctionEffects ? 1 : 0,
+ EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects ? 1 : 0,
EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
@@ -10440,8 +10439,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
FunctionEffectSet FromFX, ToFX;
std::optional<FunctionEffectSet> MergedFX;
- if (lproto) ToFX = lproto->getFunctionEffects();
- if (rproto) FromFX = rproto->getFunctionEffects();
+ if (lproto)
+ ToFX = lproto->getFunctionEffects();
+ if (rproto)
+ FromFX = rproto->getFunctionEffects();
if (ToFX != FromFX) {
// We want the intersection of the effects...
MergedFX = FunctionEffectSet::create(FromFX & ToFX);
@@ -10499,8 +10500,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
}
if (!MergedFX) { // effects changed so we can't return either side unaltered
- if (allLTypes) return lhs;
- if (allRTypes) return rhs;
+ if (allLTypes)
+ return lhs;
+ if (allRTypes)
+ return rhs;
}
FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
@@ -10543,8 +10546,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
}
if (!MergedFX) { // effects changed so we can't return either side unaltered
- if (allLTypes) return lhs;
- if (allRTypes) return rhs;
+ if (allLTypes)
+ return lhs;
+ if (allRTypes)
+ return rhs;
}
FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 69417b10049e9..bdc3aa906541b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3196,8 +3196,7 @@ class Analyzer {
CurrentCaller(CurrentCaller) {}
// -- Entry point --
- void run()
- {
+ void run() {
// The target function itself may have some implicit code paths beyond the
// body: member and base constructors and destructors. Visit these first.
if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
@@ -3225,8 +3224,8 @@ class Analyzer {
// flags include the specified flag receive a diagnostic. \p Flag describes
// the construct.
void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
- SourceLocation Loc, const Decl *Callee = nullptr)
- {
+ SourceLocation Loc,
+ const Decl *Callee = nullptr) {
// If there are ANY declared verifiable effects holding the flag, store
// just one diagnostic.
for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
@@ -3247,8 +3246,8 @@ class Analyzer {
void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
DiagnosticID D, SourceLocation Loc,
const Decl *Callee = nullptr) {
- CurrentFunction.checkAddDiagnostic(Inferring,
- Diagnostic(Effect, D, Loc, Callee));
+ CurrentFunction.checkAddDiagnostic(Inferring,
+ Diagnostic(Effect, D, Loc, Callee));
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -3334,14 +3333,14 @@ class Analyzer {
bool shouldWalkTypesOfTypeLocs() const { return false; }
bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow, DiagnosticID::Throws,
- Throw->getThrowLoc());
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow,
+ DiagnosticID::Throws, Throw->getThrowLoc());
return Proceed;
}
bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch, DiagnosticID::Catches,
- Catch->getCatchLoc());
+ diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch,
+ DiagnosticID::Catches, Catch->getCatchLoc());
return Proceed;
}
@@ -3394,7 +3393,8 @@ class Analyzer {
if (Var->isStaticLocal()) {
diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
- DiagnosticID::HasStaticLocal, Var->getLocation());
+ DiagnosticID::HasStaticLocal,
+ Var->getLocation());
}
const QualType::DestructionKind DK =
@@ -3493,7 +3493,8 @@ class Analyzer {
// At least on macOS, thread-local variables are initialized on
// first access.
diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
- DiagnosticID::AccessesThreadLocal, E->getLocation());
+ DiagnosticID::AccessesThreadLocal,
+ E->getLocation());
}
}
return Proceed;
@@ -3770,7 +3771,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
}
// TODO: skip this if the warning isn't enabled.
- FXAnalysis::Analyzer{ S }.run(*TU);
+ FXAnalysis::Analyzer{S}.run(*TU);
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index d3b9b048230f9..b120ed2224696 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -681,7 +681,8 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
diagnoseZeroToNullptrConversion(Kind, E);
- if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+ if (!isCast(CCK) && !E->isNullPointerConstant(
+ Context, Expr::NPC_NeverValueDependent /* ???*/)) {
llvm::outs() << "Sema::ImpCastExprToType\n";
diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f437db5661b00..d407018adfde6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3923,28 +3923,30 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
}
const auto OldFX = Old->getFunctionEffects();
- const auto NewFX = New->getFunctionEffects();
+ const auto NewFX = New->getFunctionEffects();
if (OldFX != NewFX) {
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
- for (const auto& Item : Diffs) {
- const FunctionEffect* Effect = Item.first;
+ for (const auto &Item : Diffs) {
+ const FunctionEffect *Effect = Item.first;
const bool Adding = Item.second;
if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
- Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << Effect->name();
+ Diag(New->getLocation(),
+ diag::warn_mismatched_func_effect_redeclaration)
+ << Effect->name();
Diag(Old->getLocation(), diag::note_previous_declaration);
}
}
const auto MergedFX = OldFX | NewFX;
- // Having diagnosed any problems, prevent further errors by applying the merged set of effects
- // to both declarations.
- auto applyMergedFX = [&](FunctionDecl* FD) {
+ // Having diagnosed any problems, prevent further errors by applying the
+ // merged set of effects to both declarations.
+ auto applyMergedFX = [&](FunctionDecl *FD) {
const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
EPI.FunctionEffects = MergedFX;
QualType ModQT = Context.getFunctionType(FD->getReturnType(),
- FPT->getParamTypes(), EPI);
+ FPT->getParamTypes(), EPI);
FD->setType(ModQT);
};
@@ -11136,8 +11138,7 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
-void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
-{
+void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
if (!D->hasBody()) {
if (const auto *FD = D->getAsFunction()) {
if (!FD->willHaveBody()) {
@@ -11148,7 +11149,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
if (Diags.getIgnoreAllWarnings() ||
(Diags.getSuppressSystemWarnings() &&
- SourceMgr.isInSystemHeader(D->getLocation())))
+ SourceMgr.isInSystemHeader(D->getLocation())))
return;
if (hasUncompilableErrorOccurred())
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index fd27ccd5c1dd1..7805922c66b57 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -398,8 +398,7 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
/// integer (boolean) expression. If not, emit an error and return false.
bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
- bool &Value)
-{
+ bool &Value) {
if (AL.isInvalid()) {
return false;
}
@@ -411,7 +410,7 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
ErrorLoc = IL->Loc;
} else if (ArgExpr != nullptr) {
if (const std::optional<llvm::APSInt> MaybeVal =
- ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+ ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
Value = MaybeVal->getBoolValue();
return true;
}
@@ -419,7 +418,7 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
AL.setInvalid();
Diag(ErrorLoc, diag::err_attribute_argument_n_type)
- << AL << ArgNum << AANT_ArgumentConstantExpr;
+ << AL << ArgNum << AANT_ArgumentConstantExpr;
return false;
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 7533d3f9054a2..2f9a4a14155fb 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18329,11 +18329,12 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
bool AnyDiags = false;
- for (const auto& Item : Diffs) {
- const FunctionEffect* Effect = Item.first;
+ for (const auto &Item : Diffs) {
+ const FunctionEffect *Effect = Item.first;
const bool Adding = Item.second;
if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
- Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << Effect->name();
+ Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
+ << Effect->name();
Diag(Old->getLocation(), diag::note_overridden_virtual_function);
AnyDiags = true;
}
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 50007d4e1de11..e698139e55a23 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4951,7 +4951,8 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
// TODO: This generates a redundant diagnostic for:
// void (^nl_block0)() NOLOCK = ^(){};
- // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/))
+ // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent
+ // /* ???*/))
// diagnoseFunctionEffectConversion(ToType, InitialFromType,
// From->getBeginLoc());
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 80a2e6a6456d0..e34aa7e970d5d 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,7 +1787,8 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
- llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType << "\n";
+ llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType
+ << "\n";
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
@@ -1880,10 +1881,11 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
auto FromFX = FromFPT->getFunctionEffects();
auto ToFX = ToFPT->getFunctionEffects();
if (FromFX != ToFX) {
- llvm::outs() << " effects change: " << FromType << " -> " << ToType << "\n";
+ llvm::outs() << " effects change: " << FromType << " -> " << ToType
+ << "\n";
- //const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
- // TODO: diagnose conflicts
+ // const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
+ // TODO: diagnose conflicts
FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
ExtInfo.FunctionEffects = ToFX;
>From 3b0277bedd5391a17012b69135ac1a26c0a236a6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 13 Mar 2024 08:08:52 -0700
Subject: [PATCH 05/71] Address most of the low-hanging fruit in the initial
review feedback.
---
clang/include/clang/AST/Type.h | 44 +++++----
clang/include/clang/Basic/Attr.td | 10 +--
clang/lib/AST/Type.cpp | 41 +++++----
clang/lib/Sema/AnalysisBasedWarnings.cpp | 46 +++++-----
clang/lib/Sema/SemaDecl.cpp | 9 +-
clang/lib/Sema/SemaType.cpp | 44 +++++----
...olock3.cpp => attr-nolock-constraints.cpp} | 2 +
...ttr-nolock.cpp => attr-nolock-parsing.cpp} | 47 +++++++---
clang/test/Sema/attr-nolock-sema.cpp | 89 +++++++++++++++++++
clang/test/Sema/attr-nolock-wip.cpp | 12 +--
clang/test/Sema/attr-nolock2.cpp | 84 -----------------
11 files changed, 228 insertions(+), 200 deletions(-)
rename clang/test/Sema/{attr-nolock3.cpp => attr-nolock-constraints.cpp} (96%)
rename clang/test/Sema/{attr-nolock.cpp => attr-nolock-parsing.cpp} (67%)
create mode 100644 clang/test/Sema/attr-nolock-sema.cpp
delete mode 100644 clang/test/Sema/attr-nolock2.cpp
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 779c72ab63648..63b78d75089f9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4274,10 +4274,6 @@ class FunctionEffectSet {
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;
}
@@ -7915,37 +7911,39 @@ class CXXMethodDecl;
/// Represents an abstract function effect.
class FunctionEffect {
public:
- enum EffectType {
- kGeneric,
- kNoLockTrue,
- kNoAllocTrue,
+ enum class Type : unsigned char {
+ NoLockTrue,
+ NoAllocTrue,
};
/// Flags describing behaviors of the effect.
+ // (Why not a struct with bitfields? There's one function that would like to
+ // test a caller-specified bit. There are some potential optimizations that
+ // would OR together the bits of multiple effects.)
using Flags = unsigned;
enum FlagBit : unsigned {
// Some effects require verification, e.g. nolock(true); others might not?
- // (no example yet)
- kRequiresVerification = 0x1,
+ // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
+ FE_RequiresVerification = 0x1,
// Does this effect want to verify all function calls originating in
- // functions having this effect?
- kVerifyCalls = 0x2,
+ // functions having this effect? TODO: maybe always true, vestigial.
+ FE_VerifyCalls = 0x2,
// Can verification inspect callees' implementations? (e.g. nolock: yes,
// tcb+types: no)
- kInferrableOnCallees = 0x4,
+ FE_InferrableOnCallees = 0x4,
// Language constructs which effects can diagnose as disallowed.
- kExcludeThrow = 0x8,
- kExcludeCatch = 0x10,
- kExcludeObjCMessageSend = 0x20,
- kExcludeStaticLocalVars = 0x40,
- kExcludeThreadLocalVars = 0x80
+ FE_ExcludeThrow = 0x8,
+ FE_ExcludeCatch = 0x10,
+ FE_ExcludeObjCMessageSend = 0x20,
+ FE_ExcludeStaticLocalVars = 0x40,
+ FE_ExcludeThreadLocalVars = 0x80
};
private:
- const EffectType Type_;
+ const Type Type_;
const Flags Flags_;
const char *Name;
@@ -7953,12 +7951,12 @@ class FunctionEffect {
using CalleeDeclOrType =
llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
- FunctionEffect(EffectType T, Flags F, const char *Name)
+ FunctionEffect(Type T, Flags F, const char *Name)
: Type_(T), Flags_(F), Name(Name) {}
virtual ~FunctionEffect();
/// The type of the effect.
- EffectType type() const { return Type_; }
+ Type type() const { return Type_; }
/// Flags describing behaviors of the effect.
Flags flags() const { return Flags_; }
@@ -8011,13 +8009,13 @@ class FunctionEffect {
/// FunctionEffect subclass for nolock and noalloc (whose behaviors are close
/// to identical).
class NoLockNoAllocEffect : public FunctionEffect {
- bool isNoLock() const { return type() == kNoLockTrue; }
+ bool isNoLock() const { return type() == Type::NoLockTrue; }
public:
static const NoLockNoAllocEffect &nolock_instance();
static const NoLockNoAllocEffect &noalloc_instance();
- NoLockNoAllocEffect(EffectType Type, const char *Name);
+ NoLockNoAllocEffect(Type Type, const char *Name);
~NoLockNoAllocEffect() override;
std::string attribute() const override;
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4033b9efb86f3..ccc8a58176c8d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1402,26 +1402,20 @@ def CXX11NoReturn : InheritableAttr {
let Documentation = [CXX11NoReturnDocs];
}
-def NoLock : DeclOrTypeAttr {
+def NoLock : TypeAttr {
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 {
+def NoAlloc : TypeAttr {
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];
}
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8cc4bc42210f9..0afc61ff6de48 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3662,7 +3662,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// Note that valid type pointers are never ambiguous with anything else.
//
// The encoding grammar begins:
- // type type* bool int bool
+ // effects type type* bool int bool
// If that final bool is true, then there is a section for the EH spec:
// bool type*
// This is followed by an optional "consumed argument" section of the
@@ -3673,13 +3673,12 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// Finally we have a trailing return type flag (bool)
// combined with AArch64 SME Attributes, to save space:
// int
- // Then add the FunctionEffects
//
// There is no ambiguity between the consumed arguments and an empty EH
// spec because of the leading 'bool' which unambiguously indicates
// whether the following bool is the EH spec or part of the arguments.
- ID.AddPointer(epi.FunctionEffects.getOpaqueValue()); // TODO: Where???
+ ID.AddPointer(epi.FunctionEffects.getOpaqueValue());
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
@@ -4961,22 +4960,22 @@ bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
}
const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
- static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+ static NoLockNoAllocEffect global(Type::NoLockTrue, "nolock");
return global;
}
const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
- static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
+ static NoLockNoAllocEffect global(Type::NoAllocTrue, "noalloc");
return global;
}
// TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType Ty, const char *Name)
+NoLockNoAllocEffect::NoLockNoAllocEffect(Type Ty, const char *Name)
: FunctionEffect(Ty,
- kRequiresVerification | kVerifyCalls |
- kInferrableOnCallees | kExcludeThrow | kExcludeCatch |
- kExcludeObjCMessageSend | kExcludeStaticLocalVars |
- kExcludeThreadLocalVars,
+ FE_RequiresVerification | FE_VerifyCalls |
+ FE_InferrableOnCallees | FE_ExcludeThrow |
+ FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+ FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars,
Name) {}
NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
@@ -4993,7 +4992,7 @@ bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
if (Adding) {
if (!isNoLock()) {
for (const auto *Effect : OldFX) {
- if (Effect->type() == kNoLockTrue)
+ if (Effect->type() == Type::NoLockTrue)
return false;
}
}
@@ -5044,10 +5043,11 @@ bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
bool NoLockNoAllocEffect::diagnoseFunctionCall(
bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
- const EffectType CallerType = type();
+ const Type CallerType = type();
for (const auto *Effect : CalleeFX) {
- const EffectType ET = Effect->type();
- if (ET == CallerType || (CallerType == kNoAllocTrue && ET == kNoLockTrue)) {
+ const Type ET = Effect->type();
+ if (ET == CallerType ||
+ (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
return false;
}
}
@@ -5088,11 +5088,16 @@ FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
// SmallSet only has contains(), so it provides no way to obtain the uniqued
// value.
- static std::set<UniquedAndSortedFX> uniquedFXSets;
+ // TODO: Put this in the ASTContext
+ // TODO: Try making this a DenseSet? Requires more methods on the members.
+ // static llvm::DenseSet<UniquedAndSortedFX> UniquedFXSets;
+ // Punt on this until we revisit FunctionFX.
+
+ static std::set<UniquedAndSortedFX> UniquedFXSets;
// See if we already have this set.
- const auto Iter = uniquedFXSets.find(NewSet);
- if (Iter != uniquedFXSets.end()) {
+ const auto Iter = UniquedFXSets.find(NewSet);
+ if (Iter != UniquedFXSets.end()) {
return FunctionEffectSet{&*Iter};
}
@@ -5102,7 +5107,7 @@ FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
// Make a new wrapper and insert it into the set.
NewSet = UniquedAndSortedFX(Storage, Items.size());
- auto [InsIter, _] = uniquedFXSets.insert(NewSet);
+ auto [InsIter, _] = UniquedFXSets.insert(NewSet);
return FunctionEffectSet(&*InsIter);
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index bdc3aa906541b..cf9b1244e81bf 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2519,9 +2519,10 @@ struct CallableInfo {
// Map effects to single diagnostics.
class EffectToDiagnosticMap {
// Since we currently only have a tiny number of effects (typically no more
- // than 1), use a sorted SmallVector.
+ // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+ // is often empty, use a unique_ptr to the SmallVector.
using Element = std::pair<const FunctionEffect *, Diagnostic>;
- using ImplVec = llvm::SmallVector<Element>;
+ using ImplVec = llvm::SmallVector<Element, 1>;
std::unique_ptr<ImplVec> Impl;
public:
@@ -2533,7 +2534,7 @@ class EffectToDiagnosticMap {
return Item.second;
}
Element Elem(Key, {});
- auto Iter = _find(Elem);
+ auto *Iter = _find(Elem);
if (Iter != Impl->end() && Iter->first == Key) {
return Iter->second;
}
@@ -2546,7 +2547,7 @@ class EffectToDiagnosticMap {
return nullptr;
}
Element elem(key, {});
- auto iter = _find(elem);
+ auto *iter = _find(elem);
if (iter != Impl->end() && iter->first == key) {
return &iter->second;
}
@@ -2599,7 +2600,7 @@ class PendingFunctionAnalysis {
FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet fx;
for (const auto *effect : cinfo.Effects) {
- if (effect->flags() & FunctionEffect::kRequiresVerification) {
+ if (effect->flags() & FunctionEffect::FE_RequiresVerification) {
fx.insert(effect);
}
}
@@ -2807,7 +2808,7 @@ class Analyzer {
MutableFunctionEffectSet inferrableEffects;
for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
const auto Flags = effect->flags();
- if (Flags & FunctionEffect::kInferrableOnCallees) {
+ if (Flags & FunctionEffect::FE_InferrableOnCallees) {
inferrableEffects.insert(effect);
}
}
@@ -2820,7 +2821,7 @@ class Analyzer {
SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
- // It's useful to use DeclsWithUnverifiedEffects as a stack for a
+ // It's helpful to use DeclsWithUnverifiedEffects as a stack for a
// depth-first traversal rather than have a secondary container. But first,
// reverse it, so Decls are verified in the order they are declared.
std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -2840,7 +2841,7 @@ class Analyzer {
verifyQueue.pop_back();
continue;
}
- llvm_unreachable("shouldn't happen");
+ llvm_unreachable("unexpected DeclAnalysis item");
}
auto *Pending = verifyDecl(D);
@@ -2861,9 +2862,10 @@ class Analyzer {
}
if (isa<PendingFunctionAnalysis *>(AP)) {
// $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+ // TODO
__builtin_trap();
}
- llvm_unreachable("shouldn't happen");
+ llvm_unreachable("unexpected DeclAnalysis item");
}
}
}
@@ -2964,7 +2966,7 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
const auto Flags = Effect->flags();
- if (Flags & FunctionEffect::kVerifyCalls) {
+ if (Flags & FunctionEffect::FE_VerifyCalls) {
const bool diagnose = Effect->diagnoseFunctionCall(
DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
CalleeEffects);
@@ -2972,7 +2974,7 @@ class Analyzer {
// If inference is not allowed, or the target is indirect (virtual
// method/function ptr?), generate a diagnostic now.
if (!IsInferencePossible ||
- !(Flags & FunctionEffect::kInferrableOnCallees)) {
+ !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
if (Callee.FuncType == SpecialFuncType::None) {
PFA.checkAddDiagnostic(Inferring,
{Effect, DiagnosticID::CallsUnsafeDecl,
@@ -3263,7 +3265,7 @@ class Analyzer {
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
- if (effect->flags() & FunctionEffect::kVerifyCalls) {
+ if (effect->flags() & FunctionEffect::FE_VerifyCalls) {
if (FPT == nullptr ||
effect->diagnoseFunctionCall(
/*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
@@ -3333,19 +3335,19 @@ class Analyzer {
bool shouldWalkTypesOfTypeLocs() const { return false; }
bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow,
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
DiagnosticID::Throws, Throw->getThrowLoc());
return Proceed;
}
bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch,
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
DiagnosticID::Catches, Catch->getCatchLoc());
return Proceed;
}
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeObjCMessageSend,
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
DiagnosticID::CallsObjC, Msg->getBeginLoc());
return Proceed;
}
@@ -3392,7 +3394,7 @@ class Analyzer {
}
if (Var->isStaticLocal()) {
- diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
DiagnosticID::HasStaticLocal,
Var->getLocation());
}
@@ -3491,8 +3493,8 @@ class Analyzer {
const auto TLSK = Var->getTLSKind();
if (TLSK != VarDecl::TLS_None) {
// At least on macOS, thread-local variables are initialized on
- // first access.
- diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
+ // first access, including a heap allocation.
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
DiagnosticID::AccessesThreadLocal,
E->getLocation());
}
@@ -3503,14 +3505,6 @@ class Analyzer {
// Unevaluated contexts: need to skip
// see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
- // bool TraverseTypeLoc(TypeLoc /*unused*/)
- // {
- // // This is a big blunt hammer so that we don't reach __invoke()'s call
- // to declval().
- // // Is it correct?
- // return Proceed;
- // }
-
bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
return TraverseStmt(Node->getResultExpr());
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d407018adfde6..c9488431d4343 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11155,17 +11155,21 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
if (hasUncompilableErrorOccurred())
return;
+#if 0
+// TODO: Does anything break if we don't do this?
+
// For code in dependent contexts, we'll do this at instantiation time
// (??? This was copied from something else in AnalysisBasedWarnings ???)
if (cast<DeclContext>(D)->isDependentContext()) {
return;
}
+#endif
// Filter out declarations that the FunctionEffect analysis should skip
- // and not verify. (??? Is this the optimal order in which to test ???)
+ // and not verify.
bool FXNeedVerification = false;
for (const auto *Effect : FX) {
- if (Effect->flags() & FunctionEffect::kRequiresVerification) {
+ if (Effect->flags() & FunctionEffect::FE_RequiresVerification) {
AllEffectsToVerify.insert(Effect);
FXNeedVerification = true;
}
@@ -15990,7 +15994,6 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
- // TODO: does this really need to be getCanonicalDecl()?
if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
CheckAddCallableWithEffects(FD, FX);
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c4f87be129fdc..a7849f42d38c1 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -172,6 +172,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_TypeNullableResult: \
case ParsedAttr::AT_TypeNullUnspecified
+enum class BoolAttrState : uint8_t { Unseen, False, True };
+
namespace {
/// An object which stores processing state for the entire
/// GetTypeForDeclarator process.
@@ -215,14 +217,15 @@ namespace {
// Manual logic for finding previous attributes would be more complex,
// unless we transformed nolock/noalloc(false) into distinct separate
// attributes from the ones which are parsed.
- unsigned char parsedNolock : 2;
- unsigned char parsedNoalloc : 2;
+ BoolAttrState parsedNolock : 2;
+ BoolAttrState parsedNoalloc : 2;
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
: sema(sema), declarator(declarator),
chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
- parsedNolock(0), parsedNoalloc(0) {}
+ parsedNolock(BoolAttrState::Unseen),
+ parsedNoalloc(BoolAttrState::Unseen) {}
Sema &getSema() const {
return sema;
@@ -349,10 +352,10 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
- void setParsedNolock(unsigned char v) { parsedNolock = v; }
- unsigned char getParsedNolock() const { return parsedNolock; }
- void setParsedNoalloc(unsigned char v) { parsedNoalloc = v; }
- unsigned char getParsedNoalloc() const { return parsedNoalloc; }
+ void setParsedNolock(BoolAttrState v) { parsedNolock = v; }
+ BoolAttrState getParsedNolock() const { return parsedNolock; }
+ void setParsedNoalloc(BoolAttrState v) { parsedNoalloc = v; }
+ BoolAttrState getParsedNoalloc() const { return parsedNoalloc; }
~TypeProcessingState() {
if (savedAttrs.empty())
@@ -7944,16 +7947,13 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
ParsedAttr &PAttr, QualType &type,
FunctionTypeUnwrapper &unwrapped) {
- // Values of nolockState / noallocState
- enum { kNotSeen = 0, kSeenFalse = 1, kSeenTrue = 2 };
-
- const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
- Sema &S = state.getSema();
-
// Delay if this is not a function type.
if (!unwrapped.isFunctionType())
return false;
+ const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
+ Sema &S = state.getSema();
+
// Require FunctionProtoType
auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
@@ -7962,6 +7962,8 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
}
// Parse the conditional expression, if any
+ // TODO: There's a better way to do this. See PR feedback.
+ // TODO: Handle a type-dependent expression.
bool Cond = true; // default
if (PAttr.getNumArgs() > 0) {
if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
@@ -7983,14 +7985,16 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
};
// check nolock(true) against nolock(false), and same for noalloc
- const unsigned newState = Cond ? kSeenTrue : kSeenFalse;
- const unsigned oppositeNewState = Cond ? kSeenFalse : kSeenTrue;
+ const BoolAttrState newState =
+ Cond ? BoolAttrState::True : BoolAttrState::False;
+ const BoolAttrState oppositeNewState =
+ Cond ? BoolAttrState::False : BoolAttrState::True;
if (isNoLock) {
if (state.getParsedNolock() == oppositeNewState) {
return incompatible("nolock(true)", "nolock(false)");
}
// also check nolock(true) against noalloc(false)
- if (Cond && state.getParsedNoalloc() == kSeenFalse) {
+ if (Cond && state.getParsedNoalloc() == BoolAttrState::False) {
return incompatible("nolock(true)", "noalloc(false)");
}
state.setParsedNolock(newState);
@@ -7999,7 +8003,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
return incompatible("noalloc(true)", "noalloc(false)");
}
// also check nolock(true) against noalloc(false)
- if (state.getParsedNolock() == kSeenTrue) {
+ if (state.getParsedNolock() == BoolAttrState::True) {
if (!Cond) {
return incompatible("nolock(true)", "noalloc(false)");
}
@@ -8022,6 +8026,8 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
return true;
}
+ // nolock(true) and noalloc(true) are represented as FunctionEffects, in a
+ // FunctionEffectSet attached to a FunctionProtoType.
const FunctionEffect *Effect = nullptr;
if (isNoLock) {
Effect = &NoLockNoAllocEffect::nolock_instance();
@@ -8031,9 +8037,9 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
MutableFunctionEffectSet newEffectSet{Effect};
if (EPI.FunctionEffects) {
- // Preserve all previous effects - except noalloc, when we are adding nolock
+ // Preserve any previous effects - except noalloc, when we are adding nolock
for (const auto *effect : EPI.FunctionEffects) {
- if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
+ if (!(isNoLock && effect->type() == FunctionEffect::Type::NoAllocTrue))
newEffectSet.insert(effect);
}
}
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
similarity index 96%
rename from clang/test/Sema/attr-nolock3.cpp
rename to clang/test/Sema/attr-nolock-constraints.cpp
index 65d610f796f87..801f971b41086 100644
--- a/clang/test/Sema/attr-nolock3.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
+// the AnalysisBasedWarnings pass from running at all.
#if !__has_attribute(clang_nolock)
#error "the 'nolock' attribute is not available"
diff --git a/clang/test/Sema/attr-nolock.cpp b/clang/test/Sema/attr-nolock-parsing.cpp
similarity index 67%
rename from clang/test/Sema/attr-nolock.cpp
rename to clang/test/Sema/attr-nolock-parsing.cpp
index 3f9938a2e5460..6fac57645a1ae 100644
--- a/clang/test/Sema/attr-nolock.cpp
+++ b/clang/test/Sema/attr-nolock-parsing.cpp
@@ -1,36 +1,34 @@
// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
-// expected-no-diagnostics
// Make sure that the attribute gets parsed and attached to the correct AST elements.
-// Update 1 Mar 2024
#pragma clang diagnostic ignored "-Wunused-variable"
// =========================================================================================
// Square brackets, true
-#define NOLOCK [[clang::nolock]]
+namespace square_brackets {
// On the type of the FunctionDecl
-void nl_function() NOLOCK;
+void nl_function() [[clang::nolock]];
// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
// On the type of the VarDecl holding a function pointer
-void (*nl_func_a)() NOLOCK;
+void (*nl_func_a)() [[clang::nolock]];
// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
// Check alternate attribute type and placement
-__attribute__((clang_nolock)) void (*nl_func_b)(void);
+__attribute__((clang_nolock)) void (*nl_func_b)();
// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nolock))'
// On the type of the ParmVarDecl of a function parameter
-static void nlReceiver(void (*nl_func)() NOLOCK);
+static void nlReceiver(void (*nl_func)() [[clang::nolock]]);
// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nolock))'
// As an AttributedType within the nested types of a typedef
-typedef void (*nl_fp_type)() NOLOCK;
+typedef void (*nl_fp_type)() [[clang::nolock]];
// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nolock))'
-using nl_fp_talias = void (*)() NOLOCK;
+using nl_fp_talias = void (*)() [[clang::nolock]];
// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nolock))'
// From a typedef or typealias, on a VarDecl
@@ -41,7 +39,7 @@ nl_fp_talias nl_fp_var2;
// On type of a FieldDecl
struct Struct {
- void (*nl_func_field)() NOLOCK;
+ void (*nl_func_field)() [[clang::nolock]];
// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nolock))'
};
@@ -55,10 +53,10 @@ void nl2() [[clang::noalloc]] [[clang::nolock]];
// --- Blocks ---
// On the type of the VarDecl holding a BlockDecl
-void (^nl_block1)() NOLOCK = ^() NOLOCK {};
+void (^nl_block1)() [[clang::nolock]] = ^() [[clang::nolock]] {};
// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nolock))'
-int (^nl_block2)() NOLOCK = ^() NOLOCK { return 0; };
+int (^nl_block2)() [[clang::nolock]] = ^() [[clang::nolock]] { return 0; };
// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nolock))'
// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
@@ -66,7 +64,7 @@ static void blockCaller() { nl_block1(); }
// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
// $$$ TODO: There are still some loose ends in all the methods of the lambda
-auto nl_lambda = []() NOLOCK {};
+auto nl_lambda = []() [[clang::nolock]] {};
// =========================================================================================
// Square brackets, false
@@ -74,5 +72,28 @@ auto nl_lambda = []() NOLOCK {};
void nl_func_false() [[clang::nolock(false)]];
// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
+} // namespace square_brackets
+
+// =========================================================================================
+// GNU-style attribute, true
+
+namespace gnu_style {
+
+// On the type of the FunctionDecl
+void nl_function() __attribute__((clang_nolock));
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// Alternate placement on the FunctionDecl
+__attribute__((clang_nolock)) void nl_function();
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() __attribute__((clang_nolock));
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+
+
+
+} // namespace gnu_style
+
// TODO: Duplicate the above for noalloc
// TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
new file mode 100644
index 0000000000000..68b056bec1bdf
--- /dev/null
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+
+// TODO: There's a problem with diagnosing type conversions in plain C.
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+// --- ATTRIBUTE SYNTAX: SUBJECTS ---
+
+int nl_var [[clang::nolock]]; // expected-warning {{'nolock' only applies to function types; type here is 'int'}}
+struct nl_struct {} [[clang::nolock]]; // expected-warning {{attribute 'nolock' is ignored, place it after "struct" to apply attribute to type declaration}}
+struct [[clang::nolock]] nl_struct2 {}; // expected-error {{'nolock' attribute cannot be applied to a declaration}}
+
+// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
+// Check invalid combinations of nolock/noalloc attributes
+
+void nl_true_false_1() [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+void nl_true_false_2() [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+
+void na_true_false_1() [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+void na_true_false_2() [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+
+void nl_true_na_true_1() [[clang::nolock]] [[clang::noalloc]];
+void nl_true_na_true_2() [[clang::noalloc]] [[clang::nolock]];
+
+void nl_true_na_false_1() [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+void nl_true_na_false_2() [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+
+void nl_false_na_true_1() [[clang::nolock(false)]] [[clang::noalloc]];
+void nl_false_na_true_2() [[clang::noalloc]] [[clang::nolock(false)]];
+
+void nl_false_na_false_1() [[clang::nolock(false)]] [[clang::noalloc(false)]];
+void nl_false_na_false_2() [[clang::noalloc(false)]] [[clang::nolock(false)]];
+
+// --- TYPE CONVERSIONS ---
+
+void unannotated();
+void nolock() [[clang::nolock]];
+void noalloc() [[clang::noalloc]];
+void type_conversions()
+{
+ // It's fine to remove a performance constraint.
+ void (*fp_plain)();
+
+ fp_plain = unannotated;
+ fp_plain = nolock;
+ fp_plain = noalloc;
+
+ // Adding/spoofing nolock is unsafe.
+ void (*fp_nolock)() [[clang::nolock]];
+ fp_nolock = nolock;
+ fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+ fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+ // Adding/spoofing noalloc is unsafe.
+ void (*fp_noalloc)() [[clang::noalloc]];
+ fp_noalloc = noalloc;
+ fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+ fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
+}
+
+// --- VIRTUAL METHODS ---
+#ifdef __cplusplus
+struct Base {
+ virtual void f1();
+ virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
+ virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+};
+
+struct Derived : public Base {
+ void f1() [[clang::nolock]] override;
+ void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
+ void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+};
+#endif // __cplusplus
+
+// --- REDECLARATIONS ---
+
+int f2();
+// redeclaration with a stronger constraint is OK.
+int f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
+int f2() { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
+int f3();
+// redeclaration with a stronger constraint is OK.
+int f3() [[clang::noalloc]]; // expected-note {{previous declaration is here}}
+int f3() { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index c30d0f42e37ae..4afd9ed10582a 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -5,20 +5,20 @@
#error "the 'nolock' attribute is not available"
#endif
-void unannotated(void);
-void nolock(void) [[clang::nolock]];
-void noalloc(void) [[clang::noalloc]];
+void unannotated();
+void nolock() [[clang::nolock]];
+void noalloc() [[clang::noalloc]];
-void callthis(void (*fp)(void));
+void callthis(void (*fp)());
-void type_conversions(void)
+void type_conversions()
{
// callthis(nolock);
// It's fine to remove a performance constraint.
- void (*fp_plain)(void);
+ void (*fp_plain)();
// fp_plain = unannotated;
fp_plain = nolock;
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
deleted file mode 100644
index d2a35453b08bb..0000000000000
--- a/clang/test/Sema/attr-nolock2.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c2x %s
-
-// TODO: There's a problem with diagnosing type conversions in plain C.
-
-#pragma clang diagnostic error "-Wstrict-prototypes"
-
-#if !__has_attribute(clang_nolock)
-#error "the 'nolock' attribute is not available"
-#endif
-
-// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
-// Check invalid combinations of nolock/noalloc attributes
-void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
-void nl_true_false_2(void) [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
-
-void na_true_false_1(void) [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
-void na_true_false_2(void) [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
-
-void nl_true_na_true_1(void) [[clang::nolock]] [[clang::noalloc]];
-void nl_true_na_true_2(void) [[clang::noalloc]] [[clang::nolock]];
-
-void nl_true_na_false_1(void) [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
-void nl_true_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
-
-void nl_false_na_true_1(void) [[clang::nolock(false)]] [[clang::noalloc]];
-void nl_false_na_true_2(void) [[clang::noalloc]] [[clang::nolock(false)]];
-
-void nl_false_na_false_1(void) [[clang::nolock(false)]] [[clang::noalloc(false)]];
-void nl_false_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock(false)]];
-
-// --- TYPE CONVERSIONS ---
-
-void unannotated(void);
-void nolock(void) [[clang::nolock]];
-void noalloc(void) [[clang::noalloc]];
-void type_conversions(void)
-{
- // It's fine to remove a performance constraint.
- void (*fp_plain)(void);
-
- fp_plain = unannotated;
- fp_plain = nolock;
- fp_plain = noalloc;
-
- // Adding/spoofing nolock is unsafe.
- void (*fp_nolock)(void) [[clang::nolock]];
- fp_nolock = nolock;
- fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
- fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-
- // Adding/spoofing noalloc is unsafe.
- void (*fp_noalloc)(void) [[clang::noalloc]];
- fp_noalloc = noalloc;
- fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
- fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
-}
-
-// --- VIRTUAL METHODS ---
-#ifdef __cplusplus
-struct Base {
- virtual void f1();
- virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
- virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
-};
-
-struct Derived : public Base {
- void f1() [[clang::nolock]] override;
- void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
- void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
-};
-#endif // __cplusplus
-
-// --- REDECLARATIONS ---
-
-int f2(void);
-// redeclaration with a stronger constraint is OK.
-int f2(void) [[clang::nolock]]; // expected-note {{previous declaration is here}}
-int f2(void) { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
-
-int f3(void);
-// redeclaration with a stronger constraint is OK.
-int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
-int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
>From 4c6486ff1281471eea66811fa2b6aa941373c879 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Thu, 14 Mar 2024 07:14:32 -0700
Subject: [PATCH 06/71] Update clang/include/clang/AST/Type.h
Co-authored-by: Shafik Yaghmour <shafik.yaghmour at intel.com>
---
clang/include/clang/AST/Type.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 63b78d75089f9..79738729ee3f3 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4235,7 +4235,7 @@ class FunctionEffectSet {
public:
using Differences =
- SmallVector<std::pair<const FunctionEffect *, bool /*added*/>>;
+ SmallVector<std::pair<const FunctionEffect *, /*added=*/bool>>;
FunctionEffectSet() : Impl(nullptr) {}
>From e6b1e3557db00bd09ae3b4d2791ae06b3f870719 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 13 Mar 2024 15:47:06 -0700
Subject: [PATCH 07/71] - Sema::CheckAddCallableWithEffects: really do skip
functions in dependent contexts. - Tweaking tests - things that came up in
review.
---
clang/lib/Sema/SemaDecl.cpp | 7 +--
...ock-parsing.cpp => attr-nolock-syntax.cpp} | 15 +++++-
clang/test/Sema/attr-nolock-wip.cpp | 52 +++++++++++++++++++
3 files changed, 67 insertions(+), 7 deletions(-)
rename clang/test/Sema/{attr-nolock-parsing.cpp => attr-nolock-syntax.cpp} (88%)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c9488431d4343..d860804102dfc 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11155,15 +11155,12 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
if (hasUncompilableErrorOccurred())
return;
-#if 0
-// TODO: Does anything break if we don't do this?
-
// For code in dependent contexts, we'll do this at instantiation time
- // (??? This was copied from something else in AnalysisBasedWarnings ???)
+ // Without this check, we would analyze the function based on placeholder
+ // template parameters, and potentially generate spurious diagnostics.
if (cast<DeclContext>(D)->isDependentContext()) {
return;
}
-#endif
// Filter out declarations that the FunctionEffect analysis should skip
// and not verify.
diff --git a/clang/test/Sema/attr-nolock-parsing.cpp b/clang/test/Sema/attr-nolock-syntax.cpp
similarity index 88%
rename from clang/test/Sema/attr-nolock-parsing.cpp
rename to clang/test/Sema/attr-nolock-syntax.cpp
index 6fac57645a1ae..2c0d71ba93ddf 100644
--- a/clang/test/Sema/attr-nolock-parsing.cpp
+++ b/clang/test/Sema/attr-nolock-syntax.cpp
@@ -50,6 +50,9 @@ void nl1() [[clang::nolock]] [[clang::noalloc]];
void nl2() [[clang::noalloc]] [[clang::nolock]];
// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nolock))'
+decltype(nl1) nl3;
+// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nolock))'
+
// --- Blocks ---
// On the type of the VarDecl holding a BlockDecl
@@ -63,8 +66,11 @@ int (^nl_block2)() [[clang::nolock]] = ^() [[clang::nolock]] { return 0; };
static void blockCaller() { nl_block1(); }
// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
-// $$$ TODO: There are still some loose ends in all the methods of the lambda
+// --- Lambdas ---
+
+// On the operator() of a lambda's CXXMethodDecl
auto nl_lambda = []() [[clang::nolock]] {};
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_nolock))' inline
// =========================================================================================
// Square brackets, false
@@ -72,11 +78,17 @@ auto nl_lambda = []() [[clang::nolock]] {};
void nl_func_false() [[clang::nolock(false)]];
// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
+// TODO: This exposes a bug where a type attribute is lost when inferring a lambda's
+// return type.
+auto nl_lambda_false = []() [[clang::nolock(false)]] {};
+
} // namespace square_brackets
// =========================================================================================
// GNU-style attribute, true
+// TODO: Duplicate more of the above for GNU-style attribute
+
namespace gnu_style {
// On the type of the FunctionDecl
@@ -96,4 +108,3 @@ void (*nl_func_a)() __attribute__((clang_nolock));
} // namespace gnu_style
// TODO: Duplicate the above for noalloc
-// TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 4afd9ed10582a..f73fbbec70f70 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -5,6 +5,9 @@
#error "the 'nolock' attribute is not available"
#endif
+// ============================================================================
+
+#if 0 // C function type problems
void unannotated();
void nolock() [[clang::nolock]];
void noalloc() [[clang::noalloc]];
@@ -24,3 +27,52 @@ void type_conversions()
fp_plain = nolock;
// fp_plain = noalloc;
}
+#endif
+
+// ============================================================================
+
+#if 0
+// https://github.com/llvm/llvm-project/pull/84983#issuecomment-1994978033
+
+// the bug where AttributedType sugar gets lost on lambdas (when the "inferred" return type gets
+// converted to a concrete one) happens here and the nolock(false) attribute is lost from h.
+
+template <class T>
+void f(T a) [[clang::nolock]] { a(); }
+
+void m()
+{
+ auto g = []() [[clang::nolock]] {
+ };
+
+ auto h = []() [[clang::nolock(false)]] {
+ };
+
+ f(g);
+ f(h);
+}
+#endif
+
+template <class _Tp, _Tp __v>
+struct integral_constant
+{
+ static constexpr const _Tp value = __v;
+ typedef _Tp value_type;
+ typedef integral_constant type;
+ constexpr operator value_type() const noexcept {return value;}
+ constexpr value_type operator ()() const noexcept {return value;}
+};
+
+template <class _Tp, _Tp __v>
+const _Tp integral_constant<_Tp, __v>::value;
+
+typedef integral_constant<bool, true> true_type;
+typedef integral_constant<bool, false> false_type;
+
+template <typename T>
+struct is_nolock;
+
+
+
+void g() [[clang::nolock]];
+void h() [[clang::nolock(false)]];
>From fc67743117c637496728099a4cb89803d2c30ff3 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 15 Mar 2024 09:01:52 -0700
Subject: [PATCH 08/71] ASTContext::mergeFunctionTypes doesn't seem to need to
do anything (?)
---
clang/lib/AST/ASTContext.cpp | 35 ++++++-----------------------------
1 file changed, 6 insertions(+), 29 deletions(-)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 68f2ac3e1329f..3e2142469babd 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10436,17 +10436,6 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
allRTypes = false;
FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
- FunctionEffectSet FromFX, ToFX;
- std::optional<FunctionEffectSet> MergedFX;
-
- if (lproto)
- ToFX = lproto->getFunctionEffects();
- if (rproto)
- FromFX = rproto->getFunctionEffects();
- if (ToFX != FromFX) {
- // We want the intersection of the effects...
- MergedFX = FunctionEffectSet::create(FromFX & ToFX);
- }
if (lproto && rproto) { // two C99 style function prototypes
assert((AllowCXX ||
@@ -10463,6 +10452,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
if (lproto->getMethodQuals() != rproto->getMethodQuals())
return {};
+ // TODO: (nolock) Does anything need to be done with FunctionEffects?
+
SmallVector<FunctionProtoType::ExtParameterInfo, 4> newParamInfos;
bool canUseLeft, canUseRight;
if (!mergeExtParameterInfo(lproto, rproto, canUseLeft, canUseRight,
@@ -10499,20 +10490,13 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
allRTypes = false;
}
- if (!MergedFX) { // effects changed so we can't return either side unaltered
- if (allLTypes)
- return lhs;
- if (allRTypes)
- return rhs;
- }
+ if (allLTypes) return lhs;
+ if (allRTypes) return rhs;
FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
EPI.ExtInfo = einfo;
EPI.ExtParameterInfos =
newParamInfos.empty() ? nullptr : newParamInfos.data();
- if (MergedFX) {
- EPI.FunctionEffects = *MergedFX;
- }
return getFunctionType(retType, types, EPI);
}
@@ -10545,18 +10529,11 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
return {};
}
- if (!MergedFX) { // effects changed so we can't return either side unaltered
- if (allLTypes)
- return lhs;
- if (allRTypes)
- return rhs;
- }
+ if (allLTypes) return lhs;
+ if (allRTypes) return rhs;
FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
EPI.ExtInfo = einfo;
- if (MergedFX) {
- EPI.FunctionEffects = *MergedFX;
- }
return getFunctionType(retType, proto->getParamTypes(), EPI);
}
>From a63812551c65e588e5a15a86576da2a9ad179e7f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 15 Mar 2024 09:07:20 -0700
Subject: [PATCH 09/71] Fix function pointer type conversion in C by making
IsFunctionConversion() not change the type for C. Add comment about #85415.
---
clang/lib/Sema/Sema.cpp | 3 --
clang/lib/Sema/SemaExpr.cpp | 2 ++
clang/lib/Sema/SemaOverload.cpp | 45 +++++++++++-----------------
clang/test/Sema/attr-nolock-sema.cpp | 4 +--
clang/test/Sema/attr-nolock-wip.cpp | 31 ++++++++++++-------
5 files changed, 42 insertions(+), 43 deletions(-)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index b120ed2224696..8e96522542427 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,8 +588,6 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- llvm::outs() << "diagnoseFunctionEffectConversion: " << SrcType << " -> "
- << DstType << "\n";
const auto SrcFX = FunctionEffectSet::get(*SrcType);
const auto DstFX = FunctionEffectSet::get(*DstType);
if (SrcFX != DstFX) {
@@ -683,7 +681,6 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseZeroToNullptrConversion(Kind, E);
if (!isCast(CCK) && !E->isNullPointerConstant(
Context, Expr::NPC_NeverValueDependent /* ???*/)) {
- llvm::outs() << "Sema::ImpCastExprToType\n";
diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 643f8e7f44488..90932a532bebf 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9986,6 +9986,8 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
return Sema::IncompatibleFunctionPointer;
return Sema::IncompatiblePointer;
}
+ // #85415: This call to IsFunctionConversion appears to have inverted
+ // arguments: ltrans -> From, rtrans -> To
if (!S.getLangOpts().CPlusPlus &&
S.IsFunctionConversion(ltrans, rtrans, ltrans))
return Sema::IncompatibleFunctionPointer;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index e34aa7e970d5d..0163969e9bf50 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,9 +1787,6 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
- llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType
- << "\n";
-
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
@@ -1828,7 +1825,6 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
if (TyClass != Type::FunctionProto && TyClass != Type::FunctionNoProto)
return false;
}
- llvm::outs() << " didn't exit early\n";
const auto *FromFn = cast<FunctionType>(CanFrom);
FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
@@ -1872,30 +1868,25 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
Changed = true;
}
-#if 1
- // NOTE (TEMP): this works for C++ to allow dropping effects.
- // For plain C, however, this creates an error when dropping effects!
-
- // Transparently add/drop effects; here we are concerned with
- // language rules/canonicalization. Adding/dropping effects is a warning.
- auto FromFX = FromFPT->getFunctionEffects();
- auto ToFX = ToFPT->getFunctionEffects();
- if (FromFX != ToFX) {
- llvm::outs() << " effects change: " << FromType << " -> " << ToType
- << "\n";
-
- // const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
- // TODO: diagnose conflicts
-
- FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
- ExtInfo.FunctionEffects = ToFX;
- QualType QT = Context.getFunctionType(FromFPT->getReturnType(),
- FromFPT->getParamTypes(), ExtInfo);
- FromFn = QT->getAs<FunctionType>();
- llvm::outs() << " produced " << QT << "\n";
- Changed = true;
+ if (getLangOpts().CPlusPlus) {
+ // TODO:
+ // For C, when called from checkPointerTypesForAssignment,
+ // we need not to change the type, or else even an innocuous cast
+ // like dropping effects will fail.
+
+ // Transparently add/drop effects; here we are concerned with
+ // language rules/canonicalization. Adding/dropping effects is a warning.
+ const auto FromFX = FromFPT->getFunctionEffects();
+ const auto ToFX = ToFPT->getFunctionEffects();
+ if (FromFX != ToFX) {
+ FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
+ ExtInfo.FunctionEffects = ToFX;
+ QualType QT = Context.getFunctionType(
+ FromFPT->getReturnType(), FromFPT->getParamTypes(), ExtInfo);
+ FromFn = QT->getAs<FunctionType>();
+ Changed = true;
+ }
}
-#endif
}
if (!Changed)
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 68b056bec1bdf..99f6242e4747c 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -1,7 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
-
-// TODO: There's a problem with diagnosing type conversions in plain C.
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
#if !__has_attribute(clang_nolock)
#error "the 'nolock' attribute is not available"
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index f73fbbec70f70..16ff464fb8aa0 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -7,25 +7,32 @@
// ============================================================================
-#if 0 // C function type problems
+#if 1 // C function type problems
void unannotated();
void nolock() [[clang::nolock]];
void noalloc() [[clang::noalloc]];
-void callthis(void (*fp)());
-
-
void type_conversions()
{
-// callthis(nolock);
-
// It's fine to remove a performance constraint.
void (*fp_plain)();
-// fp_plain = unannotated;
+ fp_plain = unannotated;
fp_plain = nolock;
-// fp_plain = noalloc;
+ fp_plain = noalloc;
+
+ // Adding/spoofing nolock is unsafe.
+ void (*fp_nolock)() [[clang::nolock]];
+ fp_nolock = nolock;
+ fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+ fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+ // Adding/spoofing noalloc is unsafe.
+ void (*fp_noalloc)() [[clang::noalloc]];
+ fp_noalloc = noalloc;
+ fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+ fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
}
#endif
@@ -53,6 +60,10 @@ void m()
}
#endif
+// ============================================================================
+
+#if 0
+// some messing around with type traits
template <class _Tp, _Tp __v>
struct integral_constant
{
@@ -72,7 +83,7 @@ typedef integral_constant<bool, false> false_type;
template <typename T>
struct is_nolock;
-
-
void g() [[clang::nolock]];
void h() [[clang::nolock(false)]];
+#endif
+
>From c2ba529ffb574659ee13e6b95ee709fd53966916 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 06:07:58 -0700
Subject: [PATCH 10/71] De-virtualize FunctionEffect; it's now a value type.
Prepare for the uniquing table to be in ASTContext.
---
clang/include/clang/AST/Type.h | 372 ++++++++++-------------
clang/lib/AST/Type.cpp | 306 +++++++++++--------
clang/lib/AST/TypePrinter.cpp | 4 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 87 +++---
clang/lib/Sema/Sema.cpp | 6 +-
clang/lib/Sema/SemaDecl.cpp | 12 +-
clang/lib/Sema/SemaDeclCXX.cpp | 6 +-
clang/lib/Sema/SemaType.cpp | 19 +-
8 files changed, 394 insertions(+), 418 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 79738729ee3f3..1d277d2ef06b1 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4185,117 +4185,198 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
}
};
-class FunctionEffect;
+// ------------------------------------------------------------------------------
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
class FunctionEffectSet;
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+ enum class Type : unsigned char {
+ None = 0,
+ NoLockTrue,
+ NoAllocTrue,
+ };
+
+ /// Flags describing behaviors of the effect.
+ // (Why not a struct with bitfields? There's one function that would like to
+ // test a caller-specified bit. There are some potential optimizations that
+ // would OR together the bits of multiple effects.)
+ using Flags = unsigned;
+ enum FlagBit : unsigned {
+ // Some effects require verification, e.g. nolock(true); others might not?
+ // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
+ FE_RequiresVerification = 0x1,
+
+ // Does this effect want to verify all function calls originating in
+ // functions having this effect? TODO: maybe always true, vestigial.
+ FE_VerifyCalls = 0x2,
+
+ // Can verification inspect callees' implementations? (e.g. nolock: yes,
+ // tcb+types: no)
+ FE_InferrableOnCallees = 0x4,
+
+ // Language constructs which effects can diagnose as disallowed.
+ FE_ExcludeThrow = 0x8,
+ FE_ExcludeCatch = 0x10,
+ FE_ExcludeObjCMessageSend = 0x20,
+ FE_ExcludeStaticLocalVars = 0x40,
+ FE_ExcludeThreadLocalVars = 0x80
+ };
+
+private:
+ // For uniqueness, currently only Type_ is significant.
+
+ Type Type_ : 2; // Expands when there are more types
+ Flags Flags_ : 8; // A constant function of Type but cached here.
+
+ // Expansion: for hypothetical TCB+types, there could be one type for TCB,
+ // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
+ // be considered for uniqueness.
+ unsigned Unused : 22;
+
+public:
+ using CalleeDeclOrType =
+ llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
+
+ FunctionEffect() : Type_(Type::None), Flags_(0), Unused(0) {}
+
+ explicit FunctionEffect(Type T);
+
+ /// The type of the effect.
+ Type 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;
+
+ /// A serializable, hashable representation.
+ uint32_t opaqueRepr() const { return unsigned(Type_) | (Flags_ << 2u); }
+
+ /// Return true if adding or removing the effect as part of a type conversion
+ /// should generate a diagnostic.
+ 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.
+ 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.
+ 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
+ /// FE_InferrableOnCallees flag set. Example: This allows nolock(false) to
+ /// prevent inference for the function.
+ bool canInferOnDecl(const Decl *Caller, FunctionEffectSet CallerFX) const;
+
+ // Called if FE_VerifyCalls flag is set; 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 diagnoseFunctionCall(bool Direct, const Decl *Caller,
+ FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
+ FunctionEffectSet CalleeFX) const;
+
+ friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ return LHS.Type_ == RHS.Type_;
+ }
+ friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ return LHS.Type_ != RHS.Type_;
+ }
+ friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ return LHS.Type_ < RHS.Type_;
+ }
+};
+
// 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> {
+class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
public:
using SmallVector::insert;
using SmallVector::SmallVector;
+ MutableFunctionEffectSet(const FunctionEffect &Effect);
+ using Base = SmallVector<FunctionEffect, 4>;
+
/// Maintains order/uniquenesss.
- void insert(const FunctionEffect *effect);
+ void insert(const FunctionEffect &Effect);
+
+ MutableFunctionEffectSet &operator|=(FunctionEffectSet RHS);
- MutableFunctionEffectSet &operator|=(FunctionEffectSet rhs);
+ operator llvm::ArrayRef<const FunctionEffect>() const {
+ return {data(), size()};
+ }
};
+/// A constant, uniqued set of FunctionEffect instances.
+// These sets will tend to be very small (0-1 elements), so represent them as
+// sorted spans, which are compatible with the STL set algorithms.
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(llvm::ArrayRef<const FunctionEffect> Array)
+ : Impl(Array) {}
- explicit FunctionEffectSet(const FunctionEffect *Single) : Impl(Single) {}
- explicit FunctionEffectSet(const UniquedAndSortedFX *Multi) : Impl(Multi) {}
+ // Points to a separately allocated array, uniqued.
+ llvm::ArrayRef<const FunctionEffect> Impl;
public:
- using Differences =
- SmallVector<std::pair<const FunctionEffect *, /*added=*/bool>>;
+ using Differences = SmallVector<std::pair<FunctionEffect, /*added=*/bool>>;
- FunctionEffectSet() : Impl(nullptr) {}
+ FunctionEffectSet() = default;
- void *getOpaqueValue() const { return Impl.getOpaqueValue(); }
+ const void *getOpaqueValue() const { return Impl.data(); }
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();
- }
+ bool empty() const { return Impl.empty(); }
+ size_t size() const { return Impl.size(); }
- using iterator = const FunctionEffect *const *;
+ using iterator = const FunctionEffect *;
- iterator begin() const {
- if (isa<const FunctionEffect *>(Impl))
- return Impl.getAddrOfPtr1();
- return cast<const UniquedAndSortedFX *>(Impl)->begin();
- }
+ iterator begin() const { return Impl.begin(); }
- iterator end() const {
- if (isa<const FunctionEffect *>(Impl))
- return begin() + (Impl.isNull() ? 0 : 1);
- return cast<const UniquedAndSortedFX *>(Impl)->end();
- }
+ iterator end() const { return Impl.end(); }
- ArrayRef<const FunctionEffect *> items() const { return {begin(), end()}; }
+ ArrayRef<const FunctionEffect> items() const { return Impl; }
- bool operator==(const FunctionEffectSet &other) const {
- return Impl == other.Impl;
+ bool operator==(const FunctionEffectSet &RHS) const {
+ return Impl.data() == RHS.Impl.data();
}
- bool operator!=(const FunctionEffectSet &other) const {
- return Impl != other.Impl;
+ bool operator!=(const FunctionEffectSet &RHS) const {
+ return Impl.data() != RHS.Impl.data();
}
- bool operator<(const FunctionEffectSet &other) const;
+ bool operator<(const FunctionEffectSet &RHS) 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);
+ /// Factory function: returns instances with uniqued implementations.
+ static FunctionEffectSet create(ASTContext &C,
+ const MutableFunctionEffectSet &FX);
- /// Union. Caller should check for incompatible effects.
- FunctionEffectSet operator|(const FunctionEffectSet &rhs) const;
/// Intersection.
- MutableFunctionEffectSet operator&(const FunctionEffectSet &rhs) const;
+ MutableFunctionEffectSet operator&(const FunctionEffectSet &RHS) const;
/// Difference.
- MutableFunctionEffectSet operator-(const FunctionEffectSet &rhs) const;
+ MutableFunctionEffectSet operator-(const FunctionEffectSet &RHS) const;
+
+ static FunctionEffectSet getUnion(ASTContext &C, const FunctionEffectSet &LHS,
+ const FunctionEffectSet &RHS);
/// Caller should short-circuit by checking for equality first.
static Differences differences(const FunctionEffectSet &Old,
@@ -7901,147 +7982,6 @@ 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 class Type : unsigned char {
- NoLockTrue,
- NoAllocTrue,
- };
-
- /// Flags describing behaviors of the effect.
- // (Why not a struct with bitfields? There's one function that would like to
- // test a caller-specified bit. There are some potential optimizations that
- // would OR together the bits of multiple effects.)
- using Flags = unsigned;
- enum FlagBit : unsigned {
- // Some effects require verification, e.g. nolock(true); others might not?
- // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
- FE_RequiresVerification = 0x1,
-
- // Does this effect want to verify all function calls originating in
- // functions having this effect? TODO: maybe always true, vestigial.
- FE_VerifyCalls = 0x2,
-
- // Can verification inspect callees' implementations? (e.g. nolock: yes,
- // tcb+types: no)
- FE_InferrableOnCallees = 0x4,
-
- // Language constructs which effects can diagnose as disallowed.
- FE_ExcludeThrow = 0x8,
- FE_ExcludeCatch = 0x10,
- FE_ExcludeObjCMessageSend = 0x20,
- FE_ExcludeStaticLocalVars = 0x40,
- FE_ExcludeThreadLocalVars = 0x80
- };
-
-private:
- const Type Type_;
- const Flags Flags_;
- const char *Name;
-
-public:
- using CalleeDeclOrType =
- llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
-
- FunctionEffect(Type T, Flags F, const char *Name)
- : Type_(T), Flags_(F), Name(Name) {}
- virtual ~FunctionEffect();
-
- /// The type of the effect.
- Type 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() == Type::NoLockTrue; }
-
-public:
- static const NoLockNoAllocEffect &nolock_instance();
- static const NoLockNoAllocEffect &noalloc_instance();
-
- NoLockNoAllocEffect(Type 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/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 0afc61ff6de48..8a9922594e9f9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,12 +4922,58 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
-FunctionEffect::~FunctionEffect() = default;
+FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
+ switch (T) {
+ case Type::NoLockTrue:
+ Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
+ FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+ FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars;
+ break;
+
+ case Type::NoAllocTrue:
+ // Same as NoLockTrue, except without FE_ExcludeStaticLocalVars
+ Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
+ FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+ FE_ExcludeThreadLocalVars;
+ break;
+ default:
+ break;
+ }
+}
+
+StringRef FunctionEffect::name() const {
+ switch (Type_) {
+ default:
+ return "";
+ case Type::NoLockTrue:
+ return "nolock";
+ case Type::NoAllocTrue:
+ return "noalloc";
+ }
+}
bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
FunctionEffectSet OldFX,
QualType NewType,
FunctionEffectSet NewFX) const {
+
+ switch (Type_) {
+ case Type::NoAllocTrue:
+ // noalloc can't be added (spoofed) during a conversion, unless we have
+ // nolock
+ if (Adding) {
+ for (const auto &Effect : OldFX) {
+ if (Effect.type() == Type::NoLockTrue)
+ return false;
+ }
+ }
+ [[fallthrough]];
+ case Type::NoLockTrue:
+ // nolock can't be added (spoofed) during a conversion
+ return Adding;
+ default:
+ break;
+ }
return false;
}
@@ -4936,6 +4982,15 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
FunctionEffectSet OldFX,
const FunctionDecl &NewFunction,
FunctionEffectSet NewFX) const {
+ switch (Type_) {
+ case Type::NoAllocTrue:
+ case Type::NoLockTrue:
+ // nolock/noalloc can't be removed in a redeclaration
+ // adding -> false, removing -> true (diagnose)
+ return !Adding;
+ default:
+ break;
+ }
return false;
}
@@ -4944,120 +4999,82 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod,
FunctionEffectSet NewFX) const {
+ switch (Type_) {
+ case Type::NoAllocTrue:
+ case Type::NoLockTrue:
+ // nolock/noalloc can't be removed from an override
+ // adding -> false, removing -> true (diagnose)
+ return !Adding;
+ default:
+ break;
+ }
return false;
}
bool FunctionEffect::canInferOnDecl(const Decl *Caller,
FunctionEffectSet CallerFX) const {
+ switch (Type_) {
+ case Type::NoAllocTrue:
+ case Type::NoLockTrue: {
+ // Does the Decl have nolock(false) / noalloc(false) ?
+ QualType QT;
+ if (isa<BlockDecl>(Caller)) {
+ const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+ QT = TSI->getType();
+ } else if (isa<ValueDecl>(Caller)) {
+ QT = cast<ValueDecl>(Caller)->getType();
+ } else {
+ return false;
+ }
+ if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
+ : attr::Kind::NoAlloc)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ default:
+ break;
+ }
return false;
}
+// TODO: Notice that we don't care about some of the parameters. Is the
+// interface overly general?
bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
FunctionEffectSet CallerFX,
CalleeDeclOrType Callee,
FunctionEffectSet CalleeFX) const {
- return false;
-}
-
-const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
- static NoLockNoAllocEffect global(Type::NoLockTrue, "nolock");
- return global;
-}
-
-const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
- static NoLockNoAllocEffect global(Type::NoAllocTrue, "noalloc");
- return global;
-}
-
-// TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(Type Ty, const char *Name)
- : FunctionEffect(Ty,
- FE_RequiresVerification | FE_VerifyCalls |
- FE_InferrableOnCallees | FE_ExcludeThrow |
- FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
- FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars,
- Name) {}
-
-NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
-
-std::string NoLockNoAllocEffect::attribute() const {
- return std::string{"__attribute__((clang_"} + name().str() + "))";
-}
-
-bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
- FunctionEffectSet OldFX,
- QualType NewType,
- FunctionEffectSet NewFX) const {
- // noalloc can't be added (spoofed) during a conversion, unless we have nolock
- if (Adding) {
- if (!isNoLock()) {
- for (const auto *Effect : OldFX) {
- if (Effect->type() == Type::NoLockTrue)
- return false;
+ switch (Type_) {
+ case Type::NoAllocTrue:
+ case Type::NoLockTrue: {
+ const Type CallerType = type();
+ for (const auto &Effect : CalleeFX) {
+ const Type ET = Effect.type();
+ // Does callee have same or stronger constraint?
+ if (ET == CallerType ||
+ (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
+ return false; // no diagnostic
}
}
- // nolock can't be added (spoofed) during a conversion.
- return true;
+ return true; // warning
+ }
+ default:
+ break;
}
return false;
}
-bool NoLockNoAllocEffect::diagnoseRedeclaration(bool Adding,
- const FunctionDecl &OldFunction,
- FunctionEffectSet OldFX,
- const FunctionDecl &NewFunction,
- FunctionEffectSet NewFX) const {
- // nolock/noalloc can't be removed in a redeclaration
- // adding -> false, removing -> true (diagnose)
- return !Adding;
-}
-
-bool NoLockNoAllocEffect::diagnoseMethodOverride(
- bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
- // nolock/noalloc can't be removed from an override
- return !Adding;
-}
-
-bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
- FunctionEffectSet CallerFX) const {
- // Does the Decl have nolock(false) / noalloc(false) ?
- QualType QT;
- if (isa<BlockDecl>(Caller)) {
- const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
- QT = TSI->getType();
- } else if (isa<ValueDecl>(Caller)) {
- QT = cast<ValueDecl>(Caller)->getType();
- } else {
- return false;
- }
- if (QT->hasAttr(isNoLock() ? attr::Kind::NoLock : attr::Kind::NoAlloc)) {
- return false;
- }
-
- return true;
-}
+// =====
-// TODO: Notice that we don't care about some of the parameters. Is the
-// interface overly general?
-bool NoLockNoAllocEffect::diagnoseFunctionCall(
- bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
- CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
- const Type CallerType = type();
- for (const auto *Effect : CalleeFX) {
- const Type ET = Effect->type();
- if (ET == CallerType ||
- (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
- return false;
- }
- }
- return true;
+MutableFunctionEffectSet::MutableFunctionEffectSet(
+ const FunctionEffect &effect) {
+ push_back(effect);
}
-// =====
-
-void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
- auto Iter = std::lower_bound(begin(), end(), Effect);
+void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
+ const auto &Iter = std::lower_bound(begin(), end(), Effect);
if (*Iter != Effect) {
insert(Iter, Effect);
}
@@ -5066,55 +5083,81 @@ void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
MutableFunctionEffectSet &
MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
- for (const auto *Effect : RHS) {
+ for (const auto &Effect : RHS) {
insert(Effect);
}
return *this;
}
-// This could be simpler if there were a simple set container that could be
-// queried by ArrayRef but which stored something else. Possibly a DenseMap with
-// void values?
-FunctionEffectSet
-FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
- if (Items.empty()) {
- return FunctionEffectSet{};
+using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
+
+llvm::hash_code hash_value(const FunctionEffect &Effect) {
+ return Effect.opaqueRepr();
+}
+
+namespace llvm {
+template <> struct DenseMapInfo<FunctionEffectSpan> {
+ static FunctionEffectSpan getEmptyKey() {
+ return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
+ }
+ static FunctionEffectSpan getTombstoneKey() {
+ return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
}
- if (Items.size() == 1) {
- return FunctionEffectSet{Items[0]};
+ static unsigned getHashValue(const FunctionEffectSpan &Val) {
+ hash_code hash1 = hash_value(Val.size());
+ // Treat the FunctionEffects as a span of integers
+ const FunctionEffect *Begin = Val.begin();
+ const FunctionEffect *End = Val.end();
+ hash_code hash2 =
+ hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
+ reinterpret_cast<const uint32_t *>(End));
+ return hash_combine(hash1, hash2);
+ }
+ static bool isEqual(const FunctionEffectSpan &LHS,
+ const FunctionEffectSpan &RHS) {
+ if (LHS.size() != RHS.size()) {
+ return false;
+ }
+ // distinguish empty from tombstone
+ if (LHS.size() == 0) {
+ return LHS.data() == RHS.data();
+ }
+ return std::equal(LHS.begin(), LHS.end(), RHS.begin());
}
+};
+} // namespace llvm
- UniquedAndSortedFX NewSet(Items); // just copies the ArrayRef
+// The ASTContext
+FunctionEffectSet
+FunctionEffectSet::create(ASTContext &C, const MutableFunctionEffectSet &FX) {
+ if (FX.empty()) {
+ return {};
+ }
- // SmallSet only has contains(), so it provides no way to obtain the uniqued
- // value.
// TODO: Put this in the ASTContext
- // TODO: Try making this a DenseSet? Requires more methods on the members.
- // static llvm::DenseSet<UniquedAndSortedFX> UniquedFXSets;
- // Punt on this until we revisit FunctionFX.
+ // TODO: And destroy the memory
+ static llvm::DenseSet<FunctionEffectSpan> UniquedFXSets;
- static std::set<UniquedAndSortedFX> UniquedFXSets;
-
- // See if we already have this set.
- const auto Iter = UniquedFXSets.find(NewSet);
+ // Do we already have the incoming set?
+ const auto Iter = UniquedFXSets.find_as(FX);
if (Iter != UniquedFXSets.end()) {
- return FunctionEffectSet{&*Iter};
+ return FunctionEffectSet(*Iter);
}
// Copy the incoming array to permanent storage.
- auto *Storage = new const FunctionEffect *[Items.size()];
- std::copy(Items.begin(), Items.end(), Storage);
+ FunctionEffect *Storage = new FunctionEffect[FX.size()];
+ std::copy(FX.begin(), FX.end(), Storage);
// Make a new wrapper and insert it into the set.
- NewSet = UniquedAndSortedFX(Storage, Items.size());
- auto [InsIter, _] = UniquedFXSets.insert(NewSet);
- return FunctionEffectSet(&*InsIter);
+ auto [InsIter, _] =
+ UniquedFXSets.insert(FunctionEffectSpan(Storage, FX.size()));
+ return FunctionEffectSet(*InsIter);
}
-FunctionEffectSet
-FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
+FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
+ const FunctionEffectSet &LHS,
+ const FunctionEffectSet &RHS) {
// Optimize for one of the two sets being empty
- const FunctionEffectSet &LHS = *this;
if (LHS.empty())
return RHS;
if (RHS.empty())
@@ -5129,7 +5172,7 @@ FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
std::back_inserter(Vec));
// The result of a set operation is an ordered/unique set.
- return FunctionEffectSet::create(Vec);
+ return FunctionEffectSet::create(C, Vec);
}
MutableFunctionEffectSet
@@ -5161,10 +5204,10 @@ FunctionEffectSet::differences(const FunctionEffectSet &Old,
const FunctionEffectSet &New) {
// TODO: Could be a one-pass algorithm.
Differences Result;
- for (const auto *Effect : (New - Old)) {
+ for (const auto &Effect : (New - Old)) {
Result.emplace_back(Effect, true);
}
- for (const auto *Effect : (Old - New)) {
+ for (const auto &Effect : (Old - New)) {
Result.emplace_back(Effect, false);
}
return Result;
@@ -5186,20 +5229,15 @@ bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
RHS.end());
}
-bool FunctionEffectSet::UniquedAndSortedFX::operator<(
- const UniquedAndSortedFX &RHS) const {
- return this < &RHS;
-}
-
void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
- OS << "FX{";
+ OS << "Effects{";
bool First = true;
- for (const auto *Effect : *this) {
+ for (const auto &Effect : *this) {
if (!First)
OS << ", ";
else
First = false;
- OS << Effect->name();
+ OS << Effect.name();
}
OS << "}";
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 252a5dfbc952b..1f539616f04c2 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -987,8 +987,8 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
T->printExceptionSpecification(OS, Policy);
if (const FunctionEffectSet FX = T->getFunctionEffects()) {
- for (const auto *Effect : FX) {
- OS << " " << Effect->attribute();
+ for (const auto &Effect : FX) {
+ OS << " __attribute__((clang_" << Effect.name() << "))";
}
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index cf9b1244e81bf..ce7b91d4a5e59 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2406,14 +2406,14 @@ enum class DiagnosticID : uint8_t {
};
struct Diagnostic {
- const FunctionEffect *Effect = nullptr;
+ FunctionEffect Effect;
const Decl *Callee = nullptr; // only valid for Calls*
SourceLocation Loc;
DiagnosticID ID = DiagnosticID::None;
Diagnostic() = default;
- Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+ Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
const Decl *Callee = nullptr)
: Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
};
@@ -2521,12 +2521,12 @@ class EffectToDiagnosticMap {
// Since we currently only have a tiny number of effects (typically no more
// than 1), use a sorted SmallVector with an inline capacity of 1. Since it
// is often empty, use a unique_ptr to the SmallVector.
- using Element = std::pair<const FunctionEffect *, Diagnostic>;
+ using Element = std::pair<FunctionEffect, Diagnostic>;
using ImplVec = llvm::SmallVector<Element, 1>;
std::unique_ptr<ImplVec> Impl;
public:
- Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
+ Diagnostic &getOrInsertDefault(FunctionEffect Key) {
if (Impl == nullptr) {
Impl = std::make_unique<llvm::SmallVector<Element>>();
auto &Item = Impl->emplace_back();
@@ -2542,7 +2542,7 @@ class EffectToDiagnosticMap {
return Iter->second;
}
- const Diagnostic *lookup(const FunctionEffect *key) {
+ const Diagnostic *lookup(FunctionEffect key) {
if (Impl == nullptr) {
return nullptr;
}
@@ -2596,20 +2596,20 @@ class PendingFunctionAnalysis {
std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(const CallableInfo &cinfo,
+ PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &cinfo,
FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet fx;
- for (const auto *effect : cinfo.Effects) {
- if (effect->flags() & FunctionEffect::FE_RequiresVerification) {
+ for (const auto &effect : cinfo.Effects) {
+ if (effect.flags() & FunctionEffect::FE_RequiresVerification) {
fx.insert(effect);
}
}
- DeclaredVerifiableEffects = FunctionEffectSet::create(fx);
+ DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, fx);
// Check for effects we are not allowed to infer
fx.clear();
- for (const auto *effect : AllInferrableEffectsToVerify) {
- if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
+ for (const auto &effect : AllInferrableEffectsToVerify) {
+ if (effect.canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
fx.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
@@ -2622,8 +2622,8 @@ class PendingFunctionAnalysis {
}
}
// fx is now the set of inferrable effects which are not prohibited
- FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) -
- DeclaredVerifiableEffects);
+ FXToInfer = FunctionEffectSet::create(
+ Ctx, FunctionEffectSet::create(Ctx, fx) - DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2655,8 +2655,7 @@ class PendingFunctionAnalysis {
return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
}
- const Diagnostic *
- diagnosticForInferrableEffect(const FunctionEffect *effect) {
+ const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
return InferrableEffectToFirstDiagnostic.lookup(effect);
}
@@ -2696,23 +2695,23 @@ class CompleteFunctionAnalysis {
EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
public:
- CompleteFunctionAnalysis(PendingFunctionAnalysis &pending,
+ CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &pending,
FunctionEffectSet funcFX,
FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet verified;
verified |= funcFX;
- for (const auto *effect : AllInferrableEffectsToVerify) {
+ for (const auto &effect : AllInferrableEffectsToVerify) {
if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
verified.insert(effect);
}
}
- VerifiedEffects = FunctionEffectSet::create(verified);
+ VerifiedEffects = FunctionEffectSet::create(Ctx, verified);
InferrableEffectToFirstDiagnostic =
std::move(pending.InferrableEffectToFirstDiagnostic);
}
- const Diagnostic *firstDiagnosticForEffect(const FunctionEffect *effect) {
+ const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &effect) {
// TODO: is this correct?
return InferrableEffectToFirstDiagnostic.lookup(effect);
}
@@ -2806,14 +2805,14 @@ class Analyzer {
// be checked, and to see which ones are inferrable.
{
MutableFunctionEffectSet inferrableEffects;
- for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
- const auto Flags = effect->flags();
+ for (const FunctionEffect &effect : Sem.AllEffectsToVerify) {
+ const auto Flags = effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees) {
inferrableEffects.insert(effect);
}
}
AllInferrableEffectsToVerify =
- FunctionEffectSet::create(inferrableEffects);
+ FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
llvm::outs() << "AllInferrableEffectsToVerify: ";
AllInferrableEffectsToVerify.dump(llvm::outs());
llvm::outs() << "\n";
@@ -2887,7 +2886,8 @@ class Analyzer {
// Build a PendingFunctionAnalysis on the stack. If it turns out to be
// complete, we'll have avoided a heap allocation; if it's incomplete, it's
// a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(CInfo, AllInferrableEffectsToVerify);
+ PendingFunctionAnalysis FAnalysis(Sem.getASTContext(), CInfo,
+ AllInferrableEffectsToVerify);
llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
FAnalysis.dump(llvm::outs());
@@ -2915,7 +2915,8 @@ class Analyzer {
emitDiagnostics(*Diags, CInfo, Sem);
}
auto *CompletePtr = new CompleteFunctionAnalysis(
- Pending, CInfo.Effects, AllInferrableEffectsToVerify);
+ Sem.getASTContext(), Pending, CInfo.Effects,
+ AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
llvm::outs() << "inserted complete " << CompletePtr << "\n";
DeclAnalysis.dump(Sem, llvm::outs());
@@ -2964,10 +2965,10 @@ class Analyzer {
llvm::outs() << "\n";
puts("");
- auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
- const auto Flags = Effect->flags();
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ const auto Flags = Effect.flags();
if (Flags & FunctionEffect::FE_VerifyCalls) {
- const bool diagnose = Effect->diagnoseFunctionCall(
+ const bool diagnose = Effect.diagnoseFunctionCall(
DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
CalleeEffects);
if (diagnose) {
@@ -2991,11 +2992,11 @@ class Analyzer {
}
};
- for (auto *Effect : PFA.DeclaredVerifiableEffects) {
+ for (const auto &Effect : PFA.DeclaredVerifiableEffects) {
check1Effect(Effect, false);
}
- for (auto *Effect : PFA.FXToInfer) {
+ for (const auto &Effect : PFA.FXToInfer) {
check1Effect(Effect, true);
}
}
@@ -3026,7 +3027,7 @@ class Analyzer {
// Top-level diagnostics are warnings.
for (const auto &Diag : Diags) {
- StringRef effectName = Diag.Effect->name();
+ StringRef effectName = Diag.Effect.name();
switch (Diag.ID) {
case DiagnosticID::None:
case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen
@@ -3230,22 +3231,22 @@ class Analyzer {
const Decl *Callee = nullptr) {
// If there are ANY declared verifiable effects holding the flag, store
// just one diagnostic.
- for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
- if (Effect->flags() & Flag) {
+ for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
+ if (Effect.flags() & Flag) {
addDiagnosticInner(/*inferring=*/false, Effect, D, Loc, Callee);
break;
}
}
// For each inferred-but-not-verifiable effect holding the flag, store a
// diagnostic, if we don't already have a diagnostic for that effect.
- for (auto *Effect : CurrentFunction.FXToInfer) {
- if (Effect->flags() & Flag) {
+ for (const auto &Effect : CurrentFunction.FXToInfer) {
+ if (Effect.flags() & Flag) {
addDiagnosticInner(/*inferring=*/true, Effect, D, Loc, Callee);
}
}
}
- void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
+ void addDiagnosticInner(bool Inferring, const FunctionEffect &Effect,
DiagnosticID D, SourceLocation Loc,
const Decl *Callee = nullptr) {
CurrentFunction.checkAddDiagnostic(Inferring,
@@ -3264,25 +3265,25 @@ class Analyzer {
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
- if (effect->flags() & FunctionEffect::FE_VerifyCalls) {
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ if (Effect.flags() & FunctionEffect::FE_VerifyCalls) {
if (FPT == nullptr ||
- effect->diagnoseFunctionCall(
+ Effect.diagnoseFunctionCall(
/*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
FPT, FPT->getFunctionEffects())) {
- addDiagnosticInner(inferring, effect,
+ addDiagnosticInner(Inferring, Effect,
DiagnosticID::CallsDisallowedExpr,
Call->getBeginLoc());
}
}
};
- for (auto *effect : CurrentFunction.DeclaredVerifiableEffects) {
- check1Effect(effect, false);
+ for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
+ check1Effect(Effect, false);
}
- for (auto *effect : CurrentFunction.FXToInfer) {
- check1Effect(effect, true);
+ for (const auto &Effect : CurrentFunction.FXToInfer) {
+ check1Effect(Effect, true);
}
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 8e96522542427..05b771939feb1 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -592,12 +592,12 @@ void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
const auto DstFX = FunctionEffectSet::get(*DstType);
if (SrcFX != DstFX) {
for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
- const FunctionEffect *Effect = Item.first;
+ const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect->diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
+ if (Effect.diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
: diag::warn_invalid_remove_func_effects)
- << Effect->name();
+ << Effect.name();
}
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d860804102dfc..1f167467cad6d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3927,17 +3927,17 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
if (OldFX != NewFX) {
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
for (const auto &Item : Diffs) {
- const FunctionEffect *Effect = Item.first;
+ const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+ if (Effect.diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
Diag(New->getLocation(),
diag::warn_mismatched_func_effect_redeclaration)
- << Effect->name();
+ << Effect.name();
Diag(Old->getLocation(), diag::note_previous_declaration);
}
}
- const auto MergedFX = OldFX | NewFX;
+ const auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
// Having diagnosed any problems, prevent further errors by applying the
// merged set of effects to both declarations.
@@ -11165,8 +11165,8 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
// Filter out declarations that the FunctionEffect analysis should skip
// and not verify.
bool FXNeedVerification = false;
- for (const auto *Effect : FX) {
- if (Effect->flags() & FunctionEffect::FE_RequiresVerification) {
+ for (const auto &Effect : FX) {
+ if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
AllEffectsToVerify.insert(Effect);
FXNeedVerification = true;
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2f9a4a14155fb..2c163ce4c14b0 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18330,11 +18330,11 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
bool AnyDiags = false;
for (const auto &Item : Diffs) {
- const FunctionEffect *Effect = Item.first;
+ const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+ if (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
- << Effect->name();
+ << Effect.name();
Diag(Old->getLocation(), diag::note_overridden_virtual_function);
AnyDiags = true;
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a7849f42d38c1..0fcafeb0172fb 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8028,23 +8028,20 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
// nolock(true) and noalloc(true) are represented as FunctionEffects, in a
// FunctionEffectSet attached to a FunctionProtoType.
- const FunctionEffect *Effect = nullptr;
- if (isNoLock) {
- Effect = &NoLockNoAllocEffect::nolock_instance();
- } else {
- Effect = &NoLockNoAllocEffect::noalloc_instance();
- }
+ const FunctionEffect NewEffect(isNoLock ? FunctionEffect::Type::NoLockTrue
+ : FunctionEffect::Type::NoAllocTrue);
- MutableFunctionEffectSet newEffectSet{Effect};
+ MutableFunctionEffectSet NewFX(NewEffect);
if (EPI.FunctionEffects) {
// Preserve any previous effects - except noalloc, when we are adding nolock
- for (const auto *effect : EPI.FunctionEffects) {
- if (!(isNoLock && effect->type() == FunctionEffect::Type::NoAllocTrue))
- newEffectSet.insert(effect);
+ for (const auto &Effect : EPI.FunctionEffects) {
+ if (!(isNoLock && Effect.type() == FunctionEffect::Type::NoAllocTrue))
+ NewFX.insert(Effect);
}
}
- EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
+ EPI.FunctionEffects =
+ FunctionEffectSet::create(state.getSema().getASTContext(), NewFX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
>From 2a5c14fe4ed70b5aea3b808fbe55ffb7d8f8f8eb Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 10:19:25 -0700
Subject: [PATCH 11/71] Remove unused parameters from FunctionEffect interface.
---
clang/include/clang/AST/Type.h | 30 +++++++++---------
clang/lib/AST/Type.cpp | 35 +++++++--------------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 39 +++++++++++++-----------
3 files changed, 48 insertions(+), 56 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1d277d2ef06b1..4da97c88a2257 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4192,11 +4192,12 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
class FunctionEffectSet;
+class TypeSourceInfo;
/// Represents an abstract function effect.
class FunctionEffect {
public:
- enum class Type : unsigned char {
+ enum class Type {
None = 0,
NoLockTrue,
NoAllocTrue,
@@ -4231,24 +4232,24 @@ class FunctionEffect {
private:
// For uniqueness, currently only Type_ is significant.
- Type Type_ : 2; // Expands when there are more types
+ LLVM_PREFERRED_TYPE(Type)
+ unsigned Type_ : 2;
Flags Flags_ : 8; // A constant function of Type but cached here.
- // Expansion: for hypothetical TCB+types, there could be one type for TCB,
+ // Expansion: for hypothetical TCB+types, there could be one Type for TCB,
// then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
// be considered for uniqueness.
- unsigned Unused : 22;
public:
using CalleeDeclOrType =
llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
- FunctionEffect() : Type_(Type::None), Flags_(0), Unused(0) {}
+ FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0) {}
explicit FunctionEffect(Type T);
/// The type of the effect.
- Type type() const { return Type_; }
+ Type type() const { return Type(Type_); }
/// Flags describing behaviors of the effect.
Flags flags() const { return Flags_; }
@@ -4257,7 +4258,7 @@ class FunctionEffect {
StringRef name() const;
/// A serializable, hashable representation.
- uint32_t opaqueRepr() const { return unsigned(Type_) | (Flags_ << 2u); }
+ uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
@@ -4279,19 +4280,18 @@ class FunctionEffect {
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
- /// FE_InferrableOnCallees flag set. Example: This allows nolock(false) to
- /// prevent inference for the function.
- bool canInferOnDecl(const Decl *Caller, FunctionEffectSet CallerFX) const;
+ /// Return true if the effect is allowed to be inferred on a Decl of the
+ /// specified type (generally a FunctionProtoType but TypeSourceInfo is
+ /// provided so any AttributedType sugar can be examined). Only used if the
+ /// effect has FE_InferrableOnCallees flag set. Example: This allows
+ /// nolock(false) to prevent inference for the function.
+ bool canInferOnFunction(const TypeSourceInfo &FType) const;
// Called if FE_VerifyCalls flag is set; 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 diagnoseFunctionCall(bool Direct, const Decl *Caller,
- FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
- FunctionEffectSet CalleeFX) const;
+ bool diagnoseFunctionCall(bool Direct, FunctionEffectSet CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.Type_ == RHS.Type_;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8a9922594e9f9..e459833bb0515 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,7 +4922,7 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
-FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
+FunctionEffect::FunctionEffect(Type T) : Type_(unsigned(T)), Flags_(0) {
switch (T) {
case Type::NoLockTrue:
Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
@@ -4942,7 +4942,7 @@ FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
}
StringRef FunctionEffect::name() const {
- switch (Type_) {
+ switch (type()) {
default:
return "";
case Type::NoLockTrue:
@@ -4957,7 +4957,7 @@ bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
QualType NewType,
FunctionEffectSet NewFX) const {
- switch (Type_) {
+ switch (type()) {
case Type::NoAllocTrue:
// noalloc can't be added (spoofed) during a conversion, unless we have
// nolock
@@ -4982,7 +4982,7 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
FunctionEffectSet OldFX,
const FunctionDecl &NewFunction,
FunctionEffectSet NewFX) const {
- switch (Type_) {
+ switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue:
// nolock/noalloc can't be removed in a redeclaration
@@ -4999,7 +4999,7 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod,
FunctionEffectSet NewFX) const {
- switch (Type_) {
+ switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue:
// nolock/noalloc can't be removed from an override
@@ -5011,21 +5011,12 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
return false;
}
-bool FunctionEffect::canInferOnDecl(const Decl *Caller,
- FunctionEffectSet CallerFX) const {
- switch (Type_) {
+bool FunctionEffect::canInferOnFunction(const TypeSourceInfo &FType) const {
+ switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue: {
- // Does the Decl have nolock(false) / noalloc(false) ?
- QualType QT;
- if (isa<BlockDecl>(Caller)) {
- const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
- QT = TSI->getType();
- } else if (isa<ValueDecl>(Caller)) {
- QT = cast<ValueDecl>(Caller)->getType();
- } else {
- return false;
- }
+ // Does the sugar have nolock(false) / noalloc(false) ?
+ QualType QT = FType.getType();
if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
: attr::Kind::NoAlloc)) {
return false;
@@ -5040,13 +5031,9 @@ bool FunctionEffect::canInferOnDecl(const Decl *Caller,
return false;
}
-// TODO: Notice that we don't care about some of the parameters. Is the
-// interface overly general?
-bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
- FunctionEffectSet CallerFX,
- CalleeDeclOrType Callee,
+bool FunctionEffect::diagnoseFunctionCall(bool Direct,
FunctionEffectSet CalleeFX) const {
- switch (Type_) {
+ switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue: {
const Type CallerType = type();
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index ce7b91d4a5e59..68d8133254fb0 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2596,21 +2596,28 @@ class PendingFunctionAnalysis {
std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &cinfo,
+ PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &CInfo,
FunctionEffectSet AllInferrableEffectsToVerify) {
- MutableFunctionEffectSet fx;
- for (const auto &effect : cinfo.Effects) {
- if (effect.flags() & FunctionEffect::FE_RequiresVerification) {
- fx.insert(effect);
+ MutableFunctionEffectSet FX;
+ for (const auto &Effect : CInfo.Effects) {
+ if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
+ FX.insert(Effect);
}
}
- DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, fx);
+ DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, FX);
// Check for effects we are not allowed to infer
- fx.clear();
+ FX.clear();
+ TypeSourceInfo *TSI = nullptr;
+ if (const auto *DD = dyn_cast<DeclaratorDecl>(CInfo.CDecl)) {
+ TSI = DD->getTypeSourceInfo();
+ } else if (const auto *BD = dyn_cast<BlockDecl>(CInfo.CDecl)) {
+ TSI = BD->getSignatureAsWritten();
+ }
+
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
- fx.insert(effect);
+ if (TSI && effect.canInferOnFunction(*TSI)) {
+ FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
// try to infer it.
@@ -2618,12 +2625,12 @@ class PendingFunctionAnalysis {
InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
diag =
Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
- cinfo.CDecl->getLocation());
+ CInfo.CDecl->getLocation());
}
}
- // fx is now the set of inferrable effects which are not prohibited
+ // FX is now the set of inferrable effects which are not prohibited
FXToInfer = FunctionEffectSet::create(
- Ctx, FunctionEffectSet::create(Ctx, fx) - DeclaredVerifiableEffects);
+ Ctx, FunctionEffectSet::create(Ctx, FX) - DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2968,9 +2975,8 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
const auto Flags = Effect.flags();
if (Flags & FunctionEffect::FE_VerifyCalls) {
- const bool diagnose = Effect.diagnoseFunctionCall(
- DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
- CalleeEffects);
+ const bool diagnose =
+ Effect.diagnoseFunctionCall(DirectCall, CalleeEffects);
if (diagnose) {
// If inference is not allowed, or the target is indirect (virtual
// method/function ptr?), generate a diagnostic now.
@@ -3269,8 +3275,7 @@ class Analyzer {
if (Effect.flags() & FunctionEffect::FE_VerifyCalls) {
if (FPT == nullptr ||
Effect.diagnoseFunctionCall(
- /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
- FPT, FPT->getFunctionEffects())) {
+ /*direct=*/false, FPT->getFunctionEffects())) {
addDiagnosticInner(Inferring, Effect,
DiagnosticID::CallsDisallowedExpr,
Call->getBeginLoc());
>From ad39599ae7670b9f696ca711a13dc3ed341b7fdc Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 15:57:48 -0700
Subject: [PATCH 12/71] Don't merge effects back to Old on redeclaration
---
clang/lib/Sema/SemaDecl.cpp | 7 +++++--
clang/test/Sema/attr-nolock-wip.cpp | 30 -----------------------------
2 files changed, 5 insertions(+), 32 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1f167467cad6d..a9d256f348470 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3951,10 +3951,13 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
FD->setType(ModQT);
};
- applyMergedFX(Old);
+ // TODO: We used to apply the merged set of effects to the old decl.
+ // Now we don't.
+
+ // applyMergedFX(Old);
applyMergedFX(New);
- OldQType = Old->getType();
+ // OldQType = Old->getType();
NewQType = New->getType();
}
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 16ff464fb8aa0..7fbe75e15e9c4 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -1,5 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
#if !__has_attribute(clang_nolock)
#error "the 'nolock' attribute is not available"
@@ -7,35 +6,6 @@
// ============================================================================
-#if 1 // C function type problems
-void unannotated();
-void nolock() [[clang::nolock]];
-void noalloc() [[clang::noalloc]];
-
-
-void type_conversions()
-{
- // It's fine to remove a performance constraint.
- void (*fp_plain)();
-
- fp_plain = unannotated;
- fp_plain = nolock;
- fp_plain = noalloc;
-
- // Adding/spoofing nolock is unsafe.
- void (*fp_nolock)() [[clang::nolock]];
- fp_nolock = nolock;
- fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
- fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-
- // Adding/spoofing noalloc is unsafe.
- void (*fp_noalloc)() [[clang::noalloc]];
- fp_noalloc = noalloc;
- fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
- fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
-}
-#endif
-
// ============================================================================
#if 0
>From 513c88ca9e2b9b87c269ffa6a3f716daeda49cf0 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 17 Mar 2024 10:46:26 -0700
Subject: [PATCH 13/71] Fix: canInferOnFunction can't always get a
TypeSourceInfo. Fix test involving redeclarations. Turn off debug logging
spew.
---
clang/include/clang/AST/Type.h | 10 ++--
clang/lib/AST/Type.cpp | 3 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 67 +++++++++++++++---------
clang/test/Sema/attr-nolock-sema.cpp | 17 ++++--
4 files changed, 61 insertions(+), 36 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 4da97c88a2257..2b559b1cc4372 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4282,10 +4282,12 @@ class FunctionEffect {
/// Return true if the effect is allowed to be inferred on a Decl of the
/// specified type (generally a FunctionProtoType but TypeSourceInfo is
- /// provided so any AttributedType sugar can be examined). Only used if the
- /// effect has FE_InferrableOnCallees flag set. Example: This allows
- /// nolock(false) to prevent inference for the function.
- bool canInferOnFunction(const TypeSourceInfo &FType) const;
+ /// provided so any AttributedType sugar can be examined). TSI can be null
+ /// on an implicit function like a default constructor.
+ ///
+ /// This is only used if the effect has FE_InferrableOnCallees flag set.
+ /// Example: This allows nolock(false) to prevent inference for the function.
+ bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
// Called if FE_VerifyCalls flag is set; return false for success. When true
// is returned for a direct call, then the FE_InferrableOnCallees flag may
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index e459833bb0515..98983cc192d75 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5011,12 +5011,11 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
return false;
}
-bool FunctionEffect::canInferOnFunction(const TypeSourceInfo &FType) const {
+bool FunctionEffect::canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const {
switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue: {
// Does the sugar have nolock(false) / noalloc(false) ?
- QualType QT = FType.getType();
if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
: attr::Kind::NoAlloc)) {
return false;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 68d8133254fb0..74b6c7e5c9978 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2451,6 +2451,7 @@ struct CallableInfo {
SpecialFuncType FuncType = SpecialFuncType::None;
FunctionEffectSet Effects;
CallType CType = CallType::Unknown;
+ QualType QT;
CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
@@ -2468,13 +2469,16 @@ struct CallableInfo {
CType = CallType::Virtual;
}
}
+ QT = FD->getType();
Effects = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
+ QT = BD->getSignatureAsWritten()->getType();
Effects = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at the type.
- Effects = FunctionEffectSet::get(*VD->getType());
+ QT = VD->getType();
+ Effects = FunctionEffectSet::get(*QT);
}
}
@@ -2596,8 +2600,9 @@ class PendingFunctionAnalysis {
std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &CInfo,
+ PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
FunctionEffectSet AllInferrableEffectsToVerify) {
+ ASTContext &Ctx = Sem.getASTContext();
MutableFunctionEffectSet FX;
for (const auto &Effect : CInfo.Effects) {
if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
@@ -2614,9 +2619,11 @@ class PendingFunctionAnalysis {
} else if (const auto *BD = dyn_cast<BlockDecl>(CInfo.CDecl)) {
TSI = BD->getSignatureAsWritten();
}
+ // N.B. TSI can be null for things like an implicit constructor (despite
+ // having a valid QualifiedType).
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (TSI && effect.canInferOnFunction(*TSI)) {
+ if (effect.canInferOnFunction(CInfo.QT, TSI)) {
FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
@@ -2745,7 +2752,7 @@ class CompleteFunctionAnalysis {
// ==========
class Analyzer {
- constexpr static int kDebugLogLevel = 3;
+ constexpr static int DebugLogLevel = 0;
// --
Sema &Sem;
@@ -2820,9 +2827,11 @@ class Analyzer {
}
AllInferrableEffectsToVerify =
FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
- llvm::outs() << "AllInferrableEffectsToVerify: ";
- AllInferrableEffectsToVerify.dump(llvm::outs());
- llvm::outs() << "\n";
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::outs());
+ llvm::outs() << "\n";
+ }
}
SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
@@ -2893,11 +2902,13 @@ class Analyzer {
// Build a PendingFunctionAnalysis on the stack. If it turns out to be
// complete, we'll have avoided a heap allocation; if it's incomplete, it's
// a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(Sem.getASTContext(), CInfo,
+ PendingFunctionAnalysis FAnalysis(Sem, CInfo,
AllInferrableEffectsToVerify);
- llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(llvm::outs());
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
+ FAnalysis.dump(llvm::outs());
+ }
FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
@@ -2909,8 +2920,10 @@ class Analyzer {
// Copy the pending analysis to the heap and save it in the map.
auto *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
DeclAnalysis[D] = PendingPtr;
- llvm::outs() << "inserted pending " << PendingPtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "inserted pending " << PendingPtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ }
return PendingPtr;
}
@@ -2925,8 +2938,10 @@ class Analyzer {
Sem.getASTContext(), Pending, CInfo.Effects,
AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
- llvm::outs() << "inserted complete " << CompletePtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ }
}
// Called after all direct calls requiring inference have been found -- or
@@ -2941,7 +2956,6 @@ class Analyzer {
}
completeAnalysis(Caller, *Pending);
delete Pending;
- llvm::outs() << "destroyed pending " << Pending << "\n";
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -2965,12 +2979,13 @@ class Analyzer {
if (!Callee.isVerifiable()) {
IsInferencePossible = false;
}
- llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
- CalleeEffects.dump(llvm::outs());
- llvm::outs() << "\n";
- puts("");
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+ << Callee.name(Sem)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::outs());
+ llvm::outs() << "\n";
+ }
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
const auto Flags = Effect.flags();
@@ -3359,7 +3374,7 @@ class Analyzer {
}
bool VisitCallExpr(CallExpr *Call) {
- if constexpr (kDebugLogLevel > 2) {
+ if constexpr (DebugLogLevel > 2) {
llvm::errs() << "VisitCallExpr : "
<< Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
<< "\n";
@@ -3393,7 +3408,7 @@ class Analyzer {
}
bool VisitVarDecl(VarDecl *Var) {
- if constexpr (kDebugLogLevel > 2) {
+ if constexpr (DebugLogLevel > 2) {
llvm::errs() << "VisitVarDecl : "
<< Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
<< "\n";
@@ -3452,7 +3467,7 @@ class Analyzer {
}
bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
- if constexpr (kDebugLogLevel > 2) {
+ if constexpr (DebugLogLevel > 2) {
llvm::errs() << "VisitCXXConstructExpr : "
<< Construct->getBeginLoc().printToString(
Outer.Sem.SourceMgr)
@@ -3591,8 +3606,8 @@ class Analyzer {
CallableFinderCallback::get(Sem, TU);
- /*if (decls.size() != Sem.DeclsWithUnverifiedEffects.size())*/ {
- llvm::errs() << "\nFXAnalysis: Sem gathered " << decls.size()
+ if constexpr (DebugLogLevel > 0) {
+ llvm::errs() << "\nFXAnalysis: Sema gathered " << decls.size()
<< " Decls; second AST pass found "
<< Sem.DeclsWithUnverifiedEffects.size() << "\n";
}
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 99f6242e4747c..b0b720792d36a 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -75,13 +75,22 @@ struct Derived : public Base {
#endif // __cplusplus
// --- REDECLARATIONS ---
+// TODO: These are now errors in C++, but not in C
+#ifdef __cplusplus
+int f2(int); // expected-note {{previous declaration is here}}
+int f2(int) [[clang::nolock]]; // expected-error {{conflicting types for 'f2'}}
+#endif // __cplusplus
+
+
+#if 0
int f2();
// redeclaration with a stronger constraint is OK.
-int f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
-int f2() { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+int f2() [[clang::nolock]]; // e xpected-note {{previous declaration is here}}
+int f2() { return 42; } // e xpected-warning {{attribute 'nolock' on function does not match previous declaration}}
int f3();
// redeclaration with a stronger constraint is OK.
-int f3() [[clang::noalloc]]; // expected-note {{previous declaration is here}}
-int f3() { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+int f3() [[clang::noalloc]]; // e xpected-note {{previous declaration is here}}
+int f3() { return 42; } // e xpected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+#endif
>From 13254292d0e3e97d4ee16989d50fa0dacbcb8800 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 08:45:14 -0700
Subject: [PATCH 14/71] Cleanup of redeclarations (Sema::MergeFunctionDecl) so
that the type checks never fail. FunctionDecl::getFunctionEffects() now
returns the union of effects present across all redeclarations.
---
clang/include/clang/AST/Decl.h | 7 +---
clang/include/clang/AST/Type.h | 2 +-
clang/lib/AST/Decl.cpp | 12 ++++++
clang/lib/AST/Type.cpp | 3 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 14 ++++---
clang/lib/Sema/SemaDecl.cpp | 42 ++++++++++-----------
clang/test/Sema/attr-nolock-constraints.cpp | 7 ++++
clang/test/Sema/attr-nolock-sema.cpp | 26 +++++++++++--
clang/test/Sema/attr-nolock-wip.cpp | 4 ++
9 files changed, 78 insertions(+), 39 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 0460f30ce8a8b..acff3dee7051d 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,12 +3008,7 @@ 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 {};
- }
+ FunctionEffectSet getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 2b559b1cc4372..298029cef7195 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4285,7 +4285,7 @@ class FunctionEffect {
/// provided so any AttributedType sugar can be examined). TSI can be null
/// on an implicit function like a default constructor.
///
- /// This is only used if the effect has FE_InferrableOnCallees flag set.
+ /// This is only used if the effect has FE_InferrableOnCallees flag set.
/// Example: This allows nolock(false) to prevent inference for the function.
bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 502c978d5b78e..84c950bdafc1c 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,6 +4502,18 @@ unsigned FunctionDecl::getODRHash() {
return ODRHash;
}
+// Effects may differ between redeclarations, so collect all effects from
+// all redeclarations.
+FunctionEffectSet FunctionDecl::getFunctionEffects() const {
+ MutableFunctionEffectSet FX;
+ for (FunctionDecl *FD : redecls()) {
+ if (const auto *FPT = FD->getType()->getAs<FunctionProtoType>()) {
+ FX |= FPT->getFunctionEffects();
+ }
+ }
+ return FunctionEffectSet::create(getASTContext(), FX);
+}
+
//===----------------------------------------------------------------------===//
// FieldDecl Implementation
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 98983cc192d75..bea2272c0cb5f 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5011,7 +5011,8 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
return false;
}
-bool FunctionEffect::canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const {
+bool FunctionEffect::canInferOnFunction(QualType QT,
+ const TypeSourceInfo *FType) const {
switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue: {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 74b6c7e5c9978..318e87eb3c57b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2458,10 +2458,13 @@ struct CallableInfo {
// llvm::errs() << "CallableInfo " << name() << "\n";
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
- assert(FD->getCanonicalDecl() == FD);
// Use the function's definition, if any.
if (auto *Def = FD->getDefinition()) {
CDecl = FD = Def;
+ // is the definition always canonical?
+ assert(FD->getCanonicalDecl() == FD);
+ } else {
+ FD = FD->getCanonicalDecl();
}
CType = CallType::Function;
if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
@@ -2902,8 +2905,7 @@ class Analyzer {
// Build a PendingFunctionAnalysis on the stack. If it turns out to be
// complete, we'll have avoided a heap allocation; if it's incomplete, it's
// a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(Sem, CInfo,
- AllInferrableEffectsToVerify);
+ PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
@@ -2981,8 +2983,8 @@ class Analyzer {
}
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ << Callee.name(Sem)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
CalleeEffects.dump(llvm::outs());
llvm::outs() << "\n";
}
@@ -3161,7 +3163,7 @@ class Analyzer {
case DiagnosticID::HasStaticLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
<< effectName << CalleeName;
- UNTESTED
+ TESTED
break;
case DiagnosticID::AccessesThreadLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a9d256f348470..5dc92ea98554a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3924,6 +3924,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
const auto OldFX = Old->getFunctionEffects();
const auto NewFX = New->getFunctionEffects();
+ QualType OldQTypeForComparison = OldQType;
if (OldFX != NewFX) {
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
for (const auto &Item : Diffs) {
@@ -3936,29 +3937,29 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
Diag(Old->getLocation(), diag::note_previous_declaration);
}
}
+ // Following a warning, we could skip merging effects from the previous
+ // declaration, but that would trigger an additional "conflicting types"
+ // error.
+ if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
+ auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
- const auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
-
- // Having diagnosed any problems, prevent further errors by applying the
- // merged set of effects to both declarations.
- auto applyMergedFX = [&](FunctionDecl *FD) {
- const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
- FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+ FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
EPI.FunctionEffects = MergedFX;
- QualType ModQT = Context.getFunctionType(FD->getReturnType(),
- FPT->getParamTypes(), EPI);
-
- FD->setType(ModQT);
- };
+ QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(),
+ NewFPT->getParamTypes(), EPI);
- // TODO: We used to apply the merged set of effects to the old decl.
- // Now we don't.
+ New->setType(ModQT);
+ NewQType = New->getType();
- // applyMergedFX(Old);
- applyMergedFX(New);
-
- // OldQType = Old->getType();
- NewQType = New->getType();
+ // Revise OldQTForComparison to include the merged effects,
+ // so as not to fail due to differences later.
+ if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) {
+ EPI = OldFPT->getExtProtoInfo();
+ EPI.FunctionEffects = MergedFX;
+ OldQTypeForComparison = Context.getFunctionType(
+ OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
+ }
+ }
}
if (getLangOpts().CPlusPlus) {
@@ -4125,9 +4126,8 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// We also want to respect all the extended bits except noreturn.
// noreturn should now match unless the old type info didn't have it.
- QualType OldQTypeForComparison = OldQType;
if (!OldTypeInfo.getNoReturn() && NewTypeInfo.getNoReturn()) {
- auto *OldType = OldQType->castAs<FunctionProtoType>();
+ auto *OldType = OldQTypeForComparison->castAs<FunctionProtoType>();
const FunctionType *OldTypeForComparison
= Context.adjustFunctionType(OldType, OldTypeInfo.withNoReturn(true));
OldQTypeForComparison = QualType(OldTypeForComparison, 0);
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
index 801f971b41086..5d1efe931a1f9 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -139,3 +139,10 @@ void nl11() [[clang::nolock]]
{
nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
}
+
+// Verify that when attached to a redeclaration, the attribute successfully attaches.
+void nl12() {
+ static int x; // expected-warning {{'nolock' function 'nl12' must not have static locals}}
+}
+void nl12() [[clang::nolock]];
+void nl13() [[clang::nolock]] { nl12(); }
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index b0b720792d36a..28542be0a2ecd 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -75,13 +75,31 @@ struct Derived : public Base {
#endif // __cplusplus
// --- REDECLARATIONS ---
-// TODO: These are now errors in C++, but not in C
#ifdef __cplusplus
-int f2(int); // expected-note {{previous declaration is here}}
-int f2(int) [[clang::nolock]]; // expected-error {{conflicting types for 'f2'}}
-#endif // __cplusplus
+// In C++, the third declaration gets seen as a redeclaration of the second.
+void f2();
+void f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
+void f2(); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+#else
+// In C, the third declaration is redeclaration of the first (?).
+void f2();
+void f2() [[clang::nolock]];
+void f2();
+#endif
+// Note: we verify that the attribute is actually seen during the constraints tests.
+
+
+// Ensure that the redeclaration's attribute is seen and diagnosed correctly.
+// void f2() {
+// static int x;
+// }
+// void f2() [[clang::nolock]];
+//
+// void f3() [[clang::nolock]] {
+// f2();
+// }
#if 0
int f2();
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 7fbe75e15e9c4..ab8cf57d69ed0 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -6,6 +6,10 @@
// ============================================================================
+void f2(int);
+void f2(int) [[clang::nolock]]; // expected-note {{previous declaration is here}}
+void f2(int); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
// ============================================================================
#if 0
>From 5ac74293f4d7e610b60ac8f3bf45438cab840acf Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 13:36:05 -0700
Subject: [PATCH 15/71] FunctionEffectSet::create() is now a method of
ASTContext; the global uniquing set is now a member of ASTContext.
---
clang/include/clang/AST/ASTContext.h | 18 +++++++
clang/include/clang/AST/Type.h | 6 +--
clang/lib/AST/ASTContext.cpp | 68 ++++++++++++++++++++++++
clang/lib/AST/Decl.cpp | 2 +-
clang/lib/AST/Type.cpp | 67 +----------------------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 10 ++--
clang/lib/Sema/SemaType.cpp | 2 +-
7 files changed, 96 insertions(+), 77 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ff6b64c7f72d5..8e57d40922440 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -69,6 +69,8 @@ class DiagnosticsEngine;
class DynTypedNodeList;
class Expr;
enum class FloatModeKind;
+class FunctionEffect;
+class FunctionEffectSet;
class GlobalDecl;
class IdentifierTable;
class LangOptions;
@@ -459,6 +461,16 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// This is the top-level (C++20) Named module we are building.
Module *CurrentCXXNamedModule = nullptr;
+ class FunctionEffectSetUniquing {
+ llvm::DenseSet<llvm::ArrayRef<const FunctionEffect>> Set;
+
+ public:
+ FunctionEffectSet getUniqued(llvm::ArrayRef<const FunctionEffect> FX);
+
+ ~FunctionEffectSetUniquing();
+ };
+ FunctionEffectSetUniquing UniquedFunctionEffectSet;
+
static constexpr unsigned ConstantArrayTypesLog2InitSize = 8;
static constexpr unsigned GeneralTypesLog2InitSize = 9;
static constexpr unsigned FunctionProtoTypesLog2InitSize = 12;
@@ -1065,6 +1077,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Get module under construction, nullptr if this is not a C++20 module.
Module *getCurrentNamedModule() const { return CurrentCXXNamedModule; }
+ /// Get or create a uniqued, immutable FunctionEffectSet.
+ FunctionEffectSet
+ getUniquedFunctionEffectSet(llvm::ArrayRef<const FunctionEffect> FX) {
+ return UniquedFunctionEffectSet.getUniqued(FX);
+ }
+
TranslationUnitDecl *getTranslationUnitDecl() const {
return TUDecl->getMostRecentDecl();
}
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 298029cef7195..508736ebe4ea0 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4333,6 +4333,8 @@ class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
// sorted spans, which are compatible with the STL set algorithms.
class FunctionEffectSet {
private:
+ friend class ASTContext; // so it can call the private constructor
+
explicit FunctionEffectSet(llvm::ArrayRef<const FunctionEffect> Array)
: Impl(Array) {}
@@ -4368,10 +4370,6 @@ class FunctionEffectSet {
void dump(llvm::raw_ostream &OS) const;
- /// Factory function: returns instances with uniqued implementations.
- static FunctionEffectSet create(ASTContext &C,
- const MutableFunctionEffectSet &FX);
-
/// Intersection.
MutableFunctionEffectSet operator&(const FunctionEffectSet &RHS) const;
/// Difference.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 3e2142469babd..6dd511be275c3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13702,3 +13702,71 @@ StringRef ASTContext::getCUIDHash() const {
CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true);
return CUIDHash;
}
+
+using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
+
+llvm::hash_code hash_value(const FunctionEffect &Effect) {
+ return Effect.opaqueRepr();
+}
+
+namespace llvm {
+template <> struct DenseMapInfo<FunctionEffectSpan> {
+ static FunctionEffectSpan getEmptyKey() {
+ return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
+ }
+ static FunctionEffectSpan getTombstoneKey() {
+ return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
+ }
+ static unsigned getHashValue(const FunctionEffectSpan &Val) {
+ hash_code hash1 = hash_value(Val.size());
+ // Treat the FunctionEffects as a span of integers
+ const FunctionEffect *Begin = Val.begin();
+ const FunctionEffect *End = Val.end();
+ hash_code hash2 =
+ hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
+ reinterpret_cast<const uint32_t *>(End));
+ return hash_combine(hash1, hash2);
+ }
+ static bool isEqual(const FunctionEffectSpan &LHS,
+ const FunctionEffectSpan &RHS) {
+ if (LHS.size() != RHS.size()) {
+ return false;
+ }
+ // distinguish empty from tombstone
+ if (LHS.size() == 0) {
+ return LHS.data() == RHS.data();
+ }
+ return std::equal(LHS.begin(), LHS.end(), RHS.begin());
+ }
+};
+} // namespace llvm
+
+FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
+ llvm::ArrayRef<const FunctionEffect> FX) {
+ if (FX.empty()) {
+ return {};
+ }
+
+ // Do we already have the incoming set?
+ const auto Iter = Set.find_as(FX);
+ if (Iter != Set.end()) {
+ return FunctionEffectSet(*Iter);
+ }
+
+ // Copy the incoming array to permanent storage.
+ FunctionEffect *Storage = new FunctionEffect[FX.size()];
+ std::copy(FX.begin(), FX.end(), Storage);
+
+ // Make a new wrapper and insert it into the set.
+ llvm::ArrayRef<const FunctionEffect> Arr(Storage, FX.size());
+ auto [InsIter, _] = Set.insert(Arr);
+ return FunctionEffectSet(*InsIter);
+}
+
+ASTContext::FunctionEffectSetUniquing::~FunctionEffectSetUniquing() {
+ for (const auto &ArrRef : Set) {
+ const FunctionEffect *ptrToFX = ArrRef.data();
+ delete[] ptrToFX;
+ }
+ Set.clear();
+}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 84c950bdafc1c..981b61931d253 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4511,7 +4511,7 @@ FunctionEffectSet FunctionDecl::getFunctionEffects() const {
FX |= FPT->getFunctionEffects();
}
}
- return FunctionEffectSet::create(getASTContext(), FX);
+ return getASTContext().getUniquedFunctionEffectSet(FX);
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index bea2272c0cb5f..06f9c0190bad6 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5076,71 +5076,6 @@ MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
return *this;
}
-using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
-
-llvm::hash_code hash_value(const FunctionEffect &Effect) {
- return Effect.opaqueRepr();
-}
-
-namespace llvm {
-template <> struct DenseMapInfo<FunctionEffectSpan> {
- static FunctionEffectSpan getEmptyKey() {
- return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
- }
- static FunctionEffectSpan getTombstoneKey() {
- return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
- }
- static unsigned getHashValue(const FunctionEffectSpan &Val) {
- hash_code hash1 = hash_value(Val.size());
- // Treat the FunctionEffects as a span of integers
- const FunctionEffect *Begin = Val.begin();
- const FunctionEffect *End = Val.end();
- hash_code hash2 =
- hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
- reinterpret_cast<const uint32_t *>(End));
- return hash_combine(hash1, hash2);
- }
- static bool isEqual(const FunctionEffectSpan &LHS,
- const FunctionEffectSpan &RHS) {
- if (LHS.size() != RHS.size()) {
- return false;
- }
- // distinguish empty from tombstone
- if (LHS.size() == 0) {
- return LHS.data() == RHS.data();
- }
- return std::equal(LHS.begin(), LHS.end(), RHS.begin());
- }
-};
-} // namespace llvm
-
-// The ASTContext
-FunctionEffectSet
-FunctionEffectSet::create(ASTContext &C, const MutableFunctionEffectSet &FX) {
- if (FX.empty()) {
- return {};
- }
-
- // TODO: Put this in the ASTContext
- // TODO: And destroy the memory
- static llvm::DenseSet<FunctionEffectSpan> UniquedFXSets;
-
- // Do we already have the incoming set?
- const auto Iter = UniquedFXSets.find_as(FX);
- if (Iter != UniquedFXSets.end()) {
- return FunctionEffectSet(*Iter);
- }
-
- // Copy the incoming array to permanent storage.
- FunctionEffect *Storage = new FunctionEffect[FX.size()];
- std::copy(FX.begin(), FX.end(), Storage);
-
- // Make a new wrapper and insert it into the set.
- auto [InsIter, _] =
- UniquedFXSets.insert(FunctionEffectSpan(Storage, FX.size()));
- return FunctionEffectSet(*InsIter);
-}
-
FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
const FunctionEffectSet &LHS,
const FunctionEffectSet &RHS) {
@@ -5159,7 +5094,7 @@ FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
std::back_inserter(Vec));
// The result of a set operation is an ordered/unique set.
- return FunctionEffectSet::create(C, Vec);
+ return C.getUniquedFunctionEffectSet(Vec);
}
MutableFunctionEffectSet
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 318e87eb3c57b..47202980232bc 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2612,7 +2612,7 @@ class PendingFunctionAnalysis {
FX.insert(Effect);
}
}
- DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, FX);
+ DeclaredVerifiableEffects = Ctx.getUniquedFunctionEffectSet(FX);
// Check for effects we are not allowed to infer
FX.clear();
@@ -2639,8 +2639,8 @@ class PendingFunctionAnalysis {
}
}
// FX is now the set of inferrable effects which are not prohibited
- FXToInfer = FunctionEffectSet::create(
- Ctx, FunctionEffectSet::create(Ctx, FX) - DeclaredVerifiableEffects);
+ FXToInfer = Ctx.getUniquedFunctionEffectSet(
+ Ctx.getUniquedFunctionEffectSet(FX) - DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2722,7 +2722,7 @@ class CompleteFunctionAnalysis {
verified.insert(effect);
}
}
- VerifiedEffects = FunctionEffectSet::create(Ctx, verified);
+ VerifiedEffects = Ctx.getUniquedFunctionEffectSet(verified);
InferrableEffectToFirstDiagnostic =
std::move(pending.InferrableEffectToFirstDiagnostic);
@@ -2829,7 +2829,7 @@ class Analyzer {
}
}
AllInferrableEffectsToVerify =
- FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
+ Sem.getASTContext().getUniquedFunctionEffectSet(inferrableEffects);
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "AllInferrableEffectsToVerify: ";
AllInferrableEffectsToVerify.dump(llvm::outs());
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 0fcafeb0172fb..174ce62b4f5d9 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8041,7 +8041,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
}
EPI.FunctionEffects =
- FunctionEffectSet::create(state.getSema().getASTContext(), NewFX);
+ state.getSema().getASTContext().getUniquedFunctionEffectSet(NewFX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
>From 81b86dffabad166f37e93e8bec833b103b762c84 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 14:08:05 -0700
Subject: [PATCH 16/71] - Serialize FunctionEffectSet as part of a
FunctionProtoType. - Rename Sema's DeclsWithUnverifiedEffects ->
DeclsWithEffectsToVerify. Read and write it in ASTReader/ASTWriter. How to
test this? - AttrDocs.td - remove trailing whitespace. - Fix hash_value for
FunctionEffect. - FunctionDecl::getFunctionEffects() can use
getMostRecentDecl() instead of doing a set operation.
---
clang/include/clang/AST/ASTContext.h | 4 ++++
clang/include/clang/AST/Type.h | 5 ++++
clang/include/clang/AST/TypeProperties.td | 9 ++++----
clang/include/clang/Basic/AttrDocs.td | 2 +-
clang/include/clang/Sema/Sema.h | 6 ++---
.../include/clang/Serialization/ASTBitCodes.h | 4 ++++
clang/include/clang/Serialization/ASTReader.h | 3 +++
clang/include/clang/Serialization/ASTWriter.h | 1 +
clang/lib/AST/ASTContext.cpp | 9 +++++++-
clang/lib/AST/Decl.cpp | 13 ++++-------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 14 +++++------
clang/lib/Sema/SemaDecl.cpp | 2 +-
clang/lib/Serialization/ASTReader.cpp | 23 +++++++++++++++++++
clang/lib/Serialization/ASTWriter.cpp | 12 ++++++++++
14 files changed, 81 insertions(+), 26 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 8e57d40922440..95bd2a901e9be 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1083,6 +1083,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
return UniquedFunctionEffectSet.getUniqued(FX);
}
+ /// Get or create a uniqued, immutable FunctionEffectSet from a serialized
+ /// span of uint32_t's.
+ FunctionEffectSet getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX);
+
TranslationUnitDecl *getTranslationUnitDecl() const {
return TUDecl->getMostRecentDecl();
}
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 508736ebe4ea0..ca4660edb2f56 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4347,6 +4347,11 @@ class FunctionEffectSet {
FunctionEffectSet() = default;
const void *getOpaqueValue() const { return Impl.data(); }
+ llvm::ArrayRef<uint32_t> serializable() const {
+ static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
+ const uint32_t *ptr = reinterpret_cast<const uint32_t *>(Impl.data());
+ return {const_cast<uint32_t *>(ptr), Impl.size()};
+ }
explicit operator bool() const { return !empty(); }
bool empty() const { return Impl.empty(); }
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 1d5d8f696977e..3121598761e44 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -326,10 +326,9 @@ 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 : Property<"functionEffects", Array<UInt32>> {
+ let Read = [{ node->getFunctionEffects().serializable() }];
+ }
def : Creator<[{
auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
@@ -346,7 +345,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- //epi.FunctionEffects = functionEffects;
+ epi.FunctionEffects = ctx.getUniquedFunctionEffectSet(functionEffects);
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index bb897c708ebbe..71a35f63ef106 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7977,7 +7977,7 @@ requirement:
def NoLockNoAllocDocs : Documentation {
let Category = DocCatType;
let Content = [{
-The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks,
+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.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 2772f882a5c09..b341e2361a555 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -723,8 +723,8 @@ class Sema final {
/// All functions/lambdas/blocks which have bodies and which have a non-empty
/// FunctionEffectSet to be verified.
- SmallVector<const Decl *> DeclsWithUnverifiedEffects;
- /// The union of all effects present on DeclsWithUnverifiedEffects.
+ SmallVector<const Decl *> DeclsWithEffectsToVerify;
+ /// The union of all effects present on DeclsWithEffectsToVerify.
MutableFunctionEffectSet AllEffectsToVerify;
/// Determine if VD, which must be a variable or function, is an external
@@ -3142,7 +3142,7 @@ class Sema final {
QualType T, TypeSourceInfo *TSInfo,
StorageClass SC);
- /// Potentially add a FunctionDecl or BlockDecl to DeclsWithUnverifiedEffects.
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);
// Contexts where using non-trivial C union types can be disallowed. This is
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index f31efa5117f0d..a2d620280e14a 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -698,6 +698,10 @@ enum ASTRecordTypes {
/// Record code for an unterminated \#pragma clang assume_nonnull begin
/// recorded in a preamble.
PP_ASSUME_NONNULL_LOC = 67,
+
+ /// Record code for Sema's vector of functions/blocks with effects to
+ /// be verified.
+ DECLS_WITH_EFFECTS_TO_VERIFY = 68
};
/// 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 370d8037a4da1..c34a7482b9a68 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -942,6 +942,9 @@ class ASTReader
/// Sema tracks these to emit deferred diags.
llvm::SmallSetVector<serialization::DeclID, 4> DeclsToCheckForDeferredDiags;
+ /// The IDs of all decls with function effects to be checked.
+ SmallVector<serialization::DeclID> DeclsWithEffectsToVerify;
+
private:
struct ImportedSubmodule {
serialization::SubmoduleID ID;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index e5db486a71a49..56e309e557d1a 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -554,6 +554,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/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6dd511be275c3..cf6a1882aea5a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13706,7 +13706,7 @@ StringRef ASTContext::getCUIDHash() const {
using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
llvm::hash_code hash_value(const FunctionEffect &Effect) {
- return Effect.opaqueRepr();
+ return llvm::hash_value(Effect.opaqueRepr());
}
namespace llvm {
@@ -13741,6 +13741,13 @@ template <> struct DenseMapInfo<FunctionEffectSpan> {
};
} // namespace llvm
+FunctionEffectSet
+ASTContext::getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX) {
+ static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
+ const auto *ptr = reinterpret_cast<const FunctionEffect *>(FX.data());
+ return UniquedFunctionEffectSet.getUniqued({ptr, FX.size()});
+}
+
FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
llvm::ArrayRef<const FunctionEffect> FX) {
if (FX.empty()) {
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 981b61931d253..827d57e07d603 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,16 +4502,13 @@ unsigned FunctionDecl::getODRHash() {
return ODRHash;
}
-// Effects may differ between redeclarations, so collect all effects from
-// all redeclarations.
+// Effects may differ between declarations, but they should be propagated from old
+// to new on any redeclaration, so it suffices to look at getMostRecentDecl().
FunctionEffectSet FunctionDecl::getFunctionEffects() const {
- MutableFunctionEffectSet FX;
- for (FunctionDecl *FD : redecls()) {
- if (const auto *FPT = FD->getType()->getAs<FunctionProtoType>()) {
- FX |= FPT->getFunctionEffects();
- }
+ if (const auto *FPT = getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
+ return FPT->getFunctionEffects();
}
- return getASTContext().getUniquedFunctionEffectSet(FX);
+ return {};
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 47202980232bc..5903b792bfe1e 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2761,7 +2761,7 @@ class Analyzer {
Sema &Sem;
// used from Sema:
- // SmallVector<const Decl *> DeclsWithUnverifiedEffects
+ // SmallVector<const Decl *> DeclsWithEffectsToVerify
// Subset of Sema.AllEffectsToVerify
FunctionEffectSet AllInferrableEffectsToVerify;
@@ -2837,9 +2837,9 @@ class Analyzer {
}
}
- SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
+ SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithEffectsToVerify;
- // It's helpful to use DeclsWithUnverifiedEffects as a stack for a
+ // It's helpful to use DeclsWithEffectsToVerify as a stack for a
// depth-first traversal rather than have a secondary container. But first,
// reverse it, so Decls are verified in the order they are declared.
std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -3545,7 +3545,7 @@ class Analyzer {
};
#if FX_ANALYZER_VERIFY_DECL_LIST
- // Sema has accumulated DeclsWithUnverifiedEffects. As a debug check, do our
+ // Sema has accumulated DeclsWithEffectsToVerify. As a debug check, do our
// own AST traversal and see what we find.
using MatchFinder = ast_matchers::MatchFinder;
@@ -3603,15 +3603,15 @@ class Analyzer {
void verifyRootDecls(const TranslationUnitDecl &TU) const {
// If this weren't debug code, it would be good to find a way to move/swap
// instead of copying.
- SmallVector<const Decl *> decls = Sem.DeclsWithUnverifiedEffects;
- Sem.DeclsWithUnverifiedEffects.clear();
+ SmallVector<const Decl *> decls = Sem.DeclsWithEffectsToVerify;
+ Sem.DeclsWithEffectsToVerify.clear();
CallableFinderCallback::get(Sem, TU);
if constexpr (DebugLogLevel > 0) {
llvm::errs() << "\nFXAnalysis: Sema gathered " << decls.size()
<< " Decls; second AST pass found "
- << Sem.DeclsWithUnverifiedEffects.size() << "\n";
+ << Sem.DeclsWithEffectsToVerify.size() << "\n";
}
}
#endif
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5dc92ea98554a..aecd9b3f52a62 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11179,7 +11179,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
}
// Record the declaration for later analysis.
- DeclsWithUnverifiedEffects.push_back(D);
+ DeclsWithEffectsToVerify.push_back(D);
}
/// Determines if we can perform a correct type check for \p D as a
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index ede9f6e93469b..3810c1d75cff0 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3838,6 +3838,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; ++I)
+ DeclsWithEffectsToVerify.push_back(getGlobalDeclID(F, Record[I]));
+ break;
+
case OPENCL_EXTENSIONS:
for (unsigned I = 0, E = Record.size(); I != E; ) {
auto Name = ReadString(Record, I);
@@ -8208,6 +8213,24 @@ void ASTReader::InitializeSema(Sema &S) {
NewOverrides.applyOverrides(SemaObj->getLangOpts());
}
+ if (!DeclsWithEffectsToVerify.empty()) {
+ for (uint64_t ID : DeclsWithEffectsToVerify) {
+ Decl *D = GetDecl(ID);
+ SemaObj->DeclsWithEffectsToVerify.push_back(D);
+
+ FunctionEffectSet FX;
+ if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ FX = FD->getFunctionEffects();
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ FX = BD->getFunctionEffects();
+ }
+ if (FX) {
+ SemaObj->AllEffectsToVerify |= FX;
+ }
+ }
+ DeclsWithEffectsToVerify.clear();
+ }
+
SemaObj->OpenCLFeatures = OpenCLExtensions;
UpdateSema();
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 3653d94c6e073..731a58ee78570 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4429,6 +4429,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.
@@ -5287,6 +5298,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
}
WritePackPragmaOptions(SemaRef);
WriteFloatControlPragmaOptions(SemaRef);
+ WriteDeclsWithEffectsToVerify(SemaRef);
// Some simple statistics
RecordData::value_type Record[] = {
>From 8414186c5d6819c805ba521f06ab73e0d6b085e9 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 20 Mar 2024 12:02:13 -0700
Subject: [PATCH 17/71] Don't repeat function names in diagnostics.
---
.../clang/Basic/DiagnosticSemaKinds.td | 34 ++++++-------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 39 +++++++--------
clang/test/Sema/attr-nolock-constraints.cpp | 48 +++++++++----------
3 files changed, 59 insertions(+), 62 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index df553a47a8dbe..f50a572fe7f5e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10779,63 +10779,63 @@ def warn_imp_cast_drops_unaligned : Warning<
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
def warn_func_effect_allocates : Warning<
- "'%0' function '%1' must not allocate or deallocate memory">,
+ "'%0' function must not allocate or deallocate memory">,
InGroup<FunctionEffects>;
def note_func_effect_allocates : Note<
- "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+ "function 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">,
+ "'%0' function 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">;
+ "function cannot be inferred '%0' because it throws or catches exceptions">;
def warn_func_effect_has_static_local : Warning<
- "'%0' function '%1' must not have static locals">,
+ "'%0' function must not have static locals">,
InGroup<FunctionEffects>;
def note_func_effect_has_static_local : Note<
- "'%1' cannot be inferred '%0' because it has a static local">;
+ "function cannot be inferred '%0' because it has a static local">;
def warn_func_effect_uses_thread_local : Warning<
- "'%0' function '%1' must not use thread-local variables">,
+ "'%0' function must not use thread-local variables">,
InGroup<FunctionEffects>;
def note_func_effect_uses_thread_local : Note<
- "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+ "function cannot be inferred '%0' because it uses a thread-local variable">;
def warn_func_effect_calls_objc : Warning<
- "'%0' function '%1' must not access an ObjC method or property">,
+ "'%0' function must not access an ObjC method or property">,
InGroup<FunctionEffects>;
def note_func_effect_calls_objc : Note<
- "'%1' cannot be inferred '%0' because it accesses an ObjC method or property">;
+ "function cannot be inferred '%0' because it accesses an ObjC method or property">;
def warn_func_effect_calls_disallowed_func : Warning<
- "'%0' function '%1' must not call non-'%0' function '%2'">,
+ "'%0' function must not call non-'%0' function">,
InGroup<FunctionEffects>;
// UNTESTED
def warn_func_effect_calls_disallowed_expr : Warning<
- "'%0' function '%1' must not call non-'%0' expression">,
+ "'%0' function must not call non-'%0' expression">,
InGroup<FunctionEffects>;
def note_func_effect_calls_disallowed_func : Note<
- "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+ "function cannot be inferred '%0' because it calls non-'%0' function">;
def note_func_effect_call_extern : Note<
- "'%1' cannot be inferred '%0' because it has no definition in this translation unit">;
+ "function cannot be inferred '%0' because it has no definition in this translation unit">;
def note_func_effect_call_not_inferrable : Note<
- "'%1' does not permit inference of '%0'">;
+ "function does not permit inference of '%0'">;
def note_func_effect_call_virtual : Note<
- "'%1' cannot be inferred '%0' because it is virtual">;
+ "virtual method cannot be inferred '%0'">;
def note_func_effect_call_func_ptr : Note<
- "'%1' cannot be inferred '%0' because it is a function pointer">;
+ "function pointer cannot be inferred '%0'">;
// TODO: Not currently being generated
// def warn_perf_annotation_implies_noexcept : Warning<
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 5903b792bfe1e..0f6de7c83e490 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3035,7 +3035,6 @@ class Analyzer {
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
});
- const auto TopFuncName = CInfo.name(S);
// TODO: Can we get better template instantiation notes?
auto checkAddTemplateNote = [&](const Decl *D) {
@@ -3059,48 +3058,47 @@ class Analyzer {
break;
case DiagnosticID::AllocatesMemory:
S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
TESTED
break;
case DiagnosticID::Throws:
case DiagnosticID::Catches:
S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
TESTED
break;
case DiagnosticID::HasStaticLocal:
S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
TESTED
break;
case DiagnosticID::AccessesThreadLocal:
S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
TESTED
break;
case DiagnosticID::CallsObjC:
S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
TESTED
break;
case DiagnosticID::CallsDisallowedExpr:
S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
- << effectName << TopFuncName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
UNTESTED
break;
case DiagnosticID::CallsUnsafeDecl: {
CallableInfo CalleeInfo{*Diag.Callee};
- auto CalleeName = CalleeInfo.name(S);
S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
- << effectName << TopFuncName << CalleeName;
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
@@ -3115,16 +3113,16 @@ class Analyzer {
// - virtual
if (CalleeInfo.CType == CallType::Virtual) {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
- << effectName << CalleeName;
+ << effectName;
TESTED
} else if (CalleeInfo.CType == CallType::Unknown) {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
- << effectName << CalleeName;
+ << effectName;
TESTED
} else {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
- << effectName << CalleeName;
+ << effectName;
TESTED
}
break;
@@ -3141,44 +3139,44 @@ class Analyzer {
break;
case DiagnosticID::DeclWithoutConstraintOrInference:
S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
- << effectName << CalleeName;
+ << effectName;
TESTED
break;
case DiagnosticID::CallsDisallowedExpr:
S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
- << effectName << CalleeName;
+ << effectName;
UNTESTED
break;
case DiagnosticID::AllocatesMemory:
S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
- << effectName << CalleeName;
+ << effectName;
TESTED
break;
case DiagnosticID::Throws:
case DiagnosticID::Catches:
S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
- << effectName << CalleeName;
+ << effectName;
TESTED
break;
case DiagnosticID::HasStaticLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
- << effectName << CalleeName;
+ << effectName;
TESTED
break;
case DiagnosticID::AccessesThreadLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
- << effectName << CalleeName;
+ << effectName;
UNTESTED
break;
case DiagnosticID::CallsObjC:
S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
- << effectName << CalleeName;
+ << effectName;
UNTESTED
break;
case DiagnosticID::CallsUnsafeDecl:
MaybeNextCallee.emplace(*Diag2.Callee);
S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
- << effectName << CalleeName << MaybeNextCallee->name(S);
+ << effectName;
TESTED
break;
}
@@ -3186,7 +3184,6 @@ class Analyzer {
Callee = Diag2.Callee;
if (MaybeNextCallee) {
CalleeInfo = *MaybeNextCallee;
- CalleeName = CalleeInfo.name(S);
}
}
} break;
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
index 5d1efe931a1f9..d2d2ddbf04a2a 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -10,66 +10,66 @@
void nl1() [[clang::nolock]]
{
- auto* pInt = new int; // expected-warning {{'nolock' function 'nl1' must not allocate or deallocate memory}}
+ auto* pInt = new int; // expected-warning {{'nolock' function must not allocate or deallocate memory}}
}
void nl2() [[clang::nolock]]
{
- static int global; // expected-warning {{'nolock' function 'nl2' must not have static locals}}
+ static int global; // expected-warning {{'nolock' function must not have static locals}}
}
void nl3() [[clang::nolock]]
{
try {
- throw 42; // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
}
- catch (...) { // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+ catch (...) { // expected-warning {{'nolock' function must not throw or catch exceptions}}
}
}
void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{'nl4_not_inline' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
void nl4() [[clang::nolock]]
{
nl4_inline(); // OK
- nl4_not_inline(); // expected-warning {{'nolock' function 'nl4' must not call non-'nolock' function 'nl4_not_inline'}}
+ nl4_not_inline(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
}
struct HasVirtual {
- virtual void unsafe(); // expected-note {{'HasVirtual::unsafe' cannot be inferred 'nolock' because it is virtual}}
+ virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nolock'}}
};
void nl5() [[clang::nolock]]
{
HasVirtual hv;
- hv.unsafe(); // expected-warning {{'nolock' function 'nl5' must not call non-'nolock' function 'HasVirtual::unsafe'}}
+ hv.unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
}
-void nl6_unsafe(); // expected-note {{'nl6_unsafe' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
void nl6_transitively_unsafe()
{
- nl6_unsafe(); // expected-note {{'nl6_transitively_unsafe' cannot be inferred 'nolock' because it calls non-'nolock' function 'nl6_unsafe'}}
+ nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
}
void nl6() [[clang::nolock]]
{
- nl6_transitively_unsafe(); // expected-warning {{'nolock' function 'nl6' must not call non-'nolock' function 'nl6_transitively_unsafe'}}
+ nl6_transitively_unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
}
thread_local int tl_var{ 42 };
bool tl_test() [[clang::nolock]]
{
- return tl_var > 0; // expected-warning {{'nolock' function 'tl_test' must not use thread-local variables}}
+ return tl_var > 0; // expected-warning {{'nolock' function must not use thread-local variables}}
}
void nl7()
{
// Make sure we verify blocks
auto blk = ^() [[clang::nolock]] {
- throw 42; // expected-warning {{'nolock' function '(block 0)' must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
};
}
@@ -77,7 +77,7 @@ void nl8()
{
// Make sure we verify lambdas
auto lambda = []() [[clang::nolock]] {
- throw 42; // expected-warning {{'nolock' function 'nl8()::(anonymous class)::operator()' must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
};
}
@@ -86,11 +86,11 @@ void nl8()
struct Adder {
static T add_explicit(T x, T y) [[clang::nolock]]
{
- return x + y; // expected-warning {{'nolock' function 'Adder<Stringy>::add_explicit' must not call non-'nolock' function 'operator+'}}
+ return x + y; // expected-warning {{'nolock' function must not call non-'nolock' function}}
}
static T add_implicit(T x, T y)
{
- return x + y; // expected-note {{'Adder<Stringy2>::add_implicit' cannot be inferred 'nolock' because it calls non-'nolock' function 'operator+'}}
+ return x + y; // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
}
};
@@ -98,7 +98,7 @@ void nl8()
friend Stringy operator+(const Stringy& x, const Stringy& y)
{
// Do something inferably unsafe
- auto* z = new char[42]; // expected-note {{'operator+' cannot be inferred 'nolock' because it allocates/deallocates memory}}
+ auto* z = new char[42]; // expected-note {{function cannot be inferred 'nolock' because it allocates/deallocates memory}}
return {};
}
};
@@ -107,7 +107,7 @@ void nl8()
friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
{
// Do something inferably unsafe
- throw 42; // expected-note {{'operator+' cannot be inferred 'nolock' because it throws or catches exceptions}}
+ throw 42; // expected-note {{function cannot be inferred 'nolock' because it throws or catches exceptions}}
}
};
@@ -117,32 +117,32 @@ void nl9() [[clang::nolock]]
Adder<int>::add_implicit(1, 2);
Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
- Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function 'nl9' must not call non-'nolock' function 'Adder<Stringy2>::add_implicit'}} \
+ Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function must not call non-'nolock' function}} \
expected-note {{in template expansion here}}
}
void nl10(
- void (*fp1)(), // expected-note {{'fp1' cannot be inferred 'nolock' because it is a function pointer}}
+ void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nolock'}}
void (*fp2)() [[clang::nolock]]
) [[clang::nolock]]
{
- fp1(); // expected-warning {{'nolock' function 'nl10' must not call non-'nolock' function 'fp1'}}
+ fp1(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
fp2();
}
// Interactions with nolock(false)
-void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
+void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{function does not permit inference of 'nolock'}}
{
}
void nl11() [[clang::nolock]]
{
- nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
+ nl11_no_inference(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
}
// Verify that when attached to a redeclaration, the attribute successfully attaches.
void nl12() {
- static int x; // expected-warning {{'nolock' function 'nl12' must not have static locals}}
+ static int x; // expected-warning {{'nolock' function must not have static locals}}
}
void nl12() [[clang::nolock]];
void nl13() [[clang::nolock]] { nl12(); }
>From 62b125efb4ded8981c2e0ba4bf338f0169adc203 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 20 Mar 2024 08:52:06 -0700
Subject: [PATCH 18/71] Functionality: - Effects can allow themselves to be
propagated from virtual methods in base class to overridden methods in
derived classes.
Fixes:
- In constraint analysis, map must be keyed with canonical decls; but decls as analyzed should be definitions wherever possible.
- FunctionEffect needs explicit padding
- Tests in Objective-C
- Don't trap on recursion; ignore recursive calls when completing function analysis.
- Handle pointers to member function, expressions of type BlockPointerType
- followCall() needs to combine declared and inferred effects of callee.
- FunctionEffectSet::get() receives a QualType instead of Type& now. It knows how to peel off references now.
- MutableFunctionEffectSet::insert fix: wasn't handling Iter == end() correctly
- Sema::IsFunctionConversion: fix: wasn't correctly removing nolock after removing noexcept
- Remove debug macros marking untested cases
---
clang/include/clang/AST/Type.h | 32 +++-
.../clang/Basic/DiagnosticSemaKinds.td | 10 +-
clang/lib/AST/Decl.cpp | 8 +-
clang/lib/AST/Type.cpp | 38 ++--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 173 +++++++++++-------
clang/lib/Sema/Sema.cpp | 4 +-
clang/lib/Sema/SemaDeclCXX.cpp | 21 ++-
clang/lib/Sema/SemaOverload.cpp | 2 +
...traints.cpp => attr-nolock-constraints.mm} | 42 +++++
clang/test/Sema/attr-nolock-sema.cpp | 57 +++---
clang/test/Sema/attr-nolock-syntax.cpp | 9 +
clang/test/Sema/attr-nolock-wip.cpp | 80 ++++++--
12 files changed, 329 insertions(+), 147 deletions(-)
rename clang/test/Sema/{attr-nolock-constraints.cpp => attr-nolock-constraints.mm} (82%)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index ca4660edb2f56..3d7ef5f951ee1 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4197,6 +4197,7 @@ class TypeSourceInfo;
/// Represents an abstract function effect.
class FunctionEffect {
public:
+ /// Identifies the particular type of effect.
enum class Type {
None = 0,
NoLockTrue,
@@ -4229,6 +4230,14 @@ class FunctionEffect {
FE_ExcludeThreadLocalVars = 0x80
};
+ /// Describes the result of effects differing between a base class's virtual
+ /// method and an overriding method in a subclass.
+ enum class OverrideResult {
+ Ignore,
+ Warn,
+ Propagate // Base method's effects are merged with those of the override.
+ };
+
private:
// For uniqueness, currently only Type_ is significant.
@@ -4240,11 +4249,15 @@ class FunctionEffect {
// then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
// be considered for uniqueness.
+ // Since this struct is serialized as if it were a uint32_t, it's important
+ // to pad and explicitly zero the extra bits.
+ [[maybe_unused]] unsigned Padding : 22;
+
public:
using CalleeDeclOrType =
llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
- FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0) {}
+ FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0), Padding(0) {}
explicit FunctionEffect(Type T);
@@ -4257,7 +4270,7 @@ class FunctionEffect {
/// The description printed in diagnostics, e.g. 'nolock'.
StringRef name() const;
- /// A serializable, hashable representation.
+ /// A hashable representation.
uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
/// Return true if adding or removing the effect as part of a type conversion
@@ -4275,10 +4288,11 @@ class FunctionEffect {
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
- bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
- FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod,
- FunctionEffectSet NewFX) const;
+ OverrideResult 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 a Decl of the
/// specified type (generally a FunctionProtoType but TypeSourceInfo is
@@ -4387,9 +4401,9 @@ class FunctionEffectSet {
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);
+ /// Extract the effects from a Type if it is a function, block, member
+ /// function pointer, reference or pointer to one.
+ static FunctionEffectSet get(QualType QT);
};
/// Represents a prototype with parameter type info, e.g.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f50a572fe7f5e..e841fdb4fd511 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10814,16 +10814,15 @@ def note_func_effect_calls_objc : Note<
"function cannot be inferred '%0' because it accesses an ObjC method or property">;
def warn_func_effect_calls_disallowed_func : Warning<
- "'%0' function must not call non-'%0' function">,
+ "'%0' function must not call non-'%0' function '%1'">,
InGroup<FunctionEffects>;
-// UNTESTED
def warn_func_effect_calls_disallowed_expr : Warning<
"'%0' function must not call non-'%0' expression">,
InGroup<FunctionEffects>;
def note_func_effect_calls_disallowed_func : Note<
- "function cannot be inferred '%0' because it calls non-'%0' function">;
+ "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
def note_func_effect_call_extern : Note<
"function cannot be inferred '%0' because it has no definition in this translation unit">;
@@ -10851,11 +10850,6 @@ def warn_func_effect_false_on_type : Warning<
def note_func_effect_from_template : Note<
"in template expansion here">;
-// TODO: Needs to be tested
-def warn_incompatible_func_effects : Warning<
- "attributes '%0' and '%1' are incompatible">,
- InGroup<FunctionEffects>;
-
// spoofing nolock/noalloc
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 827d57e07d603..e4171d2a3e0a8 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,10 +4502,12 @@ unsigned FunctionDecl::getODRHash() {
return ODRHash;
}
-// Effects may differ between declarations, but they should be propagated from old
-// to new on any redeclaration, so it suffices to look at getMostRecentDecl().
+// Effects may differ between declarations, but they should be propagated from
+// old to new on any redeclaration, so it suffices to look at
+// getMostRecentDecl().
FunctionEffectSet FunctionDecl::getFunctionEffects() const {
- if (const auto *FPT = getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
+ if (const auto *FPT =
+ getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
}
return {};
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 06f9c0190bad6..515971d650f59 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,7 +4922,8 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
-FunctionEffect::FunctionEffect(Type T) : Type_(unsigned(T)), Flags_(0) {
+FunctionEffect::FunctionEffect(Type T)
+ : Type_(unsigned(T)), Flags_(0), Padding(0) {
switch (T) {
case Type::NoLockTrue:
Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
@@ -4994,21 +4995,19 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
return false;
}
-bool FunctionEffect::diagnoseMethodOverride(bool Adding,
- const CXXMethodDecl &OldMethod,
- FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod,
- FunctionEffectSet NewFX) const {
+FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
+ bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
switch (type()) {
case Type::NoAllocTrue:
case Type::NoLockTrue:
// nolock/noalloc can't be removed from an override
// adding -> false, removing -> true (diagnose)
- return !Adding;
+ return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
default:
break;
}
- return false;
+ return OverrideResult::Ignore;
}
bool FunctionEffect::canInferOnFunction(QualType QT,
@@ -5062,7 +5061,7 @@ MutableFunctionEffectSet::MutableFunctionEffectSet(
void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
const auto &Iter = std::lower_bound(begin(), end(), Effect);
- if (*Iter != Effect) {
+ if (Iter == end() || *Iter != Effect) {
insert(Iter, Effect);
}
}
@@ -5112,12 +5111,23 @@ FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
}
// TODO: inline?
-FunctionEffectSet FunctionEffectSet::get(const Type &TyRef) {
- const Type *Ty = &TyRef;
- if (Ty->isPointerType())
- Ty = Ty->getPointeeType().getTypePtr();
- if (const auto *FPT = Ty->getAs<FunctionProtoType>())
+FunctionEffectSet FunctionEffectSet::get(QualType QT) {
+ if (QT->isReferenceType())
+ QT = QT.getNonReferenceType();
+ if (QT->isPointerType())
+ QT = QT->getPointeeType();
+
+ if (const auto *BT = QT->getAs<BlockPointerType>()) {
+ QT = BT->getPointeeType();
+ } else if (const auto *MP = QT->getAs<MemberPointerType>()) {
+ if (MP->isMemberFunctionPointer()) {
+ QT = MP->getPointeeType();
+ }
+ }
+
+ if (const auto *FPT = QT->getAs<FunctionProtoType>())
return FPT->getFunctionEffects();
+
return {};
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f6de7c83e490..10228715ea1bb 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2389,6 +2389,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
namespace FXAnalysis {
+#define tmp_assert(x) \
+ do { \
+ if (!(x)) \
+ __builtin_trap(); \
+ } while (0)
+
enum class DiagnosticID : uint8_t {
None = 0, // sentinel for an empty Diagnostic
Throws,
@@ -2445,6 +2451,7 @@ static bool functionIsVerifiable(const FunctionDecl *FD) {
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer...
struct CallableInfo {
+ // CDecl holds the function's definition, if any.
const Decl *CDecl;
mutable std::optional<std::string>
MaybeName; // mutable because built on demand in const method
@@ -2461,10 +2468,6 @@ struct CallableInfo {
// Use the function's definition, if any.
if (auto *Def = FD->getDefinition()) {
CDecl = FD = Def;
- // is the definition always canonical?
- assert(FD->getCanonicalDecl() == FD);
- } else {
- FD = FD->getCanonicalDecl();
}
CType = CallType::Function;
if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
@@ -2481,7 +2484,7 @@ struct CallableInfo {
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at the type.
QT = VD->getType();
- Effects = FunctionEffectSet::get(*QT);
+ Effects = FunctionEffectSet::get(QT);
}
}
@@ -2580,8 +2583,19 @@ class PendingFunctionAnalysis {
friend class CompleteFunctionAnalysis;
struct DirectCall {
- const Decl *Callee;
+ // Pack a Decl* and a bool indicating whether the call was detected
+ // to be recursive. Not all recursive calls are detected, just enough
+ // to break cycles.
+ llvm::PointerIntPair<const Decl *, 1> CalleeAndRecursed;
SourceLocation CallLoc;
+
+ DirectCall(const Decl *D, SourceLocation CallLoc)
+ : CalleeAndRecursed(D), CallLoc(CallLoc) {}
+
+ const Decl *callee() const { return CalleeAndRecursed.getPointer(); }
+ bool recursed() const { return CalleeAndRecursed.getInt(); }
+
+ void setRecursed() { CalleeAndRecursed.setInt(1); }
};
public:
@@ -2664,7 +2678,7 @@ class PendingFunctionAnalysis {
if (UnverifiedDirectCalls == nullptr) {
UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
}
- UnverifiedDirectCalls->emplace_back(DirectCall{D, CallLoc});
+ UnverifiedDirectCalls->emplace_back(D, CallLoc);
}
// Analysis is complete when there are no unverified direct calls.
@@ -2676,8 +2690,8 @@ class PendingFunctionAnalysis {
return InferrableEffectToFirstDiagnostic.lookup(effect);
}
- const SmallVector<DirectCall> &unverifiedCalls() const {
- assert(!isComplete());
+ SmallVector<DirectCall> &unverifiedCalls() const {
+ tmp_assert(!isComplete());
return *UnverifiedDirectCalls;
}
@@ -2685,7 +2699,8 @@ class PendingFunctionAnalysis {
return DiagnosticsForExplicitFX.get();
}
- void dump(llvm::raw_ostream &OS) const {
+ // If Sema is supplied, prints names of unverified direct calls
+ void dump(llvm::raw_ostream &OS, Sema *SemPtr = nullptr) const {
OS << "Pending: Declared ";
DeclaredVerifiableEffects.dump(OS);
OS << ", "
@@ -2693,7 +2708,15 @@ class PendingFunctionAnalysis {
<< " diags; ";
OS << " Infer ";
FXToInfer.dump(OS);
- OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+ if (SemPtr && UnverifiedDirectCalls) {
+ OS << "; Calls: ";
+ for (const auto &Call : *UnverifiedDirectCalls) {
+ CallableInfo CI(*Call.callee());
+ OS << " " << CI.name(*SemPtr);
+ }
+ }
+ OS << "\n";
}
};
@@ -2753,15 +2776,23 @@ class CompleteFunctionAnalysis {
}
*/
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+ if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ FD = FD->getCanonicalDecl();
+ tmp_assert(FD != nullptr);
+ return FD;
+ }
+ return D;
+}
+
// ==========
class Analyzer {
constexpr static int DebugLogLevel = 0;
-
// --
Sema &Sem;
// used from Sema:
- // SmallVector<const Decl *> DeclsWithEffectsToVerify
+ // SmallVector<const Decl *> CallablesWithEffectsToVerify
// Subset of Sema.AllEffectsToVerify
FunctionEffectSet AllInferrableEffectsToVerify;
@@ -2772,11 +2803,22 @@ class Analyzer {
// Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
// than complete state, so use different objects to represent them.
// The state pointers are owned by the container.
- struct AnalysisMap : public llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
+ public:
~AnalysisMap();
- // use lookup()
+ // Use non-public inheritance in order to maintain the invariant
+ // that lookups and insertions are via the canonical Decls.
+
+ FuncAnalysisPtr lookup(const Decl *Key) const {
+ return Base::lookup(CanonicalFunctionDecl(Key));
+ }
+
+ FuncAnalysisPtr &operator[](const Decl *Key) {
+ return Base::operator[](CanonicalFunctionDecl(Key));
+ }
/// Shortcut for the case where we only care about completed analysis.
CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
@@ -2789,7 +2831,7 @@ class Analyzer {
}
void dump(Sema &S, llvm::raw_ostream &OS) {
- OS << "AnalysisMap:\n";
+ OS << "\nAnalysisMap:\n";
for (const auto &item : *this) {
CallableInfo CI(*item.first);
const auto AP = item.second;
@@ -2807,6 +2849,7 @@ class Analyzer {
} else
llvm_unreachable("never");
}
+ OS << "---\n";
}
};
AnalysisMap DeclAnalysis;
@@ -2839,7 +2882,7 @@ class Analyzer {
SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithEffectsToVerify;
- // It's helpful to use DeclsWithEffectsToVerify as a stack for a
+ // It's helpful to use CallablesWithEffectsToVerify as a stack for a
// depth-first traversal rather than have a secondary container. But first,
// reverse it, so Decls are verified in the order they are declared.
std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -2869,19 +2912,21 @@ class Analyzer {
continue;
}
- for (const auto &Call : Pending->unverifiedCalls()) {
+ for (auto &Call : Pending->unverifiedCalls()) {
// This lookup could be optimized out if the results could have been
// saved from followCall when we traversed the caller's AST. It would
// however make the check for recursion more complex.
- auto AP = DeclAnalysis.lookup(Call.Callee);
+ auto AP = DeclAnalysis.lookup(Call.callee());
if (AP.isNull()) {
- verifyQueue.push_back(Call.Callee);
+ verifyQueue.push_back(Call.callee());
continue;
}
if (isa<PendingFunctionAnalysis *>(AP)) {
- // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
- // TODO
- __builtin_trap();
+ // This indicates recursion (not necessarily direct). For the
+ // purposes of effect analysis, we can just ignore it since
+ // no effects forbid recursion.
+ Call.setRecursed();
+ continue;
}
llvm_unreachable("unexpected DeclAnalysis item");
}
@@ -2892,13 +2937,8 @@ class Analyzer {
// Verify a single Decl. Return the pending structure if that was the result,
// else null. This method must not recurse.
PendingFunctionAnalysis *verifyDecl(const Decl *D) {
- // TODO: Is this in the right place?
- const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
- if (FD != nullptr) {
- // Currently, built-in functions are always considered safe.
- if (FD->getBuiltinID() != 0) {
- return nullptr;
- }
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ tmp_assert(FD->getBuiltinID() == 0);
}
CallableInfo CInfo(*D);
@@ -2951,8 +2991,16 @@ class Analyzer {
// the possibility of inference.
void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
CallableInfo Caller(*D);
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+ Pending->dump(llvm::outs(), &Sem);
+ llvm::outs() << "\n";
+ }
for (const auto &Call : Pending->unverifiedCalls()) {
- CallableInfo Callee(*Call.Callee);
+ if (Call.recursed())
+ continue;
+
+ CallableInfo Callee(*Call.callee());
followCall(Caller, *Pending, Callee, Call.CallLoc,
/*AssertNoFurtherInference=*/true);
}
@@ -2966,17 +3014,22 @@ class Analyzer {
const CallableInfo &Callee, SourceLocation CallLoc,
bool AssertNoFurtherInference) {
const bool DirectCall = Callee.isDirectCall();
+
+ // These will be its declared effects.
FunctionEffectSet CalleeEffects = Callee.Effects;
+
bool IsInferencePossible = DirectCall;
if (DirectCall) {
if (auto *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
- CalleeEffects = CFA->VerifiedEffects;
+ // Combine declared effects with those which may have been inferred.
+ CalleeEffects = FunctionEffectSet::getUnion(
+ Sem.getASTContext(), CalleeEffects, CFA->VerifiedEffects);
IsInferencePossible = false; // we've already traversed it
}
}
if (AssertNoFurtherInference) {
- assert(!IsInferencePossible);
+ tmp_assert(!IsInferencePossible);
}
if (!Callee.isVerifiable()) {
IsInferencePossible = false;
@@ -2987,6 +3040,12 @@ class Analyzer {
<< "; verifiable: " << Callee.isVerifiable() << "; callee ";
CalleeEffects.dump(llvm::outs());
llvm::outs() << "\n";
+ llvm::outs() << " callee " << Callee.CDecl << " canonical "
+ << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+ for (auto *D : Callee.CDecl->redecls()) {
+ llvm::outs() << " " << D;
+ }
+ llvm::outs() << "\n";
}
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
@@ -3027,15 +3086,12 @@ class Analyzer {
// Should only be called when determined to be complete.
void emitDiagnostics(SmallVector<Diagnostic> &Diags,
const CallableInfo &CInfo, Sema &S) {
-#define UNTESTED __builtin_trap();
-#define TESTED
const SourceManager &SM = S.getSourceManager();
std::sort(Diags.begin(), Diags.end(),
[&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
});
-
// TODO: Can we get better template instantiation notes?
auto checkAddTemplateNote = [&](const Decl *D) {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
@@ -3057,48 +3113,40 @@ class Analyzer {
llvm_unreachable("Unexpected diagnostic kind");
break;
case DiagnosticID::AllocatesMemory:
- S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
- << effectName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
break;
case DiagnosticID::Throws:
case DiagnosticID::Catches:
S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
break;
case DiagnosticID::HasStaticLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
- << effectName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
break;
case DiagnosticID::AccessesThreadLocal:
S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
break;
case DiagnosticID::CallsObjC:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
- << effectName;
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
checkAddTemplateNote(CInfo.CDecl);
- TESTED
break;
case DiagnosticID::CallsDisallowedExpr:
S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
- UNTESTED
break;
case DiagnosticID::CallsUnsafeDecl: {
CallableInfo CalleeInfo{*Diag.Callee};
+ auto CalleeName = CalleeInfo.name(S);
S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
- << effectName;
+ << effectName << CalleeName;
checkAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
@@ -3114,16 +3162,13 @@ class Analyzer {
if (CalleeInfo.CType == CallType::Virtual) {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
<< effectName;
- TESTED
} else if (CalleeInfo.CType == CallType::Unknown) {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
- TESTED
} else {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
<< effectName;
- TESTED
}
break;
}
@@ -3140,50 +3185,41 @@ class Analyzer {
case DiagnosticID::DeclWithoutConstraintOrInference:
S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
<< effectName;
- TESTED
break;
case DiagnosticID::CallsDisallowedExpr:
S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
<< effectName;
- UNTESTED
break;
case DiagnosticID::AllocatesMemory:
- S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
- << effectName;
- TESTED
+ S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
break;
case DiagnosticID::Throws:
case DiagnosticID::Catches:
S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
<< effectName;
- TESTED
break;
case DiagnosticID::HasStaticLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
<< effectName;
- TESTED
break;
case DiagnosticID::AccessesThreadLocal:
S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
<< effectName;
- UNTESTED
break;
case DiagnosticID::CallsObjC:
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
- << effectName;
- UNTESTED
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
break;
case DiagnosticID::CallsUnsafeDecl:
MaybeNextCallee.emplace(*Diag2.Callee);
S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
- << effectName;
- TESTED
+ << effectName << MaybeNextCallee->name(S);
break;
}
checkAddTemplateNote(Callee);
Callee = Diag2.Callee;
if (MaybeNextCallee) {
CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(S);
}
}
} break;
@@ -3276,6 +3312,11 @@ class Analyzer {
// Here we have a call to a Decl, either explicitly via a CallExpr or some
// other AST construct. CallableInfo pertains to the callee.
void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl)) {
+ // Currently, built-in functions are always considered safe.
+ if (FD->getBuiltinID() != 0)
+ return;
+ }
Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
/*AssertNoFurtherInference=*/false);
}
@@ -3542,7 +3583,7 @@ class Analyzer {
};
#if FX_ANALYZER_VERIFY_DECL_LIST
- // Sema has accumulated DeclsWithEffectsToVerify. As a debug check, do our
+ // Sema has accumulated CallablesWithEffectsToVerify. As a debug check, do our
// own AST traversal and see what we find.
using MatchFinder = ast_matchers::MatchFinder;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 05b771939feb1..874243495ea39 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,8 +588,8 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- const auto SrcFX = FunctionEffectSet::get(*SrcType);
- const auto DstFX = FunctionEffectSet::get(*DstType);
+ const auto SrcFX = FunctionEffectSet::get(SrcType);
+ const auto DstFX = FunctionEffectSet::get(DstType);
if (SrcFX != DstFX) {
for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
const FunctionEffect &Effect = Item.first;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2c163ce4c14b0..dfc442e646d16 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18332,11 +18332,30 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
for (const auto &Item : Diffs) {
const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+ switch (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+ case FunctionEffect::OverrideResult::Ignore:
+ break;
+ case FunctionEffect::OverrideResult::Warn:
Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
<< Effect.name();
Diag(Old->getLocation(), diag::note_overridden_virtual_function);
+ // TODO: It would be nice to have a FIXIT here!
AnyDiags = true;
+ break;
+ case FunctionEffect::OverrideResult::Propagate: {
+ auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
+
+ FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
+ EPI.FunctionEffects = MergedFX;
+ QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
+ NewFT->getParamTypes(), EPI);
+
+ // TODO: It's ugly to be mutating the incoming const method. It is
+ // mutable in the calling function, though. There is also the
+ // possibility here that we are discarding some other sort of sugar on
+ // the method's type.
+ const_cast<CXXMethodDecl *>(New)->setType(ModQT);
+ } break;
}
}
if (AnyDiags)
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 0163969e9bf50..e26d1ef9d2bb5 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1873,6 +1873,8 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
// For C, when called from checkPointerTypesForAssignment,
// we need not to change the type, or else even an innocuous cast
// like dropping effects will fail.
+ FromFPT =
+ dyn_cast<FunctionProtoType>(FromFn); // in case FromFn changed above
// Transparently add/drop effects; here we are concerned with
// language rules/canonicalization. Adding/dropping effects is a warning.
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.mm
similarity index 82%
rename from clang/test/Sema/attr-nolock-constraints.cpp
rename to clang/test/Sema/attr-nolock-constraints.mm
index d2d2ddbf04a2a..e52b50eac519f 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.mm
@@ -146,3 +146,45 @@ void nl12() {
}
void nl12() [[clang::nolock]];
void nl13() [[clang::nolock]] { nl12(); }
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nl14(OCClass *oc) [[clang::nolock]] {
+ [oc method]; // expected-warning {{'nolock' function must not access an ObjC method or property}}
+}
+void nl15(OCClass *oc) {
+ [oc method]; // expected-note {{function cannot be inferred 'nolock' because it accesses an ObjC method or property}}
+}
+void nl16(OCClass *oc) [[clang::nolock]] {
+ nl15(oc); // expected-warning {{'nolock' function must not call non-'nolock' function 'nl15'}}
+}
+
+// C++ member function pointers
+struct PTMFTester {
+ typedef void (PTMFTester::*ConvertFunction)() [[clang::nolock]];
+
+ void convert() [[clang::nolock]];
+
+ ConvertFunction mConvertFunc;
+};
+
+void PTMFTester::convert() [[clang::nolock]]
+{
+ (this->*mConvertFunc)();
+}
+
+// Block variables
+void nl17(void (^blk)() [[clang::nolock]]) [[clang::nolock]] {
+ blk();
+}
+
+// References to blocks
+void nl18(void (^block)() [[clang::nolock]]) [[clang::nolock]]
+{
+ auto &ref = block;
+ ref();
+}
+
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 28542be0a2ecd..98a11067305d5 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -59,18 +59,37 @@ void type_conversions()
fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
}
+#ifdef __cplusplus
+// There was a bug: noexcept and nolock could be individually removed in conversion, but not both
+void type_conversions_2()
+{
+ auto receives_fp = [](void (*fp)()) {
+ };
+
+ auto ne = +[]() noexcept {};
+ auto nl = +[]() [[clang::nolock]] {};
+ auto nl_ne = +[]() noexcept [[clang::nolock]] {};
+
+ receives_fp(ne);
+ receives_fp(nl);
+ receives_fp(nl_ne);
+}
+#endif
+
// --- VIRTUAL METHODS ---
+// Attributes propagate to overridden methods, so no diagnostics.
+// Check this in the syntax tests too.
#ifdef __cplusplus
struct Base {
virtual void f1();
- virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
- virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+ virtual void nolock() noexcept [[clang::nolock]];
+ virtual void noalloc() noexcept [[clang::noalloc]];
};
struct Derived : public Base {
void f1() [[clang::nolock]] override;
- void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
- void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+ void nolock() noexcept override;
+ void noalloc() noexcept override;
};
#endif // __cplusplus
@@ -89,26 +108,10 @@ void f2();
#endif
// Note: we verify that the attribute is actually seen during the constraints tests.
-
-// Ensure that the redeclaration's attribute is seen and diagnosed correctly.
-
-// void f2() {
-// static int x;
-// }
-// void f2() [[clang::nolock]];
-//
-// void f3() [[clang::nolock]] {
-// f2();
-// }
-
-#if 0
-int f2();
-// redeclaration with a stronger constraint is OK.
-int f2() [[clang::nolock]]; // e xpected-note {{previous declaration is here}}
-int f2() { return 42; } // e xpected-warning {{attribute 'nolock' on function does not match previous declaration}}
-
-int f3();
-// redeclaration with a stronger constraint is OK.
-int f3() [[clang::noalloc]]; // e xpected-note {{previous declaration is here}}
-int f3() { return 42; } // e xpected-warning {{attribute 'noalloc' on function does not match previous declaration}}
-#endif
+// --- OVERLOADS ---
+#ifdef __cplusplus
+struct S {
+ void foo(); // expected-note {{previous declaration is here}}
+ void foo(); // expected-error {{class member cannot be redeclared}}
+};
+#endif // __cplusplus
diff --git a/clang/test/Sema/attr-nolock-syntax.cpp b/clang/test/Sema/attr-nolock-syntax.cpp
index 2c0d71ba93ddf..8deb3e88cc7d2 100644
--- a/clang/test/Sema/attr-nolock-syntax.cpp
+++ b/clang/test/Sema/attr-nolock-syntax.cpp
@@ -53,6 +53,15 @@ void nl2() [[clang::noalloc]] [[clang::nolock]];
decltype(nl1) nl3;
// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nolock))'
+// Attribute propagates from base class virtual method to overrides.
+struct Base {
+ virtual void nl_method() [[clang::nolock]];
+};
+struct Derived : public Base {
+ void nl_method() override;
+ // CHECK: CXXMethodDecl {{.*}} nl_method 'void () __attribute__((clang_nolock))'
+};
+
// --- Blocks ---
// On the type of the VarDecl holding a BlockDecl
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index ab8cf57d69ed0..914c4d09764ee 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -4,36 +4,82 @@
#error "the 'nolock' attribute is not available"
#endif
-// ============================================================================
-void f2(int);
-void f2(int) [[clang::nolock]]; // expected-note {{previous declaration is here}}
-void f2(int); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+// The diagnostic for inference not following a non-inline method should override
+// the one for a virtual method.
-// ============================================================================
+struct HasVirtual {
+ virtual ~HasVirtual() = default;
+ virtual void method();
+};
+
+void nl999(HasVirtual& x) [[clang::nolock]] {
+ x.method();
+}
-#if 0
-// https://github.com/llvm/llvm-project/pull/84983#issuecomment-1994978033
-// the bug where AttributedType sugar gets lost on lambdas (when the "inferred" return type gets
-// converted to a concrete one) happens here and the nolock(false) attribute is lost from h.
-template <class T>
-void f(T a) [[clang::nolock]] { a(); }
+#if 0
+ using nl_sugar = int (*)(int) [[clang::nolock]];
+
+ void receives_fp_nl(nl_sugar fp) {
+ }
+
+ int callback(int) noexcept [[clang::nolock]];
-void m()
+void type_conversions_2()
{
- auto g = []() [[clang::nolock]] {
+ auto receives_fp = [](void (*fp)()) {
};
- auto h = []() [[clang::nolock(false)]] {
- };
+ //auto receives_fp_nl = [](void (*fp)() [[clang::nolock]]) {
+ //};
- f(g);
- f(h);
+ auto ne = +[]() noexcept {};
+ auto nl = +[]() [[clang::nolock]] {};
+ //auto nl_ne = +[](int x) noexcept [[clang::nolock]] -> int { return x; };
+
+ receives_fp(ne);
+ receives_fp(nl);
+// receives_fp(nl_ne);
+
+ receives_fp_nl(callback);
}
#endif
+#if 0
+struct S {
+ void foo();
+ // void foo() noexcept; // error, redeclaration
+ // void foo() [[clang::nolock]]; // error, redeclaration
+
+ using FP = void (*)();
+ using FPNE = void (*)() noexcept;
+ using FPNL = void (*)() [[clang::nolock]];
+
+ void bar(FP x);
+ void bar(FPNE x); // This is a distinct overload
+ void bar(FPNL x); // This is a distinct overload
+};
+#endif
+
+// ============================================================================
+
+#if 0
+#define RT_UNSAFE_BEGIN(reason) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \
+ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")
+
+#define RT_UNSAFE_END \
+ _Pragma("clang diagnostic pop")
+
+#define RT_UNSAFE(...) \
+ RT_UNSAFE_BEGIN("") \
+ __VA_ARGS__ \
+ RT_UNSAFE_END
+#endif
+
// ============================================================================
#if 0
>From 525962e73ab9dd031a7afefa8547a0353887d3bd Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 3 Apr 2024 05:54:16 -0700
Subject: [PATCH 19/71] Mass renaming: nolock/noalloc ->
nonblocking/nonallocating
---
clang/include/clang/AST/Type.h | 18 +--
clang/include/clang/Basic/Attr.td | 20 ++--
clang/include/clang/Basic/AttrDocs.td | 11 +-
clang/include/clang/Basic/DiagnosticGroups.td | 2 +-
.../clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/lib/AST/ASTContext.cpp | 2 +-
clang/lib/AST/Type.cpp | 54 ++++-----
clang/lib/AST/TypePrinter.cpp | 8 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 71 ++++++------
clang/test/Sema/attr-nolock-constraints.mm | 106 +++++++++---------
clang/test/Sema/attr-nolock-sema.cpp | 90 +++++++--------
clang/test/Sema/attr-nolock-syntax.cpp | 82 +++++++-------
clang/test/Sema/attr-nolock-wip.cpp | 26 ++---
14 files changed, 251 insertions(+), 243 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index fb1c324ef6d3a..cdfeb8eb71539 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4448,8 +4448,8 @@ class FunctionEffect {
/// Identifies the particular type of effect.
enum class Type {
None = 0,
- NoLockTrue,
- NoAllocTrue,
+ NonBlocking,
+ NonAllocating,
};
/// Flags describing behaviors of the effect.
@@ -4458,16 +4458,17 @@ class FunctionEffect {
// would OR together the bits of multiple effects.)
using Flags = unsigned;
enum FlagBit : unsigned {
- // Some effects require verification, e.g. nolock(true); others might not?
- // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
+ // Some effects require verification, e.g. nonblocking(true); others might
+ // not? (no example yet; TODO: maybe always true, vestigial from
+ // nonblocking(false)).
FE_RequiresVerification = 0x1,
// Does this effect want to verify all function calls originating in
// functions having this effect? TODO: maybe always true, vestigial.
FE_VerifyCalls = 0x2,
- // Can verification inspect callees' implementations? (e.g. nolock: yes,
- // tcb+types: no)
+ // Can verification inspect callees' implementations? (e.g. nonblocking:
+ // yes, tcb+types: no)
FE_InferrableOnCallees = 0x4,
// Language constructs which effects can diagnose as disallowed.
@@ -4515,7 +4516,7 @@ class FunctionEffect {
/// Flags describing behaviors of the effect.
Flags flags() const { return Flags_; }
- /// The description printed in diagnostics, e.g. 'nolock'.
+ /// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
/// A hashable representation.
@@ -4548,7 +4549,8 @@ class FunctionEffect {
/// on an implicit function like a default constructor.
///
/// This is only used if the effect has FE_InferrableOnCallees flag set.
- /// Example: This allows nolock(false) to prevent inference for the function.
+ /// Example: This allows nonblocking(false) to prevent inference for the
+ /// function.
bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
// Called if FE_VerifyCalls flag is set; return false for success. When true
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 7fe7b0eae7668..9f38a09838434 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1402,21 +1402,21 @@ def CXX11NoReturn : InheritableAttr {
let Documentation = [CXX11NoReturnDocs];
}
-def NoLock : TypeAttr {
- let Spellings = [CXX11<"clang", "nolock">,
- C23<"clang", "nolock">,
- GNU<"clang_nolock">];
+def NonBlocking : TypeAttr {
+ let Spellings = [CXX11<"clang", "nonblocking">,
+ C23<"clang", "nonblocking">,
+ GNU<"clang_nonblocking">];
let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
- let Documentation = [NoLockNoAllocDocs];
+ let Documentation = [NonBlockingNonAllocatingDocs];
}
-def NoAlloc : TypeAttr {
- let Spellings = [CXX11<"clang", "noalloc">,
- C23<"clang", "noalloc">,
- GNU<"clang_noalloc">];
+def NonAllocating : TypeAttr {
+ let Spellings = [CXX11<"clang", "nonallocating">,
+ C23<"clang", "nonallocating">,
+ GNU<"clang_nonallocating">];
let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
- let Documentation = [NoLockNoAllocDocs];
+ let Documentation = [NonBlockingNonAllocatingDocs];
}
// Similar to CUDA, OpenCL attributes do not receive a [[]] spelling because
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index ee70c17d62190..4b7b40feaaf26 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1,4 +1,5 @@
-//==--- AttrDocs.td - Attribute documentation ----------------------------===//
+
+ //==--- AttrDocs.td - Attribute documentation ----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -8045,17 +8046,17 @@ requirement:
}];
}
-def NoLockNoAllocDocs : Documentation {
+def NonBlockingNonAllocatingDocs : Documentation {
let Category = DocCatType;
let Content = [{
-The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks,
+The ``nonblocking`` and ``nonallocating`` 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
+Like ``noexcept``, ``nonblocking`` and ``nonallocating`` 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
+``[[clang::nonblocking(true)]]`` is equivalent to ``[[clang::nonblocking]]``, 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 a1bb05471dd6c..59eac4e2c1442 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1515,7 +1515,7 @@ 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.
+// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;
// Warnings and notes InstallAPI verification.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ef57eb9ad2cde..f2cf601b95f37 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10870,7 +10870,7 @@ def warn_func_effect_false_on_type : Warning<
def note_func_effect_from_template : Note<
"in template expansion here">;
-// spoofing nolock/noalloc
+// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
InGroup<FunctionEffects>;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 9c3913534692c..82bd24f49acd8 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10517,7 +10517,7 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
if (lproto->getMethodQuals() != rproto->getMethodQuals())
return {};
- // TODO: (nolock) Does anything need to be done with FunctionEffects?
+ // TODO: (nonblocking) Does anything need to be done with FunctionEffects?
SmallVector<FunctionProtoType::ExtParameterInfo, 4> newParamInfos;
bool canUseLeft, canUseRight;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 3517979ddce95..4455a06421a62 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5029,14 +5029,14 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
FunctionEffect::FunctionEffect(Type T)
: Type_(unsigned(T)), Flags_(0), Padding(0) {
switch (T) {
- case Type::NoLockTrue:
+ case Type::NonBlocking:
Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars;
break;
- case Type::NoAllocTrue:
- // Same as NoLockTrue, except without FE_ExcludeStaticLocalVars
+ case Type::NonAllocating:
+ // Same as NonBlocking, except without FE_ExcludeStaticLocalVars
Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
FE_ExcludeThreadLocalVars;
@@ -5050,10 +5050,10 @@ StringRef FunctionEffect::name() const {
switch (type()) {
default:
return "";
- case Type::NoLockTrue:
- return "nolock";
- case Type::NoAllocTrue:
- return "noalloc";
+ case Type::NonBlocking:
+ return "nonblocking";
+ case Type::NonAllocating:
+ return "nonallocating";
}
}
@@ -5063,18 +5063,18 @@ bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
FunctionEffectSet NewFX) const {
switch (type()) {
- case Type::NoAllocTrue:
- // noalloc can't be added (spoofed) during a conversion, unless we have
- // nolock
+ case Type::NonAllocating:
+ // nonallocating can't be added (spoofed) during a conversion, unless we
+ // have nonblocking
if (Adding) {
for (const auto &Effect : OldFX) {
- if (Effect.type() == Type::NoLockTrue)
+ if (Effect.type() == Type::NonBlocking)
return false;
}
}
[[fallthrough]];
- case Type::NoLockTrue:
- // nolock can't be added (spoofed) during a conversion
+ case Type::NonBlocking:
+ // nonblocking can't be added (spoofed) during a conversion
return Adding;
default:
break;
@@ -5088,9 +5088,9 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
const FunctionDecl &NewFunction,
FunctionEffectSet NewFX) const {
switch (type()) {
- case Type::NoAllocTrue:
- case Type::NoLockTrue:
- // nolock/noalloc can't be removed in a redeclaration
+ case Type::NonAllocating:
+ case Type::NonBlocking:
+ // nonblocking/nonallocating can't be removed in a redeclaration
// adding -> false, removing -> true (diagnose)
return !Adding;
default:
@@ -5103,9 +5103,9 @@ FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
switch (type()) {
- case Type::NoAllocTrue:
- case Type::NoLockTrue:
- // nolock/noalloc can't be removed from an override
+ case Type::NonAllocating:
+ case Type::NonBlocking:
+ // nonblocking/nonallocating can't be removed from an override
// adding -> false, removing -> true (diagnose)
return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
default:
@@ -5117,11 +5117,11 @@ FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
bool FunctionEffect::canInferOnFunction(QualType QT,
const TypeSourceInfo *FType) const {
switch (type()) {
- case Type::NoAllocTrue:
- case Type::NoLockTrue: {
- // Does the sugar have nolock(false) / noalloc(false) ?
- if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
- : attr::Kind::NoAlloc)) {
+ case Type::NonAllocating:
+ case Type::NonBlocking: {
+ // Does the sugar have nonblocking(false) / nonallocating(false) ?
+ if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::NonBlocking
+ : attr::Kind::NonAllocating)) {
return false;
}
@@ -5137,14 +5137,14 @@ bool FunctionEffect::canInferOnFunction(QualType QT,
bool FunctionEffect::diagnoseFunctionCall(bool Direct,
FunctionEffectSet CalleeFX) const {
switch (type()) {
- case Type::NoAllocTrue:
- case Type::NoLockTrue: {
+ case Type::NonAllocating:
+ case Type::NonBlocking: {
const Type CallerType = type();
for (const auto &Effect : CalleeFX) {
const Type ET = Effect.type();
// Does callee have same or stronger constraint?
if (ET == CallerType ||
- (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
+ (CallerType == Type::NonAllocating && ET == Type::NonBlocking)) {
return false; // no diagnostic
}
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index c69e3412f127d..16290808ee508 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1996,11 +1996,11 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
// Nothing to print for this attribute.
case attr::HLSLParamModifier:
break;
- case attr::NoLock:
- OS << "clang_nolock(false)";
+ case attr::NonBlocking:
+ OS << "clang_nonblocking(false)";
break;
- case attr::NoAlloc:
- OS << "clang_noalloc(false)";
+ case attr::NonAllocating:
+ OS << "clang_nonallocating(false)";
break;
}
OS << "))";
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 10228715ea1bb..9b222595a7302 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2765,7 +2765,7 @@ class CompleteFunctionAnalysis {
};
/*
- TODO: nolock and noalloc imply noexcept
+ TODO: nonblocking and nonallocating imply noexcept
if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
!= clang::CT_Cannot) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 47689d7852131..535663f77a4a2 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -145,8 +145,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
#define FUNCTION_TYPE_ATTRS_CASELIST \
case ParsedAttr::AT_NSReturnsRetained: \
case ParsedAttr::AT_NoReturn: \
- case ParsedAttr::AT_NoLock: \
- case ParsedAttr::AT_NoAlloc: \
+ case ParsedAttr::AT_NonBlocking: \
+ case ParsedAttr::AT_NonAllocating: \
case ParsedAttr::AT_Regparm: \
case ParsedAttr::AT_CmseNSCall: \
case ParsedAttr::AT_ArmStreaming: \
@@ -214,10 +214,10 @@ namespace {
/// validating that noderef was used on a pointer or array.
bool parsedNoDeref;
- // Flags to diagnose illegal permutations of nolock(cond) and noalloc(cond).
- // Manual logic for finding previous attributes would be more complex,
- // unless we transformed nolock/noalloc(false) into distinct separate
- // attributes from the ones which are parsed.
+ // Flags to diagnose illegal permutations of nonblocking(cond) and
+ // nonallocating(cond). Manual logic for finding previous attributes would
+ // be more complex, unless we transformed nonblocking/nonallocating(false)
+ // into distinct separate attributes from the ones which are parsed.
BoolAttrState parsedNolock : 2;
BoolAttrState parsedNoalloc : 2;
@@ -7977,14 +7977,15 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
-static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
- ParsedAttr &PAttr, QualType &type,
- FunctionTypeUnwrapper &unwrapped) {
+static bool
+handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
+ ParsedAttr &PAttr, QualType &type,
+ FunctionTypeUnwrapper &unwrapped) {
// Delay if this is not a function type.
if (!unwrapped.isFunctionType())
return false;
- const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
+ const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking;
Sema &S = state.getSema();
// Require FunctionProtoType
@@ -8017,58 +8018,62 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
return true;
};
- // check nolock(true) against nolock(false), and same for noalloc
+ // check nonblocking(true) against nonblocking(false), and same for
+ // nonallocating
const BoolAttrState newState =
Cond ? BoolAttrState::True : BoolAttrState::False;
const BoolAttrState oppositeNewState =
Cond ? BoolAttrState::False : BoolAttrState::True;
- if (isNoLock) {
+ if (isNonBlocking) {
if (state.getParsedNolock() == oppositeNewState) {
- return incompatible("nolock(true)", "nolock(false)");
+ return incompatible("nonblocking(true)", "nonblocking(false)");
}
- // also check nolock(true) against noalloc(false)
+ // also check nonblocking(true) against nonallocating(false)
if (Cond && state.getParsedNoalloc() == BoolAttrState::False) {
- return incompatible("nolock(true)", "noalloc(false)");
+ return incompatible("nonblocking(true)", "nonallocating(false)");
}
state.setParsedNolock(newState);
} else {
if (state.getParsedNoalloc() == oppositeNewState) {
- return incompatible("noalloc(true)", "noalloc(false)");
+ return incompatible("nonallocating(true)", "nonallocating(false)");
}
- // also check nolock(true) against noalloc(false)
+ // also check nonblocking(true) against nonallocating(false)
if (state.getParsedNolock() == BoolAttrState::True) {
if (!Cond) {
- return incompatible("nolock(true)", "noalloc(false)");
+ return incompatible("nonblocking(true)", "nonallocating(false)");
}
- // Ignore noalloc(true) since we already have nolock(true).
+ // Ignore nonallocating(true) since we already have nonblocking(true).
return true;
}
state.setParsedNoalloc(newState);
}
if (!Cond) {
- // nolock(false) and noalloc(false) are represented as sugar, with
- // AttributedType
+ // nonblocking(false) and nonallocating(false) are represented as sugar,
+ // with AttributedType
Attr *A = nullptr;
- if (isNoLock) {
- A = NoLockAttr::Create(S.Context, false);
+ if (isNonBlocking) {
+ A = NonBlockingAttr::Create(S.Context, false);
} else {
- A = NoAllocAttr::Create(S.Context, false);
+ A = NonAllocatingAttr::Create(S.Context, false);
}
type = state.getAttributedType(A, type, type);
return true;
}
- // nolock(true) and noalloc(true) are represented as FunctionEffects, in a
- // FunctionEffectSet attached to a FunctionProtoType.
- const FunctionEffect NewEffect(isNoLock ? FunctionEffect::Type::NoLockTrue
- : FunctionEffect::Type::NoAllocTrue);
+ // nonblocking(true) and nonallocating(true) are represented as
+ // FunctionEffects, in a FunctionEffectSet attached to a FunctionProtoType.
+ const FunctionEffect NewEffect(isNonBlocking
+ ? FunctionEffect::Type::NonBlocking
+ : FunctionEffect::Type::NonAllocating);
MutableFunctionEffectSet NewFX(NewEffect);
if (EPI.FunctionEffects) {
- // Preserve any previous effects - except noalloc, when we are adding nolock
+ // Preserve any previous effects - except nonallocating, when we are adding
+ // nonblocking
for (const auto &Effect : EPI.FunctionEffects) {
- if (!(isNoLock && Effect.type() == FunctionEffect::Type::NoAllocTrue))
+ if (!(isNonBlocking &&
+ Effect.type() == FunctionEffect::Type::NonAllocating))
NewFX.insert(Effect);
}
}
@@ -8394,9 +8399,9 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
- if (attr.getKind() == ParsedAttr::AT_NoLock ||
- attr.getKind() == ParsedAttr::AT_NoAlloc) {
- return handleNoLockNoAllocTypeAttr(state, attr, type, unwrapped);
+ if (attr.getKind() == ParsedAttr::AT_NonBlocking ||
+ attr.getKind() == ParsedAttr::AT_NonAllocating) {
+ return handleNonBlockingNonAllocatingTypeAttr(state, attr, type, unwrapped);
}
// Delay if the type didn't work out to a function.
diff --git a/clang/test/Sema/attr-nolock-constraints.mm b/clang/test/Sema/attr-nolock-constraints.mm
index e52b50eac519f..4be3f8c95c9ad 100644
--- a/clang/test/Sema/attr-nolock-constraints.mm
+++ b/clang/test/Sema/attr-nolock-constraints.mm
@@ -2,95 +2,95 @@
// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
// the AnalysisBasedWarnings pass from running at all.
-#if !__has_attribute(clang_nolock)
-#error "the 'nolock' attribute is not available"
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
#endif
// --- CONSTRAINTS ---
-void nl1() [[clang::nolock]]
+void nl1() [[clang::nonblocking]]
{
- auto* pInt = new int; // expected-warning {{'nolock' function must not allocate or deallocate memory}}
+ auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
}
-void nl2() [[clang::nolock]]
+void nl2() [[clang::nonblocking]]
{
- static int global; // expected-warning {{'nolock' function must not have static locals}}
+ static int global; // expected-warning {{'nonblocking' function must not have static locals}}
}
-void nl3() [[clang::nolock]]
+void nl3() [[clang::nonblocking]]
{
try {
- throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
}
- catch (...) { // expected-warning {{'nolock' function must not throw or catch exceptions}}
+ catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
}
}
void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void nl4() [[clang::nolock]]
+void nl4() [[clang::nonblocking]]
{
nl4_inline(); // OK
- nl4_not_inline(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
struct HasVirtual {
- virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nolock'}}
+ virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
};
-void nl5() [[clang::nolock]]
+void nl5() [[clang::nonblocking]]
{
HasVirtual hv;
- hv.unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
-void nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
void nl6_transitively_unsafe()
{
- nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
+ nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
}
-void nl6() [[clang::nolock]]
+void nl6() [[clang::nonblocking]]
{
- nl6_transitively_unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
thread_local int tl_var{ 42 };
-bool tl_test() [[clang::nolock]]
+bool tl_test() [[clang::nonblocking]]
{
- return tl_var > 0; // expected-warning {{'nolock' function must not use thread-local variables}}
+ return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
}
void nl7()
{
// Make sure we verify blocks
- auto blk = ^() [[clang::nolock]] {
- throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
+ auto blk = ^() [[clang::nonblocking]] {
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
};
}
void nl8()
{
// Make sure we verify lambdas
- auto lambda = []() [[clang::nolock]] {
- throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
+ auto lambda = []() [[clang::nonblocking]] {
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
};
}
// Make sure template expansions are found and verified.
template <typename T>
struct Adder {
- static T add_explicit(T x, T y) [[clang::nolock]]
+ static T add_explicit(T x, T y) [[clang::nonblocking]]
{
- return x + y; // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
static T add_implicit(T x, T y)
{
- return x + y; // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
+ return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
}
};
@@ -98,7 +98,7 @@ static T add_implicit(T x, T y)
friend Stringy operator+(const Stringy& x, const Stringy& y)
{
// Do something inferably unsafe
- auto* z = new char[42]; // expected-note {{function cannot be inferred 'nolock' because it allocates/deallocates memory}}
+ auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}}
return {};
}
};
@@ -107,82 +107,82 @@ static T add_implicit(T x, T y)
friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
{
// Do something inferably unsafe
- throw 42; // expected-note {{function cannot be inferred 'nolock' because it throws or catches exceptions}}
+ throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
}
};
-void nl9() [[clang::nolock]]
+void nl9() [[clang::nonblocking]]
{
Adder<int>::add_explicit(1, 2);
Adder<int>::add_implicit(1, 2);
Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
- Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function must not call non-'nolock' function}} \
+ Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \
expected-note {{in template expansion here}}
}
void nl10(
- void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nolock'}}
- void (*fp2)() [[clang::nolock]]
- ) [[clang::nolock]]
+ void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
+ void (*fp2)() [[clang::nonblocking]]
+ ) [[clang::nonblocking]]
{
- fp1(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
fp2();
}
-// Interactions with nolock(false)
-void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{function does not permit inference of 'nolock'}}
+// Interactions with nonblocking(false)
+void nl11_no_inference() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
{
}
-void nl11() [[clang::nolock]]
+void nl11() [[clang::nonblocking]]
{
- nl11_no_inference(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
+ nl11_no_inference(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
// Verify that when attached to a redeclaration, the attribute successfully attaches.
void nl12() {
- static int x; // expected-warning {{'nolock' function must not have static locals}}
+ static int x; // expected-warning {{'nonblocking' function must not have static locals}}
}
-void nl12() [[clang::nolock]];
-void nl13() [[clang::nolock]] { nl12(); }
+void nl12() [[clang::nonblocking]];
+void nl13() [[clang::nonblocking]] { nl12(); }
// Objective-C
@interface OCClass
- (void)method;
@end
-void nl14(OCClass *oc) [[clang::nolock]] {
- [oc method]; // expected-warning {{'nolock' function must not access an ObjC method or property}}
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+ [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
}
void nl15(OCClass *oc) {
- [oc method]; // expected-note {{function cannot be inferred 'nolock' because it accesses an ObjC method or property}}
+ [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
}
-void nl16(OCClass *oc) [[clang::nolock]] {
- nl15(oc); // expected-warning {{'nolock' function must not call non-'nolock' function 'nl15'}}
+void nl16(OCClass *oc) [[clang::nonblocking]] {
+ nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
}
// C++ member function pointers
struct PTMFTester {
- typedef void (PTMFTester::*ConvertFunction)() [[clang::nolock]];
+ typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
- void convert() [[clang::nolock]];
+ void convert() [[clang::nonblocking]];
ConvertFunction mConvertFunc;
};
-void PTMFTester::convert() [[clang::nolock]]
+void PTMFTester::convert() [[clang::nonblocking]]
{
(this->*mConvertFunc)();
}
// Block variables
-void nl17(void (^blk)() [[clang::nolock]]) [[clang::nolock]] {
+void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
blk();
}
// References to blocks
-void nl18(void (^block)() [[clang::nolock]]) [[clang::nolock]]
+void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
{
auto &ref = block;
ref();
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 98a11067305d5..1d7881b30411a 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -1,74 +1,74 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
-#if !__has_attribute(clang_nolock)
-#error "the 'nolock' attribute is not available"
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
#endif
// --- ATTRIBUTE SYNTAX: SUBJECTS ---
-int nl_var [[clang::nolock]]; // expected-warning {{'nolock' only applies to function types; type here is 'int'}}
-struct nl_struct {} [[clang::nolock]]; // expected-warning {{attribute 'nolock' is ignored, place it after "struct" to apply attribute to type declaration}}
-struct [[clang::nolock]] nl_struct2 {}; // expected-error {{'nolock' attribute cannot be applied to a declaration}}
+int nl_var [[clang::nonblocking]]; // expected-warning {{'nonblocking' only applies to function types; type here is 'int'}}
+struct nl_struct {} [[clang::nonblocking]]; // expected-warning {{attribute 'nonblocking' is ignored, place it after "struct" to apply attribute to type declaration}}
+struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' attribute cannot be applied to a declaration}}
// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
-// Check invalid combinations of nolock/noalloc attributes
+// Check invalid combinations of nonblocking/nonallocating attributes
-void nl_true_false_1() [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
-void nl_true_false_2() [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::nonblocking(false)]]; // expected-error {{nonblocking(true) and nonblocking(false) attributes are not compatible}}
+void nl_true_false_2() [[clang::nonblocking(false)]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking(true) and nonblocking(false) attributes are not compatible}}
-void na_true_false_1() [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
-void na_true_false_2() [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+void na_true_false_1() [[clang::nonallocating(true)]] [[clang::nonallocating(false)]]; // expected-error {{nonallocating(true) and nonallocating(false) attributes are not compatible}}
+void na_true_false_2() [[clang::nonallocating(false)]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating(true) and nonallocating(false) attributes are not compatible}}
-void nl_true_na_true_1() [[clang::nolock]] [[clang::noalloc]];
-void nl_true_na_true_2() [[clang::noalloc]] [[clang::nolock]];
+void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]];
+void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]];
-void nl_true_na_false_1() [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
-void nl_true_na_false_2() [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+void nl_true_na_false_1() [[clang::nonblocking]] [[clang::nonallocating(false)]]; // expected-error {{nonblocking(true) and nonallocating(false) attributes are not compatible}}
+void nl_true_na_false_2() [[clang::nonallocating(false)]] [[clang::nonblocking]]; // expected-error {{nonblocking(true) and nonallocating(false) attributes are not compatible}}
-void nl_false_na_true_1() [[clang::nolock(false)]] [[clang::noalloc]];
-void nl_false_na_true_2() [[clang::noalloc]] [[clang::nolock(false)]];
+void nl_false_na_true_1() [[clang::nonblocking(false)]] [[clang::nonallocating]];
+void nl_false_na_true_2() [[clang::nonallocating]] [[clang::nonblocking(false)]];
-void nl_false_na_false_1() [[clang::nolock(false)]] [[clang::noalloc(false)]];
-void nl_false_na_false_2() [[clang::noalloc(false)]] [[clang::nolock(false)]];
+void nl_false_na_false_1() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]];
+void nl_false_na_false_2() [[clang::nonallocating(false)]] [[clang::nonblocking(false)]];
// --- TYPE CONVERSIONS ---
void unannotated();
-void nolock() [[clang::nolock]];
-void noalloc() [[clang::noalloc]];
+void nonblocking() [[clang::nonblocking]];
+void nonallocating() [[clang::nonallocating]];
void type_conversions()
{
// It's fine to remove a performance constraint.
void (*fp_plain)();
fp_plain = unannotated;
- fp_plain = nolock;
- fp_plain = noalloc;
-
- // Adding/spoofing nolock is unsafe.
- void (*fp_nolock)() [[clang::nolock]];
- fp_nolock = nolock;
- fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
- fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-
- // Adding/spoofing noalloc is unsafe.
- void (*fp_noalloc)() [[clang::noalloc]];
- fp_noalloc = noalloc;
- fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
- fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
+ fp_plain = nonblocking;
+ fp_plain = nonallocating;
+
+ // Adding/spoofing nonblocking is unsafe.
+ void (*fp_nonblocking)() [[clang::nonblocking]];
+ fp_nonblocking = nonblocking;
+ fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
+ fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
+
+ // Adding/spoofing nonallocating is unsafe.
+ void (*fp_nonallocating)() [[clang::nonallocating]];
+ fp_nonallocating = nonallocating;
+ fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated;
+ fp_nonallocating = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}}
}
#ifdef __cplusplus
-// There was a bug: noexcept and nolock could be individually removed in conversion, but not both
+// There was a bug: noexcept and nonblocking could be individually removed in conversion, but not both
void type_conversions_2()
{
auto receives_fp = [](void (*fp)()) {
};
auto ne = +[]() noexcept {};
- auto nl = +[]() [[clang::nolock]] {};
- auto nl_ne = +[]() noexcept [[clang::nolock]] {};
+ auto nl = +[]() [[clang::nonblocking]] {};
+ auto nl_ne = +[]() noexcept [[clang::nonblocking]] {};
receives_fp(ne);
receives_fp(nl);
@@ -82,14 +82,14 @@ void type_conversions_2()
#ifdef __cplusplus
struct Base {
virtual void f1();
- virtual void nolock() noexcept [[clang::nolock]];
- virtual void noalloc() noexcept [[clang::noalloc]];
+ virtual void nonblocking() noexcept [[clang::nonblocking]];
+ virtual void nonallocating() noexcept [[clang::nonallocating]];
};
struct Derived : public Base {
- void f1() [[clang::nolock]] override;
- void nolock() noexcept override;
- void noalloc() noexcept override;
+ void f1() [[clang::nonblocking]] override;
+ void nonblocking() noexcept override;
+ void nonallocating() noexcept override;
};
#endif // __cplusplus
@@ -98,12 +98,12 @@ struct Derived : public Base {
#ifdef __cplusplus
// In C++, the third declaration gets seen as a redeclaration of the second.
void f2();
-void f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
-void f2(); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+void f2() [[clang::nonblocking]]; // expected-note {{previous declaration is here}}
+void f2(); // expected-warning {{attribute 'nonblocking' on function does not match previous declaration}}
#else
// In C, the third declaration is redeclaration of the first (?).
void f2();
-void f2() [[clang::nolock]];
+void f2() [[clang::nonblocking]];
void f2();
#endif
// Note: we verify that the attribute is actually seen during the constraints tests.
diff --git a/clang/test/Sema/attr-nolock-syntax.cpp b/clang/test/Sema/attr-nolock-syntax.cpp
index 8deb3e88cc7d2..4e486c5a973ab 100644
--- a/clang/test/Sema/attr-nolock-syntax.cpp
+++ b/clang/test/Sema/attr-nolock-syntax.cpp
@@ -10,86 +10,86 @@
namespace square_brackets {
// On the type of the FunctionDecl
-void nl_function() [[clang::nolock]];
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+void nl_function() [[clang::nonblocking]];
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
// On the type of the VarDecl holding a function pointer
-void (*nl_func_a)() [[clang::nolock]];
-// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+void (*nl_func_a)() [[clang::nonblocking]];
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
// Check alternate attribute type and placement
-__attribute__((clang_nolock)) void (*nl_func_b)();
-// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nolock))'
+__attribute__((clang_nonblocking)) void (*nl_func_b)();
+// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nonblocking))'
// On the type of the ParmVarDecl of a function parameter
-static void nlReceiver(void (*nl_func)() [[clang::nolock]]);
-// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nolock))'
+static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
// As an AttributedType within the nested types of a typedef
-typedef void (*nl_fp_type)() [[clang::nolock]];
-// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nolock))'
-using nl_fp_talias = void (*)() [[clang::nolock]];
-// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nolock))'
+typedef void (*nl_fp_type)() [[clang::nonblocking]];
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nonblocking))'
+using nl_fp_talias = void (*)() [[clang::nonblocking]];
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nonblocking))'
// From a typedef or typealias, on a VarDecl
nl_fp_type nl_fp_var1;
-// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nolock))'
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nonblocking))'
nl_fp_talias nl_fp_var2;
-// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nolock))'
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nonblocking))'
// On type of a FieldDecl
struct Struct {
- void (*nl_func_field)() [[clang::nolock]];
-// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nolock))'
+ void (*nl_func_field)() [[clang::nonblocking]];
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
};
-// noalloc should be subsumed into nolock
-void nl1() [[clang::nolock]] [[clang::noalloc]];
-// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nolock))'
+// nonallocating should be subsumed into nonblocking
+void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'
-void nl2() [[clang::noalloc]] [[clang::nolock]];
-// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nolock))'
+void nl2() [[clang::nonallocating]] [[clang::nonblocking]];
+// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nonblocking))'
decltype(nl1) nl3;
-// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nolock))'
+// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nonblocking))'
// Attribute propagates from base class virtual method to overrides.
struct Base {
- virtual void nl_method() [[clang::nolock]];
+ virtual void nl_method() [[clang::nonblocking]];
};
struct Derived : public Base {
void nl_method() override;
- // CHECK: CXXMethodDecl {{.*}} nl_method 'void () __attribute__((clang_nolock))'
+ // CHECK: CXXMethodDecl {{.*}} nl_method 'void () __attribute__((clang_nonblocking))'
};
// --- Blocks ---
// On the type of the VarDecl holding a BlockDecl
-void (^nl_block1)() [[clang::nolock]] = ^() [[clang::nolock]] {};
-// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nolock))'
+void (^nl_block1)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] {};
+// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nonblocking))'
-int (^nl_block2)() [[clang::nolock]] = ^() [[clang::nolock]] { return 0; };
-// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nolock))'
+int (^nl_block2)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] { return 0; };
+// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nonblocking))'
// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
static void blockCaller() { nl_block1(); }
-// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
+// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nonblocking))'
// --- Lambdas ---
// On the operator() of a lambda's CXXMethodDecl
-auto nl_lambda = []() [[clang::nolock]] {};
-// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_nolock))' inline
+auto nl_lambda = []() [[clang::nonblocking]] {};
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_nonblocking))' inline
// =========================================================================================
// Square brackets, false
-void nl_func_false() [[clang::nolock(false)]];
-// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
+void nl_func_false() [[clang::nonblocking(false)]];
+// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nonblocking(false)))'
// TODO: This exposes a bug where a type attribute is lost when inferring a lambda's
// return type.
-auto nl_lambda_false = []() [[clang::nolock(false)]] {};
+auto nl_lambda_false = []() [[clang::nonblocking(false)]] {};
} // namespace square_brackets
@@ -101,19 +101,19 @@ auto nl_lambda_false = []() [[clang::nolock(false)]] {};
namespace gnu_style {
// On the type of the FunctionDecl
-void nl_function() __attribute__((clang_nolock));
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+void nl_function() __attribute__((clang_nonblocking));
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
// Alternate placement on the FunctionDecl
-__attribute__((clang_nolock)) void nl_function();
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+__attribute__((clang_nonblocking)) void nl_function();
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
// On the type of the VarDecl holding a function pointer
-void (*nl_func_a)() __attribute__((clang_nolock));
-// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+void (*nl_func_a)() __attribute__((clang_nonblocking));
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
} // namespace gnu_style
-// TODO: Duplicate the above for noalloc
+// TODO: Duplicate the above for nonallocating
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 914c4d09764ee..f33a9fe254053 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-#if !__has_attribute(clang_nolock)
-#error "the 'nolock' attribute is not available"
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
#endif
@@ -13,31 +13,31 @@ struct HasVirtual {
virtual void method();
};
-void nl999(HasVirtual& x) [[clang::nolock]] {
+void nl999(HasVirtual& x) [[clang::nonblocking]] {
x.method();
}
#if 0
- using nl_sugar = int (*)(int) [[clang::nolock]];
+ using nl_sugar = int (*)(int) [[clang::nonblocking]];
void receives_fp_nl(nl_sugar fp) {
}
- int callback(int) noexcept [[clang::nolock]];
+ int callback(int) noexcept [[clang::nonblocking]];
void type_conversions_2()
{
auto receives_fp = [](void (*fp)()) {
};
- //auto receives_fp_nl = [](void (*fp)() [[clang::nolock]]) {
+ //auto receives_fp_nl = [](void (*fp)() [[clang::nonblocking]]) {
//};
auto ne = +[]() noexcept {};
- auto nl = +[]() [[clang::nolock]] {};
- //auto nl_ne = +[](int x) noexcept [[clang::nolock]] -> int { return x; };
+ auto nl = +[]() [[clang::nonblocking]] {};
+ //auto nl_ne = +[](int x) noexcept [[clang::nonblocking]] -> int { return x; };
receives_fp(ne);
receives_fp(nl);
@@ -51,11 +51,11 @@ void type_conversions_2()
struct S {
void foo();
// void foo() noexcept; // error, redeclaration
- // void foo() [[clang::nolock]]; // error, redeclaration
+ // void foo() [[clang::nonblocking]]; // error, redeclaration
using FP = void (*)();
using FPNE = void (*)() noexcept;
- using FPNL = void (*)() [[clang::nolock]];
+ using FPNL = void (*)() [[clang::nonblocking]];
void bar(FP x);
void bar(FPNE x); // This is a distinct overload
@@ -101,9 +101,9 @@ typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
template <typename T>
-struct is_nolock;
+struct is_nonblocking;
-void g() [[clang::nolock]];
-void h() [[clang::nolock(false)]];
+void g() [[clang::nonblocking]];
+void h() [[clang::nonblocking(false)]];
#endif
>From c02635584ee7c1df0adf9409fdbfd2aaf2c9cc15 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 3 Apr 2024 07:50:17 -0700
Subject: [PATCH 20/71] Remove vestigial FunctionEffect flags
---
clang/include/clang/AST/Type.h | 29 ++++--------
clang/lib/AST/Type.cpp | 11 ++---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 58 ++++++++++--------------
clang/lib/Sema/SemaDecl.cpp | 11 +----
clang/lib/Sema/SemaOverload.cpp | 1 -
5 files changed, 40 insertions(+), 70 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index cdfeb8eb71539..79cfb407fac5c 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4458,25 +4458,16 @@ class FunctionEffect {
// would OR together the bits of multiple effects.)
using Flags = unsigned;
enum FlagBit : unsigned {
- // Some effects require verification, e.g. nonblocking(true); others might
- // not? (no example yet; TODO: maybe always true, vestigial from
- // nonblocking(false)).
- FE_RequiresVerification = 0x1,
-
- // Does this effect want to verify all function calls originating in
- // functions having this effect? TODO: maybe always true, vestigial.
- FE_VerifyCalls = 0x2,
-
// Can verification inspect callees' implementations? (e.g. nonblocking:
// yes, tcb+types: no)
- FE_InferrableOnCallees = 0x4,
+ FE_InferrableOnCallees = 0x1,
// Language constructs which effects can diagnose as disallowed.
- FE_ExcludeThrow = 0x8,
- FE_ExcludeCatch = 0x10,
- FE_ExcludeObjCMessageSend = 0x20,
- FE_ExcludeStaticLocalVars = 0x40,
- FE_ExcludeThreadLocalVars = 0x80
+ FE_ExcludeThrow = 0x2,
+ FE_ExcludeCatch = 0x4,
+ FE_ExcludeObjCMessageSend = 0x8,
+ FE_ExcludeStaticLocalVars = 0x10,
+ FE_ExcludeThreadLocalVars = 0x20
};
/// Describes the result of effects differing between a base class's virtual
@@ -4553,10 +4544,10 @@ class FunctionEffect {
/// function.
bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
- // Called if FE_VerifyCalls flag is set; 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).
+ // 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 diagnoseFunctionCall(bool Direct, FunctionEffectSet CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 4455a06421a62..939562ab2d176 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5030,16 +5030,15 @@ FunctionEffect::FunctionEffect(Type T)
: Type_(unsigned(T)), Flags_(0), Padding(0) {
switch (T) {
case Type::NonBlocking:
- Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
- FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
- FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars;
+ Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
+ FE_ExcludeThreadLocalVars;
break;
case Type::NonAllocating:
// Same as NonBlocking, except without FE_ExcludeStaticLocalVars
- Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
- FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
- FE_ExcludeThreadLocalVars;
+ Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
break;
default:
break;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 9b222595a7302..f76571afcfa2f 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2620,16 +2620,10 @@ class PendingFunctionAnalysis {
PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
FunctionEffectSet AllInferrableEffectsToVerify) {
ASTContext &Ctx = Sem.getASTContext();
- MutableFunctionEffectSet FX;
- for (const auto &Effect : CInfo.Effects) {
- if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
- FX.insert(Effect);
- }
- }
- DeclaredVerifiableEffects = Ctx.getUniquedFunctionEffectSet(FX);
+ DeclaredVerifiableEffects = CInfo.Effects;
// Check for effects we are not allowed to infer
- FX.clear();
+ MutableFunctionEffectSet FX;
TypeSourceInfo *TSI = nullptr;
if (const auto *DD = dyn_cast<DeclaratorDecl>(CInfo.CDecl)) {
TSI = DD->getTypeSourceInfo();
@@ -3050,26 +3044,24 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
const auto Flags = Effect.flags();
- if (Flags & FunctionEffect::FE_VerifyCalls) {
- const bool diagnose =
- Effect.diagnoseFunctionCall(DirectCall, CalleeEffects);
- if (diagnose) {
- // If inference is not allowed, or the target is indirect (virtual
- // method/function ptr?), generate a diagnostic now.
- if (!IsInferencePossible ||
- !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
- if (Callee.FuncType == SpecialFuncType::None) {
- PFA.checkAddDiagnostic(Inferring,
- {Effect, DiagnosticID::CallsUnsafeDecl,
- CallLoc, Callee.CDecl});
- } else {
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
- }
+ const bool diagnose =
+ Effect.diagnoseFunctionCall(DirectCall, CalleeEffects);
+ if (diagnose) {
+ // If inference is not allowed, or the target is indirect (virtual
+ // method/function ptr?), generate a diagnostic now.
+ if (!IsInferencePossible ||
+ !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
+ if (Callee.FuncType == SpecialFuncType::None) {
+ PFA.checkAddDiagnostic(
+ Inferring,
+ {Effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl});
} else {
- // Inference is allowed and necessary; defer it.
- PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
}
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
}
}
};
@@ -3327,14 +3319,12 @@ class Analyzer {
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- if (Effect.flags() & FunctionEffect::FE_VerifyCalls) {
- if (FPT == nullptr ||
- Effect.diagnoseFunctionCall(
- /*direct=*/false, FPT->getFunctionEffects())) {
- addDiagnosticInner(Inferring, Effect,
- DiagnosticID::CallsDisallowedExpr,
- Call->getBeginLoc());
- }
+ if (FPT == nullptr ||
+ Effect.diagnoseFunctionCall(
+ /*direct=*/false, FPT->getFunctionEffects())) {
+ addDiagnosticInner(Inferring, Effect,
+ DiagnosticID::CallsDisallowedExpr,
+ Call->getBeginLoc());
}
};
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 3df4d15a028c1..d6d1cd2dd68ad 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11153,16 +11153,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
// Filter out declarations that the FunctionEffect analysis should skip
// and not verify.
- bool FXNeedVerification = false;
- for (const auto &Effect : FX) {
- if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
- AllEffectsToVerify.insert(Effect);
- FXNeedVerification = true;
- }
- }
- if (!FXNeedVerification) {
- return;
- }
+ AllEffectsToVerify |= FX;
// Record the declaration for later analysis.
DeclsWithEffectsToVerify.push_back(D);
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index bcbcb8729c6d7..02a4f106a648f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1788,7 +1788,6 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
/// type.
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
QualType &ResultTy) {
-
if (Context.hasSameUnqualifiedType(FromType, ToType))
return false;
>From 02a3b7aa689c33220bcb266f6d7dfbb469939198 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 4 Apr 2024 09:17:11 -0700
Subject: [PATCH 21/71] ArrayRef<const FunctionEffect> ->
ArrayRef<FunctionEffect> (it's already const)
---
clang/include/clang/AST/ASTContext.h | 6 +++---
clang/include/clang/AST/Type.h | 10 ++++------
clang/lib/AST/ASTContext.cpp | 6 +++---
3 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 15cb320fb0e25..de984ace425eb 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -467,10 +467,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
Module *CurrentCXXNamedModule = nullptr;
class FunctionEffectSetUniquing {
- llvm::DenseSet<llvm::ArrayRef<const FunctionEffect>> Set;
+ llvm::DenseSet<llvm::ArrayRef<FunctionEffect>> Set;
public:
- FunctionEffectSet getUniqued(llvm::ArrayRef<const FunctionEffect> FX);
+ FunctionEffectSet getUniqued(llvm::ArrayRef<FunctionEffect> FX);
~FunctionEffectSetUniquing();
};
@@ -1084,7 +1084,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Get or create a uniqued, immutable FunctionEffectSet.
FunctionEffectSet
- getUniquedFunctionEffectSet(llvm::ArrayRef<const FunctionEffect> FX) {
+ getUniquedFunctionEffectSet(llvm::ArrayRef<FunctionEffect> FX) {
return UniquedFunctionEffectSet.getUniqued(FX);
}
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 79cfb407fac5c..657f3ee6fc779 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4578,9 +4578,7 @@ class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
MutableFunctionEffectSet &operator|=(FunctionEffectSet RHS);
- operator llvm::ArrayRef<const FunctionEffect>() const {
- return {data(), size()};
- }
+ operator llvm::ArrayRef<FunctionEffect>() const { return {data(), size()}; }
};
/// A constant, uniqued set of FunctionEffect instances.
@@ -4590,11 +4588,11 @@ class FunctionEffectSet {
private:
friend class ASTContext; // so it can call the private constructor
- explicit FunctionEffectSet(llvm::ArrayRef<const FunctionEffect> Array)
+ explicit FunctionEffectSet(llvm::ArrayRef<FunctionEffect> Array)
: Impl(Array) {}
// Points to a separately allocated array, uniqued.
- llvm::ArrayRef<const FunctionEffect> Impl;
+ llvm::ArrayRef<FunctionEffect> Impl;
public:
using Differences = SmallVector<std::pair<FunctionEffect, /*added=*/bool>>;
@@ -4618,7 +4616,7 @@ class FunctionEffectSet {
iterator end() const { return Impl.end(); }
- ArrayRef<const FunctionEffect> items() const { return Impl; }
+ ArrayRef<FunctionEffect> items() const { return Impl; }
bool operator==(const FunctionEffectSet &RHS) const {
return Impl.data() == RHS.Impl.data();
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 82bd24f49acd8..26523c1ab370c 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13808,7 +13808,7 @@ StringRef ASTContext::getCUIDHash() const {
return CUIDHash;
}
-using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
+using FunctionEffectSpan = llvm::ArrayRef<FunctionEffect>;
llvm::hash_code hash_value(const FunctionEffect &Effect) {
return llvm::hash_value(Effect.opaqueRepr());
@@ -13854,7 +13854,7 @@ ASTContext::getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX) {
}
FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
- llvm::ArrayRef<const FunctionEffect> FX) {
+ llvm::ArrayRef<FunctionEffect> FX) {
if (FX.empty()) {
return {};
}
@@ -13870,7 +13870,7 @@ FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
std::copy(FX.begin(), FX.end(), Storage);
// Make a new wrapper and insert it into the set.
- llvm::ArrayRef<const FunctionEffect> Arr(Storage, FX.size());
+ llvm::ArrayRef<FunctionEffect> Arr(Storage, FX.size());
auto [InsIter, _] = Set.insert(Arr);
return FunctionEffectSet(*InsIter);
}
>From 8121422216aec6c52582314e72bcfa0e2ca598c8 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 5 Apr 2024 08:04:28 -0700
Subject: [PATCH 22/71] Adding the `blocking` and `allocating` attributes, as
synonyms for `nonblocking/nonallocating(false)`. Rename test files, remove
"wip". Misc. cleanup from PR.
---
clang/include/clang/Basic/Attr.td | 15 +++
clang/lib/AST/Type.cpp | 13 +--
clang/lib/AST/TypePrinter.cpp | 12 +-
clang/lib/Sema/Sema.cpp | 4 +-
clang/lib/Sema/SemaDecl.cpp | 2 -
clang/lib/Sema/SemaExprCXX.cpp | 9 +-
clang/lib/Sema/SemaOverload.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 77 +++++++------
clang/test/Sema/attr-nolock-wip.cpp | 109 ------------------
...nts.mm => attr-nonblocking-constraints.mm} | 0
...ock-sema.cpp => attr-nonblocking-sema.cpp} | 20 ++--
...syntax.cpp => attr-nonblocking-syntax.cpp} | 14 +--
12 files changed, 91 insertions(+), 186 deletions(-)
delete mode 100644 clang/test/Sema/attr-nolock-wip.cpp
rename clang/test/Sema/{attr-nolock-constraints.mm => attr-nonblocking-constraints.mm} (100%)
rename clang/test/Sema/{attr-nolock-sema.cpp => attr-nonblocking-sema.cpp} (72%)
rename clang/test/Sema/{attr-nolock-syntax.cpp => attr-nonblocking-syntax.cpp} (95%)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 9f38a09838434..908edba2222f8 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1419,6 +1419,21 @@ def NonAllocating : TypeAttr {
let Documentation = [NonBlockingNonAllocatingDocs];
}
+def Blocking : TypeAttr {
+ let Spellings = [CXX11<"clang", "blocking">,
+ C23<"clang", "blocking">,
+ GNU<"clang_blocking">];
+
+ let Documentation = [NonBlockingNonAllocatingDocs];
+}
+
+def Allocating : TypeAttr {
+ let Spellings = [CXX11<"clang", "allocating">,
+ C23<"clang", "allocating">,
+ GNU<"clang_allocating">];
+ let Documentation = [NonBlockingNonAllocatingDocs];
+}
+
// 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/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 939562ab2d176..070bedceebedd 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3733,7 +3733,6 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// whether the following bool is the EH spec or part of the arguments.
ID.AddPointer(epi.FunctionEffects.getOpaqueValue());
-
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -5027,7 +5026,7 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
}
FunctionEffect::FunctionEffect(Type T)
- : Type_(unsigned(T)), Flags_(0), Padding(0) {
+ : Type_(static_cast<unsigned>(T)), Flags_(0), Padding(0) {
switch (T) {
case Type::NonBlocking:
Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
@@ -5104,8 +5103,8 @@ FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
switch (type()) {
case Type::NonAllocating:
case Type::NonBlocking:
- // nonblocking/nonallocating can't be removed from an override
- // adding -> false, removing -> true (diagnose)
+ // if added on an override, that's fine and not diagnosed.
+ // if missing from an override (removed), propagate from base to derived.
return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
default:
break;
@@ -5119,8 +5118,8 @@ bool FunctionEffect::canInferOnFunction(QualType QT,
case Type::NonAllocating:
case Type::NonBlocking: {
// Does the sugar have nonblocking(false) / nonallocating(false) ?
- if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::NonBlocking
- : attr::Kind::NonAllocating)) {
+ if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::Blocking
+ : attr::Kind::Allocating)) {
return false;
}
@@ -5264,7 +5263,7 @@ bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
RHS.end());
}
-void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
+LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
for (const auto &Effect : *this) {
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 16290808ee508..c086f9829dc40 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1992,16 +1992,16 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmMveStrictPolymorphism:
OS << "__clang_arm_mve_strict_polymorphism";
break;
+ case attr::Blocking:
+ OS << "clang_blocking";
+ break;
+ case attr::Allocating:
+ OS << "clang_allocating";
+ break;
// Nothing to print for this attribute.
case attr::HLSLParamModifier:
break;
- case attr::NonBlocking:
- OS << "clang_nonblocking(false)";
- break;
- case attr::NonAllocating:
- OS << "clang_nonallocating(false)";
- break;
}
OS << "))";
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index d0eb9194bc65b..2cb1b931cf6fa 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -682,8 +682,8 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
diagnoseZeroToNullptrConversion(Kind, E);
- if (!isCast(CCK) && !E->isNullPointerConstant(
- Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+ if (!isCast(CCK) &&
+ !E->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNotNull)) {
diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d6d1cd2dd68ad..f68e207805b4d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11151,8 +11151,6 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
return;
}
- // Filter out declarations that the FunctionEffect analysis should skip
- // and not verify.
AllEffectsToVerify |= FX;
// Record the declaration for later analysis.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index e0371b9cf64df..76bb78aa8b545 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4953,17 +4953,10 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
// If this conversion sequence succeeded and involved implicitly converting a
// _Nullable type to a _Nonnull one, complain.
- if (!isCast(CCK)) {
+ if (!isCast(CCK))
diagnoseNullableToNonnullConversion(ToType, InitialFromType,
From->getBeginLoc());
- // TODO: This generates a redundant diagnostic for:
- // void (^nl_block0)() NOLOCK = ^(){};
- // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent
- // /* ???*/))
- // diagnoseFunctionEffectConversion(ToType, InitialFromType,
- // From->getBeginLoc());
- }
return From;
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 02a4f106a648f..1396534949caf 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1870,10 +1870,10 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
}
if (getLangOpts().CPlusPlus) {
- // TODO:
// For C, when called from checkPointerTypesForAssignment,
// we need not to change the type, or else even an innocuous cast
// like dropping effects will fail.
+ // TODO: Is this correct?
FromFPT =
dyn_cast<FunctionProtoType>(FromFn); // in case FromFn changed above
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 535663f77a4a2..80123ba9a59a1 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -147,6 +147,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_NoReturn: \
case ParsedAttr::AT_NonBlocking: \
case ParsedAttr::AT_NonAllocating: \
+ case ParsedAttr::AT_Blocking: \
+ case ParsedAttr::AT_Allocating: \
case ParsedAttr::AT_Regparm: \
case ParsedAttr::AT_CmseNSCall: \
case ParsedAttr::AT_ArmStreaming: \
@@ -218,15 +220,15 @@ namespace {
// nonallocating(cond). Manual logic for finding previous attributes would
// be more complex, unless we transformed nonblocking/nonallocating(false)
// into distinct separate attributes from the ones which are parsed.
- BoolAttrState parsedNolock : 2;
- BoolAttrState parsedNoalloc : 2;
+ BoolAttrState parsedNonBlocking : 2;
+ BoolAttrState parsedNonAllocating : 2;
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
: sema(sema), declarator(declarator),
chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
- parsedNolock(BoolAttrState::Unseen),
- parsedNoalloc(BoolAttrState::Unseen) {}
+ parsedNonBlocking(BoolAttrState::Unseen),
+ parsedNonAllocating(BoolAttrState::Unseen) {}
Sema &getSema() const {
return sema;
@@ -353,10 +355,10 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
- void setParsedNolock(BoolAttrState v) { parsedNolock = v; }
- BoolAttrState getParsedNolock() const { return parsedNolock; }
- void setParsedNoalloc(BoolAttrState v) { parsedNoalloc = v; }
- BoolAttrState getParsedNoalloc() const { return parsedNoalloc; }
+ void setParsedNonBlocking(BoolAttrState v) { parsedNonBlocking = v; }
+ BoolAttrState getParsedNonBlocking() const { return parsedNonBlocking; }
+ void setParsedNonAllocating(BoolAttrState v) { parsedNonAllocating = v; }
+ BoolAttrState getParsedNonAllocating() const { return parsedNonAllocating; }
~TypeProcessingState() {
if (savedAttrs.empty())
@@ -7985,7 +7987,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
if (!unwrapped.isFunctionType())
return false;
- const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking;
+ const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ PAttr.getKind() == ParsedAttr::AT_Blocking;
Sema &S = state.getSema();
// Require FunctionProtoType
@@ -7995,15 +7998,21 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
return false;
}
- // Parse the conditional expression, if any
- // TODO: There's a better way to do this. See PR feedback.
- // TODO: Handle a type-dependent expression.
bool Cond = true; // default
- if (PAttr.getNumArgs() > 0) {
- if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
- PAttr.setInvalid();
- return false;
+
+ if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ PAttr.getKind() == ParsedAttr::AT_NonAllocating) {
+ // Parse the conditional expression, if any
+ // TODO: There's a better way to do this. See PR feedback.
+ // TODO: Handle a type-dependent expression.
+ if (PAttr.getNumArgs() > 0) {
+ if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
+ PAttr.setInvalid();
+ return false;
+ }
}
+ } else {
+ Cond = false;
}
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
@@ -8018,44 +8027,44 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
return true;
};
- // check nonblocking(true) against nonblocking(false), and same for
+ // check nonblocking(true) against blocking, and same for
// nonallocating
const BoolAttrState newState =
Cond ? BoolAttrState::True : BoolAttrState::False;
const BoolAttrState oppositeNewState =
Cond ? BoolAttrState::False : BoolAttrState::True;
if (isNonBlocking) {
- if (state.getParsedNolock() == oppositeNewState) {
- return incompatible("nonblocking(true)", "nonblocking(false)");
+ if (state.getParsedNonBlocking() == oppositeNewState) {
+ return incompatible("nonblocking(true)", "blocking");
}
- // also check nonblocking(true) against nonallocating(false)
- if (Cond && state.getParsedNoalloc() == BoolAttrState::False) {
- return incompatible("nonblocking(true)", "nonallocating(false)");
+ // also check nonblocking(true) against allocating
+ if (Cond && state.getParsedNonAllocating() == BoolAttrState::False) {
+ return incompatible("nonblocking(true)", "allocating");
}
- state.setParsedNolock(newState);
+ state.setParsedNonBlocking(newState);
} else {
- if (state.getParsedNoalloc() == oppositeNewState) {
- return incompatible("nonallocating(true)", "nonallocating(false)");
+ if (state.getParsedNonAllocating() == oppositeNewState) {
+ return incompatible("nonallocating(true)", "allocating");
}
- // also check nonblocking(true) against nonallocating(false)
- if (state.getParsedNolock() == BoolAttrState::True) {
+ // also check nonblocking(true) against allocating
+ if (state.getParsedNonBlocking() == BoolAttrState::True) {
if (!Cond) {
- return incompatible("nonblocking(true)", "nonallocating(false)");
+ return incompatible("nonblocking(true)", "allocating");
}
// Ignore nonallocating(true) since we already have nonblocking(true).
return true;
}
- state.setParsedNoalloc(newState);
+ state.setParsedNonAllocating(newState);
}
if (!Cond) {
- // nonblocking(false) and nonallocating(false) are represented as sugar,
+ // blocking and allocating are represented as sugar,
// with AttributedType
Attr *A = nullptr;
if (isNonBlocking) {
- A = NonBlockingAttr::Create(S.Context, false);
+ A = BlockingAttr::Create(S.Context);
} else {
- A = NonAllocatingAttr::Create(S.Context, false);
+ A = AllocatingAttr::Create(S.Context);
}
type = state.getAttributedType(A, type, type);
return true;
@@ -8400,7 +8409,9 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
}
if (attr.getKind() == ParsedAttr::AT_NonBlocking ||
- attr.getKind() == ParsedAttr::AT_NonAllocating) {
+ attr.getKind() == ParsedAttr::AT_NonAllocating ||
+ attr.getKind() == ParsedAttr::AT_Blocking ||
+ attr.getKind() == ParsedAttr::AT_Allocating) {
return handleNonBlockingNonAllocatingTypeAttr(state, attr, type, unwrapped);
}
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
deleted file mode 100644
index f33a9fe254053..0000000000000
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-
-#if !__has_attribute(clang_nonblocking)
-#error "the 'nonblocking' attribute is not available"
-#endif
-
-
-// The diagnostic for inference not following a non-inline method should override
-// the one for a virtual method.
-
-struct HasVirtual {
- virtual ~HasVirtual() = default;
- virtual void method();
-};
-
-void nl999(HasVirtual& x) [[clang::nonblocking]] {
- x.method();
-}
-
-
-
-#if 0
- using nl_sugar = int (*)(int) [[clang::nonblocking]];
-
- void receives_fp_nl(nl_sugar fp) {
- }
-
- int callback(int) noexcept [[clang::nonblocking]];
-
-void type_conversions_2()
-{
- auto receives_fp = [](void (*fp)()) {
- };
-
- //auto receives_fp_nl = [](void (*fp)() [[clang::nonblocking]]) {
- //};
-
- auto ne = +[]() noexcept {};
- auto nl = +[]() [[clang::nonblocking]] {};
- //auto nl_ne = +[](int x) noexcept [[clang::nonblocking]] -> int { return x; };
-
- receives_fp(ne);
- receives_fp(nl);
-// receives_fp(nl_ne);
-
- receives_fp_nl(callback);
-}
-#endif
-
-#if 0
-struct S {
- void foo();
- // void foo() noexcept; // error, redeclaration
- // void foo() [[clang::nonblocking]]; // error, redeclaration
-
- using FP = void (*)();
- using FPNE = void (*)() noexcept;
- using FPNL = void (*)() [[clang::nonblocking]];
-
- void bar(FP x);
- void bar(FPNE x); // This is a distinct overload
- void bar(FPNL x); // This is a distinct overload
-};
-#endif
-
-// ============================================================================
-
-#if 0
-#define RT_UNSAFE_BEGIN(reason) \
- _Pragma("clang diagnostic push") \
- _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \
- _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")
-
-#define RT_UNSAFE_END \
- _Pragma("clang diagnostic pop")
-
-#define RT_UNSAFE(...) \
- RT_UNSAFE_BEGIN("") \
- __VA_ARGS__ \
- RT_UNSAFE_END
-#endif
-
-// ============================================================================
-
-#if 0
-// some messing around with type traits
-template <class _Tp, _Tp __v>
-struct integral_constant
-{
- static constexpr const _Tp value = __v;
- typedef _Tp value_type;
- typedef integral_constant type;
- constexpr operator value_type() const noexcept {return value;}
- constexpr value_type operator ()() const noexcept {return value;}
-};
-
-template <class _Tp, _Tp __v>
-const _Tp integral_constant<_Tp, __v>::value;
-
-typedef integral_constant<bool, true> true_type;
-typedef integral_constant<bool, false> false_type;
-
-template <typename T>
-struct is_nonblocking;
-
-void g() [[clang::nonblocking]];
-void h() [[clang::nonblocking(false)]];
-#endif
-
diff --git a/clang/test/Sema/attr-nolock-constraints.mm b/clang/test/Sema/attr-nonblocking-constraints.mm
similarity index 100%
rename from clang/test/Sema/attr-nolock-constraints.mm
rename to clang/test/Sema/attr-nonblocking-constraints.mm
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
similarity index 72%
rename from clang/test/Sema/attr-nolock-sema.cpp
rename to clang/test/Sema/attr-nonblocking-sema.cpp
index 1d7881b30411a..a8e725b3a4b61 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -14,23 +14,23 @@ struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' a
// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
// Check invalid combinations of nonblocking/nonallocating attributes
-void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::nonblocking(false)]]; // expected-error {{nonblocking(true) and nonblocking(false) attributes are not compatible}}
-void nl_true_false_2() [[clang::nonblocking(false)]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking(true) and nonblocking(false) attributes are not compatible}}
+void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{nonblocking(true) and blocking attributes are not compatible}}
+void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking(true) and blocking attributes are not compatible}}
-void na_true_false_1() [[clang::nonallocating(true)]] [[clang::nonallocating(false)]]; // expected-error {{nonallocating(true) and nonallocating(false) attributes are not compatible}}
-void na_true_false_2() [[clang::nonallocating(false)]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating(true) and nonallocating(false) attributes are not compatible}}
+void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{nonallocating(true) and allocating attributes are not compatible}}
+void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating(true) and allocating attributes are not compatible}}
void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]];
void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]];
-void nl_true_na_false_1() [[clang::nonblocking]] [[clang::nonallocating(false)]]; // expected-error {{nonblocking(true) and nonallocating(false) attributes are not compatible}}
-void nl_true_na_false_2() [[clang::nonallocating(false)]] [[clang::nonblocking]]; // expected-error {{nonblocking(true) and nonallocating(false) attributes are not compatible}}
+void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{nonblocking(true) and allocating attributes are not compatible}}
+void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{nonblocking(true) and allocating attributes are not compatible}}
-void nl_false_na_true_1() [[clang::nonblocking(false)]] [[clang::nonallocating]];
-void nl_false_na_true_2() [[clang::nonallocating]] [[clang::nonblocking(false)]];
+void nl_false_na_true_1() [[clang::blocking]] [[clang::nonallocating]];
+void nl_false_na_true_2() [[clang::nonallocating]] [[clang::blocking]];
-void nl_false_na_false_1() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]];
-void nl_false_na_false_2() [[clang::nonallocating(false)]] [[clang::nonblocking(false)]];
+void nl_false_na_false_1() [[clang::blocking]] [[clang::allocating]];
+void nl_false_na_false_2() [[clang::allocating]] [[clang::blocking]];
// --- TYPE CONVERSIONS ---
diff --git a/clang/test/Sema/attr-nolock-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
similarity index 95%
rename from clang/test/Sema/attr-nolock-syntax.cpp
rename to clang/test/Sema/attr-nonblocking-syntax.cpp
index 4e486c5a973ab..bc68c401aafd9 100644
--- a/clang/test/Sema/attr-nolock-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -17,10 +17,6 @@ void nl_function() [[clang::nonblocking]];
void (*nl_func_a)() [[clang::nonblocking]];
// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
-// Check alternate attribute type and placement
-__attribute__((clang_nonblocking)) void (*nl_func_b)();
-// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nonblocking))'
-
// On the type of the ParmVarDecl of a function parameter
static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
@@ -84,12 +80,12 @@ auto nl_lambda = []() [[clang::nonblocking]] {};
// =========================================================================================
// Square brackets, false
-void nl_func_false() [[clang::nonblocking(false)]];
-// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nonblocking(false)))'
+void nl_func_false() [[clang::blocking]];
+// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_blocking))'
// TODO: This exposes a bug where a type attribute is lost when inferring a lambda's
// return type.
-auto nl_lambda_false = []() [[clang::nonblocking(false)]] {};
+auto nl_lambda_false = []() [[clang::blocking]] {};
} // namespace square_brackets
@@ -112,7 +108,9 @@ __attribute__((clang_nonblocking)) void nl_function();
void (*nl_func_a)() __attribute__((clang_nonblocking));
// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
-
+// Alternate attribute placement on VarDecl
+__attribute__((clang_nonblocking)) void (*nl_func_b)();
+// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nonblocking))'
} // namespace gnu_style
>From 9471aa13c30d15248da851ca496b9d6b1b3a290b Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 5 Apr 2024 09:23:15 -0700
Subject: [PATCH 23/71] Look for blocking and allocating attributes across all
redeclarations.
---
clang/include/clang/AST/Type.h | 9 +++----
clang/lib/AST/Type.cpp | 30 ++++++++++++++++++------
clang/lib/AST/TypePrinter.cpp | 2 ++
clang/lib/Sema/AnalysisBasedWarnings.cpp | 18 +++-----------
4 files changed, 31 insertions(+), 28 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 657f3ee6fc779..a45e7fbbbd292 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4534,15 +4534,12 @@ class FunctionEffect {
const CXXMethodDecl &NewMethod,
FunctionEffectSet NewFX) const;
- /// Return true if the effect is allowed to be inferred on a Decl of the
- /// specified type (generally a FunctionProtoType but TypeSourceInfo is
- /// provided so any AttributedType sugar can be examined). TSI can be null
- /// on an implicit function like a default constructor.
- ///
+ /// Return true if the effect is allowed to be inferred on the callee,
+ /// which is either a FunctionDecl or BlockDecl.
/// This is only used if the effect has FE_InferrableOnCallees flag set.
/// Example: This allows nonblocking(false) to prevent inference for the
/// function.
- bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
+ bool canInferOnFunction(const Decl &Callee) const;
// Return false for success. When true is returned for a direct call, then the
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 070bedceebedd..35f20a9c9f233 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5112,17 +5112,33 @@ FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
return OverrideResult::Ignore;
}
-bool FunctionEffect::canInferOnFunction(QualType QT,
- const TypeSourceInfo *FType) const {
+bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
switch (type()) {
case Type::NonAllocating:
case Type::NonBlocking: {
- // Does the sugar have nonblocking(false) / nonallocating(false) ?
- if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::Blocking
- : attr::Kind::Allocating)) {
- return false;
+ // Do any of the callee's Decls have type sugar for blocking or allocating?
+ for (const Decl *D : Callee.redecls()) {
+ QualType QT;
+ if (auto *FD = D->getAsFunction()) {
+ QT = FD->getType();
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten())
+ QT = TSI->getType();
+ else
+ continue;
+ } else
+ continue;
+
+ // c.f. Sema::getCallingConvAttributedType
+ const AttributedType *AT = QT->getAs<AttributedType>();
+ while (AT) {
+ if (AT->getAttrKind() == attr::Allocating)
+ return false;
+ if (type() == Type::NonBlocking && AT->getAttrKind() == attr::Blocking)
+ return false;
+ AT = AT->getModifiedType()->getAs<AttributedType>();
+ }
}
-
return true;
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index c086f9829dc40..ffa0b8a6285c2 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1933,6 +1933,8 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmOut:
case attr::ArmInOut:
case attr::ArmPreserves:
+ case attr::NonBlocking:
+ case attr::NonAllocating:
llvm_unreachable("This attribute should have been handled already");
case attr::NSReturnsRetained:
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index f76571afcfa2f..ac3cb92f98862 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2458,7 +2458,6 @@ struct CallableInfo {
SpecialFuncType FuncType = SpecialFuncType::None;
FunctionEffectSet Effects;
CallType CType = CallType::Unknown;
- QualType QT;
CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
@@ -2475,16 +2474,13 @@ struct CallableInfo {
CType = CallType::Virtual;
}
}
- QT = FD->getType();
Effects = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
- QT = BD->getSignatureAsWritten()->getType();
Effects = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
- // ValueDecl is function, enum, or variable, so just look at the type.
- QT = VD->getType();
- Effects = FunctionEffectSet::get(QT);
+ // ValueDecl is function, enum, or variable, so just look at its type.
+ Effects = FunctionEffectSet::get(VD->getType());
}
}
@@ -2624,17 +2620,9 @@ class PendingFunctionAnalysis {
// Check for effects we are not allowed to infer
MutableFunctionEffectSet FX;
- TypeSourceInfo *TSI = nullptr;
- if (const auto *DD = dyn_cast<DeclaratorDecl>(CInfo.CDecl)) {
- TSI = DD->getTypeSourceInfo();
- } else if (const auto *BD = dyn_cast<BlockDecl>(CInfo.CDecl)) {
- TSI = BD->getSignatureAsWritten();
- }
- // N.B. TSI can be null for things like an implicit constructor (despite
- // having a valid QualifiedType).
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(CInfo.QT, TSI)) {
+ if (effect.canInferOnFunction(*CInfo.CDecl)) {
FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
>From 12e213ab04899f51778bfcbe9e64d4f920606b29 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 5 Apr 2024 13:16:00 -0700
Subject: [PATCH 24/71] Reinstate the warning that nonblocking/nonallocating
should be accompanied by noexcept.
---
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 7 ++--
clang/lib/Sema/AnalysisBasedWarnings.cpp | 36 ++++++++++++-------
.../test/Sema/attr-nonblocking-constraints.mm | 10 ++++++
4 files changed, 37 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 59eac4e2c1442..a56df9a89375f 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1517,6 +1517,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInCon
// Warnings and notes related to the function effects system underlying
// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f2cf601b95f37..3608c9403dd34 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10856,10 +10856,9 @@ def note_func_effect_call_virtual : Note<
def note_func_effect_call_func_ptr : Note<
"function pointer cannot be inferred '%0'">;
-// TODO: Not currently being generated
-// def warn_perf_annotation_implies_noexcept : Warning<
-// "'%0' function should be declared noexcept">,
-// InGroup<PerfAnnotationImpliesNoexcept>;
+def warn_perf_constraint_implies_noexcept : Warning<
+ "'%0' function should be declared noexcept">,
+ InGroup<PerfConstraintImpliesNoexcept>;
// TODO: Not currently being generated
def warn_func_effect_false_on_type : Warning<
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index ac3cb92f98862..5811aab454c40 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2746,18 +2746,6 @@ class CompleteFunctionAnalysis {
}
};
-/*
- TODO: nonblocking and nonallocating imply noexcept
- if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
- if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
- != clang::CT_Cannot) {
- S.Diag(Callable->getBeginLoc(),
- diag::warn_perf_annotation_implies_noexcept)
- << getPerfAnnotationSpelling(CInfo.PerfAnnot);
- }
- }
-*/
-
const Decl *CanonicalFunctionDecl(const Decl *D) {
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
FD = FD->getCanonicalDecl();
@@ -2919,10 +2907,32 @@ class Analyzer {
// Verify a single Decl. Return the pending structure if that was the result,
// else null. This method must not recurse.
PendingFunctionAnalysis *verifyDecl(const Decl *D) {
+ CallableInfo CInfo(*D);
+
+ // If any of the Decl's declared effects forbid throwing (e.g. nonblocking)
+ // then the function should also be declared noexcept.
+ for (const auto &Effect : CInfo.Effects) {
+ if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
+ continue;
+
+ const FunctionProtoType *FPT = nullptr;
+ if (auto *FD = D->getAsFunction()) {
+ FPT = FD->getType()->getAs<FunctionProtoType>();
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten()) {
+ FPT = TSI->getType()->getAs<FunctionProtoType>();
+ }
+ }
+ if (FPT && FPT->canThrow() != clang::CT_Cannot) {
+ Sem.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
+ << Effect.name();
+ }
+ break;
+ }
+
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
tmp_assert(FD->getBuiltinID() == 0);
}
- CallableInfo CInfo(*D);
// 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
diff --git a/clang/test/Sema/attr-nonblocking-constraints.mm b/clang/test/Sema/attr-nonblocking-constraints.mm
index 4be3f8c95c9ad..e805a526a1bbd 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.mm
+++ b/clang/test/Sema/attr-nonblocking-constraints.mm
@@ -6,6 +6,9 @@
#error "the 'nonblocking' attribute is not available"
#endif
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
// --- CONSTRAINTS ---
void nl1() [[clang::nonblocking]]
@@ -188,3 +191,10 @@ void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
ref();
}
+
+// --- nonblocking implies noexcept ---
+#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
+
+void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+{
+}
>From 2a58ddab9a700665cf859e63afbf2e51aca3f90a Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 9 Apr 2024 16:15:19 -0700
Subject: [PATCH 25/71] Groundwork for `nonblocking(expr)`
---
clang/include/clang/Basic/Attr.td | 4 +-
clang/include/clang/Sema/Sema.h | 3 -
clang/lib/AST/TypePrinter.cpp | 9 ++
clang/lib/Sema/SemaDeclAttr.cpp | 27 -----
clang/lib/Sema/SemaType.cpp | 121 ++++++++++++++------
clang/test/Sema/attr-nonblocking-sema.cpp | 12 +-
clang/test/Sema/attr-nonblocking-syntax.cpp | 1 +
7 files changed, 101 insertions(+), 76 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 908edba2222f8..88b94c946b382 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1407,7 +1407,7 @@ def NonBlocking : TypeAttr {
C23<"clang", "nonblocking">,
GNU<"clang_nonblocking">];
- let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+ let Args = [ExprArgument<"Cond", /*optional*/1>];
let Documentation = [NonBlockingNonAllocatingDocs];
}
@@ -1415,7 +1415,7 @@ def NonAllocating : TypeAttr {
let Spellings = [CXX11<"clang", "nonallocating">,
C23<"clang", "nonallocating">,
GNU<"clang_nonallocating">];
- let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+ let Args = [ExprArgument<"Cond", /*optional*/1>];
let Documentation = [NonBlockingNonAllocatingDocs];
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5400a62bce91d..872947afbcf41 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3764,9 +3764,6 @@ class Sema final {
StringRef &Str,
SourceLocation *ArgLocation = nullptr);
- bool checkBoolExprArgumentAttr(const ParsedAttr &Attr, unsigned ArgNum,
- bool &Value);
-
/// Determine if type T is a valid subject for a nonnull and similar
/// attributes. By default, we look through references (the behavior used by
/// nonnull), but if the second parameter is true, then we treat a reference
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ffa0b8a6285c2..89a66cd070029 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1877,6 +1877,15 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
return;
}
+ if (T->getAttrKind() == attr::NonBlocking) {
+ OS << " [[clang::nonblocking(...)]]";
+ return;
+ }
+ if (T->getAttrKind() == attr::NonAllocating) {
+ OS << " [[clang::nonallocating(...)]]";
+ return;
+ }
+
if (T->getAttrKind() == attr::ArmStreaming) {
OS << "__arm_streaming";
return;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 3cc73db06740e..8bce04640e748 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -395,33 +395,6 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
}
-/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
-/// integer (boolean) expression. If not, emit an error and return false.
-bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
- bool &Value) {
- if (AL.isInvalid()) {
- return false;
- }
- Expr *ArgExpr = AL.getArgAsExpr(ArgNum);
- SourceLocation ErrorLoc(AL.getLoc());
-
- if (AL.isArgIdent(ArgNum)) {
- IdentifierLoc *IL = AL.getArgAsIdent(ArgNum);
- ErrorLoc = IL->Loc;
- } else if (ArgExpr != nullptr) {
- if (const std::optional<llvm::APSInt> MaybeVal =
- ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
- Value = MaybeVal->getBoolValue();
- return true;
- }
- }
-
- AL.setInvalid();
- Diag(ErrorLoc, diag::err_attribute_argument_n_type)
- << AL << ArgNum << AANT_ArgumentConstantExpr;
- return false;
-}
-
/// Applies the given attribute to the Decl without performing any
/// additional semantic checking.
template <typename AttrType>
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 80123ba9a59a1..b1416c0d365c0 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -175,7 +175,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_TypeNullableResult: \
case ParsedAttr::AT_TypeNullUnspecified
-enum class BoolAttrState : uint8_t { Unseen, False, True };
+enum class BoolAttrState : uint8_t { Unseen, False, True, Dependent };
namespace {
/// An object which stores processing state for the entire
@@ -7987,10 +7987,6 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
if (!unwrapped.isFunctionType())
return false;
- const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
- PAttr.getKind() == ParsedAttr::AT_Blocking;
- Sema &S = state.getSema();
-
// Require FunctionProtoType
auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
@@ -7998,68 +7994,115 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
return false;
}
- bool Cond = true; // default
+ // Parse the new attribute.
+ // non/blocking or non/allocating? Or dependent?
+ const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ PAttr.getKind() == ParsedAttr::AT_Blocking;
+ Sema &S = state.getSema();
+
+ BoolAttrState NewState = BoolAttrState::Unseen;
+ Expr *CondExpr = nullptr; // only valid if dependent
if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_NonAllocating) {
// Parse the conditional expression, if any
- // TODO: There's a better way to do this. See PR feedback.
- // TODO: Handle a type-dependent expression.
if (PAttr.getNumArgs() > 0) {
- if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
- PAttr.setInvalid();
- return false;
+ // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+ CondExpr = PAttr.getArgAsExpr(0);
+ if (!CondExpr->isTypeDependent()) {
+ ExprResult E = S.PerformContextuallyConvertToBool(CondExpr);
+ if (E.isInvalid())
+ return false;
+ CondExpr = E.get();
+ if (!CondExpr->isValueDependent()) {
+ llvm::APSInt CondInt;
+ E = S.VerifyIntegerConstantExpression(
+ E.get(), &CondInt,
+ // TODO: have our own diagnostic
+ diag::err_constexpr_if_condition_expression_is_not_constant);
+ if (E.isInvalid()) {
+ return false;
+ }
+ NewState = (CondInt != 0) ? BoolAttrState::True : BoolAttrState::False;
+ } else {
+ NewState = BoolAttrState::Dependent;
+ }
+ } else {
+ NewState = BoolAttrState::Dependent;
}
+ } else {
+ NewState = BoolAttrState::True;
}
} else {
- Cond = false;
- }
-
- FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+ // This is the `blocking` or `allocating` attribute.
+ NewState = BoolAttrState::False;
+ }
+
+ auto attrName = [](bool NB, BoolAttrState value) {
+ switch (value) {
+ case BoolAttrState::False:
+ return NB ? "blocking" : "allocating";
+ case BoolAttrState::True:
+ return NB ? "nonblocking" : "nonallocating";
+ case BoolAttrState::Dependent:
+ return NB ? "nonblocking(expr)" : "nonallocating(expr)";
+ default:
+ llvm_unreachable("'unseen' shouldn't happen");
+ }
+ };
- auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
+ // Diagnose the newly provided attribute as incompatible with a previous one.
+ auto incompatible = [&](bool prevNB, BoolAttrState prevValue) {
Sema &S = state.getSema();
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
- << attrTrue << attrFalse << false;
+ << attrName(isNonBlocking, NewState) << attrName(prevNB, prevValue) << false;
// we don't necessarily have the location of the previous attribute,
// so no note.
PAttr.setInvalid();
return true;
};
- // check nonblocking(true) against blocking, and same for
- // nonallocating
- const BoolAttrState newState =
- Cond ? BoolAttrState::True : BoolAttrState::False;
- const BoolAttrState oppositeNewState =
- Cond ? BoolAttrState::False : BoolAttrState::True;
+ const BoolAttrState PrevState = isNonBlocking ? state.getParsedNonBlocking() :
+ state.getParsedNonAllocating();
+ if (PrevState != BoolAttrState::Unseen) {
+ // Only one attribute per constraint is allowed.
+ return incompatible(isNonBlocking, PrevState);
+ }
+
if (isNonBlocking) {
- if (state.getParsedNonBlocking() == oppositeNewState) {
- return incompatible("nonblocking(true)", "blocking");
- }
// also check nonblocking(true) against allocating
- if (Cond && state.getParsedNonAllocating() == BoolAttrState::False) {
- return incompatible("nonblocking(true)", "allocating");
+ if (NewState == BoolAttrState::True && state.getParsedNonAllocating() == BoolAttrState::False) {
+ return incompatible(false, BoolAttrState::False);
}
- state.setParsedNonBlocking(newState);
+ state.setParsedNonBlocking(NewState);
} else {
- if (state.getParsedNonAllocating() == oppositeNewState) {
- return incompatible("nonallocating(true)", "allocating");
- }
// also check nonblocking(true) against allocating
if (state.getParsedNonBlocking() == BoolAttrState::True) {
- if (!Cond) {
- return incompatible("nonblocking(true)", "allocating");
+ if (NewState == BoolAttrState::False) {
+ return incompatible(true, BoolAttrState::True);
}
// Ignore nonallocating(true) since we already have nonblocking(true).
return true;
}
- state.setParsedNonAllocating(newState);
+ state.setParsedNonAllocating(NewState);
}
- if (!Cond) {
- // blocking and allocating are represented as sugar,
- // with AttributedType
+ if (NewState == BoolAttrState::Dependent) {
+ // nonblocking(expr)/nonallocating(expr) are represented as AttributedType sugar,
+ // using those attributes. TODO: Currently no one else tries to find it there,
+ // and this may turn out to be the wrong place.
+ Attr *A = nullptr;
+ if (isNonBlocking) {
+ A = NonBlockingAttr::Create(S.Context, CondExpr);
+ } else {
+ A = NonAllocatingAttr::Create(S.Context, CondExpr);
+ }
+ type = state.getAttributedType(A, type, type);
+ return true;
+ }
+ if (NewState == BoolAttrState::False) {
+ // blocking and allocating are represented as AttributedType sugar,
+ // using those attributes.
Attr *A = nullptr;
if (isNonBlocking) {
A = BlockingAttr::Create(S.Context);
@@ -8077,6 +8120,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
: FunctionEffect::Type::NonAllocating);
MutableFunctionEffectSet NewFX(NewEffect);
+ FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+
if (EPI.FunctionEffects) {
// Preserve any previous effects - except nonallocating, when we are adding
// nonblocking
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index a8e725b3a4b61..ba86420d6c814 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -14,17 +14,17 @@ struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' a
// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
// Check invalid combinations of nonblocking/nonallocating attributes
-void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{nonblocking(true) and blocking attributes are not compatible}}
-void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking(true) and blocking attributes are not compatible}}
+void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{blocking and nonblocking attributes are not compatible}}
+void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking and blocking attributes are not compatible}}
-void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{nonallocating(true) and allocating attributes are not compatible}}
-void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating(true) and allocating attributes are not compatible}}
+void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{allocating and nonallocating attributes are not compatible}}
+void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating and allocating attributes are not compatible}}
void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]];
void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]];
-void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{nonblocking(true) and allocating attributes are not compatible}}
-void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{nonblocking(true) and allocating attributes are not compatible}}
+void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{allocating and nonblocking attributes are not compatible}}
+void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{nonblocking and allocating attributes are not compatible}}
void nl_false_na_true_1() [[clang::blocking]] [[clang::nonallocating]];
void nl_false_na_true_2() [[clang::nonallocating]] [[clang::blocking]];
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index bc68c401aafd9..5a451f23f291f 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
>From e942a902a5a0d96af442f9fa2c39b59afd569dd0 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 10 Apr 2024 11:31:36 -0700
Subject: [PATCH 26/71] Low-hanging fruit in review feedback.
---
clang/include/clang/AST/Type.h | 64 +++++++++----------
clang/lib/AST/Type.cpp | 78 ++++++++++++------------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 9 +--
clang/lib/Sema/Sema.cpp | 3 +-
clang/lib/Sema/SemaDecl.cpp | 5 +-
clang/lib/Sema/SemaDeclCXX.cpp | 5 +-
clang/lib/Sema/SemaType.cpp | 54 ++++++++--------
clang/lib/Serialization/ASTReader.cpp | 2 +-
8 files changed, 110 insertions(+), 110 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index a45e7fbbbd292..e4f0c545f80b9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4435,8 +4435,6 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
// ------------------------------------------------------------------------------
-// TODO: Should FunctionEffect be located elsewhere, where Decl is not
-// forward-declared?
class Decl;
class CXXMethodDecl;
class FunctionEffectSet;
@@ -4457,7 +4455,7 @@ class FunctionEffect {
// test a caller-specified bit. There are some potential optimizations that
// would OR together the bits of multiple effects.)
using Flags = unsigned;
- enum FlagBit : unsigned {
+ enum FlagBit : Flags {
// Can verification inspect callees' implementations? (e.g. nonblocking:
// yes, tcb+types: no)
FE_InferrableOnCallees = 0x1,
@@ -4475,15 +4473,15 @@ class FunctionEffect {
enum class OverrideResult {
Ignore,
Warn,
- Propagate // Base method's effects are merged with those of the override.
+ Merge // Base method's effects are merged with those of the override.
};
private:
- // For uniqueness, currently only Type_ is significant.
+ // For uniqueness, currently only EfType is significant.
LLVM_PREFERRED_TYPE(Type)
- unsigned Type_ : 2;
- Flags Flags_ : 8; // A constant function of Type but cached here.
+ unsigned EfType : 2;
+ Flags EfFlags : 8; // A constant function of Type but cached here.
// Expansion: for hypothetical TCB+types, there could be one Type for TCB,
// then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
@@ -4494,45 +4492,42 @@ class FunctionEffect {
[[maybe_unused]] unsigned Padding : 22;
public:
- using CalleeDeclOrType =
- llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
-
- FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0), Padding(0) {}
+ FunctionEffect() : EfType(unsigned(Type::None)), EfFlags(0), Padding(0) {}
explicit FunctionEffect(Type T);
/// The type of the effect.
- Type type() const { return Type(Type_); }
+ Type type() const { return Type(EfType); }
/// Flags describing behaviors of the effect.
- Flags flags() const { return Flags_; }
+ Flags flags() const { return EfFlags; }
/// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
/// A hashable representation.
- uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
+ uint32_t opaqueRepr() const { return EfType | (EfFlags << 2u); }
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
- bool diagnoseConversion(bool Adding, QualType OldType,
- FunctionEffectSet OldFX, QualType NewType,
- FunctionEffectSet NewFX) const;
+ bool shouldDiagnoseConversion(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.
- bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
- FunctionEffectSet OldFX,
- const FunctionDecl &NewFunction,
- FunctionEffectSet NewFX) const;
+ bool shouldDiagnoseRedeclaration(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.
- OverrideResult diagnoseMethodOverride(bool Adding,
- const CXXMethodDecl &OldMethod,
- FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod,
- FunctionEffectSet NewFX) const;
+ OverrideResult shouldDiagnoseMethodOverride(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 callee,
/// which is either a FunctionDecl or BlockDecl.
@@ -4545,16 +4540,17 @@ class FunctionEffect {
// 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 diagnoseFunctionCall(bool Direct, FunctionEffectSet CalleeFX) const;
+ bool shouldDiagnoseFunctionCall(bool Direct,
+ FunctionEffectSet CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.Type_ == RHS.Type_;
+ return LHS.EfType == RHS.EfType;
}
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.Type_ != RHS.Type_;
+ return LHS.EfType != RHS.EfType;
}
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.Type_ < RHS.Type_;
+ return LHS.EfType < RHS.EfType;
}
};
@@ -4573,7 +4569,7 @@ class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
/// Maintains order/uniquenesss.
void insert(const FunctionEffect &Effect);
- MutableFunctionEffectSet &operator|=(FunctionEffectSet RHS);
+ MutableFunctionEffectSet &insertMultiple(FunctionEffectSet RHS);
operator llvm::ArrayRef<FunctionEffect>() const { return {data(), size()}; }
};
@@ -4625,10 +4621,8 @@ class FunctionEffectSet {
void dump(llvm::raw_ostream &OS) const;
- /// Intersection.
- MutableFunctionEffectSet operator&(const FunctionEffectSet &RHS) const;
- /// Difference.
- MutableFunctionEffectSet operator-(const FunctionEffectSet &RHS) const;
+ MutableFunctionEffectSet intersection(const FunctionEffectSet &RHS) const;
+ MutableFunctionEffectSet difference(const FunctionEffectSet &RHS) const;
static FunctionEffectSet getUnion(ASTContext &C, const FunctionEffectSet &LHS,
const FunctionEffectSet &RHS);
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 35f20a9c9f233..5d22d058070ab 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5026,39 +5026,41 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
}
FunctionEffect::FunctionEffect(Type T)
- : Type_(static_cast<unsigned>(T)), Flags_(0), Padding(0) {
+ : EfType(static_cast<unsigned>(T)), EfFlags(0), Padding(0) {
switch (T) {
case Type::NonBlocking:
- Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
- FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
- FE_ExcludeThreadLocalVars;
+ EfFlags = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
+ FE_ExcludeThreadLocalVars;
break;
case Type::NonAllocating:
// Same as NonBlocking, except without FE_ExcludeStaticLocalVars
- Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
- FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
+ EfFlags = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
break;
- default:
+ case Type::None:
break;
}
+ llvm_unreachable("unknown effect type");
}
StringRef FunctionEffect::name() const {
switch (type()) {
- default:
- return "";
case Type::NonBlocking:
return "nonblocking";
case Type::NonAllocating:
return "nonallocating";
+ case Type::None:
+ break;
}
+ llvm_unreachable("unknown effect type");
}
-bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
- FunctionEffectSet OldFX,
- QualType NewType,
- FunctionEffectSet NewFX) const {
+bool FunctionEffect::shouldDiagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX,
+ QualType NewType,
+ FunctionEffectSet NewFX) const {
switch (type()) {
case Type::NonAllocating:
@@ -5074,30 +5076,28 @@ bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
case Type::NonBlocking:
// nonblocking can't be added (spoofed) during a conversion
return Adding;
- default:
+ case Type::None:
break;
}
- return false;
+ llvm_unreachable("unknown effect type");
}
-bool FunctionEffect::diagnoseRedeclaration(bool Adding,
- const FunctionDecl &OldFunction,
- FunctionEffectSet OldFX,
- const FunctionDecl &NewFunction,
- FunctionEffectSet NewFX) const {
+bool FunctionEffect::shouldDiagnoseRedeclaration(
+ bool Adding, const FunctionDecl &OldFunction, FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction, FunctionEffectSet NewFX) const {
switch (type()) {
case Type::NonAllocating:
case Type::NonBlocking:
// nonblocking/nonallocating can't be removed in a redeclaration
// adding -> false, removing -> true (diagnose)
return !Adding;
- default:
+ case Type::None:
break;
}
- return false;
+ llvm_unreachable("unknown effect type");
}
-FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
+FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
switch (type()) {
@@ -5105,17 +5105,17 @@ FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
case Type::NonBlocking:
// if added on an override, that's fine and not diagnosed.
// if missing from an override (removed), propagate from base to derived.
- return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
- default:
+ return Adding ? OverrideResult::Ignore : OverrideResult::Merge;
+ case Type::None:
break;
}
- return OverrideResult::Ignore;
+ llvm_unreachable("unknown effect type");
}
bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
switch (type()) {
case Type::NonAllocating:
- case Type::NonBlocking: {
+ case Type::NonBlocking:
// Do any of the callee's Decls have type sugar for blocking or allocating?
for (const Decl *D : Callee.redecls()) {
QualType QT;
@@ -5140,16 +5140,14 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
}
}
return true;
- }
-
- default:
+ case Type::None:
break;
}
- return false;
+ llvm_unreachable("unknown effect type");
}
-bool FunctionEffect::diagnoseFunctionCall(bool Direct,
- FunctionEffectSet CalleeFX) const {
+bool FunctionEffect::shouldDiagnoseFunctionCall(
+ bool Direct, FunctionEffectSet CalleeFX) const {
switch (type()) {
case Type::NonAllocating:
case Type::NonBlocking: {
@@ -5164,10 +5162,10 @@ bool FunctionEffect::diagnoseFunctionCall(bool Direct,
}
return true; // warning
}
- default:
+ case Type::None:
break;
}
- return false;
+ llvm_unreachable("unknown effect type");
}
// =====
@@ -5185,7 +5183,7 @@ void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
}
MutableFunctionEffectSet &
-MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
+MutableFunctionEffectSet::insertMultiple(FunctionEffectSet RHS) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &Effect : RHS) {
insert(Effect);
@@ -5215,7 +5213,7 @@ FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
}
MutableFunctionEffectSet
-FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
+FunctionEffectSet::intersection(const FunctionEffectSet &RHS) const {
const FunctionEffectSet &LHS = *this;
if (LHS.empty() || RHS.empty()) {
return {};
@@ -5254,17 +5252,17 @@ FunctionEffectSet::differences(const FunctionEffectSet &Old,
const FunctionEffectSet &New) {
// TODO: Could be a one-pass algorithm.
Differences Result;
- for (const auto &Effect : (New - Old)) {
+ for (const auto &Effect : New.difference(Old)) {
Result.emplace_back(Effect, true);
}
- for (const auto &Effect : (Old - New)) {
+ for (const auto &Effect : Old.difference(New)) {
Result.emplace_back(Effect, false);
}
return Result;
}
MutableFunctionEffectSet
-FunctionEffectSet::operator-(const FunctionEffectSet &RHS) const {
+FunctionEffectSet::difference(const FunctionEffectSet &RHS) const {
const FunctionEffectSet &LHS = *this;
MutableFunctionEffectSet Result;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 5811aab454c40..008d1798f3628 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2636,7 +2636,8 @@ class PendingFunctionAnalysis {
}
// FX is now the set of inferrable effects which are not prohibited
FXToInfer = Ctx.getUniquedFunctionEffectSet(
- Ctx.getUniquedFunctionEffectSet(FX) - DeclaredVerifiableEffects);
+ Ctx.getUniquedFunctionEffectSet(FX).difference(
+ DeclaredVerifiableEffects));
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2721,7 +2722,7 @@ class CompleteFunctionAnalysis {
FunctionEffectSet funcFX,
FunctionEffectSet AllInferrableEffectsToVerify) {
MutableFunctionEffectSet verified;
- verified |= funcFX;
+ verified.insertMultiple(funcFX);
for (const auto &effect : AllInferrableEffectsToVerify) {
if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
verified.insert(effect);
@@ -3043,7 +3044,7 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
const auto Flags = Effect.flags();
const bool diagnose =
- Effect.diagnoseFunctionCall(DirectCall, CalleeEffects);
+ Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
if (diagnose) {
// If inference is not allowed, or the target is indirect (virtual
// method/function ptr?), generate a diagnostic now.
@@ -3318,7 +3319,7 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
if (FPT == nullptr ||
- Effect.diagnoseFunctionCall(
+ Effect.shouldDiagnoseFunctionCall(
/*direct=*/false, FPT->getFunctionEffects())) {
addDiagnosticInner(Inferring, Effect,
DiagnosticID::CallsDisallowedExpr,
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2cb1b931cf6fa..01d334f8d8af9 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -596,7 +596,8 @@ void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect.diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
+ if (Effect.shouldDiagnoseConversion(Adding, SrcType, SrcFX, DstType,
+ DstFX)) {
Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
: diag::warn_invalid_remove_func_effects)
<< Effect.name();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f68e207805b4d..2108ee75ddc67 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3928,7 +3928,8 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
for (const auto &Item : Diffs) {
const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- if (Effect.diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+ if (Effect.shouldDiagnoseRedeclaration(Adding, *Old, OldFX, *New,
+ NewFX)) {
Diag(New->getLocation(),
diag::warn_mismatched_func_effect_redeclaration)
<< Effect.name();
@@ -11151,7 +11152,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
return;
}
- AllEffectsToVerify |= FX;
+ AllEffectsToVerify.insertMultiple(FX);
// Record the declaration for later analysis.
DeclsWithEffectsToVerify.push_back(D);
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index da061e25fe313..04ec7ee7ae702 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18335,7 +18335,8 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
for (const auto &Item : Diffs) {
const FunctionEffect &Effect = Item.first;
const bool Adding = Item.second;
- switch (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+ switch (Effect.shouldDiagnoseMethodOverride(Adding, *Old, OldFX, *New,
+ NewFX)) {
case FunctionEffect::OverrideResult::Ignore:
break;
case FunctionEffect::OverrideResult::Warn:
@@ -18345,7 +18346,7 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
// TODO: It would be nice to have a FIXIT here!
AnyDiags = true;
break;
- case FunctionEffect::OverrideResult::Propagate: {
+ case FunctionEffect::OverrideResult::Merge: {
auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index b1416c0d365c0..d00752822bfcb 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7980,15 +7980,15 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
}
static bool
-handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
- ParsedAttr &PAttr, QualType &type,
- FunctionTypeUnwrapper &unwrapped) {
+handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
+ ParsedAttr &PAttr, QualType &QT,
+ FunctionTypeUnwrapper &Unwrapped) {
// Delay if this is not a function type.
- if (!unwrapped.isFunctionType())
+ if (!Unwrapped.isFunctionType())
return false;
// Require FunctionProtoType
- auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
+ auto *FPT = Unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
// TODO: special diagnostic?
return false;
@@ -7998,7 +7998,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
// non/blocking or non/allocating? Or dependent?
const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_Blocking;
- Sema &S = state.getSema();
+ Sema &S = TPState.getSema();
BoolAttrState NewState = BoolAttrState::Unseen;
Expr *CondExpr = nullptr; // only valid if dependent
@@ -8017,13 +8017,14 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
if (!CondExpr->isValueDependent()) {
llvm::APSInt CondInt;
E = S.VerifyIntegerConstantExpression(
- E.get(), &CondInt,
- // TODO: have our own diagnostic
- diag::err_constexpr_if_condition_expression_is_not_constant);
+ E.get(), &CondInt,
+ // TODO: have our own diagnostic
+ diag::err_constexpr_if_condition_expression_is_not_constant);
if (E.isInvalid()) {
return false;
}
- NewState = (CondInt != 0) ? BoolAttrState::True : BoolAttrState::False;
+ NewState =
+ (CondInt != 0) ? BoolAttrState::True : BoolAttrState::False;
} else {
NewState = BoolAttrState::Dependent;
}
@@ -8053,17 +8054,18 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
// Diagnose the newly provided attribute as incompatible with a previous one.
auto incompatible = [&](bool prevNB, BoolAttrState prevValue) {
- Sema &S = state.getSema();
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
- << attrName(isNonBlocking, NewState) << attrName(prevNB, prevValue) << false;
+ << attrName(isNonBlocking, NewState) << attrName(prevNB, prevValue)
+ << false;
// we don't necessarily have the location of the previous attribute,
// so no note.
PAttr.setInvalid();
return true;
};
- const BoolAttrState PrevState = isNonBlocking ? state.getParsedNonBlocking() :
- state.getParsedNonAllocating();
+ const BoolAttrState PrevState = isNonBlocking
+ ? TPState.getParsedNonBlocking()
+ : TPState.getParsedNonAllocating();
if (PrevState != BoolAttrState::Unseen) {
// Only one attribute per constraint is allowed.
return incompatible(isNonBlocking, PrevState);
@@ -8071,35 +8073,37 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
if (isNonBlocking) {
// also check nonblocking(true) against allocating
- if (NewState == BoolAttrState::True && state.getParsedNonAllocating() == BoolAttrState::False) {
+ if (NewState == BoolAttrState::True &&
+ TPState.getParsedNonAllocating() == BoolAttrState::False) {
return incompatible(false, BoolAttrState::False);
}
- state.setParsedNonBlocking(NewState);
+ TPState.setParsedNonBlocking(NewState);
} else {
// also check nonblocking(true) against allocating
- if (state.getParsedNonBlocking() == BoolAttrState::True) {
+ if (TPState.getParsedNonBlocking() == BoolAttrState::True) {
if (NewState == BoolAttrState::False) {
return incompatible(true, BoolAttrState::True);
}
// Ignore nonallocating(true) since we already have nonblocking(true).
return true;
}
- state.setParsedNonAllocating(NewState);
+ TPState.setParsedNonAllocating(NewState);
}
if (NewState == BoolAttrState::Dependent) {
- // nonblocking(expr)/nonallocating(expr) are represented as AttributedType sugar,
- // using those attributes. TODO: Currently no one else tries to find it there,
- // and this may turn out to be the wrong place.
+ // nonblocking(expr)/nonallocating(expr) are represented as AttributedType
+ // sugar, using those attributes. TODO: Currently no one else tries to find
+ // it there, and this may turn out to be the wrong place.
Attr *A = nullptr;
if (isNonBlocking) {
A = NonBlockingAttr::Create(S.Context, CondExpr);
} else {
A = NonAllocatingAttr::Create(S.Context, CondExpr);
}
- type = state.getAttributedType(A, type, type);
+ QT = TPState.getAttributedType(A, QT, QT);
return true;
}
+
if (NewState == BoolAttrState::False) {
// blocking and allocating are represented as AttributedType sugar,
// using those attributes.
@@ -8109,7 +8113,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
} else {
A = AllocatingAttr::Create(S.Context);
}
- type = state.getAttributedType(A, type, type);
+ QT = TPState.getAttributedType(A, QT, QT);
return true;
}
@@ -8133,10 +8137,10 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
}
EPI.FunctionEffects =
- state.getSema().getASTContext().getUniquedFunctionEffectSet(NewFX);
+ TPState.getSema().getASTContext().getUniquedFunctionEffectSet(NewFX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
- type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
+ QT = Unwrapped.wrap(S, newtype->getAs<FunctionType>());
return true;
}
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index bfc25f82a76d0..2a46eb68b4092 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8233,7 +8233,7 @@ void ASTReader::InitializeSema(Sema &S) {
FX = BD->getFunctionEffects();
}
if (FX) {
- SemaObj->AllEffectsToVerify |= FX;
+ SemaObj->AllEffectsToVerify.insertMultiple(FX);
}
}
DeclsWithEffectsToVerify.clear();
>From 45389bd879af7dd2ab008c0ddd3a2dc7f8d2467d Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 11 Apr 2024 11:37:23 -0700
Subject: [PATCH 27/71] - During parsing, don't remove nonallocating when
nonblocking is added; keep both. - Fix hacky serialization of
FunctionEffectSet. - Rename CheckAddCallableWithEffects ->
MaybeAddDeclWithEffects. - Don't unique FunctionEffectSets in the AST any
more (overkill). - FunctionEffect::Type -> Kind as per coding guidelines (and
to disambiguate from Type)
---
clang/include/clang/AST/ASTContext.h | 22 --
clang/include/clang/AST/AbstractBasicReader.h | 8 +
clang/include/clang/AST/AbstractBasicWriter.h | 6 +
clang/include/clang/AST/PropertiesBase.td | 1 +
clang/include/clang/AST/Type.h | 185 +++++++-------
clang/include/clang/AST/TypeProperties.td | 6 +-
clang/include/clang/Sema/Sema.h | 4 +-
clang/lib/AST/ASTContext.cpp | 75 ------
clang/lib/AST/Type.cpp | 241 +++++++++---------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 35 +--
clang/lib/Sema/SemaDecl.cpp | 8 +-
clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/lib/Sema/SemaLambda.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 18 +-
clang/lib/Serialization/ASTReader.cpp | 2 +-
clang/test/Sema/attr-nonblocking-syntax.cpp | 2 +-
17 files changed, 258 insertions(+), 361 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index de984ace425eb..08f71051e6cbf 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -69,8 +69,6 @@ class DiagnosticsEngine;
class DynTypedNodeList;
class Expr;
enum class FloatModeKind;
-class FunctionEffect;
-class FunctionEffectSet;
class GlobalDecl;
class IdentifierTable;
class LangOptions;
@@ -466,16 +464,6 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// This is the top-level (C++20) Named module we are building.
Module *CurrentCXXNamedModule = nullptr;
- class FunctionEffectSetUniquing {
- llvm::DenseSet<llvm::ArrayRef<FunctionEffect>> Set;
-
- public:
- FunctionEffectSet getUniqued(llvm::ArrayRef<FunctionEffect> FX);
-
- ~FunctionEffectSetUniquing();
- };
- FunctionEffectSetUniquing UniquedFunctionEffectSet;
-
static constexpr unsigned ConstantArrayTypesLog2InitSize = 8;
static constexpr unsigned GeneralTypesLog2InitSize = 9;
static constexpr unsigned FunctionProtoTypesLog2InitSize = 12;
@@ -1082,16 +1070,6 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Get module under construction, nullptr if this is not a C++20 module.
Module *getCurrentNamedModule() const { return CurrentCXXNamedModule; }
- /// Get or create a uniqued, immutable FunctionEffectSet.
- FunctionEffectSet
- getUniquedFunctionEffectSet(llvm::ArrayRef<FunctionEffect> FX) {
- return UniquedFunctionEffectSet.getUniqued(FX);
- }
-
- /// Get or create a uniqued, immutable FunctionEffectSet from a serialized
- /// span of uint32_t's.
- FunctionEffectSet getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX);
-
TranslationUnitDecl *getTranslationUnitDecl() const {
return TUDecl->getMostRecentDecl();
}
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index 1f2797cc70145..be057d8c06584 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -244,6 +244,14 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
return FunctionProtoType::ExtParameterInfo::getFromOpaqueValue(value);
}
+ FunctionEffect readFunctionEffect() {
+ static_assert(sizeof(FunctionEffect().getAsOpaqueValue()) <=
+ sizeof(uint32_t),
+ "update this if size changes");
+ uint32_t value = asImpl().readUInt32();
+ return FunctionEffect::getFromOpaqueValue(value);
+ }
+
NestedNameSpecifier *readNestedNameSpecifier() {
auto &ctx = getASTContext();
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 07afa388de2c1..9f32523c58bbb 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -222,6 +222,12 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
asImpl().writeUInt32(epi.getOpaqueValue());
}
+ void writeFunctionEffect(FunctionEffect effect) {
+ static_assert(sizeof(effect.getAsOpaqueValue()) <= sizeof(uint32_t),
+ "update this if the value size changes");
+ asImpl().writeUInt32(effect.getAsOpaqueValue());
+ }
+
void writeNestedNameSpecifier(NestedNameSpecifier *NNS) {
// Nested name specifiers usually aren't too long. I think that 8 would
// typically accommodate the vast majority.
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index 6df1d93a7ba2e..457d09c2f3cd9 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -117,6 +117,7 @@ def ExtParameterInfo : PropertyType<"FunctionProtoType::ExtParameterInfo">;
def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> {
let PassByReference = 1;
}
+def FunctionEffect : PropertyType<"FunctionEffect">;
def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; }
def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">;
def LValuePathSerializationHelper :
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e4f0c545f80b9..e9d80845da494 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4438,22 +4438,19 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
class FunctionEffectSet;
-class TypeSourceInfo;
-/// Represents an abstract function effect.
+/// Represents an abstract function effect, using just an enumeration describing
+/// its type. Encapsulates its semantic behaviors.
class FunctionEffect {
public:
- /// Identifies the particular type of effect.
- enum class Type {
- None = 0,
+ /// Identifies the particular effect.
+ enum class Kind : uint8_t {
+ None,
NonBlocking,
NonAllocating,
};
- /// Flags describing behaviors of the effect.
- // (Why not a struct with bitfields? There's one function that would like to
- // test a caller-specified bit. There are some potential optimizations that
- // would OR together the bits of multiple effects.)
+ /// Flags describing some behaviors of the effect.
using Flags = unsigned;
enum FlagBit : Flags {
// Can verification inspect callees' implementations? (e.g. nonblocking:
@@ -4477,57 +4474,69 @@ class FunctionEffect {
};
private:
- // For uniqueness, currently only EfType is significant.
+ Kind FKind;
- LLVM_PREFERRED_TYPE(Type)
- unsigned EfType : 2;
- Flags EfFlags : 8; // A constant function of Type but cached here.
-
- // Expansion: for hypothetical TCB+types, there could be one Type for TCB,
- // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
+ // Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
+ // then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
// be considered for uniqueness.
- // Since this struct is serialized as if it were a uint32_t, it's important
- // to pad and explicitly zero the extra bits.
- [[maybe_unused]] unsigned Padding : 22;
-
public:
- FunctionEffect() : EfType(unsigned(Type::None)), EfFlags(0), Padding(0) {}
+ FunctionEffect() : FKind(Kind::None) {}
- explicit FunctionEffect(Type T);
+ explicit FunctionEffect(Kind T) : FKind(T) {}
- /// The type of the effect.
- Type type() const { return Type(EfType); }
+ /// The kind of the effect.
+ Kind kind() const { return FKind; }
- /// Flags describing behaviors of the effect.
- Flags flags() const { return EfFlags; }
+ /// Return an opaque integer, as a serializable representation.
+ uint32_t getAsOpaqueValue() const { return llvm::to_underlying(FKind); }
+
+ /// Construct from a serialized representation.
+ static FunctionEffect getFromOpaqueValue(uint32_t V) {
+ return FunctionEffect(static_cast<Kind>(V));
+ }
+
+ /// Flags describing some behaviors of the effect.
+ Flags flags() const {
+ switch (FKind) {
+ case Kind::NonBlocking:
+ return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
+ FE_ExcludeThreadLocalVars;
+ case Kind::NonAllocating:
+ // Same as NonBlocking, except without FE_ExcludeStaticLocalVars
+ return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
+ case Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+ }
/// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
- /// A hashable representation.
- uint32_t opaqueRepr() const { return EfType | (EfFlags << 2u); }
-
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
bool shouldDiagnoseConversion(bool Adding, QualType OldType,
- FunctionEffectSet OldFX, QualType NewType,
- FunctionEffectSet NewFX) const;
+ const FunctionEffectSet &OldFX,
+ QualType NewType,
+ const FunctionEffectSet &NewFX) const;
/// Return true if adding or removing the effect in a redeclaration should
/// generate a diagnostic.
bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
- FunctionEffectSet OldFX,
+ const FunctionEffectSet &OldFX,
const FunctionDecl &NewFunction,
- FunctionEffectSet NewFX) const;
+ const FunctionEffectSet &NewFX) const;
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
- OverrideResult shouldDiagnoseMethodOverride(bool Adding,
- const CXXMethodDecl &OldMethod,
- FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod,
- FunctionEffectSet NewFX) const;
+ OverrideResult
+ shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+ const FunctionEffectSet &OldFX,
+ const CXXMethodDecl &NewMethod,
+ const FunctionEffectSet &NewFX) const;
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
@@ -4541,99 +4550,91 @@ class FunctionEffect {
// diagnostic. Caller should be assumed to have the effect (it may not have it
// explicitly when inferring).
bool shouldDiagnoseFunctionCall(bool Direct,
- FunctionEffectSet CalleeFX) const;
+ const FunctionEffectSet &CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.EfType == RHS.EfType;
+ return LHS.FKind == RHS.FKind;
}
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.EfType != RHS.EfType;
+ return LHS.FKind != RHS.FKind;
}
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.EfType < RHS.EfType;
+ return LHS.FKind < RHS.FKind;
}
};
-// 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<FunctionEffect, 4> {
-public:
- using SmallVector::insert;
- using SmallVector::SmallVector;
-
- MutableFunctionEffectSet(const FunctionEffect &Effect);
- using Base = SmallVector<FunctionEffect, 4>;
-
- /// Maintains order/uniquenesss.
- void insert(const FunctionEffect &Effect);
-
- MutableFunctionEffectSet &insertMultiple(FunctionEffectSet RHS);
-
- operator llvm::ArrayRef<FunctionEffect>() const { return {data(), size()}; }
-};
-
-/// A constant, uniqued set of FunctionEffect instances.
-// These sets will tend to be very small (0-1 elements), so represent them as
-// sorted spans, which are compatible with the STL set algorithms.
+/// A value type, representing a set of FunctionEffects. To reduce superfluous
+/// retain/release, however, prefer to pass by reference.
class FunctionEffectSet {
private:
- friend class ASTContext; // so it can call the private constructor
+ // Implementation is as a SmallVector, kept sorted.
+ // With a SmallVector size of 4, footprint is 32 bytes (on arm64).
+ // Use indirection through a possibly null reference-counted pointer to
+ // optimize the overwhelmingly common case of an empty set, and to
+ // minimize memory footprint.
+ struct ImplVec : public SmallVector<FunctionEffect, 4>,
+ public RefCountedBase<ImplVec> {};
+ using ImplPtr = IntrusiveRefCntPtr<ImplVec>;
- explicit FunctionEffectSet(llvm::ArrayRef<FunctionEffect> Array)
- : Impl(Array) {}
+ ImplPtr PImpl;
- // Points to a separately allocated array, uniqued.
- llvm::ArrayRef<FunctionEffect> Impl;
+ explicit FunctionEffectSet(ImplPtr &&Ptr) : PImpl(std::move(Ptr)) {}
+ ImplVec &mutableVec();
public:
using Differences = SmallVector<std::pair<FunctionEffect, /*added=*/bool>>;
FunctionEffectSet() = default;
- const void *getOpaqueValue() const { return Impl.data(); }
- llvm::ArrayRef<uint32_t> serializable() const {
- static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
- const uint32_t *ptr = reinterpret_cast<const uint32_t *>(Impl.data());
- return {const_cast<uint32_t *>(ptr), Impl.size()};
- }
+ void Profile(llvm::FoldingSetNodeID &ID) const;
+
+ FunctionEffectSet &operator=(const llvm::ArrayRef<FunctionEffect> arr);
explicit operator bool() const { return !empty(); }
- bool empty() const { return Impl.empty(); }
- size_t size() const { return Impl.size(); }
+ bool empty() const { return PImpl == nullptr || PImpl->empty(); }
+ size_t size() const { return PImpl ? PImpl->size() : 0; }
using iterator = const FunctionEffect *;
- iterator begin() const { return Impl.begin(); }
+ iterator begin() const { return PImpl ? PImpl->begin() : nullptr; }
- iterator end() const { return Impl.end(); }
+ iterator end() const { return PImpl ? PImpl->end() : nullptr; }
- ArrayRef<FunctionEffect> items() const { return Impl; }
+ ArrayRef<FunctionEffect> items() const {
+ if (PImpl)
+ return *PImpl;
+ return {};
+ }
bool operator==(const FunctionEffectSet &RHS) const {
- return Impl.data() == RHS.Impl.data();
+ const FunctionEffectSet &LHS = *this;
+ if (LHS.PImpl == nullptr)
+ return RHS.PImpl == nullptr;
+ if (RHS.PImpl == nullptr)
+ return false;
+ return LHS.PImpl == RHS.PImpl || *LHS.PImpl == *RHS.PImpl;
}
bool operator!=(const FunctionEffectSet &RHS) const {
- return Impl.data() != RHS.Impl.data();
+ return !(*this == RHS);
}
- bool operator<(const FunctionEffectSet &RHS) const;
void dump(llvm::raw_ostream &OS) const;
- MutableFunctionEffectSet intersection(const FunctionEffectSet &RHS) const;
- MutableFunctionEffectSet difference(const FunctionEffectSet &RHS) const;
-
- static FunctionEffectSet getUnion(ASTContext &C, const FunctionEffectSet &LHS,
- const FunctionEffectSet &RHS);
+ FunctionEffectSet getIntersection(const FunctionEffectSet &RHS) const;
+ FunctionEffectSet getDifference(const FunctionEffectSet &RHS) const;
+ FunctionEffectSet getUnion(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 function, block, member
- /// function pointer, reference or pointer to one.
+ /// Extract the effects from a Type if it is a function, block, or member
+ /// function pointer, or a reference or pointer to one.
static FunctionEffectSet get(QualType QT);
+
+ // Mutators
+ void insert(const FunctionEffect &Effect);
+ void insert(const FunctionEffectSet &Set);
};
/// Represents a prototype with parameter type info, e.g.
@@ -4810,7 +4811,7 @@ class FunctionProtoType final
}
unsigned numTrailingObjects(OverloadToken<FunctionEffectSet>) const {
- return hasFunctionEffects();
+ return hasFunctionEffects() ? 1 : 0;
}
/// Determine whether there are any argument types that
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 538523f444757..8ad6a13b3bffe 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -352,8 +352,8 @@ let Class = FunctionProtoType in {
def : Property<"AArch64SMEAttributes", UInt32> {
let Read = [{ node->getAArch64SMEAttributes() }];
}
- def : Property<"functionEffects", Array<UInt32>> {
- let Read = [{ node->getFunctionEffects().serializable() }];
+ def : Property<"functionEffects", Array<FunctionEffect>> {
+ let Read = [{ node->getFunctionEffects().items() }];
}
def : Creator<[{
@@ -371,7 +371,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- epi.FunctionEffects = ctx.getUniquedFunctionEffectSet(functionEffects);
+ epi.FunctionEffects = functionEffects;
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 872947afbcf41..3aa86b2a79ebc 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -723,7 +723,7 @@ class Sema final {
/// FunctionEffectSet to be verified.
SmallVector<const Decl *> DeclsWithEffectsToVerify;
/// The union of all effects present on DeclsWithEffectsToVerify.
- MutableFunctionEffectSet AllEffectsToVerify;
+ FunctionEffectSet AllEffectsToVerify;
/// Determine if VD, which must be a variable or function, is an external
/// symbol that nonetheless can't be referenced from outside this translation
@@ -3155,7 +3155,7 @@ class Sema final {
StorageClass SC);
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);
+ void MaybeAddDeclWithEffects(const Decl *D, const FunctionEffectSet &FX);
// Contexts where using non-trivial C union types can be disallowed. This is
// passed to err_non_trivial_c_union_in_invalid_context.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 26523c1ab370c..ee0550107d71e 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13807,78 +13807,3 @@ StringRef ASTContext::getCUIDHash() const {
CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true);
return CUIDHash;
}
-
-using FunctionEffectSpan = llvm::ArrayRef<FunctionEffect>;
-
-llvm::hash_code hash_value(const FunctionEffect &Effect) {
- return llvm::hash_value(Effect.opaqueRepr());
-}
-
-namespace llvm {
-template <> struct DenseMapInfo<FunctionEffectSpan> {
- static FunctionEffectSpan getEmptyKey() {
- return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
- }
- static FunctionEffectSpan getTombstoneKey() {
- return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
- }
- static unsigned getHashValue(const FunctionEffectSpan &Val) {
- hash_code hash1 = hash_value(Val.size());
- // Treat the FunctionEffects as a span of integers
- const FunctionEffect *Begin = Val.begin();
- const FunctionEffect *End = Val.end();
- hash_code hash2 =
- hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
- reinterpret_cast<const uint32_t *>(End));
- return hash_combine(hash1, hash2);
- }
- static bool isEqual(const FunctionEffectSpan &LHS,
- const FunctionEffectSpan &RHS) {
- if (LHS.size() != RHS.size()) {
- return false;
- }
- // distinguish empty from tombstone
- if (LHS.size() == 0) {
- return LHS.data() == RHS.data();
- }
- return std::equal(LHS.begin(), LHS.end(), RHS.begin());
- }
-};
-} // namespace llvm
-
-FunctionEffectSet
-ASTContext::getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX) {
- static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
- const auto *ptr = reinterpret_cast<const FunctionEffect *>(FX.data());
- return UniquedFunctionEffectSet.getUniqued({ptr, FX.size()});
-}
-
-FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
- llvm::ArrayRef<FunctionEffect> FX) {
- if (FX.empty()) {
- return {};
- }
-
- // Do we already have the incoming set?
- const auto Iter = Set.find_as(FX);
- if (Iter != Set.end()) {
- return FunctionEffectSet(*Iter);
- }
-
- // Copy the incoming array to permanent storage.
- FunctionEffect *Storage = new FunctionEffect[FX.size()];
- std::copy(FX.begin(), FX.end(), Storage);
-
- // Make a new wrapper and insert it into the set.
- llvm::ArrayRef<FunctionEffect> Arr(Storage, FX.size());
- auto [InsIter, _] = Set.insert(Arr);
- return FunctionEffectSet(*InsIter);
-}
-
-ASTContext::FunctionEffectSetUniquing::~FunctionEffectSetUniquing() {
- for (const auto &ArrRef : Set) {
- const FunctionEffect *ptrToFX = ArrRef.data();
- delete[] ptrToFX;
- }
- Set.clear();
-}
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 5d22d058070ab..a9328b1c0d366 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3642,7 +3642,9 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
ExtraBits.HasFunctionEffects = true;
- *getTrailingObjects<FunctionEffectSet>() = epi.FunctionEffects;
+ // N.B. This is uninitialized storage.
+ FunctionEffectSet *PFX = getTrailingObjects<FunctionEffectSet>();
+ new (PFX) FunctionEffectSet(epi.FunctionEffects);
}
}
@@ -3732,7 +3734,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// spec because of the leading 'bool' which unambiguously indicates
// whether the following bool is the EH spec or part of the arguments.
- ID.AddPointer(epi.FunctionEffects.getOpaqueValue());
+ epi.FunctionEffects.Profile(ID);
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -5025,97 +5027,77 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
-FunctionEffect::FunctionEffect(Type T)
- : EfType(static_cast<unsigned>(T)), EfFlags(0), Padding(0) {
- switch (T) {
- case Type::NonBlocking:
- EfFlags = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
- FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
- FE_ExcludeThreadLocalVars;
- break;
-
- case Type::NonAllocating:
- // Same as NonBlocking, except without FE_ExcludeStaticLocalVars
- EfFlags = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
- FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
- break;
- case Type::None:
- break;
- }
- llvm_unreachable("unknown effect type");
-}
-
StringRef FunctionEffect::name() const {
- switch (type()) {
- case Type::NonBlocking:
+ switch (kind()) {
+ case Kind::NonBlocking:
return "nonblocking";
- case Type::NonAllocating:
+ case Kind::NonAllocating:
return "nonallocating";
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
-bool FunctionEffect::shouldDiagnoseConversion(bool Adding, QualType OldType,
- FunctionEffectSet OldFX,
- QualType NewType,
- FunctionEffectSet NewFX) const {
+bool FunctionEffect::shouldDiagnoseConversion(
+ bool Adding, QualType OldType, const FunctionEffectSet &OldFX,
+ QualType NewType, const FunctionEffectSet &NewFX) const {
- switch (type()) {
- case Type::NonAllocating:
+ switch (kind()) {
+ case Kind::NonAllocating:
// nonallocating can't be added (spoofed) during a conversion, unless we
// have nonblocking
if (Adding) {
for (const auto &Effect : OldFX) {
- if (Effect.type() == Type::NonBlocking)
+ if (Effect.kind() == Kind::NonBlocking)
return false;
}
}
[[fallthrough]];
- case Type::NonBlocking:
+ case Kind::NonBlocking:
// nonblocking can't be added (spoofed) during a conversion
return Adding;
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
bool FunctionEffect::shouldDiagnoseRedeclaration(
- bool Adding, const FunctionDecl &OldFunction, FunctionEffectSet OldFX,
- const FunctionDecl &NewFunction, FunctionEffectSet NewFX) const {
- switch (type()) {
- case Type::NonAllocating:
- case Type::NonBlocking:
+ bool Adding, const FunctionDecl &OldFunction,
+ const FunctionEffectSet &OldFX, const FunctionDecl &NewFunction,
+ const FunctionEffectSet &NewFX) const {
+ switch (kind()) {
+ case Kind::NonAllocating:
+ case Kind::NonBlocking:
// nonblocking/nonallocating can't be removed in a redeclaration
// adding -> false, removing -> true (diagnose)
return !Adding;
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
- bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
- const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
- switch (type()) {
- case Type::NonAllocating:
- case Type::NonBlocking:
+ bool Adding, const CXXMethodDecl &OldMethod, const FunctionEffectSet &OldFX,
+ const CXXMethodDecl &NewMethod, const FunctionEffectSet &NewFX) const {
+ switch (kind()) {
+ case Kind::NonAllocating:
+ case Kind::NonBlocking:
// if added on an override, that's fine and not diagnosed.
// if missing from an override (removed), propagate from base to derived.
return Adding ? OverrideResult::Ignore : OverrideResult::Merge;
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
- switch (type()) {
- case Type::NonAllocating:
- case Type::NonBlocking:
+ switch (kind()) {
+ case Kind::NonAllocating:
+ case Kind::NonBlocking:
// Do any of the callee's Decls have type sugar for blocking or allocating?
for (const Decl *D : Callee.redecls()) {
QualType QT;
@@ -5134,96 +5116,115 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
while (AT) {
if (AT->getAttrKind() == attr::Allocating)
return false;
- if (type() == Type::NonBlocking && AT->getAttrKind() == attr::Blocking)
+ if (kind() == Kind::NonBlocking && AT->getAttrKind() == attr::Blocking)
return false;
AT = AT->getModifiedType()->getAs<AttributedType>();
}
}
return true;
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
bool FunctionEffect::shouldDiagnoseFunctionCall(
- bool Direct, FunctionEffectSet CalleeFX) const {
- switch (type()) {
- case Type::NonAllocating:
- case Type::NonBlocking: {
- const Type CallerType = type();
+ bool Direct, const FunctionEffectSet &CalleeFX) const {
+ switch (kind()) {
+ case Kind::NonAllocating:
+ case Kind::NonBlocking: {
+ const Kind CallerKind = kind();
for (const auto &Effect : CalleeFX) {
- const Type ET = Effect.type();
+ const Kind EK = Effect.kind();
// Does callee have same or stronger constraint?
- if (ET == CallerType ||
- (CallerType == Type::NonAllocating && ET == Type::NonBlocking)) {
+ if (EK == CallerKind ||
+ (CallerKind == Kind::NonAllocating && EK == Kind::NonBlocking)) {
return false; // no diagnostic
}
}
return true; // warning
}
- case Type::None:
+ case Kind::None:
break;
}
- llvm_unreachable("unknown effect type");
+ llvm_unreachable("unknown effect kind");
}
// =====
-MutableFunctionEffectSet::MutableFunctionEffectSet(
- const FunctionEffect &effect) {
- push_back(effect);
+void FunctionEffectSet::Profile(llvm::FoldingSetNodeID &ID) const {
+ if (PImpl)
+ for (const auto &Effect : *PImpl)
+ ID.AddInteger(llvm::to_underlying(Effect.kind()));
}
-void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
- const auto &Iter = std::lower_bound(begin(), end(), Effect);
- if (Iter == end() || *Iter != Effect) {
- insert(Iter, Effect);
- }
-}
-
-MutableFunctionEffectSet &
-MutableFunctionEffectSet::insertMultiple(FunctionEffectSet RHS) {
- // TODO: For large RHS sets, use set_union or a custom insert-in-place
- for (const auto &Effect : RHS) {
- insert(Effect);
+FunctionEffectSet &
+FunctionEffectSet::operator=(llvm::ArrayRef<FunctionEffect> Arr) {
+ if (Arr.empty()) {
+ PImpl.reset();
+ } else {
+ if (PImpl == nullptr)
+ PImpl = new ImplVec;
+ PImpl->assign(Arr.begin(), Arr.end());
}
return *this;
}
-FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
- const FunctionEffectSet &LHS,
- const FunctionEffectSet &RHS) {
- // Optimize for one of the two sets being empty
+FunctionEffectSet
+FunctionEffectSet::getUnion(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ // Optimize for either of the two sets being empty (very common).
if (LHS.empty())
return RHS;
if (RHS.empty())
return LHS;
- // Optimize the case where the two sets are identical
+ // Optimize for the two sets being identical (very common).
if (LHS == RHS)
return LHS;
- MutableFunctionEffectSet Vec;
- Vec.reserve(LHS.size() + RHS.size());
+ ImplPtr PVec(new ImplVec);
+ PVec->reserve(LHS.size() + RHS.size());
std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Vec));
- // The result of a set operation is an ordered/unique set.
- return C.getUniquedFunctionEffectSet(Vec);
+ std::back_inserter(*PVec));
+ return FunctionEffectSet(std::move(PVec));
}
-MutableFunctionEffectSet
-FunctionEffectSet::intersection(const FunctionEffectSet &RHS) const {
+FunctionEffectSet
+FunctionEffectSet::getIntersection(const FunctionEffectSet &RHS) const {
const FunctionEffectSet &LHS = *this;
if (LHS.empty() || RHS.empty()) {
return {};
}
- MutableFunctionEffectSet Vec;
+ ImplPtr PVec(new ImplVec);
std::set_intersection(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Vec));
- // The result of a set operation is an ordered/unique set.
- return Vec;
+ std::back_inserter(*PVec));
+ return FunctionEffectSet(std::move(PVec));
+}
+
+FunctionEffectSet::Differences
+FunctionEffectSet::differences(const FunctionEffectSet &Old,
+ const FunctionEffectSet &New) {
+ // TODO: Could be a one-pass algorithm.
+ Differences Result;
+ for (const auto &Effect : New.getDifference(Old)) {
+ Result.emplace_back(Effect, true);
+ }
+ for (const auto &Effect : Old.getDifference(New)) {
+ Result.emplace_back(Effect, false);
+ }
+ return Result;
+}
+
+FunctionEffectSet
+FunctionEffectSet::getDifference(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ ImplPtr PVec(new ImplVec);
+
+ std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(*PVec));
+ return FunctionEffectSet(std::move(PVec));
}
// TODO: inline?
@@ -5233,6 +5234,7 @@ FunctionEffectSet FunctionEffectSet::get(QualType QT) {
if (QT->isPointerType())
QT = QT->getPointeeType();
+ // TODO: Why aren't these included in isPointerType()?
if (const auto *BT = QT->getAs<BlockPointerType>()) {
QT = BT->getPointeeType();
} else if (const auto *MP = QT->getAs<MemberPointerType>()) {
@@ -5247,34 +5249,29 @@ FunctionEffectSet FunctionEffectSet::get(QualType QT) {
return {};
}
-FunctionEffectSet::Differences
-FunctionEffectSet::differences(const FunctionEffectSet &Old,
- const FunctionEffectSet &New) {
- // TODO: Could be a one-pass algorithm.
- Differences Result;
- for (const auto &Effect : New.difference(Old)) {
- Result.emplace_back(Effect, true);
- }
- for (const auto &Effect : Old.difference(New)) {
- Result.emplace_back(Effect, false);
- }
- return Result;
+// For use in mutating methods: only allow mutating the vector in
+// place when we hold the only reference. Otherwise, copy it first.
+FunctionEffectSet::ImplVec &FunctionEffectSet::mutableVec() {
+ if (PImpl->UseCount() > 1)
+ PImpl = new ImplVec(*PImpl);
+ assert(PImpl->UseCount() == 1 && "mutating a shared function effect set");
+ return *PImpl;
}
-MutableFunctionEffectSet
-FunctionEffectSet::difference(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
- MutableFunctionEffectSet Result;
-
- std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result));
- return Result;
+void FunctionEffectSet::insert(const FunctionEffect &Effect) {
+ if (PImpl == nullptr)
+ PImpl = new ImplVec;
+ auto *Iter = std::lower_bound(PImpl->begin(), PImpl->end(), Effect);
+ if (Iter == PImpl->end() || *Iter != Effect) {
+ mutableVec().insert(Iter, Effect);
+ }
}
-bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
- return std::lexicographical_compare(LHS.begin(), LHS.end(), RHS.begin(),
- RHS.end());
+void FunctionEffectSet::insert(const FunctionEffectSet &Set) {
+ // TODO: For large RHS sets, use set_union or a custom insert-in-place
+ for (const auto &Effect : Set) {
+ insert(Effect);
+ }
}
LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 008d1798f3628..947f5ff98f74d 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2613,13 +2613,13 @@ class PendingFunctionAnalysis {
std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
- FunctionEffectSet AllInferrableEffectsToVerify) {
- ASTContext &Ctx = Sem.getASTContext();
+ PendingFunctionAnalysis(
+ Sema &Sem, const CallableInfo &CInfo,
+ const FunctionEffectSet &AllInferrableEffectsToVerify) {
DeclaredVerifiableEffects = CInfo.Effects;
// Check for effects we are not allowed to infer
- MutableFunctionEffectSet FX;
+ FunctionEffectSet FX;
for (const auto &effect : AllInferrableEffectsToVerify) {
if (effect.canInferOnFunction(*CInfo.CDecl)) {
@@ -2635,9 +2635,7 @@ class PendingFunctionAnalysis {
}
}
// FX is now the set of inferrable effects which are not prohibited
- FXToInfer = Ctx.getUniquedFunctionEffectSet(
- Ctx.getUniquedFunctionEffectSet(FX).difference(
- DeclaredVerifiableEffects));
+ FXToInfer = FX.getDifference(DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2718,17 +2716,16 @@ class CompleteFunctionAnalysis {
EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
public:
- CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &pending,
- FunctionEffectSet funcFX,
- FunctionEffectSet AllInferrableEffectsToVerify) {
- MutableFunctionEffectSet verified;
- verified.insertMultiple(funcFX);
+ CompleteFunctionAnalysis(
+ ASTContext &Ctx, PendingFunctionAnalysis &pending,
+ const FunctionEffectSet &funcFX,
+ const FunctionEffectSet &AllInferrableEffectsToVerify) {
+ VerifiedEffects.insert(funcFX);
for (const auto &effect : AllInferrableEffectsToVerify) {
if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
- verified.insert(effect);
+ VerifiedEffects.insert(effect);
}
}
- VerifiedEffects = Ctx.getUniquedFunctionEffectSet(verified);
InferrableEffectToFirstDiagnostic =
std::move(pending.InferrableEffectToFirstDiagnostic);
@@ -2835,15 +2832,12 @@ class Analyzer {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
{
- MutableFunctionEffectSet inferrableEffects;
for (const FunctionEffect &effect : Sem.AllEffectsToVerify) {
const auto Flags = effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees) {
- inferrableEffects.insert(effect);
+ AllInferrableEffectsToVerify.insert(effect);
}
}
- AllInferrableEffectsToVerify =
- Sem.getASTContext().getUniquedFunctionEffectSet(inferrableEffects);
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "AllInferrableEffectsToVerify: ";
AllInferrableEffectsToVerify.dump(llvm::outs());
@@ -3016,8 +3010,7 @@ class Analyzer {
if (DirectCall) {
if (auto *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
// Combine declared effects with those which may have been inferred.
- CalleeEffects = FunctionEffectSet::getUnion(
- Sem.getASTContext(), CalleeEffects, CFA->VerifiedEffects);
+ CalleeEffects.insert(CFA->VerifiedEffects);
IsInferencePossible = false; // we've already traversed it
}
}
@@ -3589,7 +3582,7 @@ class Analyzer {
if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
if (const auto FX = functionEffectsForDecl(Callable)) {
// Reuse this filtering method in Sema
- Sem.CheckAddCallableWithEffects(Callable, FX);
+ Sem.MaybeAddDeclWithEffects(Callable, FX);
}
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2108ee75ddc67..3421709fd24aa 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3940,7 +3940,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// declaration, but that would trigger an additional "conflicting types"
// error.
if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
- auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
+ auto MergedFX = OldFX.getUnion(NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
EPI.FunctionEffects = MergedFX;
@@ -11128,7 +11128,7 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
-void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
+void Sema::MaybeAddDeclWithEffects(const Decl *D, const FunctionEffectSet &FX) {
if (!D->hasBody()) {
if (const auto *FD = D->getAsFunction()) {
if (!FD->willHaveBody()) {
@@ -11152,7 +11152,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
return;
}
- AllEffectsToVerify.insertMultiple(FX);
+ AllEffectsToVerify.insert(FX);
// Record the declaration for later analysis.
DeclsWithEffectsToVerify.push_back(D);
@@ -16064,7 +16064,7 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
- CheckAddCallableWithEffects(FD, FX);
+ MaybeAddDeclWithEffects(FD, FX);
}
return D;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 04ec7ee7ae702..e87e34555cc80 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18347,7 +18347,7 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
AnyDiags = true;
break;
case FunctionEffect::OverrideResult::Merge: {
- auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
+ auto MergedFX = OldFX.getUnion(NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
EPI.FunctionEffects = MergedFX;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 26a798675e3c7..2b4e1693cb1f1 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17157,7 +17157,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockDecl *BD = BSI->TheDecl;
if (const auto FX = BD->getFunctionEffects()) {
- CheckAddCallableWithEffects(BD, FX);
+ MaybeAddDeclWithEffects(BD, FX);
}
if (BSI->HasImplicitReturnType)
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 0e1a0f8dfdbab..0772a2dec7b56 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1890,7 +1890,7 @@ ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
ActOnFinishFunctionBody(LSI.CallOperator, Body);
if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
- CheckAddCallableWithEffects(LSI.CallOperator, FX);
+ MaybeAddDeclWithEffects(LSI.CallOperator, FX);
}
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d00752822bfcb..08fce7ac781ce 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8120,24 +8120,12 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// nonblocking(true) and nonallocating(true) are represented as
// FunctionEffects, in a FunctionEffectSet attached to a FunctionProtoType.
const FunctionEffect NewEffect(isNonBlocking
- ? FunctionEffect::Type::NonBlocking
- : FunctionEffect::Type::NonAllocating);
+ ? FunctionEffect::Kind::NonBlocking
+ : FunctionEffect::Kind::NonAllocating);
- MutableFunctionEffectSet NewFX(NewEffect);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+ EPI.FunctionEffects.insert(NewEffect);
- if (EPI.FunctionEffects) {
- // Preserve any previous effects - except nonallocating, when we are adding
- // nonblocking
- for (const auto &Effect : EPI.FunctionEffects) {
- if (!(isNonBlocking &&
- Effect.type() == FunctionEffect::Type::NonAllocating))
- NewFX.insert(Effect);
- }
- }
-
- EPI.FunctionEffects =
- TPState.getSema().getASTContext().getUniquedFunctionEffectSet(NewFX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
QT = Unwrapped.wrap(S, newtype->getAs<FunctionType>());
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 2a46eb68b4092..2648f46b41422 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8233,7 +8233,7 @@ void ASTReader::InitializeSema(Sema &S) {
FX = BD->getFunctionEffects();
}
if (FX) {
- SemaObj->AllEffectsToVerify.insertMultiple(FX);
+ SemaObj->AllEffectsToVerify.insert(FX);
}
}
DeclsWithEffectsToVerify.clear();
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 5a451f23f291f..8fe3cfe529df4 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -45,7 +45,7 @@ void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'
void nl2() [[clang::nonallocating]] [[clang::nonblocking]];
-// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nonblocking))'
+// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
decltype(nl1) nl3;
// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nonblocking))'
>From 80d5e7a03ccc34e7f95273b4597698391ba7ae33 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 11 Apr 2024 12:00:55 -0700
Subject: [PATCH 28/71] - Get rid of ugly const_cast in
Sema::CheckOverridingFunctionAttributes. - Clarify comment in
Sema::IsFunctionConversion.
---
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Sema/SemaDeclCXX.cpp | 9 ++-------
clang/lib/Sema/SemaOverload.cpp | 8 ++++----
3 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3aa86b2a79ebc..33d9160d51020 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4745,7 +4745,7 @@ class Sema final {
std::string getAmbiguousPathsDisplayString(CXXBasePaths &Paths);
- bool CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
+ bool CheckOverridingFunctionAttributes(CXXMethodDecl *New,
const CXXMethodDecl *Old);
/// CheckOverridingFunctionReturnType - Checks whether the return types are
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index e87e34555cc80..95b8d4231b401 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18288,7 +18288,7 @@ void Sema::SetFunctionBodyKind(Decl *D, SourceLocation Loc,
}
}
-bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
+bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
const CXXMethodDecl *Old) {
const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
const auto *OldFT = Old->getType()->castAs<FunctionProtoType>();
@@ -18353,12 +18353,7 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
EPI.FunctionEffects = MergedFX;
QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
NewFT->getParamTypes(), EPI);
-
- // TODO: It's ugly to be mutating the incoming const method. It is
- // mutable in the calling function, though. There is also the
- // possibility here that we are discarding some other sort of sugar on
- // the method's type.
- const_cast<CXXMethodDecl *>(New)->setType(ModQT);
+ New->setType(ModQT);
} break;
}
}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 1396534949caf..6dfe62417db79 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1869,11 +1869,11 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
Changed = true;
}
+ // For C, when called from checkPointerTypesForAssignment,
+ // we need not to alter FromFn, or else even an innocuous cast
+ // like dropping effects will fail. In C++ however we do want to
+ // alter FromFn. TODO: Is this correct?
if (getLangOpts().CPlusPlus) {
- // For C, when called from checkPointerTypesForAssignment,
- // we need not to change the type, or else even an innocuous cast
- // like dropping effects will fail.
- // TODO: Is this correct?
FromFPT =
dyn_cast<FunctionProtoType>(FromFn); // in case FromFn changed above
>From 9e223a398d943b019fe1431202c023b335148a7d Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 11 Apr 2024 12:07:13 -0700
Subject: [PATCH 29/71] Split out the Objective-C tests into a separate source
file.
---
...ts.mm => attr-nonblocking-constraints.cpp} | 15 ------------
.../attr-nonblocking-constraints.mm | 23 +++++++++++++++++++
2 files changed, 23 insertions(+), 15 deletions(-)
rename clang/test/Sema/{attr-nonblocking-constraints.mm => attr-nonblocking-constraints.cpp} (91%)
create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
diff --git a/clang/test/Sema/attr-nonblocking-constraints.mm b/clang/test/Sema/attr-nonblocking-constraints.cpp
similarity index 91%
rename from clang/test/Sema/attr-nonblocking-constraints.mm
rename to clang/test/Sema/attr-nonblocking-constraints.cpp
index e805a526a1bbd..a273beac1baf7 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.mm
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -150,21 +150,6 @@ void nl12() {
void nl12() [[clang::nonblocking]];
void nl13() [[clang::nonblocking]] { nl12(); }
-// Objective-C
- at interface OCClass
-- (void)method;
- at end
-
-void nl14(OCClass *oc) [[clang::nonblocking]] {
- [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
-}
-void nl15(OCClass *oc) {
- [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
-}
-void nl16(OCClass *oc) [[clang::nonblocking]] {
- nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
-}
-
// C++ member function pointers
struct PTMFTester {
typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
new file mode 100644
index 0000000000000..aeb8b21f56e44
--- /dev/null
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
+#endif
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+ [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
+}
+void nl15(OCClass *oc) {
+ [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
+}
+void nl16(OCClass *oc) [[clang::nonblocking]] {
+ nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
+}
+
>From 9bac8d28ab5cb5d9d58125baea6c75695d622a98 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 11 Apr 2024 13:59:14 -0700
Subject: [PATCH 30/71] Make FunctionEffectSet::operator==() more elegant.
Tweak FunctionProtoType::Profile.
---
clang/include/clang/AST/Type.h | 8 ++++----
clang/lib/AST/Type.cpp | 4 ++++
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e9d80845da494..2df18f4fac002 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4608,11 +4608,11 @@ class FunctionEffectSet {
bool operator==(const FunctionEffectSet &RHS) const {
const FunctionEffectSet &LHS = *this;
- if (LHS.PImpl == nullptr)
- return RHS.PImpl == nullptr;
- if (RHS.PImpl == nullptr)
+ if (LHS.PImpl == RHS.PImpl)
+ return true;
+ if (LHS.PImpl == nullptr || RHS.PImpl == nullptr)
return false;
- return LHS.PImpl == RHS.PImpl || *LHS.PImpl == *RHS.PImpl;
+ return *LHS.PImpl == *RHS.PImpl;
}
bool operator!=(const FunctionEffectSet &RHS) const {
return !(*this == RHS);
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index a9328b1c0d366..ae0d8e082492d 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3734,7 +3734,10 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// spec because of the leading 'bool' which unambiguously indicates
// whether the following bool is the EH spec or part of the arguments.
+ // TODO: The effect set is variable-length, though prefaced with a size.
+ // Does this create potential ambiguity?
epi.FunctionEffects.Profile(ID);
+
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -5153,6 +5156,7 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
// =====
void FunctionEffectSet::Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.AddInteger(size());
if (PImpl)
for (const auto &Effect : *PImpl)
ID.AddInteger(llvm::to_underlying(Effect.kind()));
>From b955435ba4e2b0d449bf4035f19b2cb64a594b10 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 13 Apr 2024 16:05:49 -0700
Subject: [PATCH 31/71] Create a flat effect representation in
FunctionProtoType. Renaming of multiple flavors of effect set.
---
clang/include/clang/AST/AbstractBasicReader.h | 8 +-
clang/include/clang/AST/AbstractBasicWriter.h | 7 +-
clang/include/clang/AST/Decl.h | 4 +-
clang/include/clang/AST/PropertiesBase.td | 2 +-
clang/include/clang/AST/Type.h | 215 +++++++++++-------
clang/include/clang/AST/TypeProperties.td | 6 +-
clang/include/clang/Sema/Sema.h | 25 +-
clang/lib/AST/ASTContext.cpp | 4 +-
clang/lib/AST/Decl.cpp | 4 +-
clang/lib/AST/Type.cpp | 193 +++++++---------
clang/lib/AST/TypePrinter.cpp | 9 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 171 ++++++++++++--
clang/lib/Sema/Sema.cpp | 8 +-
clang/lib/Sema/SemaDecl.cpp | 18 +-
clang/lib/Sema/SemaDeclCXX.cpp | 8 +-
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/lib/Sema/SemaLambda.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 15 +-
clang/lib/Serialization/ASTReader.cpp | 4 +-
clang/test/Sema/attr-nonblocking-syntax.cpp | 6 +-
20 files changed, 439 insertions(+), 272 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index be057d8c06584..7ab4cad479161 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -244,12 +244,12 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
return FunctionProtoType::ExtParameterInfo::getFromOpaqueValue(value);
}
- FunctionEffect readFunctionEffect() {
- static_assert(sizeof(FunctionEffect().getAsOpaqueValue()) <=
- sizeof(uint32_t),
+ CondFunctionEffect readCondFunctionEffect() {
+ static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
"update this if size changes");
uint32_t value = asImpl().readUInt32();
- return FunctionEffect::getFromOpaqueValue(value);
+ Expr *cond = asImpl().readExprRef();
+ return CondFunctionEffect(static_cast<FunctionEffect::Kind>(value), cond);
}
NestedNameSpecifier *readNestedNameSpecifier() {
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 9f32523c58bbb..24d6d18d9e8b4 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -222,10 +222,11 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
asImpl().writeUInt32(epi.getOpaqueValue());
}
- void writeFunctionEffect(FunctionEffect effect) {
- static_assert(sizeof(effect.getAsOpaqueValue()) <= sizeof(uint32_t),
+ void writeCondFunctionEffect(const CondFunctionEffect &effect) {
+ static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
"update this if the value size changes");
- asImpl().writeUInt32(effect.getAsOpaqueValue());
+ asImpl().writeUInt32(llvm::to_underlying(effect.effect().kind()));
+ asImpl().writeExprRef(effect.condition());
}
void writeNestedNameSpecifier(NestedNameSpecifier *NNS) {
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index acff3dee7051d..0709223042ad4 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,7 +3008,7 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
- FunctionEffectSet getFunctionEffects() const;
+ FunctionTypeEffects getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
@@ -4635,7 +4635,7 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
- FunctionEffectSet getFunctionEffects() const;
+ FunctionTypeEffects getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index 457d09c2f3cd9..157d75ccdd584 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -117,7 +117,7 @@ def ExtParameterInfo : PropertyType<"FunctionProtoType::ExtParameterInfo">;
def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> {
let PassByReference = 1;
}
-def FunctionEffect : PropertyType<"FunctionEffect">;
+def CondFunctionEffect : PropertyType<"CondFunctionEffect">;
def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; }
def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">;
def LValuePathSerializationHelper :
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 2df18f4fac002..552aca902f658 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4295,12 +4295,11 @@ class FunctionType : public Type {
LLVM_PREFERRED_TYPE(bool)
unsigned HasArmTypeAttributes : 1;
- LLVM_PREFERRED_TYPE(bool)
- unsigned HasFunctionEffects : 1;
+ unsigned NumFunctionEffects : 4;
FunctionTypeExtraBitfields()
: NumExceptionType(0), HasArmTypeAttributes(false),
- HasFunctionEffects(false) {}
+ NumFunctionEffects(0) {}
};
/// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number
@@ -4437,10 +4436,11 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
-class FunctionEffectSet;
+class FunctionTypeEffects;
+class FunctionTypeEffectSet;
/// Represents an abstract function effect, using just an enumeration describing
-/// its type. Encapsulates its semantic behaviors.
+/// its kind.
class FunctionEffect {
public:
/// Identifies the particular effect.
@@ -4474,28 +4474,20 @@ class FunctionEffect {
};
private:
- Kind FKind;
+ Kind FKind = Kind::None;
// Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
// then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
// be considered for uniqueness.
public:
- FunctionEffect() : FKind(Kind::None) {}
+ FunctionEffect() = default;
- explicit FunctionEffect(Kind T) : FKind(T) {}
+ explicit FunctionEffect(Kind K) : FKind(K) {}
/// The kind of the effect.
Kind kind() const { return FKind; }
- /// Return an opaque integer, as a serializable representation.
- uint32_t getAsOpaqueValue() const { return llvm::to_underlying(FKind); }
-
- /// Construct from a serialized representation.
- static FunctionEffect getFromOpaqueValue(uint32_t V) {
- return FunctionEffect(static_cast<Kind>(V));
- }
-
/// Flags describing some behaviors of the effect.
Flags flags() const {
switch (FKind) {
@@ -4519,24 +4511,24 @@ class FunctionEffect {
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
bool shouldDiagnoseConversion(bool Adding, QualType OldType,
- const FunctionEffectSet &OldFX,
+ const FunctionTypeEffects &OldFX,
QualType NewType,
- const FunctionEffectSet &NewFX) const;
+ const FunctionTypeEffects &NewFX) const;
/// Return true if adding or removing the effect in a redeclaration should
/// generate a diagnostic.
bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
- const FunctionEffectSet &OldFX,
+ const FunctionTypeEffects &OldFX,
const FunctionDecl &NewFunction,
- const FunctionEffectSet &NewFX) const;
+ const FunctionTypeEffects &NewFX) const;
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
OverrideResult
shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionEffectSet &OldFX,
+ const FunctionTypeEffects &OldFX,
const CXXMethodDecl &NewMethod,
- const FunctionEffectSet &NewFX) const;
+ const FunctionTypeEffects &NewFX) const;
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
@@ -4550,91 +4542,134 @@ class FunctionEffect {
// diagnostic. Caller should be assumed to have the effect (it may not have it
// explicitly when inferring).
bool shouldDiagnoseFunctionCall(bool Direct,
- const FunctionEffectSet &CalleeFX) const;
+ ArrayRef<FunctionEffect> CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.FKind == RHS.FKind;
}
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.FKind != RHS.FKind;
+ return !(LHS == RHS);
}
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.FKind < RHS.FKind;
}
};
-/// A value type, representing a set of FunctionEffects. To reduce superfluous
-/// retain/release, however, prefer to pass by reference.
-class FunctionEffectSet {
-private:
- // Implementation is as a SmallVector, kept sorted.
- // With a SmallVector size of 4, footprint is 32 bytes (on arm64).
- // Use indirection through a possibly null reference-counted pointer to
- // optimize the overwhelmingly common case of an empty set, and to
- // minimize memory footprint.
- struct ImplVec : public SmallVector<FunctionEffect, 4>,
- public RefCountedBase<ImplVec> {};
- using ImplPtr = IntrusiveRefCntPtr<ImplVec>;
+/// A FunctionEffect plus a potential boolean expression determining whether
+/// the effect is declared (e.g. nonblocking(expr)). Generally the condition
+/// expression when present, is dependent.
+class CondFunctionEffect {
+ FunctionEffect Effect;
+ const Expr *Cond = nullptr; // if null, unconditional
- ImplPtr PImpl;
+public:
+ CondFunctionEffect() = default;
+ CondFunctionEffect(FunctionEffect::Kind K, const Expr *Cond)
+ : Effect(K), Cond(Cond) {}
- explicit FunctionEffectSet(ImplPtr &&Ptr) : PImpl(std::move(Ptr)) {}
- ImplVec &mutableVec();
+ const FunctionEffect &effect() const { return Effect; }
+ const Expr *condition() const { return Cond; }
-public:
- using Differences = SmallVector<std::pair<FunctionEffect, /*added=*/bool>>;
+private:
+ // Comparisons are tricky when the condition might be involved, so limit who
+ // might be doing them.
+ friend FunctionTypeEffects;
+ friend FunctionTypeEffectSet;
- FunctionEffectSet() = default;
+ friend bool operator==(const CondFunctionEffect &LHS,
+ const CondFunctionEffect &RHS) {
+ return LHS.Effect == RHS.Effect && LHS.Cond == RHS.Cond;
+ }
+ friend bool operator!=(const CondFunctionEffect &LHS,
+ const CondFunctionEffect &RHS) {
+ return !(LHS == RHS);
+ }
+ friend bool operator<(const CondFunctionEffect &LHS,
+ const CondFunctionEffect &RHS) {
+ return LHS.Effect < RHS.Effect;
+ }
+};
- void Profile(llvm::FoldingSetNodeID &ID) const;
+// Container naming:
+// "type effects" are the ones which include a condition
+// FunctionTypeEffectsRef - holds pointers
+// FunctionTypeEffectSet - mutable
+// FunctionEffectSet - mutable, no condition
- FunctionEffectSet &operator=(const llvm::ArrayRef<FunctionEffect> arr);
+/// An immutable set of CondFunctionEffect. The effects reside in memory not
+/// managed by this object (typically, trailing objects in FunctionProtoType).
+class FunctionTypeEffects {
+ ArrayRef<CondFunctionEffect> Items;
- explicit operator bool() const { return !empty(); }
- bool empty() const { return PImpl == nullptr || PImpl->empty(); }
- size_t size() const { return PImpl ? PImpl->size() : 0; }
+public:
+ /// Extract the effects from a Type if it is a function, block, or member
+ /// function pointer, or a reference or pointer to one.
+ static FunctionTypeEffects get(QualType QT);
- using iterator = const FunctionEffect *;
+ FunctionTypeEffects() = default;
- iterator begin() const { return PImpl ? PImpl->begin() : nullptr; }
+ // The array is expected to have been sorted by the caller.
+ explicit FunctionTypeEffects(ArrayRef<CondFunctionEffect> Arr) : Items(Arr) {}
- iterator end() const { return PImpl ? PImpl->end() : nullptr; }
+ operator bool() const { return !Items.empty(); }
+ size_t size() const { return Items.size(); }
+ operator ArrayRef<CondFunctionEffect>() const { return Items; }
- ArrayRef<FunctionEffect> items() const {
- if (PImpl)
- return *PImpl;
- return {};
- }
+ using iterator = const CondFunctionEffect *;
+ iterator begin() const { return Items.begin(); }
+ iterator end() const { return Items.end(); }
- bool operator==(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
- if (LHS.PImpl == RHS.PImpl)
- return true;
- if (LHS.PImpl == nullptr || RHS.PImpl == nullptr)
- return false;
- return *LHS.PImpl == *RHS.PImpl;
+ friend bool operator==(const FunctionTypeEffects &LHS,
+ const FunctionTypeEffects &RHS) {
+ return LHS.Items == RHS.Items;
}
- bool operator!=(const FunctionEffectSet &RHS) const {
- return !(*this == RHS);
+ friend bool operator!=(const FunctionTypeEffects &LHS,
+ const FunctionTypeEffects &RHS) {
+ return !(LHS == RHS);
}
+ void Profile(llvm::FoldingSetNodeID &ID) const;
void dump(llvm::raw_ostream &OS) const;
+};
- FunctionEffectSet getIntersection(const FunctionEffectSet &RHS) const;
- FunctionEffectSet getDifference(const FunctionEffectSet &RHS) const;
- FunctionEffectSet getUnion(const FunctionEffectSet &RHS) const;
+/// A mutable set of CondFunctionEffect.
+// Used transitorily within Sema to compare and merge effects on declarations.
+class FunctionTypeEffectSet {
+ SmallVector<CondFunctionEffect> Impl;
- /// Caller should short-circuit by checking for equality first.
- static Differences differences(const FunctionEffectSet &Old,
- const FunctionEffectSet &New);
+public:
+ FunctionTypeEffectSet() = default;
+ explicit FunctionTypeEffectSet(ArrayRef<CondFunctionEffect> Arr)
+ : Impl(Arr.begin(), Arr.end()) {}
- /// Extract the effects from a Type if it is a function, block, or member
- /// function pointer, or a reference or pointer to one.
- static FunctionEffectSet get(QualType QT);
+ explicit operator bool() const { return !Impl.empty(); }
+
+ // Implicit conversion to ArrayRef - careful with lifetime.
+ operator ArrayRef<CondFunctionEffect>() const { return Impl; }
+
+ using iterator = const CondFunctionEffect *;
+ iterator begin() const { return Impl.begin(); }
+ iterator end() const { return Impl.end(); }
+
+ void dump(llvm::raw_ostream &OS) const;
// Mutators
- void insert(const FunctionEffect &Effect);
- void insert(const FunctionEffectSet &Set);
+ void insert(const CondFunctionEffect &Effect);
+ void insert(ArrayRef<CondFunctionEffect> Arr);
+ void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+
+ // Set operations, using ArrayRef to support FunctionTypeEffects
+
+ using Differences =
+ SmallVector<std::pair<CondFunctionEffect, /*added=*/bool>>;
+ /// Caller should short-circuit by checking for equality first.
+ static Differences differences(const FunctionTypeEffects &Old,
+ const FunctionTypeEffects &New);
+
+ static FunctionTypeEffectSet getUnion(ArrayRef<CondFunctionEffect> LHS,
+ ArrayRef<CondFunctionEffect> RHS);
+ static FunctionTypeEffectSet difference(ArrayRef<CondFunctionEffect> LHS,
+ ArrayRef<CondFunctionEffect> RHS);
};
/// Represents a prototype with parameter type info, e.g.
@@ -4652,7 +4687,7 @@ class FunctionProtoType final
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
- FunctionEffectSet, Qualifiers> {
+ CondFunctionEffect, Qualifiers> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -4683,8 +4718,7 @@ 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, an array of getNumFunctionEffects() CondFunctionEffect.
//
// * Optionally a Qualifiers object to represent extra qualifiers that can't
// be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
@@ -4744,7 +4778,7 @@ class FunctionProtoType final
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
- FunctionEffectSet FunctionEffects;
+ FunctionTypeEffects FunctionEffects;
ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false),
@@ -4810,8 +4844,8 @@ class FunctionProtoType final
return hasExtParameterInfos() ? getNumParams() : 0;
}
- unsigned numTrailingObjects(OverloadToken<FunctionEffectSet>) const {
- return hasFunctionEffects() ? 1 : 0;
+ unsigned numTrailingObjects(OverloadToken<CondFunctionEffect>) const {
+ return getNumFunctionEffects();
}
/// Determine whether there are any argument types that
@@ -5127,15 +5161,20 @@ class FunctionProtoType final
return false;
}
- bool hasFunctionEffects() const {
- if (!hasExtraBitfields())
- return false;
- return getTrailingObjects<FunctionTypeExtraBitfields>()->HasFunctionEffects;
+ unsigned getNumFunctionEffects() const {
+ return hasExtraBitfields()
+ ? getTrailingObjects<FunctionTypeExtraBitfields>()
+ ->NumFunctionEffects
+ : 0;
}
- FunctionEffectSet getFunctionEffects() const {
- if (hasFunctionEffects())
- return *getTrailingObjects<FunctionEffectSet>();
+ FunctionTypeEffects getFunctionEffects() const {
+ if (hasExtraBitfields()) {
+ const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
+ if (Bitfields->NumFunctionEffects > 0)
+ return FunctionTypeEffects({getTrailingObjects<CondFunctionEffect>(),
+ Bitfields->NumFunctionEffects});
+ }
return {};
}
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 8ad6a13b3bffe..6d8cb8b1c2a6d 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -352,8 +352,8 @@ let Class = FunctionProtoType in {
def : Property<"AArch64SMEAttributes", UInt32> {
let Read = [{ node->getAArch64SMEAttributes() }];
}
- def : Property<"functionEffects", Array<FunctionEffect>> {
- let Read = [{ node->getFunctionEffects().items() }];
+ def : Property<"functionEffects", Array<CondFunctionEffect>> {
+ let Read = [{ node->getFunctionEffects() }];
}
def : Creator<[{
@@ -371,7 +371,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- epi.FunctionEffects = functionEffects;
+ epi.FunctionEffects = FunctionTypeEffects(functionEffects);
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 33d9160d51020..5951f44004d92 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -719,12 +719,6 @@ class Sema final {
/// Load weak undeclared identifiers from the external source.
void LoadExternalWeakUndeclaredIdentifiers();
- /// All functions/lambdas/blocks which have bodies and which have a non-empty
- /// FunctionEffectSet to be verified.
- SmallVector<const Decl *> DeclsWithEffectsToVerify;
- /// The union of all effects present on DeclsWithEffectsToVerify.
- FunctionEffectSet AllEffectsToVerify;
-
/// Determine if VD, which must be a variable or function, is an external
/// symbol that nonetheless can't be referenced from outside this translation
/// unit because its type has no linkage and it's not extern "C".
@@ -944,10 +938,26 @@ class Sema final {
/// Warn when implicitly casting 0 to nullptr.
void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
+ // ----- function effects --- where ?????
+ // Ultimately some more of the effects implementation could/should be moved
+ // out of Type.h, but where to?
+
+ /// All functions/lambdas/blocks which have bodies and which have a non-empty
+ /// FunctionTypeEffects to be verified.
+ SmallVector<const Decl *> DeclsWithEffectsToVerify;
+ /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
+ /// are all null.
+ FunctionTypeEffectSet AllEffectsToVerify;
+
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionTypeEffects &FX);
+
+ // ----- function effects --- where ?????
+
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
@@ -3154,9 +3164,6 @@ class Sema final {
QualType T, TypeSourceInfo *TSInfo,
StorageClass SC);
- /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void MaybeAddDeclWithEffects(const Decl *D, const FunctionEffectSet &FX);
-
// Contexts where using non-trivial C union types can be disallowed. This is
// passed to err_non_trivial_c_union_in_invalid_context.
enum NonTrivialCUnionContext {
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ee0550107d71e..2893cd692f3ee 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4563,11 +4563,11 @@ QualType ASTContext::getFunctionTypeInternal(
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
- FunctionEffectSet, Qualifiers>(
+ CondFunctionEffect, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
- EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects ? 1 : 0,
+ EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects.size(),
EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 3aecc17cf4ead..f57f60b2cab99 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4508,7 +4508,7 @@ unsigned FunctionDecl::getODRHash() {
// Effects may differ between declarations, but they should be propagated from
// old to new on any redeclaration, so it suffices to look at
// getMostRecentDecl().
-FunctionEffectSet FunctionDecl::getFunctionEffects() const {
+FunctionTypeEffects FunctionDecl::getFunctionEffects() const {
if (const auto *FPT =
getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
@@ -5240,7 +5240,7 @@ SourceRange BlockDecl::getSourceRange() const {
return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
}
-FunctionEffectSet BlockDecl::getFunctionEffects() const {
+FunctionTypeEffects BlockDecl::getFunctionEffects() const {
if (auto *TSI = getSignatureAsWritten()) {
if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index ae0d8e082492d..069febb3e71e5 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3640,11 +3640,12 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
if (epi.FunctionEffects) {
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
- ExtraBits.HasFunctionEffects = true;
-
- // N.B. This is uninitialized storage.
- FunctionEffectSet *PFX = getTrailingObjects<FunctionEffectSet>();
- new (PFX) FunctionEffectSet(epi.FunctionEffects);
+ // TODO: bitfield overflow?
+ if (epi.FunctionEffects) {
+ ExtraBits.NumFunctionEffects = epi.FunctionEffects.size();
+ CondFunctionEffect *CFE = getTrailingObjects<CondFunctionEffect>();
+ std::copy(epi.FunctionEffects.begin(), epi.FunctionEffects.end(), CFE);
+ }
}
}
@@ -3718,7 +3719,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// Note that valid type pointers are never ambiguous with anything else.
//
// The encoding grammar begins:
- // effects type type* bool int bool
+ // type type* bool int bool
// If that final bool is true, then there is a section for the EH spec:
// bool type*
// This is followed by an optional "consumed argument" section of the
@@ -3729,15 +3730,12 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// Finally we have a trailing return type flag (bool)
// combined with AArch64 SME Attributes, to save space:
// int
+ // combined with any FunctionEffects
//
// There is no ambiguity between the consumed arguments and an empty EH
// spec because of the leading 'bool' which unambiguously indicates
// whether the following bool is the EH spec or part of the arguments.
- // TODO: The effect set is variable-length, though prefaced with a size.
- // Does this create potential ambiguity?
- epi.FunctionEffects.Profile(ID);
-
ID.AddPointer(Result.getAsOpaquePtr());
for (unsigned i = 0; i != NumParams; ++i)
ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -3768,6 +3766,8 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
epi.ExtInfo.Profile(ID);
ID.AddInteger((epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
+
+ epi.FunctionEffects.Profile(ID);
}
void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID,
@@ -5043,16 +5043,16 @@ StringRef FunctionEffect::name() const {
}
bool FunctionEffect::shouldDiagnoseConversion(
- bool Adding, QualType OldType, const FunctionEffectSet &OldFX,
- QualType NewType, const FunctionEffectSet &NewFX) const {
+ bool Adding, QualType OldType, const FunctionTypeEffects &OldFX,
+ QualType NewType, const FunctionTypeEffects &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
// nonallocating can't be added (spoofed) during a conversion, unless we
// have nonblocking
if (Adding) {
- for (const auto &Effect : OldFX) {
- if (Effect.kind() == Kind::NonBlocking)
+ for (const auto &CFE : OldFX) {
+ if (CFE.effect().kind() == Kind::NonBlocking)
return false;
}
}
@@ -5068,8 +5068,8 @@ bool FunctionEffect::shouldDiagnoseConversion(
bool FunctionEffect::shouldDiagnoseRedeclaration(
bool Adding, const FunctionDecl &OldFunction,
- const FunctionEffectSet &OldFX, const FunctionDecl &NewFunction,
- const FunctionEffectSet &NewFX) const {
+ const FunctionTypeEffects &OldFX, const FunctionDecl &NewFunction,
+ const FunctionTypeEffects &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5083,8 +5083,9 @@ bool FunctionEffect::shouldDiagnoseRedeclaration(
}
FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
- bool Adding, const CXXMethodDecl &OldMethod, const FunctionEffectSet &OldFX,
- const CXXMethodDecl &NewMethod, const FunctionEffectSet &NewFX) const {
+ bool Adding, const CXXMethodDecl &OldMethod,
+ const FunctionTypeEffects &OldFX, const CXXMethodDecl &NewMethod,
+ const FunctionTypeEffects &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5132,7 +5133,7 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
}
bool FunctionEffect::shouldDiagnoseFunctionCall(
- bool Direct, const FunctionEffectSet &CalleeFX) const {
+ bool Direct, ArrayRef<FunctionEffect> CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
@@ -5155,84 +5156,98 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
// =====
-void FunctionEffectSet::Profile(llvm::FoldingSetNodeID &ID) const {
+void FunctionTypeEffects::Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(size());
- if (PImpl)
- for (const auto &Effect : *PImpl)
- ID.AddInteger(llvm::to_underlying(Effect.kind()));
-}
-
-FunctionEffectSet &
-FunctionEffectSet::operator=(llvm::ArrayRef<FunctionEffect> Arr) {
- if (Arr.empty()) {
- PImpl.reset();
- } else {
- if (PImpl == nullptr)
- PImpl = new ImplVec;
- PImpl->assign(Arr.begin(), Arr.end());
+ for (const auto &CFE : Items) {
+ ID.AddInteger(llvm::to_underlying(CFE.effect().kind()));
+ ID.AddPointer(CFE.condition());
}
- return *this;
}
-FunctionEffectSet
-FunctionEffectSet::getUnion(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
+FunctionTypeEffectSet
+FunctionTypeEffectSet::getUnion(ArrayRef<CondFunctionEffect> LHS,
+ ArrayRef<CondFunctionEffect> RHS) {
// Optimize for either of the two sets being empty (very common).
if (LHS.empty())
- return RHS;
+ return FunctionTypeEffectSet(RHS);
if (RHS.empty())
- return LHS;
+ return FunctionTypeEffectSet(LHS);
// Optimize for the two sets being identical (very common).
if (LHS == RHS)
- return LHS;
+ return FunctionTypeEffectSet(LHS);
- ImplPtr PVec(new ImplVec);
- PVec->reserve(LHS.size() + RHS.size());
+ FunctionTypeEffectSet Result;
+ Result.Impl.reserve(LHS.size() + RHS.size());
std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(*PVec));
- return FunctionEffectSet(std::move(PVec));
-}
-
-FunctionEffectSet
-FunctionEffectSet::getIntersection(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
- if (LHS.empty() || RHS.empty()) {
- return {};
- }
-
- ImplPtr PVec(new ImplVec);
- std::set_intersection(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(*PVec));
- return FunctionEffectSet(std::move(PVec));
+ std::back_inserter(Result.Impl));
+ return Result;
}
-FunctionEffectSet::Differences
-FunctionEffectSet::differences(const FunctionEffectSet &Old,
- const FunctionEffectSet &New) {
+FunctionTypeEffectSet::Differences
+FunctionTypeEffectSet::differences(const FunctionTypeEffects &Old,
+ const FunctionTypeEffects &New) {
// TODO: Could be a one-pass algorithm.
Differences Result;
- for (const auto &Effect : New.getDifference(Old)) {
+ for (const auto &Effect : FunctionTypeEffectSet::difference(New, Old)) {
Result.emplace_back(Effect, true);
}
- for (const auto &Effect : Old.getDifference(New)) {
+ for (const auto &Effect : FunctionTypeEffectSet::difference(Old, New)) {
Result.emplace_back(Effect, false);
}
return Result;
}
-FunctionEffectSet
-FunctionEffectSet::getDifference(const FunctionEffectSet &RHS) const {
- const FunctionEffectSet &LHS = *this;
- ImplPtr PVec(new ImplVec);
-
+FunctionTypeEffectSet
+FunctionTypeEffectSet::difference(ArrayRef<CondFunctionEffect> LHS,
+ ArrayRef<CondFunctionEffect> RHS) {
+ FunctionTypeEffectSet Result;
std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(*PVec));
- return FunctionEffectSet(std::move(PVec));
+ std::back_inserter(Result.Impl));
+ return Result;
+}
+
+void FunctionTypeEffectSet::insert(const CondFunctionEffect &Effect) {
+ auto *Iter = std::lower_bound(Impl.begin(), Impl.end(), Effect);
+ if (Iter == Impl.end() || *Iter != Effect) {
+ Impl.insert(Iter, Effect);
+ }
+}
+
+void FunctionTypeEffectSet::insert(ArrayRef<CondFunctionEffect> Arr) {
+ // TODO: For large RHS sets, use set_union or a custom insert-in-place
+ for (const auto &CFE : Arr) {
+ insert(CFE);
+ }
+}
+
+void FunctionTypeEffectSet::insertIgnoringConditions(
+ ArrayRef<CondFunctionEffect> Arr) {
+ // TODO: For large RHS sets, use set_union or a custom insert-in-place
+ for (const auto &CFE : Arr) {
+ insert(CondFunctionEffect(CFE.effect().kind(), nullptr));
+ }
+}
+
+LLVM_DUMP_METHOD void FunctionTypeEffects::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();
+ }
+ OS << "}";
+}
+
+LLVM_DUMP_METHOD void FunctionTypeEffectSet::dump(llvm::raw_ostream &OS) const {
+ FunctionTypeEffects(*this).dump(OS);
}
// TODO: inline?
-FunctionEffectSet FunctionEffectSet::get(QualType QT) {
+FunctionTypeEffects FunctionTypeEffects::get(QualType QT) {
if (QT->isReferenceType())
QT = QT.getNonReferenceType();
if (QT->isPointerType())
@@ -5252,41 +5267,3 @@ FunctionEffectSet FunctionEffectSet::get(QualType QT) {
return {};
}
-
-// For use in mutating methods: only allow mutating the vector in
-// place when we hold the only reference. Otherwise, copy it first.
-FunctionEffectSet::ImplVec &FunctionEffectSet::mutableVec() {
- if (PImpl->UseCount() > 1)
- PImpl = new ImplVec(*PImpl);
- assert(PImpl->UseCount() == 1 && "mutating a shared function effect set");
- return *PImpl;
-}
-
-void FunctionEffectSet::insert(const FunctionEffect &Effect) {
- if (PImpl == nullptr)
- PImpl = new ImplVec;
- auto *Iter = std::lower_bound(PImpl->begin(), PImpl->end(), Effect);
- if (Iter == PImpl->end() || *Iter != Effect) {
- mutableVec().insert(Iter, Effect);
- }
-}
-
-void FunctionEffectSet::insert(const FunctionEffectSet &Set) {
- // TODO: For large RHS sets, use set_union or a custom insert-in-place
- for (const auto &Effect : Set) {
- insert(Effect);
- }
-}
-
-LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
- OS << "Effects{";
- bool First = true;
- for (const auto &Effect : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << Effect.name();
- }
- OS << "}";
-}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 89a66cd070029..054aa3de413c8 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -998,11 +998,10 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
}
T->printExceptionSpecification(OS, Policy);
- if (const FunctionEffectSet FX = T->getFunctionEffects()) {
- for (const auto &Effect : FX) {
- OS << " __attribute__((clang_" << Effect.name() << "))";
- }
- }
+ const FunctionTypeEffects FX = T->getFunctionEffects();
+ for (const auto &CFE : FX)
+ OS << " __attribute__((clang_" << CFE.effect().name() << "))";
+ // $$$ TODO: Conditions ???
if (T->hasTrailingReturn()) {
OS << " -> ";
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 947f5ff98f74d..dacabfa8f32a6 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2384,6 +2384,11 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
// =============================================================================
+// Temporary feature enablement
+#define FX_ANALYZER_ENABLED 1
+
+#if FX_ANALYZER_ENABLED
+
// Temporary debugging option
#define FX_ANALYZER_VERIFY_DECL_LIST 1
@@ -2448,6 +2453,133 @@ static bool functionIsVerifiable(const FunctionDecl *FD) {
return true;
}
+#if 0
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class FunctionEffectSet {
+ SmallVector<FunctionEffect, 4> Impl;
+public:
+ FunctionEffectSet() = default;
+
+ operator ArrayRef<FunctionEffect>() const { return Impl; }
+
+ using iterator = const FunctionEffect *;
+ iterator begin() const { return Impl.begin(); }
+ iterator end() const { return Impl.end(); }
+
+ void insert(const FunctionEffect &Effect);
+ void insert(const FunctionEffectSet &Set);
+ void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+
+ void dump(llvm::raw_ostream &OS) const;
+
+ static FunctionEffectSet difference(ArrayRef<FunctionEffect> LHS, ArrayRef<FunctionEffect> RHS);
+};
+#endif
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+// (This implementation optimizes footprint. As long as FunctionEffect is only 1
+// byte, and there are only 2 possible effects, this is more than sufficient. In
+// AnalysisBasedWarnings, we hold one of these for every function visited,
+// which, due to inference, can be many more functions than have effects.)
+class FunctionEffectSet {
+ template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
+ SizeT Count = 0;
+ T Items[Capacity] = {};
+
+ using value_type = T;
+
+ using iterator = T *;
+ using const_iterator = const T *;
+ iterator begin() { return &Items[0]; }
+ iterator end() { return &Items[Count]; }
+ const_iterator cbegin() const { return &Items[0]; }
+ const_iterator cend() const { return &Items[Count]; }
+
+ void insert(iterator I, const T &Value) {
+ assert(Count < Capacity);
+ iterator E = end();
+ if (I != E)
+ std::copy_backward(I, E, E + 1);
+ *I = Value;
+ ++Count;
+ }
+
+ void push_back(const T &Value) {
+ assert(Count < Capacity);
+ Items[Count++] = Value;
+ }
+ };
+
+ FixedVector<FunctionEffect, uint8_t, 7> Impl;
+
+public:
+ FunctionEffectSet() = default;
+
+ operator ArrayRef<FunctionEffect>() const {
+ return ArrayRef(Impl.cbegin(), Impl.cend());
+ }
+
+ using iterator = const FunctionEffect *;
+ iterator begin() const { return Impl.cbegin(); }
+ iterator end() const { return Impl.cend(); }
+
+ void insert(const FunctionEffect &Effect);
+ void insert(const FunctionEffectSet &Set);
+ void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+
+ void dump(llvm::raw_ostream &OS) const;
+
+ static FunctionEffectSet difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS);
+};
+
+void FunctionEffectSet::insert(const FunctionEffect &Effect) {
+ FunctionEffect *Iter = Impl.begin();
+ FunctionEffect *End = Impl.end();
+ // lower_bound is overkill for a tiny vector like this
+ for (; Iter != End; ++Iter) {
+ if (*Iter == Effect)
+ return;
+ if (Effect < *Iter)
+ break;
+ }
+ Impl.insert(Iter, Effect);
+}
+
+void FunctionEffectSet::insert(const FunctionEffectSet &Set) {
+ for (auto &Item : Set)
+ insert(Item);
+}
+
+void FunctionEffectSet::insertIgnoringConditions(
+ ArrayRef<CondFunctionEffect> Arr) {
+ for (auto &Item : Arr)
+ insert(Item.effect());
+}
+
+LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
+ OS << "Effects{";
+ bool First = true;
+ for (const auto &Effect : *this) {
+ if (!First)
+ OS << ", ";
+ else
+ First = false;
+ OS << Effect.name();
+ }
+ OS << "}";
+}
+
+FunctionEffectSet FunctionEffectSet::difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS) {
+ FunctionEffectSet Result;
+ std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Result.Impl));
+ return Result;
+}
+
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer...
struct CallableInfo {
@@ -2462,6 +2594,7 @@ struct CallableInfo {
CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
// llvm::errs() << "CallableInfo " << name() << "\n";
+ FunctionTypeEffects FX;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
@@ -2474,14 +2607,15 @@ struct CallableInfo {
CType = CallType::Virtual;
}
}
- Effects = FD->getFunctionEffects();
+ FX = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
- Effects = BD->getFunctionEffects();
+ FX = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
- Effects = FunctionEffectSet::get(VD->getType());
+ FX = FunctionTypeEffects::get(VD->getType());
}
+ Effects.insertIgnoringConditions(FX);
}
bool isDirectCall() const {
@@ -2574,8 +2708,8 @@ class EffectToDiagnosticMap {
// ----------
// State pertaining to a function whose AST is walked. Since there are
// potentially a large number of these objects, it needs care about size.
+// TODO: FunctionEffectSet could be made much smaller.
class PendingFunctionAnalysis {
- // Current size: 5 pointers
friend class CompleteFunctionAnalysis;
struct DirectCall {
@@ -2615,7 +2749,7 @@ class PendingFunctionAnalysis {
public:
PendingFunctionAnalysis(
Sema &Sem, const CallableInfo &CInfo,
- const FunctionEffectSet &AllInferrableEffectsToVerify) {
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
DeclaredVerifiableEffects = CInfo.Effects;
// Check for effects we are not allowed to infer
@@ -2635,7 +2769,7 @@ class PendingFunctionAnalysis {
}
}
// FX is now the set of inferrable effects which are not prohibited
- FXToInfer = FX.getDifference(DeclaredVerifiableEffects);
+ FXToInfer = FunctionEffectSet::difference(FX, DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2719,7 +2853,7 @@ class CompleteFunctionAnalysis {
CompleteFunctionAnalysis(
ASTContext &Ctx, PendingFunctionAnalysis &pending,
const FunctionEffectSet &funcFX,
- const FunctionEffectSet &AllInferrableEffectsToVerify) {
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
VerifiedEffects.insert(funcFX);
for (const auto &effect : AllInferrableEffectsToVerify) {
if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
@@ -2832,10 +2966,11 @@ class Analyzer {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
{
- for (const FunctionEffect &effect : Sem.AllEffectsToVerify) {
- const auto Flags = effect.flags();
+ for (const CondFunctionEffect &CFE : Sem.AllEffectsToVerify) {
+ const FunctionEffect &Effect = CFE.effect();
+ const auto Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees) {
- AllInferrableEffectsToVerify.insert(effect);
+ AllInferrableEffectsToVerify.insert(Effect);
}
}
if constexpr (DebugLogLevel > 0) {
@@ -3309,11 +3444,14 @@ class Analyzer {
const auto CalleeType = CalleeExpr->getType();
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+ FunctionEffectSet CalleeFX;
+ if (FPT)
+ CalleeFX.insertIgnoringConditions(FPT->getFunctionEffects());
+ static_assert(sizeof(FunctionEffect) == 1);
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- if (FPT == nullptr ||
- Effect.shouldDiagnoseFunctionCall(
- /*direct=*/false, FPT->getFunctionEffects())) {
+ if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+ /*direct=*/false, CalleeFX)) {
addDiagnosticInner(Inferring, Effect,
DiagnosticID::CallsDisallowedExpr,
Call->getBeginLoc());
@@ -3582,12 +3720,12 @@ class Analyzer {
if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
if (const auto FX = functionEffectsForDecl(Callable)) {
// Reuse this filtering method in Sema
- Sem.MaybeAddDeclWithEffects(Callable, FX);
+ Sem.maybeAddDeclWithEffects(Callable, FX);
}
}
}
- static FunctionEffectSet functionEffectsForDecl(const Decl *D) {
+ static FunctionTypeEffects functionEffectsForDecl(const Decl *D) {
if (auto *FD = D->getAsFunction()) {
return FD->getFunctionEffects();
}
@@ -3649,6 +3787,7 @@ Analyzer::AnalysisMap::~AnalysisMap() {
}
} // namespace FXAnalysis
+#endif // FX_ANALYZER_ENABLED
// =============================================================================
@@ -3807,8 +3946,10 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
}
+#if FX_ANALYZER_ENABLED
// TODO: skip this if the warning isn't enabled.
FXAnalysis::Analyzer{S}.run(*TU);
+#endif
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 01d334f8d8af9..fdef8dc9f877f 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -590,11 +590,11 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- const auto SrcFX = FunctionEffectSet::get(SrcType);
- const auto DstFX = FunctionEffectSet::get(DstType);
+ const auto SrcFX = FunctionTypeEffects::get(SrcType);
+ const auto DstFX = FunctionTypeEffects::get(DstType);
if (SrcFX != DstFX) {
- for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
- const FunctionEffect &Effect = Item.first;
+ for (const auto &Item : FunctionTypeEffectSet::differences(SrcFX, DstFX)) {
+ const FunctionEffect &Effect = Item.first.effect();
const bool Adding = Item.second;
if (Effect.shouldDiagnoseConversion(Adding, SrcType, SrcFX, DstType,
DstFX)) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 3421709fd24aa..1a48d759e3a8e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3924,9 +3924,9 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
const auto NewFX = New->getFunctionEffects();
QualType OldQTypeForComparison = OldQType;
if (OldFX != NewFX) {
- const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionTypeEffectSet::differences(OldFX, NewFX);
for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first;
+ const FunctionEffect &Effect = Item.first.effect();
const bool Adding = Item.second;
if (Effect.shouldDiagnoseRedeclaration(Adding, *Old, OldFX, *New,
NewFX)) {
@@ -3940,10 +3940,10 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// declaration, but that would trigger an additional "conflicting types"
// error.
if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
- auto MergedFX = OldFX.getUnion(NewFX);
+ auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
- EPI.FunctionEffects = MergedFX;
+ EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(),
NewFPT->getParamTypes(), EPI);
@@ -3954,7 +3954,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// so as not to fail due to differences later.
if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) {
EPI = OldFPT->getExtProtoInfo();
- EPI.FunctionEffects = MergedFX;
+ EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
OldQTypeForComparison = Context.getFunctionType(
OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
}
@@ -11128,7 +11128,8 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
-void Sema::MaybeAddDeclWithEffects(const Decl *D, const FunctionEffectSet &FX) {
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+ const FunctionTypeEffects &FX) {
if (!D->hasBody()) {
if (const auto *FD = D->getAsFunction()) {
if (!FD->willHaveBody()) {
@@ -11152,7 +11153,8 @@ void Sema::MaybeAddDeclWithEffects(const Decl *D, const FunctionEffectSet &FX) {
return;
}
- AllEffectsToVerify.insert(FX);
+ // Ignore any conditions when building the list of effects.
+ AllEffectsToVerify.insertIgnoringConditions(FX);
// Record the declaration for later analysis.
DeclsWithEffectsToVerify.push_back(D);
@@ -16064,7 +16066,7 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
- MaybeAddDeclWithEffects(FD, FX);
+ maybeAddDeclWithEffects(FD, FX);
}
return D;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 95b8d4231b401..4c5e47c66ee5b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18329,11 +18329,11 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
const auto NewFX = New->getFunctionEffects();
if (OldFX != NewFX) {
- const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionTypeEffectSet::differences(OldFX, NewFX);
bool AnyDiags = false;
for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first;
+ const FunctionEffect &Effect = Item.first.effect();
const bool Adding = Item.second;
switch (Effect.shouldDiagnoseMethodOverride(Adding, *Old, OldFX, *New,
NewFX)) {
@@ -18347,10 +18347,10 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
AnyDiags = true;
break;
case FunctionEffect::OverrideResult::Merge: {
- auto MergedFX = OldFX.getUnion(NewFX);
+ auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
- EPI.FunctionEffects = MergedFX;
+ EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
NewFT->getParamTypes(), EPI);
New->setType(ModQT);
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2b4e1693cb1f1..d5cfc4669c9ab 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17157,7 +17157,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockDecl *BD = BSI->TheDecl;
if (const auto FX = BD->getFunctionEffects()) {
- MaybeAddDeclWithEffects(BD, FX);
+ maybeAddDeclWithEffects(BD, FX);
}
if (BSI->HasImplicitReturnType)
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 0772a2dec7b56..bb6ee041ad944 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1890,7 +1890,7 @@ ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
ActOnFinishFunctionBody(LSI.CallOperator, Body);
if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
- MaybeAddDeclWithEffects(LSI.CallOperator, FX);
+ maybeAddDeclWithEffects(LSI.CallOperator, FX);
}
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 08fce7ac781ce..542853050bae4 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8084,8 +8084,6 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
if (NewState == BoolAttrState::False) {
return incompatible(true, BoolAttrState::True);
}
- // Ignore nonallocating(true) since we already have nonblocking(true).
- return true;
}
TPState.setParsedNonAllocating(NewState);
}
@@ -8118,13 +8116,16 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
// nonblocking(true) and nonallocating(true) are represented as
- // FunctionEffects, in a FunctionEffectSet attached to a FunctionProtoType.
- const FunctionEffect NewEffect(isNonBlocking
- ? FunctionEffect::Kind::NonBlocking
- : FunctionEffect::Kind::NonAllocating);
+ // FunctionEffects, in a FunctionTypeEffects attached to a FunctionProtoType.
+ const CondFunctionEffect NewEffect(isNonBlocking
+ ? FunctionEffect::Kind::NonBlocking
+ : FunctionEffect::Kind::NonAllocating,
+ nullptr /* no condition yet */);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
- EPI.FunctionEffects.insert(NewEffect);
+ FunctionTypeEffectSet FX(EPI.FunctionEffects);
+ FX.insert(NewEffect);
+ EPI.FunctionEffects = FunctionTypeEffects(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 2648f46b41422..d4bd45f3b7434 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8226,14 +8226,14 @@ void ASTReader::InitializeSema(Sema &S) {
Decl *D = GetDecl(ID);
SemaObj->DeclsWithEffectsToVerify.push_back(D);
- FunctionEffectSet FX;
+ FunctionTypeEffects FX;
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
FX = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(D)) {
FX = BD->getFunctionEffects();
}
if (FX) {
- SemaObj->AllEffectsToVerify.insert(FX);
+ SemaObj->AllEffectsToVerify.insertIgnoringConditions(FX);
}
}
DeclsWithEffectsToVerify.clear();
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 8fe3cfe529df4..f6f58f95fd70d 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -40,15 +40,15 @@ struct Struct {
// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
};
-// nonallocating should be subsumed into nonblocking
+// nonallocating should NOT be subsumed into nonblocking
void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
-// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
void nl2() [[clang::nonallocating]] [[clang::nonblocking]];
// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
decltype(nl1) nl3;
-// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nonblocking))'
+// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
// Attribute propagates from base class virtual method to overrides.
struct Base {
>From 60455738256ccb41fa707172c50e03b2b91c709a Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 15 Apr 2024 15:34:05 -0700
Subject: [PATCH 32/71] Separate effects and conditions.
---
clang/include/clang/AST/AbstractBasicReader.h | 9 +-
clang/include/clang/AST/AbstractBasicWriter.h | 9 +-
clang/include/clang/AST/Decl.h | 4 +-
clang/include/clang/AST/PropertiesBase.td | 3 +-
clang/include/clang/AST/Type.h | 256 ++++++++++++------
clang/include/clang/AST/TypeProperties.td | 9 +-
clang/include/clang/Sema/Sema.h | 4 +-
clang/lib/AST/ASTContext.cpp | 3 +-
clang/lib/AST/Decl.cpp | 4 +-
clang/lib/AST/Type.cpp | 176 ++++++++----
clang/lib/AST/TypePrinter.cpp | 4 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 18 +-
clang/lib/Sema/Sema.cpp | 6 +-
clang/lib/Sema/SemaDecl.cpp | 11 +-
clang/lib/Sema/SemaDeclCXX.cpp | 4 +-
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/lib/Sema/SemaLambda.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 14 +-
clang/lib/Serialization/ASTReader.cpp | 4 +-
19 files changed, 356 insertions(+), 186 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index 7ab4cad479161..f932b7608d474 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -244,12 +244,15 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
return FunctionProtoType::ExtParameterInfo::getFromOpaqueValue(value);
}
- CondFunctionEffect readCondFunctionEffect() {
+ FunctionEffect readFunctionEffect() {
static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
"update this if size changes");
uint32_t value = asImpl().readUInt32();
- Expr *cond = asImpl().readExprRef();
- return CondFunctionEffect(static_cast<FunctionEffect::Kind>(value), cond);
+ return FunctionEffect(static_cast<FunctionEffect::Kind>(value));
+ }
+
+ FunctionEffectCondExpr readFunctionEffectCondExpr() {
+ return FunctionEffectCondExpr{asImpl().readExprRef()};
}
NestedNameSpecifier *readNestedNameSpecifier() {
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 24d6d18d9e8b4..51dc280efcd9c 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -222,11 +222,14 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
asImpl().writeUInt32(epi.getOpaqueValue());
}
- void writeCondFunctionEffect(const CondFunctionEffect &effect) {
+ void writeFunctionEffect(FunctionEffect E) {
static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
"update this if the value size changes");
- asImpl().writeUInt32(llvm::to_underlying(effect.effect().kind()));
- asImpl().writeExprRef(effect.condition());
+ asImpl().writeUInt32(llvm::to_underlying(E.kind()));
+ }
+
+ void writeFunctionEffectCondExpr(FunctionEffectCondExpr CE) {
+ asImpl().writeExprRef(CE.Cond);
}
void writeNestedNameSpecifier(NestedNameSpecifier *NNS) {
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 0709223042ad4..71aed63c87258 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,7 +3008,7 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
- FunctionTypeEffects getFunctionEffects() const;
+ FunctionTypeEffectsRef getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
@@ -4635,7 +4635,7 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
- FunctionTypeEffects getFunctionEffects() const;
+ FunctionTypeEffectsRef getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index 157d75ccdd584..b596b22d94329 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -117,7 +117,8 @@ def ExtParameterInfo : PropertyType<"FunctionProtoType::ExtParameterInfo">;
def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> {
let PassByReference = 1;
}
-def CondFunctionEffect : PropertyType<"CondFunctionEffect">;
+def FunctionEffect : PropertyType<"FunctionEffect">;
+def FunctionEffectCondExpr : PropertyType<"FunctionEffectCondExpr">;
def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; }
def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">;
def LValuePathSerializationHelper :
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 552aca902f658..dad5eacbaea6b 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4295,11 +4295,13 @@ class FunctionType : public Type {
LLVM_PREFERRED_TYPE(bool)
unsigned HasArmTypeAttributes : 1;
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned EffectsHaveConditions : 1;
unsigned NumFunctionEffects : 4;
FunctionTypeExtraBitfields()
: NumExceptionType(0), HasArmTypeAttributes(false),
- NumFunctionEffects(0) {}
+ EffectsHaveConditions(false), NumFunctionEffects(0) {}
};
/// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number
@@ -4436,9 +4438,25 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
-class FunctionTypeEffects;
+class FunctionTypeEffectsRef;
class FunctionTypeEffectSet;
+/*
+ TODO: Idea about how to move most of the FunctionEffect business out of
+ Type.h, thus removing these forward declarations.
+
+ - Keep FunctionEffect itself here but make it more minimal. Don't define flags
+ or any behaviors, just the Kind and an accessor.
+ - Keep FunctionEffectCondExpr here.
+ - Make FunctionProtoType and ExtProtoInfo use only ArrayRef<FunctionEffect>
+ and ArrayRef<FunctionEffectCondExpr>.
+ - Somewhere in Sema, define ExtFunctionEffect, which holds a FunctionEffect
+ and has all the behavior-related methods.
+ - There too, define the containers. FunctionTypeEffectsRef can have a
+ constructor or factory method that initializes itself from a
+ FunctionProtoType.
+*/
+
/// Represents an abstract function effect, using just an enumeration describing
/// its kind.
class FunctionEffect {
@@ -4511,24 +4529,24 @@ class FunctionEffect {
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
bool shouldDiagnoseConversion(bool Adding, QualType OldType,
- const FunctionTypeEffects &OldFX,
+ const FunctionTypeEffectsRef &OldFX,
QualType NewType,
- const FunctionTypeEffects &NewFX) const;
+ const FunctionTypeEffectsRef &NewFX) const;
/// Return true if adding or removing the effect in a redeclaration should
/// generate a diagnostic.
bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
- const FunctionTypeEffects &OldFX,
+ const FunctionTypeEffectsRef &OldFX,
const FunctionDecl &NewFunction,
- const FunctionTypeEffects &NewFX) const;
+ const FunctionTypeEffectsRef &NewFX) const;
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
OverrideResult
shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionTypeEffects &OldFX,
+ const FunctionTypeEffectsRef &OldFX,
const CXXMethodDecl &NewMethod,
- const FunctionTypeEffects &NewFX) const;
+ const FunctionTypeEffectsRef &NewFX) const;
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
@@ -4553,78 +4571,97 @@ class FunctionEffect {
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.FKind < RHS.FKind;
}
+ friend bool operator>(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ return LHS.FKind > RHS.FKind;
+ }
+};
+
+/// Wrap a function effect's condition expression in another struct so
+/// that FunctionProtoType's TrailingObjects can treat it separately.
+struct FunctionEffectCondExpr {
+ const Expr *Cond = nullptr; // if null, unconditional
+
+ bool operator==(const FunctionEffectCondExpr &RHS) const {
+ return Cond == RHS.Cond;
+ }
};
/// A FunctionEffect plus a potential boolean expression determining whether
/// the effect is declared (e.g. nonblocking(expr)). Generally the condition
/// expression when present, is dependent.
-class CondFunctionEffect {
+struct CondFunctionEffect {
FunctionEffect Effect;
const Expr *Cond = nullptr; // if null, unconditional
+};
-public:
- CondFunctionEffect() = default;
- CondFunctionEffect(FunctionEffect::Kind K, const Expr *Cond)
- : Effect(K), Cond(Cond) {}
-
- const FunctionEffect &effect() const { return Effect; }
- const Expr *condition() const { return Cond; }
-
-private:
- // Comparisons are tricky when the condition might be involved, so limit who
- // might be doing them.
- friend FunctionTypeEffects;
- friend FunctionTypeEffectSet;
+/// Support iteration in parallel through a pair of FunctionEffect and
+/// FunctionEffectCondExpr containers.
+template <typename Container> class FunctionEffectIterator {
+ const Container &Outer;
+ size_t Idx;
- friend bool operator==(const CondFunctionEffect &LHS,
- const CondFunctionEffect &RHS) {
- return LHS.Effect == RHS.Effect && LHS.Cond == RHS.Cond;
+public:
+ FunctionEffectIterator(const Container &O, size_t I) : Outer(O), Idx(I) {}
+ bool operator==(const FunctionEffectIterator &Other) const {
+ return Idx == Other.Idx;
}
- friend bool operator!=(const CondFunctionEffect &LHS,
- const CondFunctionEffect &RHS) {
- return !(LHS == RHS);
+ bool operator!=(const FunctionEffectIterator &Other) const {
+ return Idx != Other.Idx;
+ }
+
+ // prefix increment
+ FunctionEffectIterator operator++() {
+ ++Idx;
+ return *this;
}
- friend bool operator<(const CondFunctionEffect &LHS,
- const CondFunctionEffect &RHS) {
- return LHS.Effect < RHS.Effect;
+
+ CondFunctionEffect operator*() const {
+ const bool HasConds = !Outer.Conditions.empty();
+ return CondFunctionEffect{Outer.Effects[Idx],
+ HasConds ? Outer.Conditions[Idx].Cond : nullptr};
}
};
-// Container naming:
-// "type effects" are the ones which include a condition
-// FunctionTypeEffectsRef - holds pointers
-// FunctionTypeEffectSet - mutable
-// FunctionEffectSet - mutable, no condition
+/// An immutable set of FunctionEffects and possibly conditions attached to
+/// them. The effects and conditions reside in memory not managed by this object
+/// (typically, trailing objects in FunctionProtoType, or borrowed references
+/// from a FunctionTypeEffectSet).
+class FunctionTypeEffectsRef {
+ ArrayRef<FunctionEffect> Effects;
-/// An immutable set of CondFunctionEffect. The effects reside in memory not
-/// managed by this object (typically, trailing objects in FunctionProtoType).
-class FunctionTypeEffects {
- ArrayRef<CondFunctionEffect> Items;
+ // The array of conditions is either empty or has the same size
+ // as the array of effects.
+ ArrayRef<FunctionEffectCondExpr> Conditions;
public:
/// Extract the effects from a Type if it is a function, block, or member
/// function pointer, or a reference or pointer to one.
- static FunctionTypeEffects get(QualType QT);
+ static FunctionTypeEffectsRef get(QualType QT);
- FunctionTypeEffects() = default;
+ FunctionTypeEffectsRef() = default;
- // The array is expected to have been sorted by the caller.
- explicit FunctionTypeEffects(ArrayRef<CondFunctionEffect> Arr) : Items(Arr) {}
+ // The arrays are expected to have been sorted by the caller.
+ FunctionTypeEffectsRef(ArrayRef<FunctionEffect> FX,
+ ArrayRef<FunctionEffectCondExpr> Conds)
+ : Effects(FX), Conditions(Conds) {}
- operator bool() const { return !Items.empty(); }
- size_t size() const { return Items.size(); }
- operator ArrayRef<CondFunctionEffect>() const { return Items; }
+ bool empty() const { return Effects.empty(); }
+ size_t size() const { return Effects.size(); }
- using iterator = const CondFunctionEffect *;
- iterator begin() const { return Items.begin(); }
- iterator end() const { return Items.end(); }
+ ArrayRef<FunctionEffect> effects() const { return Effects; }
+ ArrayRef<FunctionEffectCondExpr> conditions() const { return Conditions; }
- friend bool operator==(const FunctionTypeEffects &LHS,
- const FunctionTypeEffects &RHS) {
- return LHS.Items == RHS.Items;
+ using iterator = FunctionEffectIterator<FunctionTypeEffectsRef>;
+ friend iterator;
+ iterator begin() const { return iterator(*this, 0); }
+ iterator end() const { return iterator(*this, size()); }
+
+ friend bool operator==(const FunctionTypeEffectsRef &LHS,
+ const FunctionTypeEffectsRef &RHS) {
+ return LHS.Effects == RHS.Effects && LHS.Conditions == RHS.Conditions;
}
- friend bool operator!=(const FunctionTypeEffects &LHS,
- const FunctionTypeEffects &RHS) {
+ friend bool operator!=(const FunctionTypeEffectsRef &LHS,
+ const FunctionTypeEffectsRef &RHS) {
return !(LHS == RHS);
}
@@ -4632,44 +4669,47 @@ class FunctionTypeEffects {
void dump(llvm::raw_ostream &OS) const;
};
-/// A mutable set of CondFunctionEffect.
-// Used transitorily within Sema to compare and merge effects on declarations.
+/// A mutable set of FunctionEffects and possibly conditions attached to them.
+/// Used transitorily within Sema to compare and merge effects on declarations.
class FunctionTypeEffectSet {
- SmallVector<CondFunctionEffect> Impl;
+ SmallVector<FunctionEffect> Effects;
+ // The vector of conditions is either empty or has the same size
+ // as the vector of effects.
+ SmallVector<FunctionEffectCondExpr> Conditions;
public:
FunctionTypeEffectSet() = default;
- explicit FunctionTypeEffectSet(ArrayRef<CondFunctionEffect> Arr)
- : Impl(Arr.begin(), Arr.end()) {}
- explicit operator bool() const { return !Impl.empty(); }
+ explicit FunctionTypeEffectSet(const FunctionTypeEffectsRef &FX)
+ : Effects(FX.effects()), Conditions(FX.conditions()) {}
+
+ bool empty() const { return Effects.empty(); }
+ size_t size() const { return Effects.size(); }
- // Implicit conversion to ArrayRef - careful with lifetime.
- operator ArrayRef<CondFunctionEffect>() const { return Impl; }
+ using iterator = FunctionEffectIterator<FunctionTypeEffectSet>;
+ friend iterator;
+ iterator begin() const { return iterator(*this, 0); }
+ iterator end() const { return iterator(*this, size()); }
- using iterator = const CondFunctionEffect *;
- iterator begin() const { return Impl.begin(); }
- iterator end() const { return Impl.end(); }
+ operator FunctionTypeEffectsRef() const { return {Effects, Conditions}; }
void dump(llvm::raw_ostream &OS) const;
// Mutators
- void insert(const CondFunctionEffect &Effect);
- void insert(ArrayRef<CondFunctionEffect> Arr);
- void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+ void insert(FunctionEffect Effect, const Expr *Cond);
+ void insert(const FunctionTypeEffectsRef &Set);
+ void insertIgnoringConditions(const FunctionTypeEffectsRef &Set);
- // Set operations, using ArrayRef to support FunctionTypeEffects
+ // Set operations
using Differences =
SmallVector<std::pair<CondFunctionEffect, /*added=*/bool>>;
/// Caller should short-circuit by checking for equality first.
- static Differences differences(const FunctionTypeEffects &Old,
- const FunctionTypeEffects &New);
+ static Differences differences(const FunctionTypeEffectsRef &Old,
+ const FunctionTypeEffectsRef &New);
- static FunctionTypeEffectSet getUnion(ArrayRef<CondFunctionEffect> LHS,
- ArrayRef<CondFunctionEffect> RHS);
- static FunctionTypeEffectSet difference(ArrayRef<CondFunctionEffect> LHS,
- ArrayRef<CondFunctionEffect> RHS);
+ static FunctionTypeEffectSet getUnion(FunctionTypeEffectsRef LHS,
+ FunctionTypeEffectsRef RHS);
};
/// Represents a prototype with parameter type info, e.g.
@@ -4687,7 +4727,7 @@ class FunctionProtoType final
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
- CondFunctionEffect, Qualifiers> {
+ FunctionEffect, FunctionEffectCondExpr, Qualifiers> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -4718,7 +4758,11 @@ class FunctionProtoType final
// an ExtParameterInfo for each of the parameters. Present if and
// only if hasExtParameterInfos() is true.
//
- // * Optionally, an array of getNumFunctionEffects() CondFunctionEffect.
+ // * Optionally, an array of getNumFunctionEffects() FunctionEffect.
+ // Present only when getNumFunctionEffects() > 0
+ //
+ // * Optionally, an array of getNumFunctionEffects() FunctionEffectCondExpr.
+ // Present only when getNumFunctionEffectConditions() > 0.
//
// * Optionally a Qualifiers object to represent extra qualifiers that can't
// be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
@@ -4778,7 +4822,7 @@ class FunctionProtoType final
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
- FunctionTypeEffects FunctionEffects;
+ FunctionTypeEffectsRef FunctionEffects;
ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false),
@@ -4796,7 +4840,8 @@ class FunctionProtoType final
bool requiresFunctionProtoTypeExtraBitfields() const {
return ExceptionSpec.Type == EST_Dynamic ||
- requiresFunctionProtoTypeArmAttributes() || FunctionEffects;
+ requiresFunctionProtoTypeArmAttributes() ||
+ !FunctionEffects.empty();
}
bool requiresFunctionProtoTypeArmAttributes() const {
@@ -4844,10 +4889,14 @@ class FunctionProtoType final
return hasExtParameterInfos() ? getNumParams() : 0;
}
- unsigned numTrailingObjects(OverloadToken<CondFunctionEffect>) const {
+ unsigned numTrailingObjects(OverloadToken<FunctionEffect>) const {
return getNumFunctionEffects();
}
+ unsigned numTrailingObjects(OverloadToken<FunctionEffectCondExpr>) const {
+ return getNumFunctionEffectConditions();
+ }
+
/// Determine whether there are any argument types that
/// contain an unexpanded parameter pack.
static bool containsAnyUnexpandedParameterPack(const QualType *ArgArray,
@@ -5168,12 +5217,51 @@ class FunctionProtoType final
: 0;
}
- FunctionTypeEffects getFunctionEffects() const {
+ // For serialization.
+ ArrayRef<FunctionEffect> getFunctionEffectsOnly() const {
if (hasExtraBitfields()) {
const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
if (Bitfields->NumFunctionEffects > 0)
- return FunctionTypeEffects({getTrailingObjects<CondFunctionEffect>(),
- Bitfields->NumFunctionEffects});
+ return {getTrailingObjects<FunctionEffect>(),
+ Bitfields->NumFunctionEffects};
+ }
+ return {};
+ }
+
+ unsigned getNumFunctionEffectConditions() const {
+ if (hasExtraBitfields()) {
+ const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
+ if (Bitfields->EffectsHaveConditions)
+ return Bitfields->NumFunctionEffects;
+ }
+ return 0;
+ }
+
+ // For serialization.
+ ArrayRef<FunctionEffectCondExpr> getFunctionEffectConditions() const {
+ if (hasExtraBitfields()) {
+ const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
+ if (Bitfields->EffectsHaveConditions)
+ return {getTrailingObjects<FunctionEffectCondExpr>(),
+ Bitfields->NumFunctionEffects};
+ }
+ return {};
+ }
+
+ // Combines effects with their conditions.
+ FunctionTypeEffectsRef getFunctionEffects() const {
+ if (hasExtraBitfields()) {
+ const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
+ if (Bitfields->NumFunctionEffects > 0) {
+ const size_t NumConds = Bitfields->EffectsHaveConditions
+ ? Bitfields->NumFunctionEffects
+ : 0;
+ return FunctionTypeEffectsRef(
+ {getTrailingObjects<FunctionEffect>(),
+ Bitfields->NumFunctionEffects},
+ {NumConds ? getTrailingObjects<FunctionEffectCondExpr>() : nullptr,
+ NumConds});
+ }
}
return {};
}
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 6d8cb8b1c2a6d..b21b031f9c477 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -352,8 +352,11 @@ let Class = FunctionProtoType in {
def : Property<"AArch64SMEAttributes", UInt32> {
let Read = [{ node->getAArch64SMEAttributes() }];
}
- def : Property<"functionEffects", Array<CondFunctionEffect>> {
- let Read = [{ node->getFunctionEffects() }];
+ def : Property<"functionEffects", Array<FunctionEffect>> {
+ let Read = [{ node->getFunctionEffectsOnly() }];
+ }
+ def : Property<"functionEffectConds", Array<FunctionEffectCondExpr>> {
+ let Read = [{ node->getFunctionEffectConditions() }];
}
def : Creator<[{
@@ -371,7 +374,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- epi.FunctionEffects = FunctionTypeEffects(functionEffects);
+ epi.FunctionEffects = FunctionTypeEffectsRef(functionEffects, functionEffectConds);
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5951f44004d92..ff11b4f6b4d0f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -943,7 +943,7 @@ class Sema final {
// out of Type.h, but where to?
/// All functions/lambdas/blocks which have bodies and which have a non-empty
- /// FunctionTypeEffects to be verified.
+ /// FunctionTypeEffectsRef to be verified.
SmallVector<const Decl *> DeclsWithEffectsToVerify;
/// The union of all effects present on DeclsWithEffectsToVerify. Conditions
/// are all null.
@@ -954,7 +954,7 @@ class Sema final {
SourceLocation Loc);
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void maybeAddDeclWithEffects(const Decl *D, const FunctionTypeEffects &FX);
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionTypeEffectsRef &FX);
// ----- function effects --- where ?????
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 2893cd692f3ee..5d513f6e6b3d2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4563,11 +4563,12 @@ QualType ASTContext::getFunctionTypeInternal(
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
- CondFunctionEffect, Qualifiers>(
+ FunctionEffect, FunctionEffectCondExpr, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects.size(),
+ EPI.FunctionEffects.conditions().size(),
EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index f57f60b2cab99..740542b376719 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4508,7 +4508,7 @@ unsigned FunctionDecl::getODRHash() {
// Effects may differ between declarations, but they should be propagated from
// old to new on any redeclaration, so it suffices to look at
// getMostRecentDecl().
-FunctionTypeEffects FunctionDecl::getFunctionEffects() const {
+FunctionTypeEffectsRef FunctionDecl::getFunctionEffects() const {
if (const auto *FPT =
getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
@@ -5240,7 +5240,7 @@ SourceRange BlockDecl::getSourceRange() const {
return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
}
-FunctionTypeEffects BlockDecl::getFunctionEffects() const {
+FunctionTypeEffectsRef BlockDecl::getFunctionEffects() const {
if (auto *TSI = getSignatureAsWritten()) {
if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 069febb3e71e5..d9df0f3acf9aa 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3638,13 +3638,20 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
EllipsisLoc = epi.EllipsisLoc;
}
- if (epi.FunctionEffects) {
+ if (!epi.FunctionEffects.empty()) {
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
// TODO: bitfield overflow?
- if (epi.FunctionEffects) {
- ExtraBits.NumFunctionEffects = epi.FunctionEffects.size();
- CondFunctionEffect *CFE = getTrailingObjects<CondFunctionEffect>();
- std::copy(epi.FunctionEffects.begin(), epi.FunctionEffects.end(), CFE);
+ ExtraBits.NumFunctionEffects = epi.FunctionEffects.size();
+
+ ArrayRef<FunctionEffect> SrcFX = epi.FunctionEffects.effects();
+ auto *DestFX = getTrailingObjects<FunctionEffect>();
+ std::copy(SrcFX.begin(), SrcFX.end(), DestFX);
+
+ ArrayRef<FunctionEffectCondExpr> SrcConds =
+ epi.FunctionEffects.conditions();
+ if (!SrcConds.empty()) {
+ auto *DestConds = getTrailingObjects<FunctionEffectCondExpr>();
+ std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
}
}
}
@@ -5043,8 +5050,8 @@ StringRef FunctionEffect::name() const {
}
bool FunctionEffect::shouldDiagnoseConversion(
- bool Adding, QualType OldType, const FunctionTypeEffects &OldFX,
- QualType NewType, const FunctionTypeEffects &NewFX) const {
+ bool Adding, QualType OldType, const FunctionTypeEffectsRef &OldFX,
+ QualType NewType, const FunctionTypeEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
@@ -5052,7 +5059,7 @@ bool FunctionEffect::shouldDiagnoseConversion(
// have nonblocking
if (Adding) {
for (const auto &CFE : OldFX) {
- if (CFE.effect().kind() == Kind::NonBlocking)
+ if (CFE.Effect.kind() == Kind::NonBlocking)
return false;
}
}
@@ -5068,8 +5075,8 @@ bool FunctionEffect::shouldDiagnoseConversion(
bool FunctionEffect::shouldDiagnoseRedeclaration(
bool Adding, const FunctionDecl &OldFunction,
- const FunctionTypeEffects &OldFX, const FunctionDecl &NewFunction,
- const FunctionTypeEffects &NewFX) const {
+ const FunctionTypeEffectsRef &OldFX, const FunctionDecl &NewFunction,
+ const FunctionTypeEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5084,8 +5091,8 @@ bool FunctionEffect::shouldDiagnoseRedeclaration(
FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionTypeEffects &OldFX, const CXXMethodDecl &NewMethod,
- const FunctionTypeEffects &NewFX) const {
+ const FunctionTypeEffectsRef &OldFX, const CXXMethodDecl &NewMethod,
+ const FunctionTypeEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5156,65 +5163,125 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
// =====
-void FunctionTypeEffects::Profile(llvm::FoldingSetNodeID &ID) const {
- ID.AddInteger(size());
- for (const auto &CFE : Items) {
- ID.AddInteger(llvm::to_underlying(CFE.effect().kind()));
- ID.AddPointer(CFE.condition());
+void FunctionTypeEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
+ const bool HasConds = !Conditions.empty();
+
+ ID.AddInteger(size() | (HasConds << 31u));
+ for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
+ ID.AddInteger(llvm::to_underlying(Effects[Idx].kind()));
+ if (HasConds)
+ ID.AddPointer(Conditions[Idx].Cond);
}
}
+void FunctionTypeEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
+ // lower_bound would be overkill
+ unsigned Idx = 0;
+ for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
+ const auto &IterEffect = Effects[Idx];
+ if (IterEffect == Effect) {
+ // TODO: Is it okay to assume the caller has already diagnosed
+ // any potential conflict with conditions here?
+ return;
+ }
+ if (Effect < IterEffect)
+ break;
+ }
+
+ if (Cond != nullptr) {
+ if (Conditions.empty() && !Effects.empty())
+ Conditions.resize(Effects.size());
+ Conditions.insert(Conditions.begin() + Idx, FunctionEffectCondExpr{Cond});
+ }
+ Effects.insert(Effects.begin() + Idx, Effect);
+}
+
+void FunctionTypeEffectSet::insert(const FunctionTypeEffectsRef &Set) {
+ for (const auto &Item : Set)
+ insert(Item.Effect, Item.Cond);
+}
+
+void FunctionTypeEffectSet::insertIgnoringConditions(
+ const FunctionTypeEffectsRef &Set) {
+ for (const auto &Item : Set)
+ insert(Item.Effect, nullptr);
+}
+
FunctionTypeEffectSet
-FunctionTypeEffectSet::getUnion(ArrayRef<CondFunctionEffect> LHS,
- ArrayRef<CondFunctionEffect> RHS) {
+FunctionTypeEffectSet::getUnion(FunctionTypeEffectsRef LHS,
+ FunctionTypeEffectsRef RHS) {
// Optimize for either of the two sets being empty (very common).
if (LHS.empty())
return FunctionTypeEffectSet(RHS);
- if (RHS.empty())
- return FunctionTypeEffectSet(LHS);
-
- // Optimize for the two sets being identical (very common).
- if (LHS == RHS)
- return FunctionTypeEffectSet(LHS);
- FunctionTypeEffectSet Result;
- Result.Impl.reserve(LHS.size() + RHS.size());
- std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result.Impl));
+ FunctionTypeEffectSet Result(LHS);
+ Result.insert(RHS);
return Result;
}
FunctionTypeEffectSet::Differences
-FunctionTypeEffectSet::differences(const FunctionTypeEffects &Old,
- const FunctionTypeEffects &New) {
- // TODO: Could be a one-pass algorithm.
- Differences Result;
- for (const auto &Effect : FunctionTypeEffectSet::difference(New, Old)) {
- Result.emplace_back(Effect, true);
- }
- for (const auto &Effect : FunctionTypeEffectSet::difference(Old, New)) {
- Result.emplace_back(Effect, false);
+FunctionTypeEffectSet::differences(const FunctionTypeEffectsRef &Old,
+ const FunctionTypeEffectsRef &New) {
+
+ FunctionTypeEffectSet::Differences Result;
+
+ FunctionTypeEffectsRef::iterator POld = Old.begin();
+ FunctionTypeEffectsRef::iterator OldEnd = Old.end();
+ FunctionTypeEffectsRef::iterator PNew = New.begin();
+ FunctionTypeEffectsRef::iterator NewEnd = New.end();
+
+ auto compare = [](const CondFunctionEffect &LHS,
+ const CondFunctionEffect &RHS) {
+ if (LHS.Effect < RHS.Effect)
+ return -1;
+ if (LHS.Effect > RHS.Effect)
+ return 1;
+ if (LHS.Cond < RHS.Cond)
+ return -1;
+ if (LHS.Cond > RHS.Cond)
+ return 1;
+ return 0;
+ };
+
+ while (true) {
+ int cmp = 0;
+ if (POld == OldEnd) {
+ if (PNew == NewEnd)
+ break;
+ cmp = 1;
+ } else if (PNew == NewEnd)
+ cmp = -1;
+ else
+ cmp = compare(*POld, *PNew);
+
+ if (cmp < 0) {
+ // removal
+ Result.push_back({*POld, false});
+ ++POld;
+ } else if (cmp > 0) {
+ // addition
+ Result.push_back({*PNew, true});
+ ++PNew;
+ } else {
+ ++POld;
+ ++PNew;
+ }
}
+
return Result;
}
+#if 0
FunctionTypeEffectSet
-FunctionTypeEffectSet::difference(ArrayRef<CondFunctionEffect> LHS,
- ArrayRef<CondFunctionEffect> RHS) {
+FunctionTypeEffectSet::difference(FunctionTypeEffectsRef LHS,
+ FunctionTypeEffectsRef RHS) {
FunctionTypeEffectSet Result;
std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
std::back_inserter(Result.Impl));
return Result;
}
-void FunctionTypeEffectSet::insert(const CondFunctionEffect &Effect) {
- auto *Iter = std::lower_bound(Impl.begin(), Impl.end(), Effect);
- if (Iter == Impl.end() || *Iter != Effect) {
- Impl.insert(Iter, Effect);
- }
-}
-
-void FunctionTypeEffectSet::insert(ArrayRef<CondFunctionEffect> Arr) {
+void FunctionTypeEffectSet::insert(FunctionTypeEffectsRef Arr) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &CFE : Arr) {
insert(CFE);
@@ -5222,14 +5289,16 @@ void FunctionTypeEffectSet::insert(ArrayRef<CondFunctionEffect> Arr) {
}
void FunctionTypeEffectSet::insertIgnoringConditions(
- ArrayRef<CondFunctionEffect> Arr) {
+ FunctionTypeEffectsRef Arr) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &CFE : Arr) {
insert(CondFunctionEffect(CFE.effect().kind(), nullptr));
}
}
+#endif
-LLVM_DUMP_METHOD void FunctionTypeEffects::dump(llvm::raw_ostream &OS) const {
+LLVM_DUMP_METHOD void
+FunctionTypeEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
for (const auto &CFE : *this) {
@@ -5237,17 +5306,18 @@ LLVM_DUMP_METHOD void FunctionTypeEffects::dump(llvm::raw_ostream &OS) const {
OS << ", ";
else
First = false;
- OS << CFE.effect().name();
+ OS << CFE.Effect.name();
+ // TODO: Condition
}
OS << "}";
}
LLVM_DUMP_METHOD void FunctionTypeEffectSet::dump(llvm::raw_ostream &OS) const {
- FunctionTypeEffects(*this).dump(OS);
+ FunctionTypeEffectsRef(*this).dump(OS);
}
// TODO: inline?
-FunctionTypeEffects FunctionTypeEffects::get(QualType QT) {
+FunctionTypeEffectsRef FunctionTypeEffectsRef::get(QualType QT) {
if (QT->isReferenceType())
QT = QT.getNonReferenceType();
if (QT->isPointerType())
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 054aa3de413c8..01708bb85b5fe 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -998,9 +998,9 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
}
T->printExceptionSpecification(OS, Policy);
- const FunctionTypeEffects FX = T->getFunctionEffects();
+ const FunctionTypeEffectsRef FX = T->getFunctionEffects();
for (const auto &CFE : FX)
- OS << " __attribute__((clang_" << CFE.effect().name() << "))";
+ OS << " __attribute__((clang_" << CFE.Effect.name() << "))";
// $$$ TODO: Conditions ???
if (T->hasTrailingReturn()) {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index dacabfa8f32a6..482358839767b 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2527,7 +2527,7 @@ class FunctionEffectSet {
void insert(const FunctionEffect &Effect);
void insert(const FunctionEffectSet &Set);
- void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+ void insertIgnoringConditions(const FunctionTypeEffectsRef &FX);
void dump(llvm::raw_ostream &OS) const;
@@ -2554,9 +2554,9 @@ void FunctionEffectSet::insert(const FunctionEffectSet &Set) {
}
void FunctionEffectSet::insertIgnoringConditions(
- ArrayRef<CondFunctionEffect> Arr) {
- for (auto &Item : Arr)
- insert(Item.effect());
+ const FunctionTypeEffectsRef &FX) {
+ for (const auto &Item : FX)
+ insert(Item.Effect);
}
LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
@@ -2594,7 +2594,7 @@ struct CallableInfo {
CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
// llvm::errs() << "CallableInfo " << name() << "\n";
- FunctionTypeEffects FX;
+ FunctionTypeEffectsRef FX;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
@@ -2613,7 +2613,7 @@ struct CallableInfo {
FX = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
- FX = FunctionTypeEffects::get(VD->getType());
+ FX = FunctionTypeEffectsRef::get(VD->getType());
}
Effects.insertIgnoringConditions(FX);
}
@@ -2967,7 +2967,7 @@ class Analyzer {
// be checked, and to see which ones are inferrable.
{
for (const CondFunctionEffect &CFE : Sem.AllEffectsToVerify) {
- const FunctionEffect &Effect = CFE.effect();
+ const FunctionEffect &Effect = CFE.Effect;
const auto Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees) {
AllInferrableEffectsToVerify.insert(Effect);
@@ -3718,14 +3718,14 @@ class Analyzer {
void run(const MatchFinder::MatchResult &Result) override {
if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
- if (const auto FX = functionEffectsForDecl(Callable)) {
+ if (const auto FX = functionEffectsForDecl(Callable); !FX.empty()) {
// Reuse this filtering method in Sema
Sem.maybeAddDeclWithEffects(Callable, FX);
}
}
}
- static FunctionTypeEffects functionEffectsForDecl(const Decl *D) {
+ static FunctionTypeEffectsRef functionEffectsForDecl(const Decl *D) {
if (auto *FD = D->getAsFunction()) {
return FD->getFunctionEffects();
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index fdef8dc9f877f..b0cbdedbc2875 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -590,11 +590,11 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- const auto SrcFX = FunctionTypeEffects::get(SrcType);
- const auto DstFX = FunctionTypeEffects::get(DstType);
+ const auto SrcFX = FunctionTypeEffectsRef::get(SrcType);
+ const auto DstFX = FunctionTypeEffectsRef::get(DstType);
if (SrcFX != DstFX) {
for (const auto &Item : FunctionTypeEffectSet::differences(SrcFX, DstFX)) {
- const FunctionEffect &Effect = Item.first.effect();
+ const FunctionEffect &Effect = Item.first.Effect;
const bool Adding = Item.second;
if (Effect.shouldDiagnoseConversion(Adding, SrcType, SrcFX, DstType,
DstFX)) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1a48d759e3a8e..17aec3f57ac97 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3926,7 +3926,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
if (OldFX != NewFX) {
const auto Diffs = FunctionTypeEffectSet::differences(OldFX, NewFX);
for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first.effect();
+ const FunctionEffect &Effect = Item.first.Effect;
const bool Adding = Item.second;
if (Effect.shouldDiagnoseRedeclaration(Adding, *Old, OldFX, *New,
NewFX)) {
@@ -3943,7 +3943,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
+ EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(),
NewFPT->getParamTypes(), EPI);
@@ -3954,7 +3954,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// so as not to fail due to differences later.
if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) {
EPI = OldFPT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
+ EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
OldQTypeForComparison = Context.getFunctionType(
OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
}
@@ -11129,7 +11129,7 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
void Sema::maybeAddDeclWithEffects(const Decl *D,
- const FunctionTypeEffects &FX) {
+ const FunctionTypeEffectsRef &FX) {
if (!D->hasBody()) {
if (const auto *FD = D->getAsFunction()) {
if (!FD->willHaveBody()) {
@@ -16065,7 +16065,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
- if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
+ if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
+ !FX.empty()) {
maybeAddDeclWithEffects(FD, FX);
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 4c5e47c66ee5b..fb4850fefe8c9 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18333,7 +18333,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
bool AnyDiags = false;
for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first.effect();
+ const FunctionEffect &Effect = Item.first.Effect;
const bool Adding = Item.second;
switch (Effect.shouldDiagnoseMethodOverride(Adding, *Old, OldFX, *New,
NewFX)) {
@@ -18350,7 +18350,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffects(MergedFX);
+ EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
NewFT->getParamTypes(), EPI);
New->setType(ModQT);
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d5cfc4669c9ab..a386bd11e9be9 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17156,7 +17156,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
BlockDecl *BD = BSI->TheDecl;
- if (const auto FX = BD->getFunctionEffects()) {
+ if (const auto FX = BD->getFunctionEffects(); !FX.empty()) {
maybeAddDeclWithEffects(BD, FX);
}
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index bb6ee041ad944..c0ff29174c42a 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1889,7 +1889,7 @@ ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
ActOnFinishFunctionBody(LSI.CallOperator, Body);
- if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
+ if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty()) {
maybeAddDeclWithEffects(LSI.CallOperator, FX);
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 542853050bae4..68fe306cf326e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8116,16 +8116,16 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
// nonblocking(true) and nonallocating(true) are represented as
- // FunctionEffects, in a FunctionTypeEffects attached to a FunctionProtoType.
- const CondFunctionEffect NewEffect(isNonBlocking
- ? FunctionEffect::Kind::NonBlocking
- : FunctionEffect::Kind::NonAllocating,
- nullptr /* no condition yet */);
+ // FunctionEffects, in a FunctionTypeEffectsRef attached to a
+ // FunctionProtoType.
+ const FunctionEffect NewEffect(isNonBlocking
+ ? FunctionEffect::Kind::NonBlocking
+ : FunctionEffect::Kind::NonAllocating);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionTypeEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEffect);
- EPI.FunctionEffects = FunctionTypeEffects(FX);
+ FX.insert(NewEffect, nullptr);
+ EPI.FunctionEffects = FunctionTypeEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index d4bd45f3b7434..7d6fc9d03f4c1 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8226,13 +8226,13 @@ void ASTReader::InitializeSema(Sema &S) {
Decl *D = GetDecl(ID);
SemaObj->DeclsWithEffectsToVerify.push_back(D);
- FunctionTypeEffects FX;
+ FunctionTypeEffectsRef FX;
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
FX = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(D)) {
FX = BD->getFunctionEffects();
}
- if (FX) {
+ if (!FX.empty()) {
SemaObj->AllEffectsToVerify.insertIgnoringConditions(FX);
}
}
>From 57d09cefde0124d67008528fca9d21d260cd2f44 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 16 Apr 2024 07:39:36 -0700
Subject: [PATCH 33/71] FunctionEffectCondExpr => FunctionEffectCondition
---
clang/include/clang/AST/AbstractBasicReader.h | 4 +--
clang/include/clang/AST/AbstractBasicWriter.h | 2 +-
clang/include/clang/AST/PropertiesBase.td | 2 +-
clang/include/clang/AST/Type.h | 30 +++++++++----------
clang/include/clang/AST/TypeProperties.td | 2 +-
clang/lib/AST/ASTContext.cpp | 2 +-
clang/lib/AST/Type.cpp | 6 ++--
7 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index f932b7608d474..f341822278019 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -251,8 +251,8 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
return FunctionEffect(static_cast<FunctionEffect::Kind>(value));
}
- FunctionEffectCondExpr readFunctionEffectCondExpr() {
- return FunctionEffectCondExpr{asImpl().readExprRef()};
+ FunctionEffectCondition readFunctionEffectCondition() {
+ return FunctionEffectCondition{asImpl().readExprRef()};
}
NestedNameSpecifier *readNestedNameSpecifier() {
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 51dc280efcd9c..c269d3f7ed9c3 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -228,7 +228,7 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
asImpl().writeUInt32(llvm::to_underlying(E.kind()));
}
- void writeFunctionEffectCondExpr(FunctionEffectCondExpr CE) {
+ void writeFunctionEffectCondition(FunctionEffectCondition CE) {
asImpl().writeExprRef(CE.Cond);
}
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index b596b22d94329..aa25a1c7c3acf 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -118,7 +118,7 @@ def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> {
let PassByReference = 1;
}
def FunctionEffect : PropertyType<"FunctionEffect">;
-def FunctionEffectCondExpr : PropertyType<"FunctionEffectCondExpr">;
+def FunctionEffectCondition : PropertyType<"FunctionEffectCondition">;
def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; }
def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">;
def LValuePathSerializationHelper :
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index dad5eacbaea6b..008eb2d177e94 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4447,9 +4447,9 @@ class FunctionTypeEffectSet;
- Keep FunctionEffect itself here but make it more minimal. Don't define flags
or any behaviors, just the Kind and an accessor.
- - Keep FunctionEffectCondExpr here.
+ - Keep FunctionEffectCondition here.
- Make FunctionProtoType and ExtProtoInfo use only ArrayRef<FunctionEffect>
- and ArrayRef<FunctionEffectCondExpr>.
+ and ArrayRef<FunctionEffectCondition>.
- Somewhere in Sema, define ExtFunctionEffect, which holds a FunctionEffect
and has all the behavior-related methods.
- There too, define the containers. FunctionTypeEffectsRef can have a
@@ -4578,10 +4578,10 @@ class FunctionEffect {
/// Wrap a function effect's condition expression in another struct so
/// that FunctionProtoType's TrailingObjects can treat it separately.
-struct FunctionEffectCondExpr {
+struct FunctionEffectCondition {
const Expr *Cond = nullptr; // if null, unconditional
- bool operator==(const FunctionEffectCondExpr &RHS) const {
+ bool operator==(const FunctionEffectCondition &RHS) const {
return Cond == RHS.Cond;
}
};
@@ -4595,7 +4595,7 @@ struct CondFunctionEffect {
};
/// Support iteration in parallel through a pair of FunctionEffect and
-/// FunctionEffectCondExpr containers.
+/// FunctionEffectCondition containers.
template <typename Container> class FunctionEffectIterator {
const Container &Outer;
size_t Idx;
@@ -4631,7 +4631,7 @@ class FunctionTypeEffectsRef {
// The array of conditions is either empty or has the same size
// as the array of effects.
- ArrayRef<FunctionEffectCondExpr> Conditions;
+ ArrayRef<FunctionEffectCondition> Conditions;
public:
/// Extract the effects from a Type if it is a function, block, or member
@@ -4642,14 +4642,14 @@ class FunctionTypeEffectsRef {
// The arrays are expected to have been sorted by the caller.
FunctionTypeEffectsRef(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondExpr> Conds)
+ ArrayRef<FunctionEffectCondition> Conds)
: Effects(FX), Conditions(Conds) {}
bool empty() const { return Effects.empty(); }
size_t size() const { return Effects.size(); }
ArrayRef<FunctionEffect> effects() const { return Effects; }
- ArrayRef<FunctionEffectCondExpr> conditions() const { return Conditions; }
+ ArrayRef<FunctionEffectCondition> conditions() const { return Conditions; }
using iterator = FunctionEffectIterator<FunctionTypeEffectsRef>;
friend iterator;
@@ -4675,7 +4675,7 @@ class FunctionTypeEffectSet {
SmallVector<FunctionEffect> Effects;
// The vector of conditions is either empty or has the same size
// as the vector of effects.
- SmallVector<FunctionEffectCondExpr> Conditions;
+ SmallVector<FunctionEffectCondition> Conditions;
public:
FunctionTypeEffectSet() = default;
@@ -4727,7 +4727,7 @@ class FunctionProtoType final
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
- FunctionEffect, FunctionEffectCondExpr, Qualifiers> {
+ FunctionEffect, FunctionEffectCondition, Qualifiers> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -4761,7 +4761,7 @@ class FunctionProtoType final
// * Optionally, an array of getNumFunctionEffects() FunctionEffect.
// Present only when getNumFunctionEffects() > 0
//
- // * Optionally, an array of getNumFunctionEffects() FunctionEffectCondExpr.
+ // * Optionally, an array of getNumFunctionEffects() FunctionEffectCondition.
// Present only when getNumFunctionEffectConditions() > 0.
//
// * Optionally a Qualifiers object to represent extra qualifiers that can't
@@ -4893,7 +4893,7 @@ class FunctionProtoType final
return getNumFunctionEffects();
}
- unsigned numTrailingObjects(OverloadToken<FunctionEffectCondExpr>) const {
+ unsigned numTrailingObjects(OverloadToken<FunctionEffectCondition>) const {
return getNumFunctionEffectConditions();
}
@@ -5238,11 +5238,11 @@ class FunctionProtoType final
}
// For serialization.
- ArrayRef<FunctionEffectCondExpr> getFunctionEffectConditions() const {
+ ArrayRef<FunctionEffectCondition> getFunctionEffectConditions() const {
if (hasExtraBitfields()) {
const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
if (Bitfields->EffectsHaveConditions)
- return {getTrailingObjects<FunctionEffectCondExpr>(),
+ return {getTrailingObjects<FunctionEffectCondition>(),
Bitfields->NumFunctionEffects};
}
return {};
@@ -5259,7 +5259,7 @@ class FunctionProtoType final
return FunctionTypeEffectsRef(
{getTrailingObjects<FunctionEffect>(),
Bitfields->NumFunctionEffects},
- {NumConds ? getTrailingObjects<FunctionEffectCondExpr>() : nullptr,
+ {NumConds ? getTrailingObjects<FunctionEffectCondition>() : nullptr,
NumConds});
}
}
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index b21b031f9c477..0cbbdbd2911ea 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -355,7 +355,7 @@ let Class = FunctionProtoType in {
def : Property<"functionEffects", Array<FunctionEffect>> {
let Read = [{ node->getFunctionEffectsOnly() }];
}
- def : Property<"functionEffectConds", Array<FunctionEffectCondExpr>> {
+ def : Property<"functionEffectConds", Array<FunctionEffectCondition>> {
let Read = [{ node->getFunctionEffectConditions() }];
}
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 5d513f6e6b3d2..cd0e1dcc01e88 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4563,7 +4563,7 @@ QualType ASTContext::getFunctionTypeInternal(
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
- FunctionEffect, FunctionEffectCondExpr, Qualifiers>(
+ FunctionEffect, FunctionEffectCondition, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index d9df0f3acf9aa..2c21db8ff46e7 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3647,10 +3647,10 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
auto *DestFX = getTrailingObjects<FunctionEffect>();
std::copy(SrcFX.begin(), SrcFX.end(), DestFX);
- ArrayRef<FunctionEffectCondExpr> SrcConds =
+ ArrayRef<FunctionEffectCondition> SrcConds =
epi.FunctionEffects.conditions();
if (!SrcConds.empty()) {
- auto *DestConds = getTrailingObjects<FunctionEffectCondExpr>();
+ auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
}
}
@@ -5191,7 +5191,7 @@ void FunctionTypeEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
if (Cond != nullptr) {
if (Conditions.empty() && !Effects.empty())
Conditions.resize(Effects.size());
- Conditions.insert(Conditions.begin() + Idx, FunctionEffectCondExpr{Cond});
+ Conditions.insert(Conditions.begin() + Idx, FunctionEffectCondition{Cond});
}
Effects.insert(Effects.begin() + Idx, Effect);
}
>From a9edec6b6b69c5189fd547a788d1c256688849ba Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 16 Apr 2024 07:47:44 -0700
Subject: [PATCH 34/71] FunctionTypeEffectSet => FunctionEffectSet
FunctionTypeEffectsRef => FunctionEffectsRef
---
clang/include/clang/AST/Decl.h | 4 +-
clang/include/clang/AST/Type.h | 68 +++++++++++-----------
clang/include/clang/AST/TypeProperties.td | 2 +-
clang/include/clang/Sema/Sema.h | 6 +-
clang/lib/AST/Decl.cpp | 4 +-
clang/lib/AST/Type.cpp | 70 +++++++++++------------
clang/lib/AST/TypePrinter.cpp | 2 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 63 ++++++++++----------
clang/lib/Sema/Sema.cpp | 6 +-
clang/lib/Sema/SemaDecl.cpp | 10 ++--
clang/lib/Sema/SemaDeclCXX.cpp | 6 +-
clang/lib/Sema/SemaType.cpp | 6 +-
clang/lib/Serialization/ASTReader.cpp | 2 +-
13 files changed, 123 insertions(+), 126 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 71aed63c87258..6d4f9a3c02c1f 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,7 +3008,7 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
- FunctionTypeEffectsRef getFunctionEffects() const;
+ FunctionEffectsRef getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
@@ -4635,7 +4635,7 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
- FunctionTypeEffectsRef getFunctionEffects() const;
+ FunctionEffectsRef getFunctionEffects() const;
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 008eb2d177e94..2942a5c5d8487 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4438,8 +4438,8 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
-class FunctionTypeEffectsRef;
-class FunctionTypeEffectSet;
+class FunctionEffectsRef;
+class FunctionEffectSet;
/*
TODO: Idea about how to move most of the FunctionEffect business out of
@@ -4452,7 +4452,7 @@ class FunctionTypeEffectSet;
and ArrayRef<FunctionEffectCondition>.
- Somewhere in Sema, define ExtFunctionEffect, which holds a FunctionEffect
and has all the behavior-related methods.
- - There too, define the containers. FunctionTypeEffectsRef can have a
+ - There too, define the containers. FunctionEffectsRef can have a
constructor or factory method that initializes itself from a
FunctionProtoType.
*/
@@ -4529,24 +4529,24 @@ class FunctionEffect {
/// Return true if adding or removing the effect as part of a type conversion
/// should generate a diagnostic.
bool shouldDiagnoseConversion(bool Adding, QualType OldType,
- const FunctionTypeEffectsRef &OldFX,
+ const FunctionEffectsRef &OldFX,
QualType NewType,
- const FunctionTypeEffectsRef &NewFX) const;
+ const FunctionEffectsRef &NewFX) const;
/// Return true if adding or removing the effect in a redeclaration should
/// generate a diagnostic.
bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
- const FunctionTypeEffectsRef &OldFX,
+ const FunctionEffectsRef &OldFX,
const FunctionDecl &NewFunction,
- const FunctionTypeEffectsRef &NewFX) const;
+ const FunctionEffectsRef &NewFX) const;
/// Return true if adding or removing the effect in a C++ virtual method
/// override should generate a diagnostic.
OverrideResult
shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionTypeEffectsRef &OldFX,
+ const FunctionEffectsRef &OldFX,
const CXXMethodDecl &NewMethod,
- const FunctionTypeEffectsRef &NewFX) const;
+ const FunctionEffectsRef &NewFX) const;
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
@@ -4625,8 +4625,8 @@ template <typename Container> class FunctionEffectIterator {
/// An immutable set of FunctionEffects and possibly conditions attached to
/// them. The effects and conditions reside in memory not managed by this object
/// (typically, trailing objects in FunctionProtoType, or borrowed references
-/// from a FunctionTypeEffectSet).
-class FunctionTypeEffectsRef {
+/// from a FunctionEffectSet).
+class FunctionEffectsRef {
ArrayRef<FunctionEffect> Effects;
// The array of conditions is either empty or has the same size
@@ -4636,13 +4636,13 @@ class FunctionTypeEffectsRef {
public:
/// Extract the effects from a Type if it is a function, block, or member
/// function pointer, or a reference or pointer to one.
- static FunctionTypeEffectsRef get(QualType QT);
+ static FunctionEffectsRef get(QualType QT);
- FunctionTypeEffectsRef() = default;
+ FunctionEffectsRef() = default;
// The arrays are expected to have been sorted by the caller.
- FunctionTypeEffectsRef(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondition> Conds)
+ FunctionEffectsRef(ArrayRef<FunctionEffect> FX,
+ ArrayRef<FunctionEffectCondition> Conds)
: Effects(FX), Conditions(Conds) {}
bool empty() const { return Effects.empty(); }
@@ -4651,17 +4651,17 @@ class FunctionTypeEffectsRef {
ArrayRef<FunctionEffect> effects() const { return Effects; }
ArrayRef<FunctionEffectCondition> conditions() const { return Conditions; }
- using iterator = FunctionEffectIterator<FunctionTypeEffectsRef>;
+ using iterator = FunctionEffectIterator<FunctionEffectsRef>;
friend iterator;
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, size()); }
- friend bool operator==(const FunctionTypeEffectsRef &LHS,
- const FunctionTypeEffectsRef &RHS) {
+ friend bool operator==(const FunctionEffectsRef &LHS,
+ const FunctionEffectsRef &RHS) {
return LHS.Effects == RHS.Effects && LHS.Conditions == RHS.Conditions;
}
- friend bool operator!=(const FunctionTypeEffectsRef &LHS,
- const FunctionTypeEffectsRef &RHS) {
+ friend bool operator!=(const FunctionEffectsRef &LHS,
+ const FunctionEffectsRef &RHS) {
return !(LHS == RHS);
}
@@ -4671,45 +4671,45 @@ class FunctionTypeEffectsRef {
/// A mutable set of FunctionEffects and possibly conditions attached to them.
/// Used transitorily within Sema to compare and merge effects on declarations.
-class FunctionTypeEffectSet {
+class FunctionEffectSet {
SmallVector<FunctionEffect> Effects;
// The vector of conditions is either empty or has the same size
// as the vector of effects.
SmallVector<FunctionEffectCondition> Conditions;
public:
- FunctionTypeEffectSet() = default;
+ FunctionEffectSet() = default;
- explicit FunctionTypeEffectSet(const FunctionTypeEffectsRef &FX)
+ explicit FunctionEffectSet(const FunctionEffectsRef &FX)
: Effects(FX.effects()), Conditions(FX.conditions()) {}
bool empty() const { return Effects.empty(); }
size_t size() const { return Effects.size(); }
- using iterator = FunctionEffectIterator<FunctionTypeEffectSet>;
+ using iterator = FunctionEffectIterator<FunctionEffectSet>;
friend iterator;
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, size()); }
- operator FunctionTypeEffectsRef() const { return {Effects, Conditions}; }
+ operator FunctionEffectsRef() const { return {Effects, Conditions}; }
void dump(llvm::raw_ostream &OS) const;
// Mutators
void insert(FunctionEffect Effect, const Expr *Cond);
- void insert(const FunctionTypeEffectsRef &Set);
- void insertIgnoringConditions(const FunctionTypeEffectsRef &Set);
+ void insert(const FunctionEffectsRef &Set);
+ void insertIgnoringConditions(const FunctionEffectsRef &Set);
// Set operations
using Differences =
SmallVector<std::pair<CondFunctionEffect, /*added=*/bool>>;
/// Caller should short-circuit by checking for equality first.
- static Differences differences(const FunctionTypeEffectsRef &Old,
- const FunctionTypeEffectsRef &New);
+ static Differences differences(const FunctionEffectsRef &Old,
+ const FunctionEffectsRef &New);
- static FunctionTypeEffectSet getUnion(FunctionTypeEffectsRef LHS,
- FunctionTypeEffectsRef RHS);
+ static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS);
};
/// Represents a prototype with parameter type info, e.g.
@@ -4822,7 +4822,7 @@ class FunctionProtoType final
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
- FunctionTypeEffectsRef FunctionEffects;
+ FunctionEffectsRef FunctionEffects;
ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false),
@@ -5249,14 +5249,14 @@ class FunctionProtoType final
}
// Combines effects with their conditions.
- FunctionTypeEffectsRef getFunctionEffects() const {
+ FunctionEffectsRef getFunctionEffects() const {
if (hasExtraBitfields()) {
const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
if (Bitfields->NumFunctionEffects > 0) {
const size_t NumConds = Bitfields->EffectsHaveConditions
? Bitfields->NumFunctionEffects
: 0;
- return FunctionTypeEffectsRef(
+ return FunctionEffectsRef(
{getTrailingObjects<FunctionEffect>(),
Bitfields->NumFunctionEffects},
{NumConds ? getTrailingObjects<FunctionEffectCondition>() : nullptr,
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 0cbbdbd2911ea..f98659dca73c6 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -374,7 +374,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- epi.FunctionEffects = FunctionTypeEffectsRef(functionEffects, functionEffectConds);
+ epi.FunctionEffects = FunctionEffectsRef(functionEffects, functionEffectConds);
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ff11b4f6b4d0f..f82b8db2ef841 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -943,18 +943,18 @@ class Sema final {
// out of Type.h, but where to?
/// All functions/lambdas/blocks which have bodies and which have a non-empty
- /// FunctionTypeEffectsRef to be verified.
+ /// FunctionEffectsRef to be verified.
SmallVector<const Decl *> DeclsWithEffectsToVerify;
/// The union of all effects present on DeclsWithEffectsToVerify. Conditions
/// are all null.
- FunctionTypeEffectSet AllEffectsToVerify;
+ FunctionEffectSet AllEffectsToVerify;
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void maybeAddDeclWithEffects(const Decl *D, const FunctionTypeEffectsRef &FX);
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
// ----- function effects --- where ?????
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 740542b376719..524956ea18678 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4508,7 +4508,7 @@ unsigned FunctionDecl::getODRHash() {
// Effects may differ between declarations, but they should be propagated from
// old to new on any redeclaration, so it suffices to look at
// getMostRecentDecl().
-FunctionTypeEffectsRef FunctionDecl::getFunctionEffects() const {
+FunctionEffectsRef FunctionDecl::getFunctionEffects() const {
if (const auto *FPT =
getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
@@ -5240,7 +5240,7 @@ SourceRange BlockDecl::getSourceRange() const {
return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
}
-FunctionTypeEffectsRef BlockDecl::getFunctionEffects() const {
+FunctionEffectsRef BlockDecl::getFunctionEffects() const {
if (auto *TSI = getSignatureAsWritten()) {
if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
return FPT->getFunctionEffects();
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 2c21db8ff46e7..b6db9bededa9a 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5050,8 +5050,8 @@ StringRef FunctionEffect::name() const {
}
bool FunctionEffect::shouldDiagnoseConversion(
- bool Adding, QualType OldType, const FunctionTypeEffectsRef &OldFX,
- QualType NewType, const FunctionTypeEffectsRef &NewFX) const {
+ bool Adding, QualType OldType, const FunctionEffectsRef &OldFX,
+ QualType NewType, const FunctionEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
@@ -5075,8 +5075,8 @@ bool FunctionEffect::shouldDiagnoseConversion(
bool FunctionEffect::shouldDiagnoseRedeclaration(
bool Adding, const FunctionDecl &OldFunction,
- const FunctionTypeEffectsRef &OldFX, const FunctionDecl &NewFunction,
- const FunctionTypeEffectsRef &NewFX) const {
+ const FunctionEffectsRef &OldFX, const FunctionDecl &NewFunction,
+ const FunctionEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5091,8 +5091,8 @@ bool FunctionEffect::shouldDiagnoseRedeclaration(
FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionTypeEffectsRef &OldFX, const CXXMethodDecl &NewMethod,
- const FunctionTypeEffectsRef &NewFX) const {
+ const FunctionEffectsRef &OldFX, const CXXMethodDecl &NewMethod,
+ const FunctionEffectsRef &NewFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking:
@@ -5163,7 +5163,7 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
// =====
-void FunctionTypeEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
+void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
const bool HasConds = !Conditions.empty();
ID.AddInteger(size() | (HasConds << 31u));
@@ -5174,7 +5174,7 @@ void FunctionTypeEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
}
}
-void FunctionTypeEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
+void FunctionEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
// lower_bound would be overkill
unsigned Idx = 0;
for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
@@ -5196,39 +5196,38 @@ void FunctionTypeEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
Effects.insert(Effects.begin() + Idx, Effect);
}
-void FunctionTypeEffectSet::insert(const FunctionTypeEffectsRef &Set) {
+void FunctionEffectSet::insert(const FunctionEffectsRef &Set) {
for (const auto &Item : Set)
insert(Item.Effect, Item.Cond);
}
-void FunctionTypeEffectSet::insertIgnoringConditions(
- const FunctionTypeEffectsRef &Set) {
+void FunctionEffectSet::insertIgnoringConditions(
+ const FunctionEffectsRef &Set) {
for (const auto &Item : Set)
insert(Item.Effect, nullptr);
}
-FunctionTypeEffectSet
-FunctionTypeEffectSet::getUnion(FunctionTypeEffectsRef LHS,
- FunctionTypeEffectsRef RHS) {
+FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS) {
// Optimize for either of the two sets being empty (very common).
if (LHS.empty())
- return FunctionTypeEffectSet(RHS);
+ return FunctionEffectSet(RHS);
- FunctionTypeEffectSet Result(LHS);
+ FunctionEffectSet Result(LHS);
Result.insert(RHS);
return Result;
}
-FunctionTypeEffectSet::Differences
-FunctionTypeEffectSet::differences(const FunctionTypeEffectsRef &Old,
- const FunctionTypeEffectsRef &New) {
+FunctionEffectSet::Differences
+FunctionEffectSet::differences(const FunctionEffectsRef &Old,
+ const FunctionEffectsRef &New) {
- FunctionTypeEffectSet::Differences Result;
+ FunctionEffectSet::Differences Result;
- FunctionTypeEffectsRef::iterator POld = Old.begin();
- FunctionTypeEffectsRef::iterator OldEnd = Old.end();
- FunctionTypeEffectsRef::iterator PNew = New.begin();
- FunctionTypeEffectsRef::iterator NewEnd = New.end();
+ FunctionEffectsRef::iterator POld = Old.begin();
+ FunctionEffectsRef::iterator OldEnd = Old.end();
+ FunctionEffectsRef::iterator PNew = New.begin();
+ FunctionEffectsRef::iterator NewEnd = New.end();
auto compare = [](const CondFunctionEffect &LHS,
const CondFunctionEffect &RHS) {
@@ -5272,24 +5271,24 @@ FunctionTypeEffectSet::differences(const FunctionTypeEffectsRef &Old,
}
#if 0
-FunctionTypeEffectSet
-FunctionTypeEffectSet::difference(FunctionTypeEffectsRef LHS,
- FunctionTypeEffectsRef RHS) {
- FunctionTypeEffectSet Result;
+FunctionEffectSet
+FunctionEffectSet::difference(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS) {
+ FunctionEffectSet Result;
std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
std::back_inserter(Result.Impl));
return Result;
}
-void FunctionTypeEffectSet::insert(FunctionTypeEffectsRef Arr) {
+void FunctionEffectSet::insert(FunctionEffectsRef Arr) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &CFE : Arr) {
insert(CFE);
}
}
-void FunctionTypeEffectSet::insertIgnoringConditions(
- FunctionTypeEffectsRef Arr) {
+void FunctionEffectSet::insertIgnoringConditions(
+ FunctionEffectsRef Arr) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &CFE : Arr) {
insert(CondFunctionEffect(CFE.effect().kind(), nullptr));
@@ -5297,8 +5296,7 @@ void FunctionTypeEffectSet::insertIgnoringConditions(
}
#endif
-LLVM_DUMP_METHOD void
-FunctionTypeEffectsRef::dump(llvm::raw_ostream &OS) const {
+LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
for (const auto &CFE : *this) {
@@ -5312,12 +5310,12 @@ FunctionTypeEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "}";
}
-LLVM_DUMP_METHOD void FunctionTypeEffectSet::dump(llvm::raw_ostream &OS) const {
- FunctionTypeEffectsRef(*this).dump(OS);
+LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
+ FunctionEffectsRef(*this).dump(OS);
}
// TODO: inline?
-FunctionTypeEffectsRef FunctionTypeEffectsRef::get(QualType QT) {
+FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
if (QT->isReferenceType())
QT = QT.getNonReferenceType();
if (QT->isPointerType())
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 01708bb85b5fe..e0e2782b972bd 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -998,7 +998,7 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
}
T->printExceptionSpecification(OS, Policy);
- const FunctionTypeEffectsRef FX = T->getFunctionEffects();
+ const FunctionEffectsRef FX = T->getFunctionEffects();
for (const auto &CFE : FX)
OS << " __attribute__((clang_" << CFE.Effect.name() << "))";
// $$$ TODO: Conditions ???
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 482358839767b..2ba97e5eac108 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2456,10 +2456,10 @@ static bool functionIsVerifiable(const FunctionDecl *FD) {
#if 0
/// A mutable set of FunctionEffect, for use in places where any conditions
/// have been resolved or can be ignored.
-class FunctionEffectSet {
+class EffectSet {
SmallVector<FunctionEffect, 4> Impl;
public:
- FunctionEffectSet() = default;
+ EffectSet() = default;
operator ArrayRef<FunctionEffect>() const { return Impl; }
@@ -2468,12 +2468,12 @@ class FunctionEffectSet {
iterator end() const { return Impl.end(); }
void insert(const FunctionEffect &Effect);
- void insert(const FunctionEffectSet &Set);
+ void insert(const EffectSet &Set);
void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
void dump(llvm::raw_ostream &OS) const;
- static FunctionEffectSet difference(ArrayRef<FunctionEffect> LHS, ArrayRef<FunctionEffect> RHS);
+ static EffectSet difference(ArrayRef<FunctionEffect> LHS, ArrayRef<FunctionEffect> RHS);
};
#endif
@@ -2483,7 +2483,7 @@ class FunctionEffectSet {
// byte, and there are only 2 possible effects, this is more than sufficient. In
// AnalysisBasedWarnings, we hold one of these for every function visited,
// which, due to inference, can be many more functions than have effects.)
-class FunctionEffectSet {
+class EffectSet {
template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
SizeT Count = 0;
T Items[Capacity] = {};
@@ -2515,7 +2515,7 @@ class FunctionEffectSet {
FixedVector<FunctionEffect, uint8_t, 7> Impl;
public:
- FunctionEffectSet() = default;
+ EffectSet() = default;
operator ArrayRef<FunctionEffect>() const {
return ArrayRef(Impl.cbegin(), Impl.cend());
@@ -2526,16 +2526,16 @@ class FunctionEffectSet {
iterator end() const { return Impl.cend(); }
void insert(const FunctionEffect &Effect);
- void insert(const FunctionEffectSet &Set);
- void insertIgnoringConditions(const FunctionTypeEffectsRef &FX);
+ void insert(const EffectSet &Set);
+ void insertIgnoringConditions(const FunctionEffectsRef &FX);
void dump(llvm::raw_ostream &OS) const;
- static FunctionEffectSet difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS);
+ static EffectSet difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS);
};
-void FunctionEffectSet::insert(const FunctionEffect &Effect) {
+void EffectSet::insert(const FunctionEffect &Effect) {
FunctionEffect *Iter = Impl.begin();
FunctionEffect *End = Impl.end();
// lower_bound is overkill for a tiny vector like this
@@ -2548,18 +2548,17 @@ void FunctionEffectSet::insert(const FunctionEffect &Effect) {
Impl.insert(Iter, Effect);
}
-void FunctionEffectSet::insert(const FunctionEffectSet &Set) {
+void EffectSet::insert(const EffectSet &Set) {
for (auto &Item : Set)
insert(Item);
}
-void FunctionEffectSet::insertIgnoringConditions(
- const FunctionTypeEffectsRef &FX) {
+void EffectSet::insertIgnoringConditions(const FunctionEffectsRef &FX) {
for (const auto &Item : FX)
insert(Item.Effect);
}
-LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
for (const auto &Effect : *this) {
@@ -2572,9 +2571,9 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
OS << "}";
}
-FunctionEffectSet FunctionEffectSet::difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS) {
- FunctionEffectSet Result;
+EffectSet EffectSet::difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS) {
+ EffectSet Result;
std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
std::back_inserter(Result.Impl));
return Result;
@@ -2588,13 +2587,13 @@ struct CallableInfo {
mutable std::optional<std::string>
MaybeName; // mutable because built on demand in const method
SpecialFuncType FuncType = SpecialFuncType::None;
- FunctionEffectSet Effects;
+ EffectSet Effects;
CallType CType = CallType::Unknown;
CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
// llvm::errs() << "CallableInfo " << name() << "\n";
- FunctionTypeEffectsRef FX;
+ FunctionEffectsRef FX;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
@@ -2613,7 +2612,7 @@ struct CallableInfo {
FX = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
- FX = FunctionTypeEffectsRef::get(VD->getType());
+ FX = FunctionEffectsRef::get(VD->getType());
}
Effects.insertIgnoringConditions(FX);
}
@@ -2708,7 +2707,7 @@ class EffectToDiagnosticMap {
// ----------
// State pertaining to a function whose AST is walked. Since there are
// potentially a large number of these objects, it needs care about size.
-// TODO: FunctionEffectSet could be made much smaller.
+// TODO: EffectSet could be made much smaller.
class PendingFunctionAnalysis {
friend class CompleteFunctionAnalysis;
@@ -2732,8 +2731,8 @@ class PendingFunctionAnalysis {
// We always have two disjoint sets of effects to verify:
// 1. Effects declared explicitly by this function.
// 2. All other inferrable effects needing verification.
- FunctionEffectSet DeclaredVerifiableEffects;
- FunctionEffectSet FXToInfer;
+ EffectSet DeclaredVerifiableEffects;
+ EffectSet FXToInfer;
private:
// Diagnostics pertaining to the function's explicit effects. Use a unique_ptr
@@ -2753,7 +2752,7 @@ class PendingFunctionAnalysis {
DeclaredVerifiableEffects = CInfo.Effects;
// Check for effects we are not allowed to infer
- FunctionEffectSet FX;
+ EffectSet FX;
for (const auto &effect : AllInferrableEffectsToVerify) {
if (effect.canInferOnFunction(*CInfo.CDecl)) {
@@ -2769,7 +2768,7 @@ class PendingFunctionAnalysis {
}
}
// FX is now the set of inferrable effects which are not prohibited
- FXToInfer = FunctionEffectSet::difference(FX, DeclaredVerifiableEffects);
+ FXToInfer = EffectSet::difference(FX, DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2843,7 +2842,7 @@ class CompleteFunctionAnalysis {
// ones which have been successfully inferred. These are all considered
// "verified" for the purposes of callers; any issue with verifying declared
// effects has already been reported and is not the problem of any caller.
- FunctionEffectSet VerifiedEffects;
+ EffectSet VerifiedEffects;
private:
// This is used to generate notes about failed inference.
@@ -2852,7 +2851,7 @@ class CompleteFunctionAnalysis {
public:
CompleteFunctionAnalysis(
ASTContext &Ctx, PendingFunctionAnalysis &pending,
- const FunctionEffectSet &funcFX,
+ const EffectSet &funcFX,
ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
VerifiedEffects.insert(funcFX);
for (const auto &effect : AllInferrableEffectsToVerify) {
@@ -2897,7 +2896,7 @@ class Analyzer {
// SmallVector<const Decl *> CallablesWithEffectsToVerify
// Subset of Sema.AllEffectsToVerify
- FunctionEffectSet AllInferrableEffectsToVerify;
+ EffectSet AllInferrableEffectsToVerify;
using FuncAnalysisPtr =
llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
@@ -3138,7 +3137,7 @@ class Analyzer {
const bool DirectCall = Callee.isDirectCall();
// These will be its declared effects.
- FunctionEffectSet CalleeEffects = Callee.Effects;
+ EffectSet CalleeEffects = Callee.Effects;
bool IsInferencePossible = DirectCall;
@@ -3444,7 +3443,7 @@ class Analyzer {
const auto CalleeType = CalleeExpr->getType();
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- FunctionEffectSet CalleeFX;
+ EffectSet CalleeFX;
if (FPT)
CalleeFX.insertIgnoringConditions(FPT->getFunctionEffects());
static_assert(sizeof(FunctionEffect) == 1);
@@ -3725,7 +3724,7 @@ class Analyzer {
}
}
- static FunctionTypeEffectsRef functionEffectsForDecl(const Decl *D) {
+ static FunctionEffectsRef functionEffectsForDecl(const Decl *D) {
if (auto *FD = D->getAsFunction()) {
return FD->getFunctionEffects();
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index b0cbdedbc2875..4ac0208512f18 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -590,10 +590,10 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
// Generate diagnostics when adding or removing effects in a type conversion.
void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc) {
- const auto SrcFX = FunctionTypeEffectsRef::get(SrcType);
- const auto DstFX = FunctionTypeEffectsRef::get(DstType);
+ const auto SrcFX = FunctionEffectsRef::get(SrcType);
+ const auto DstFX = FunctionEffectsRef::get(DstType);
if (SrcFX != DstFX) {
- for (const auto &Item : FunctionTypeEffectSet::differences(SrcFX, DstFX)) {
+ for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
const FunctionEffect &Effect = Item.first.Effect;
const bool Adding = Item.second;
if (Effect.shouldDiagnoseConversion(Adding, SrcType, SrcFX, DstType,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 17aec3f57ac97..60c7fe7472950 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3924,7 +3924,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
const auto NewFX = New->getFunctionEffects();
QualType OldQTypeForComparison = OldQType;
if (OldFX != NewFX) {
- const auto Diffs = FunctionTypeEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
for (const auto &Item : Diffs) {
const FunctionEffect &Effect = Item.first.Effect;
const bool Adding = Item.second;
@@ -3940,10 +3940,10 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// declaration, but that would trigger an additional "conflicting types"
// error.
if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
- auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
+ auto MergedFX = FunctionEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
+ EPI.FunctionEffects = FunctionEffectsRef(MergedFX);
QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(),
NewFPT->getParamTypes(), EPI);
@@ -3954,7 +3954,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// so as not to fail due to differences later.
if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) {
EPI = OldFPT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
+ EPI.FunctionEffects = FunctionEffectsRef(MergedFX);
OldQTypeForComparison = Context.getFunctionType(
OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
}
@@ -11129,7 +11129,7 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
void Sema::maybeAddDeclWithEffects(const Decl *D,
- const FunctionTypeEffectsRef &FX) {
+ const FunctionEffectsRef &FX) {
if (!D->hasBody()) {
if (const auto *FD = D->getAsFunction()) {
if (!FD->willHaveBody()) {
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index fb4850fefe8c9..1e89f97bc5f20 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18329,7 +18329,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
const auto NewFX = New->getFunctionEffects();
if (OldFX != NewFX) {
- const auto Diffs = FunctionTypeEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
bool AnyDiags = false;
for (const auto &Item : Diffs) {
@@ -18347,10 +18347,10 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
AnyDiags = true;
break;
case FunctionEffect::OverrideResult::Merge: {
- auto MergedFX = FunctionTypeEffectSet::getUnion(OldFX, NewFX);
+ auto MergedFX = FunctionEffectSet::getUnion(OldFX, NewFX);
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionTypeEffectsRef(MergedFX);
+ EPI.FunctionEffects = FunctionEffectsRef(MergedFX);
QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
NewFT->getParamTypes(), EPI);
New->setType(ModQT);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 68fe306cf326e..eb396c6f37204 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8116,16 +8116,16 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
// nonblocking(true) and nonallocating(true) are represented as
- // FunctionEffects, in a FunctionTypeEffectsRef attached to a
+ // FunctionEffects, in a FunctionEffectsRef attached to a
// FunctionProtoType.
const FunctionEffect NewEffect(isNonBlocking
? FunctionEffect::Kind::NonBlocking
: FunctionEffect::Kind::NonAllocating);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
- FunctionTypeEffectSet FX(EPI.FunctionEffects);
+ FunctionEffectSet FX(EPI.FunctionEffects);
FX.insert(NewEffect, nullptr);
- EPI.FunctionEffects = FunctionTypeEffectsRef(FX);
+ EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 7d6fc9d03f4c1..dede16d84dc1b 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8226,7 +8226,7 @@ void ASTReader::InitializeSema(Sema &S) {
Decl *D = GetDecl(ID);
SemaObj->DeclsWithEffectsToVerify.push_back(D);
- FunctionTypeEffectsRef FX;
+ FunctionEffectsRef FX;
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
FX = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(D)) {
>From 80495b7ca14301a43c2e5d6152abca14f6d24627 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 16 Apr 2024 08:24:12 -0700
Subject: [PATCH 35/71] nonblocking(cond) now parsed into new form.
---
clang/lib/AST/Type.cpp | 1 +
clang/lib/AST/TypePrinter.cpp | 21 +++++++++------------
clang/lib/Sema/SemaType.cpp | 21 +++------------------
clang/test/Sema/attr-nonblocking-syntax.cpp | 13 ++++++++++---
4 files changed, 23 insertions(+), 33 deletions(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index b6db9bededa9a..213468b4f10d5 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3650,6 +3650,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
ArrayRef<FunctionEffectCondition> SrcConds =
epi.FunctionEffects.conditions();
if (!SrcConds.empty()) {
+ ExtraBits.EffectsHaveConditions = true;
auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index e0e2782b972bd..ca50b0c046dcb 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -999,9 +999,15 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
T->printExceptionSpecification(OS, Policy);
const FunctionEffectsRef FX = T->getFunctionEffects();
- for (const auto &CFE : FX)
- OS << " __attribute__((clang_" << CFE.Effect.name() << "))";
- // $$$ TODO: Conditions ???
+ for (const auto &CFE : FX) {
+ OS << " __attribute__((clang_" << CFE.Effect.name();
+ if (CFE.Cond) {
+ OS << '(';
+ CFE.Cond->printPretty(OS, nullptr, Policy);
+ OS << ')';
+ }
+ OS << "))";
+ }
if (T->hasTrailingReturn()) {
OS << " -> ";
@@ -1876,15 +1882,6 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
return;
}
- if (T->getAttrKind() == attr::NonBlocking) {
- OS << " [[clang::nonblocking(...)]]";
- return;
- }
- if (T->getAttrKind() == attr::NonAllocating) {
- OS << " [[clang::nonallocating(...)]]";
- return;
- }
-
if (T->getAttrKind() == attr::ArmStreaming) {
OS << "__arm_streaming";
return;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index eb396c6f37204..e134822132f6a 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8088,20 +8088,6 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
TPState.setParsedNonAllocating(NewState);
}
- if (NewState == BoolAttrState::Dependent) {
- // nonblocking(expr)/nonallocating(expr) are represented as AttributedType
- // sugar, using those attributes. TODO: Currently no one else tries to find
- // it there, and this may turn out to be the wrong place.
- Attr *A = nullptr;
- if (isNonBlocking) {
- A = NonBlockingAttr::Create(S.Context, CondExpr);
- } else {
- A = NonAllocatingAttr::Create(S.Context, CondExpr);
- }
- QT = TPState.getAttributedType(A, QT, QT);
- return true;
- }
-
if (NewState == BoolAttrState::False) {
// blocking and allocating are represented as AttributedType sugar,
// using those attributes.
@@ -8115,16 +8101,15 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
return true;
}
- // nonblocking(true) and nonallocating(true) are represented as
- // FunctionEffects, in a FunctionEffectsRef attached to a
- // FunctionProtoType.
+ // nonblocking/nonallocating(true/expr) are represented in a
+ // FunctionEffectsRef attached to a FunctionProtoType.
const FunctionEffect NewEffect(isNonBlocking
? FunctionEffect::Kind::NonBlocking
: FunctionEffect::Kind::NonAllocating);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEffect, nullptr);
+ FX.insert(NewEffect, NewState == BoolAttrState::Dependent ? CondExpr : nullptr);
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index f6f58f95fd70d..48ca62f3401e4 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -52,11 +52,18 @@ decltype(nl1) nl3;
// Attribute propagates from base class virtual method to overrides.
struct Base {
- virtual void nl_method() [[clang::nonblocking]];
+ virtual void nb_method() [[clang::nonblocking]];
};
struct Derived : public Base {
- void nl_method() override;
- // CHECK: CXXMethodDecl {{.*}} nl_method 'void () __attribute__((clang_nonblocking))'
+ void nb_method() override;
+ // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
+};
+
+// Dependent expression
+template <bool V>
+struct Dependent {
+ void nb_method2() [[clang::nonblocking(V)]];
+ // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((clang_nonblocking(V)))'
};
// --- Blocks ---
>From 1853b39f0e7a28b9593c6e87956a2456ca17b3f3 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 18 Apr 2024 09:05:50 -0700
Subject: [PATCH 36/71] wip with computed effects
---
clang/include/clang/AST/AbstractBasicWriter.h | 2 +-
clang/include/clang/AST/Type.h | 27 ++--
.../clang/Basic/DiagnosticSemaKinds.td | 8 +-
clang/include/clang/Sema/Sema.h | 14 ++
clang/lib/AST/Type.cpp | 41 +++--
clang/lib/AST/TypePrinter.cpp | 4 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 145 +++++++++++-------
clang/lib/Sema/SemaType.cpp | 111 +++++++-------
clang/lib/Sema/TreeTransform.h | 67 ++++++++
9 files changed, 283 insertions(+), 136 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index c269d3f7ed9c3..c6a5748bfbb1b 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -229,7 +229,7 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
}
void writeFunctionEffectCondition(FunctionEffectCondition CE) {
- asImpl().writeExprRef(CE.Cond);
+ asImpl().writeExprRef(CE.expr());
}
void writeNestedNameSpecifier(NestedNameSpecifier *NNS) {
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 2942a5c5d8487..5f003f6e4ecc1 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4578,8 +4578,14 @@ class FunctionEffect {
/// Wrap a function effect's condition expression in another struct so
/// that FunctionProtoType's TrailingObjects can treat it separately.
-struct FunctionEffectCondition {
- const Expr *Cond = nullptr; // if null, unconditional
+class FunctionEffectCondition {
+ Expr *Cond = nullptr; // if null, unconditional
+
+public:
+ FunctionEffectCondition() = default;
+ FunctionEffectCondition(Expr *E) : Cond(E) {} // implicit OK
+
+ Expr *expr() const { return Cond; }
bool operator==(const FunctionEffectCondition &RHS) const {
return Cond == RHS.Cond;
@@ -4589,9 +4595,9 @@ struct FunctionEffectCondition {
/// A FunctionEffect plus a potential boolean expression determining whether
/// the effect is declared (e.g. nonblocking(expr)). Generally the condition
/// expression when present, is dependent.
-struct CondFunctionEffect {
+struct FunctionEffectWithCondition {
FunctionEffect Effect;
- const Expr *Cond = nullptr; // if null, unconditional
+ FunctionEffectCondition Cond;
};
/// Support iteration in parallel through a pair of FunctionEffect and
@@ -4615,10 +4621,10 @@ template <typename Container> class FunctionEffectIterator {
return *this;
}
- CondFunctionEffect operator*() const {
+ FunctionEffectWithCondition operator*() const {
const bool HasConds = !Outer.Conditions.empty();
- return CondFunctionEffect{Outer.Effects[Idx],
- HasConds ? Outer.Conditions[Idx].Cond : nullptr};
+ return FunctionEffectWithCondition{Outer.Effects[Idx],
+ HasConds ? Outer.Conditions[Idx] : FunctionEffectCondition()};
}
};
@@ -4696,14 +4702,17 @@ class FunctionEffectSet {
void dump(llvm::raw_ostream &OS) const;
// Mutators
- void insert(FunctionEffect Effect, const Expr *Cond);
+ void insert(FunctionEffect Effect, Expr *Cond);
void insert(const FunctionEffectsRef &Set);
void insertIgnoringConditions(const FunctionEffectsRef &Set);
+ void replaceCondition(unsigned Idx, Expr *Cond);
+ void erase(unsigned Idx);
+
// Set operations
using Differences =
- SmallVector<std::pair<CondFunctionEffect, /*added=*/bool>>;
+ SmallVector<std::pair<FunctionEffectWithCondition, /*added=*/bool>>;
/// Caller should short-circuit by checking for equality first.
static Differences differences(const FunctionEffectsRef &Old,
const FunctionEffectsRef &New);
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3608c9403dd34..3ce35abc088b0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10833,21 +10833,21 @@ def warn_func_effect_calls_objc : Warning<
def note_func_effect_calls_objc : Note<
"function cannot be inferred '%0' because it accesses an ObjC method or property">;
-def warn_func_effect_calls_disallowed_func : Warning<
+def warn_func_effect_calls_func_without_effect : Warning<
"'%0' function must not call non-'%0' function '%1'">,
InGroup<FunctionEffects>;
-def warn_func_effect_calls_disallowed_expr : Warning<
+def warn_func_effect_calls_expr_without_effect : Warning<
"'%0' function must not call non-'%0' expression">,
InGroup<FunctionEffects>;
-def note_func_effect_calls_disallowed_func : Note<
+def note_func_effect_calls_func_without_effect : Note<
"function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
def note_func_effect_call_extern : Note<
"function cannot be inferred '%0' because it has no definition in this translation unit">;
-def note_func_effect_call_not_inferrable : Note<
+def note_func_effect_call_disallows_inference : Note<
"function does not permit inference of '%0'">;
def note_func_effect_call_virtual : Note<
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f82b8db2ef841..5b417870a9ebb 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -420,6 +420,14 @@ enum class TemplateDeductionResult {
AlreadyDiagnosed
};
+/// Used with attributes/effects with a boolean condition, e.g. `nonblocking`.
+enum class FunctionEffectMode {
+ None, // effect is not present
+ False, // effect(false)
+ True, // effect(true)
+ Dependent // effect(expr) where expr is dependent
+};
+
/// Sema - This implements semantic analysis and AST building for C.
/// \nosubgrouping
class Sema final {
@@ -956,6 +964,12 @@ class Sema final {
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+ /// Try to parse the conditional expression attached to an effect attribute
+ /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). If RequireConstexpr,
+ /// then this will fail if the expression is dependent.
+ ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
+ bool RequireConstexpr = false);
+
// ----- function effects --- where ?????
bool makeUnavailableInSystemHeader(SourceLocation loc,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 213468b4f10d5..bdf6d23eec269 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3653,6 +3653,9 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
ExtraBits.EffectsHaveConditions = true;
auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
+
+ assert(isCanonicalUnqualified()); // TODO: because I don't understand this yet...
+ addDependence(TypeDependence::DependentInstantiation);
}
}
}
@@ -5171,11 +5174,11 @@ void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
ID.AddInteger(llvm::to_underlying(Effects[Idx].kind()));
if (HasConds)
- ID.AddPointer(Conditions[Idx].Cond);
+ ID.AddPointer(Conditions[Idx].expr());
}
}
-void FunctionEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
+void FunctionEffectSet::insert(FunctionEffect Effect, Expr *Cond) {
// lower_bound would be overkill
unsigned Idx = 0;
for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
@@ -5189,17 +5192,17 @@ void FunctionEffectSet::insert(FunctionEffect Effect, const Expr *Cond) {
break;
}
- if (Cond != nullptr) {
+ if (Cond) {
if (Conditions.empty() && !Effects.empty())
Conditions.resize(Effects.size());
- Conditions.insert(Conditions.begin() + Idx, FunctionEffectCondition{Cond});
+ Conditions.insert(Conditions.begin() + Idx, Cond);
}
Effects.insert(Effects.begin() + Idx, Effect);
}
void FunctionEffectSet::insert(const FunctionEffectsRef &Set) {
for (const auto &Item : Set)
- insert(Item.Effect, Item.Cond);
+ insert(Item.Effect, Item.Cond.expr());
}
void FunctionEffectSet::insertIgnoringConditions(
@@ -5208,6 +5211,18 @@ void FunctionEffectSet::insertIgnoringConditions(
insert(Item.Effect, nullptr);
}
+void FunctionEffectSet::replaceCondition(unsigned Idx, Expr *Cond) {
+ assert(Idx < Conditions.size());
+ Conditions[Idx] = FunctionEffectCondition(Cond);
+}
+
+void FunctionEffectSet::erase(unsigned Idx) {
+ assert(Idx < Effects.size());
+ Effects.erase(Effects.begin() + Idx);
+ if (!Conditions.empty())
+ Conditions.erase(Conditions.begin() + Idx);
+}
+
FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
FunctionEffectsRef RHS) {
// Optimize for either of the two sets being empty (very common).
@@ -5230,15 +5245,15 @@ FunctionEffectSet::differences(const FunctionEffectsRef &Old,
FunctionEffectsRef::iterator PNew = New.begin();
FunctionEffectsRef::iterator NewEnd = New.end();
- auto compare = [](const CondFunctionEffect &LHS,
- const CondFunctionEffect &RHS) {
+ auto compare = [](const FunctionEffectWithCondition &LHS,
+ const FunctionEffectWithCondition &RHS) {
if (LHS.Effect < RHS.Effect)
return -1;
if (LHS.Effect > RHS.Effect)
return 1;
- if (LHS.Cond < RHS.Cond)
+ if (LHS.Cond.expr() < RHS.Cond.expr())
return -1;
- if (LHS.Cond > RHS.Cond)
+ if (RHS.Cond.expr() < LHS.Cond.expr())
return 1;
return 0;
};
@@ -5292,7 +5307,7 @@ void FunctionEffectSet::insertIgnoringConditions(
FunctionEffectsRef Arr) {
// TODO: For large RHS sets, use set_union or a custom insert-in-place
for (const auto &CFE : Arr) {
- insert(CondFunctionEffect(CFE.effect().kind(), nullptr));
+ insert(FunctionEffectWithCondition(CFE.effect().kind(), nullptr));
}
}
#endif
@@ -5306,7 +5321,11 @@ LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
else
First = false;
OS << CFE.Effect.name();
- // TODO: Condition
+ if (Expr * E = CFE.Cond.expr()) {
+ OS << '(';
+ E->dump();
+ OS << ')';
+ }
}
OS << "}";
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ca50b0c046dcb..a87adb5da0724 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1001,9 +1001,9 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
const FunctionEffectsRef FX = T->getFunctionEffects();
for (const auto &CFE : FX) {
OS << " __attribute__((clang_" << CFE.Effect.name();
- if (CFE.Cond) {
+ if (const Expr *E = CFE.Cond.expr()) {
OS << '(';
- CFE.Cond->printPretty(OS, nullptr, Policy);
+ E->printPretty(OS, nullptr, Policy);
OS << ')';
}
OS << "))";
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 2ba97e5eac108..f82b3eee055b6 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2385,7 +2385,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
// =============================================================================
// Temporary feature enablement
-#define FX_ANALYZER_ENABLED 1
+#define FX_ANALYZER_ENABLED 0
#if FX_ANALYZER_ENABLED
@@ -2410,10 +2410,10 @@ enum class DiagnosticID : uint8_t {
AccessesThreadLocal,
// These only apply to callees, where the analysis stops at the Decl
- DeclWithoutConstraintOrInference,
+ DeclDisallowsInference,
- CallsUnsafeDecl,
- CallsDisallowedExpr,
+ CallsDeclWithoutEffect,
+ CallsExprWithoutEffect,
};
struct Diagnostic {
@@ -2469,7 +2469,7 @@ class EffectSet {
void insert(const FunctionEffect &Effect);
void insert(const EffectSet &Set);
- void insertIgnoringConditions(ArrayRef<CondFunctionEffect> Arr);
+ void insertIgnoringConditions(ArrayRef<FunctionEffectWithCondition> Arr);
void dump(llvm::raw_ostream &OS) const;
@@ -2528,6 +2528,9 @@ class EffectSet {
void insert(const FunctionEffect &Effect);
void insert(const EffectSet &Set);
void insertIgnoringConditions(const FunctionEffectsRef &FX);
+ bool contains(const FunctionEffect &E) const {
+ return std::find(begin(), end(), E) != end();
+ }
void dump(llvm::raw_ostream &OS) const;
@@ -2579,6 +2582,31 @@ EffectSet EffectSet::difference(ArrayRef<FunctionEffect> LHS,
return Result;
}
+// Represent the declared effects, and absent effects (e.g. nonblocking(false)).
+// Centralize the resolution of computed effects.
+struct CondEffectSet {
+ EffectSet EffectsPresent;
+ EffectSet EffectsExplicitlyAbsent;
+
+ CondEffectSet() = default;
+
+ CondEffectSet(Sema &SemaRef, FunctionEffectsRef FX) {
+ for (const auto Item : FX) {
+ if (Expr *Cond = Item.Cond) {
+ FunctionEffectMode Mode = FunctionEffectMode::None;
+ ExprResult ER = SemaRef.ActOnEffectExpression(Cond, Mode, /*RequireConstexpr=*/true);
+ if (ER.isInvalid() || Mode == FunctionEffectMode::Dependent)
+ continue;
+ if (Mode == FunctionEffectMode::False) {
+ EffectsExplicitlyAbsent.insert(Item.Effect);
+ continue;
+ }
+ }
+ EffectsPresent.insert(Item.Effect);
+ }
+ }
+};
+
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer...
struct CallableInfo {
@@ -2587,13 +2615,13 @@ struct CallableInfo {
mutable std::optional<std::string>
MaybeName; // mutable because built on demand in const method
SpecialFuncType FuncType = SpecialFuncType::None;
- EffectSet Effects;
+ CondEffectSet Effects;
CallType CType = CallType::Unknown;
- CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+ CallableInfo(Sema &SemaRef, const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
// llvm::errs() << "CallableInfo " << name() << "\n";
- FunctionEffectsRef FX;
+ FunctionEffectsRef FXRef;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
@@ -2606,15 +2634,15 @@ struct CallableInfo {
CType = CallType::Virtual;
}
}
- FX = FD->getFunctionEffects();
+ FXRef = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
- FX = BD->getFunctionEffects();
+ FXRef = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
- FX = FunctionEffectsRef::get(VD->getType());
+ FXRef = FunctionEffectsRef::get(VD->getType());
}
- Effects.insertIgnoringConditions(FX);
+ Effects = CondEffectSet(SemaRef, FXRef);
}
bool isDirectCall() const {
@@ -2749,13 +2777,13 @@ class PendingFunctionAnalysis {
PendingFunctionAnalysis(
Sema &Sem, const CallableInfo &CInfo,
ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- DeclaredVerifiableEffects = CInfo.Effects;
+ DeclaredVerifiableEffects = CInfo.Effects.EffectsPresent;
// Check for effects we are not allowed to infer
EffectSet FX;
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl)) {
+ if (effect.canInferOnFunction(*CInfo.CDecl) && !CInfo.Effects.EffectsExplicitlyAbsent.contains(effect)) {
FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
@@ -2763,7 +2791,7 @@ class PendingFunctionAnalysis {
auto &diag =
InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
diag =
- Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
+ Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
CInfo.CDecl->getLocation());
}
}
@@ -2813,8 +2841,7 @@ class PendingFunctionAnalysis {
return DiagnosticsForExplicitFX.get();
}
- // If Sema is supplied, prints names of unverified direct calls
- void dump(llvm::raw_ostream &OS, Sema *SemPtr = nullptr) const {
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
OS << "Pending: Declared ";
DeclaredVerifiableEffects.dump(OS);
OS << ", "
@@ -2823,11 +2850,11 @@ class PendingFunctionAnalysis {
OS << " Infer ";
FXToInfer.dump(OS);
OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
- if (SemPtr && UnverifiedDirectCalls) {
+ if (UnverifiedDirectCalls) {
OS << "; Calls: ";
for (const auto &Call : *UnverifiedDirectCalls) {
- CallableInfo CI(*Call.callee());
- OS << " " << CI.name(*SemPtr);
+ CallableInfo CI(SemaRef, *Call.callee());
+ OS << " " << CI.name(SemaRef);
}
}
OS << "\n";
@@ -2888,7 +2915,7 @@ const Decl *CanonicalFunctionDecl(const Decl *D) {
// ==========
class Analyzer {
- constexpr static int DebugLogLevel = 0;
+ constexpr static int DebugLogLevel = 3;
// --
Sema &Sem;
@@ -2931,12 +2958,12 @@ class Analyzer {
return nullptr;
}
- void dump(Sema &S, llvm::raw_ostream &OS) {
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
OS << "\nAnalysisMap:\n";
for (const auto &item : *this) {
- CallableInfo CI(*item.first);
+ CallableInfo CI(SemaRef, *item.first);
const auto AP = item.second;
- OS << item.first << " " << CI.name(S) << " : ";
+ OS << item.first << " " << CI.name(SemaRef) << " : ";
if (AP.isNull()) {
OS << "null\n";
} else if (isa<CompleteFunctionAnalysis *>(AP)) {
@@ -2946,7 +2973,7 @@ class Analyzer {
} else if (isa<PendingFunctionAnalysis *>(AP)) {
auto *PFA = AP.get<PendingFunctionAnalysis *>();
OS << PFA << " ";
- PFA->dump(OS);
+ PFA->dump(SemaRef, OS);
} else
llvm_unreachable("never");
}
@@ -2965,7 +2992,7 @@ class Analyzer {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
{
- for (const CondFunctionEffect &CFE : Sem.AllEffectsToVerify) {
+ for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
const FunctionEffect &Effect = CFE.Effect;
const auto Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees) {
@@ -3036,11 +3063,11 @@ class Analyzer {
// Verify a single Decl. Return the pending structure if that was the result,
// else null. This method must not recurse.
PendingFunctionAnalysis *verifyDecl(const Decl *D) {
- CallableInfo CInfo(*D);
+ CallableInfo CInfo(Sem, *D);
// If any of the Decl's declared effects forbid throwing (e.g. nonblocking)
// then the function should also be declared noexcept.
- for (const auto &Effect : CInfo.Effects) {
+ for (const auto &Effect : CInfo.Effects.EffectsPresent) {
if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
continue;
@@ -3070,7 +3097,7 @@ class Analyzer {
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(llvm::outs());
+ FAnalysis.dump(Sem, llvm::outs());
}
FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
@@ -3098,7 +3125,7 @@ class Analyzer {
emitDiagnostics(*Diags, CInfo, Sem);
}
auto *CompletePtr = new CompleteFunctionAnalysis(
- Sem.getASTContext(), Pending, CInfo.Effects,
+ Sem.getASTContext(), Pending, CInfo.Effects.EffectsPresent,
AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
if constexpr (DebugLogLevel > 0) {
@@ -3111,17 +3138,17 @@ class Analyzer {
// not. Generally replicates FunctionBodyASTVisitor::followCall() but without
// the possibility of inference.
void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
- CallableInfo Caller(*D);
+ CallableInfo Caller(Sem, *D);
if constexpr (DebugLogLevel > 0) {
llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
- Pending->dump(llvm::outs(), &Sem);
+ Pending->dump(Sem, llvm::outs());
llvm::outs() << "\n";
}
for (const auto &Call : Pending->unverifiedCalls()) {
if (Call.recursed())
continue;
- CallableInfo Callee(*Call.callee());
+ CallableInfo Callee(Sem, *Call.callee());
followCall(Caller, *Pending, Callee, Call.CallLoc,
/*AssertNoFurtherInference=*/true);
}
@@ -3137,7 +3164,7 @@ class Analyzer {
const bool DirectCall = Callee.isDirectCall();
// These will be its declared effects.
- EffectSet CalleeEffects = Callee.Effects;
+ EffectSet CalleeEffects = Callee.Effects.EffectsPresent;
bool IsInferencePossible = DirectCall;
@@ -3180,7 +3207,7 @@ class Analyzer {
if (Callee.FuncType == SpecialFuncType::None) {
PFA.checkAddDiagnostic(
Inferring,
- {Effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl});
+ {Effect, DiagnosticID::CallsDeclWithoutEffect, CallLoc, Callee.CDecl});
} else {
PFA.checkAddDiagnostic(
Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
@@ -3226,7 +3253,7 @@ class Analyzer {
StringRef effectName = Diag.Effect.name();
switch (Diag.ID) {
case DiagnosticID::None:
- case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen
+ case DiagnosticID::DeclDisallowsInference: // shouldn't happen
// here
llvm_unreachable("Unexpected diagnostic kind");
break;
@@ -3253,17 +3280,17 @@ class Analyzer {
S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::CallsDisallowedExpr:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
+ case DiagnosticID::CallsExprWithoutEffect:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::CallsUnsafeDecl: {
- CallableInfo CalleeInfo{*Diag.Callee};
+ case DiagnosticID::CallsDeclWithoutEffect: {
+ CallableInfo CalleeInfo(S, *Diag.Callee);
auto CalleeName = CalleeInfo.name(S);
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
<< effectName << CalleeName;
checkAddTemplateNote(CInfo.CDecl);
@@ -3284,6 +3311,9 @@ class Analyzer {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
+ } else if (CalleeInfo.Effects.EffectsExplicitlyAbsent.contains(Diag.Effect)) {
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_disallows_inference)
+ << effectName;
} else {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
<< effectName;
@@ -3300,11 +3330,11 @@ class Analyzer {
case DiagnosticID::None:
llvm_unreachable("Unexpected diagnostic kind");
break;
- case DiagnosticID::DeclWithoutConstraintOrInference:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
+ case DiagnosticID::DeclDisallowsInference:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
<< effectName;
break;
- case DiagnosticID::CallsDisallowedExpr:
+ case DiagnosticID::CallsExprWithoutEffect:
S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
<< effectName;
break;
@@ -3327,9 +3357,9 @@ class Analyzer {
case DiagnosticID::CallsObjC:
S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
break;
- case DiagnosticID::CallsUnsafeDecl:
- MaybeNextCallee.emplace(*Diag2.Callee);
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
+ case DiagnosticID::CallsDeclWithoutEffect:
+ MaybeNextCallee.emplace(S, *Diag2.Callee);
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
<< effectName << MaybeNextCallee->name(S);
break;
}
@@ -3444,15 +3474,16 @@ class Analyzer {
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
EffectSet CalleeFX;
- if (FPT)
- CalleeFX.insertIgnoringConditions(FPT->getFunctionEffects());
- static_assert(sizeof(FunctionEffect) == 1);
+ if (FPT) {
+ CondEffectSet CFE(Outer.Sem, FPT->getFunctionEffects());
+ CalleeFX.insert(CFE.EffectsPresent);
+ }
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
/*direct=*/false, CalleeFX)) {
addDiagnosticInner(Inferring, Effect,
- DiagnosticID::CallsDisallowedExpr,
+ DiagnosticID::CallsExprWithoutEffect,
Call->getBeginLoc());
}
};
@@ -3495,7 +3526,7 @@ class Analyzer {
if (Ty->isRecordType()) {
if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
if (auto *Dtor = Class->getDestructor()) {
- CallableInfo CI{*Dtor};
+ CallableInfo CI(Outer.Sem, *Dtor);
followCall(CI, Dtor->getLocation());
}
}
@@ -3541,7 +3572,7 @@ class Analyzer {
Expr *CalleeExpr = Call->getCallee();
if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
- CallableInfo CI(*Callee);
+ CallableInfo CI(Outer.Sem, *Callee);
followCall(CI, Call->getBeginLoc());
return Proceed;
}
@@ -3587,7 +3618,7 @@ class Analyzer {
if (const auto *CxxRec =
dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
if (const auto *Dtor = CxxRec->getDestructor()) {
- CallableInfo CI(*Dtor);
+ CallableInfo CI(Outer.Sem, *Dtor);
followCall(CI, Var->getLocation());
}
}
@@ -3600,7 +3631,7 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator new.
if (auto *FD = New->getOperatorNew()) {
- CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
followCall(CI, New->getBeginLoc());
}
@@ -3616,7 +3647,7 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator delete.
if (auto *FD = Delete->getOperatorDelete()) {
- CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
followCall(CI, Delete->getBeginLoc());
}
@@ -3636,7 +3667,7 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to the constructor.
const CXXConstructorDecl *Ctor = Construct->getConstructor();
- CallableInfo CI(*Ctor);
+ CallableInfo CI(Outer.Sem, *Ctor);
followCall(CI, Construct->getLocation());
return Proceed;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e134822132f6a..b7cbb2524d748 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -175,8 +175,6 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_TypeNullableResult: \
case ParsedAttr::AT_TypeNullUnspecified
-enum class BoolAttrState : uint8_t { Unseen, False, True, Dependent };
-
namespace {
/// An object which stores processing state for the entire
/// GetTypeForDeclarator process.
@@ -220,15 +218,15 @@ namespace {
// nonallocating(cond). Manual logic for finding previous attributes would
// be more complex, unless we transformed nonblocking/nonallocating(false)
// into distinct separate attributes from the ones which are parsed.
- BoolAttrState parsedNonBlocking : 2;
- BoolAttrState parsedNonAllocating : 2;
+ FunctionEffectMode parsedNonBlocking : 2;
+ FunctionEffectMode parsedNonAllocating : 2;
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
: sema(sema), declarator(declarator),
chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
- parsedNonBlocking(BoolAttrState::Unseen),
- parsedNonAllocating(BoolAttrState::Unseen) {}
+ parsedNonBlocking(FunctionEffectMode::None),
+ parsedNonAllocating(FunctionEffectMode::None) {}
Sema &getSema() const {
return sema;
@@ -355,10 +353,10 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
- void setParsedNonBlocking(BoolAttrState v) { parsedNonBlocking = v; }
- BoolAttrState getParsedNonBlocking() const { return parsedNonBlocking; }
- void setParsedNonAllocating(BoolAttrState v) { parsedNonAllocating = v; }
- BoolAttrState getParsedNonAllocating() const { return parsedNonAllocating; }
+ void setParsedNonBlocking(FunctionEffectMode v) { parsedNonBlocking = v; }
+ FunctionEffectMode getParsedNonBlocking() const { return parsedNonBlocking; }
+ void setParsedNonAllocating(FunctionEffectMode v) { parsedNonAllocating = v; }
+ FunctionEffectMode getParsedNonAllocating() const { return parsedNonAllocating; }
~TypeProcessingState() {
if (savedAttrs.empty())
@@ -7979,6 +7977,34 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode, bool RequireConstexpr)
+{
+ // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+ if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ ExprResult E = PerformContextuallyConvertToBool(CondExpr);
+ if (E.isInvalid())
+ return E;
+ CondExpr = E.get();
+ if (RequireConstexpr || !CondExpr->isValueDependent()) {
+ llvm::APSInt CondInt;
+ E = VerifyIntegerConstantExpression(
+ E.get(), &CondInt,
+ // TODO: have our own diagnostic
+ diag::err_constexpr_if_condition_expression_is_not_constant);
+ if (E.isInvalid()) {
+ return E;
+ }
+ Mode =
+ (CondInt != 0) ? FunctionEffectMode::True : FunctionEffectMode::False;
+ } else {
+ Mode = FunctionEffectMode::Dependent;
+ }
+ } else {
+ Mode = FunctionEffectMode::Dependent;
+ }
+ return CondExpr;
+}
+
static bool
handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
ParsedAttr &PAttr, QualType &QT,
@@ -8000,60 +8026,41 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
PAttr.getKind() == ParsedAttr::AT_Blocking;
Sema &S = TPState.getSema();
- BoolAttrState NewState = BoolAttrState::Unseen;
+ FunctionEffectMode NewState = FunctionEffectMode::None;
Expr *CondExpr = nullptr; // only valid if dependent
if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_NonAllocating) {
// Parse the conditional expression, if any
if (PAttr.getNumArgs() > 0) {
- // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
CondExpr = PAttr.getArgAsExpr(0);
- if (!CondExpr->isTypeDependent()) {
- ExprResult E = S.PerformContextuallyConvertToBool(CondExpr);
- if (E.isInvalid())
- return false;
- CondExpr = E.get();
- if (!CondExpr->isValueDependent()) {
- llvm::APSInt CondInt;
- E = S.VerifyIntegerConstantExpression(
- E.get(), &CondInt,
- // TODO: have our own diagnostic
- diag::err_constexpr_if_condition_expression_is_not_constant);
- if (E.isInvalid()) {
- return false;
- }
- NewState =
- (CondInt != 0) ? BoolAttrState::True : BoolAttrState::False;
- } else {
- NewState = BoolAttrState::Dependent;
- }
- } else {
- NewState = BoolAttrState::Dependent;
- }
+ ExprResult E = S.ActOnEffectExpression(CondExpr, NewState);
+ if (E.isInvalid())
+ return false;
+ CondExpr = E.get();
} else {
- NewState = BoolAttrState::True;
+ NewState = FunctionEffectMode::True;
}
} else {
// This is the `blocking` or `allocating` attribute.
- NewState = BoolAttrState::False;
+ NewState = FunctionEffectMode::False;
}
- auto attrName = [](bool NB, BoolAttrState value) {
+ auto attrName = [](bool NB, FunctionEffectMode value) {
switch (value) {
- case BoolAttrState::False:
+ case FunctionEffectMode::False:
return NB ? "blocking" : "allocating";
- case BoolAttrState::True:
+ case FunctionEffectMode::True:
return NB ? "nonblocking" : "nonallocating";
- case BoolAttrState::Dependent:
+ case FunctionEffectMode::Dependent:
return NB ? "nonblocking(expr)" : "nonallocating(expr)";
default:
- llvm_unreachable("'unseen' shouldn't happen");
+ llvm_unreachable("'FunctionEffectMode::None' shouldn't happen");
}
};
// Diagnose the newly provided attribute as incompatible with a previous one.
- auto incompatible = [&](bool prevNB, BoolAttrState prevValue) {
+ auto incompatible = [&](bool prevNB, FunctionEffectMode prevValue) {
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
<< attrName(isNonBlocking, NewState) << attrName(prevNB, prevValue)
<< false;
@@ -8063,32 +8070,32 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
return true;
};
- const BoolAttrState PrevState = isNonBlocking
+ const FunctionEffectMode PrevState = isNonBlocking
? TPState.getParsedNonBlocking()
: TPState.getParsedNonAllocating();
- if (PrevState != BoolAttrState::Unseen) {
+ if (PrevState != FunctionEffectMode::None) {
// Only one attribute per constraint is allowed.
return incompatible(isNonBlocking, PrevState);
}
if (isNonBlocking) {
// also check nonblocking(true) against allocating
- if (NewState == BoolAttrState::True &&
- TPState.getParsedNonAllocating() == BoolAttrState::False) {
- return incompatible(false, BoolAttrState::False);
+ if (NewState == FunctionEffectMode::True &&
+ TPState.getParsedNonAllocating() == FunctionEffectMode::False) {
+ return incompatible(false, FunctionEffectMode::False);
}
TPState.setParsedNonBlocking(NewState);
} else {
// also check nonblocking(true) against allocating
- if (TPState.getParsedNonBlocking() == BoolAttrState::True) {
- if (NewState == BoolAttrState::False) {
- return incompatible(true, BoolAttrState::True);
+ if (TPState.getParsedNonBlocking() == FunctionEffectMode::True) {
+ if (NewState == FunctionEffectMode::False) {
+ return incompatible(true, FunctionEffectMode::True);
}
}
TPState.setParsedNonAllocating(NewState);
}
- if (NewState == BoolAttrState::False) {
+ if (NewState == FunctionEffectMode::False) {
// blocking and allocating are represented as AttributedType sugar,
// using those attributes.
Attr *A = nullptr;
@@ -8109,7 +8116,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEffect, NewState == BoolAttrState::Dependent ? CondExpr : nullptr);
+ FX.insert(NewEffect, NewState == FunctionEffectMode::Dependent ? CondExpr : nullptr);
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index a2568ad0f82cc..c8816d6651e6d 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6234,6 +6234,59 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
EPI.ExtParameterInfos = nullptr;
}
+#define XFORM_FALSE_FX_TO_SUGAR 0
+
+ // Transform any function effects with unevaluated conditions.
+ // Hold this set in a local for the rest of this function, since EPI
+ // is going to hold a FunctionEffectsRef pointing into it.
+ std::optional <FunctionEffectSet> NewFX;
+#if XFORM_FALSE_FX_TO_SUGAR
+ SmallVector<Attr *> TypeAttrsToAdd;
+#endif
+ if (ArrayRef FXConds = EPI.FunctionEffects.conditions(); !FXConds.empty()) {
+ NewFX.emplace(EPI.FunctionEffects);
+ EnterExpressionEvaluationContext Unevaluated(
+ getSema(), Sema::ExpressionEvaluationContext::ConstantEvaluated);
+
+ for (unsigned Idx = 0, Count = FXConds.size(); Idx != Count; ) {
+ if (Expr *CondExpr = FXConds[Idx].expr()) {
+ ExprResult NewExpr = getDerived().TransformExpr(CondExpr);
+ if (NewExpr.isInvalid())
+ return QualType();
+ FunctionEffectMode Mode = FunctionEffectMode::None;
+ NewExpr = SemaRef.ActOnEffectExpression(NewExpr.get(), Mode);
+ if (NewExpr.isInvalid())
+ return QualType();
+
+#if XFORM_FALSE_FX_TO_SUGAR
+ if (Mode == FunctionEffectMode::False) {
+ NewFX->erase(Idx);
+ --Count;
+ const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
+ if (Effect.kind() == FunctionEffect::Kind::NonAllocating) {
+ TypeAttrsToAdd.push_back(AllocatingAttr::Create(SemaRef.Context));
+ } else if (Effect.kind() == FunctionEffect::Kind::NonBlocking) {
+ TypeAttrsToAdd.push_back(BlockingAttr::Create(SemaRef.Context));
+ }
+ continue;
+ }
+ assert(Mode == FunctionEffectMode::True);
+ NewFX->replaceCondition(Idx, NewExpr.get());
+#else
+ // The condition expression has been transformed, and re-evaluated.
+ // If it is now a constant of 'true', we can discard it because a null
+ // condition in FunctionEffectSet means true. If the expression is still
+ // dependent or constant 'false', just propagate it into the new EPI.
+ FunctionEffectCondition Cond(Mode == FunctionEffectMode::True ? nullptr : NewExpr.get());
+ NewFX->replaceCondition(Idx, Cond.expr());
+#endif
+ }
+ ++Idx;
+ }
+ EPI.FunctionEffects = *NewFX;
+ EPIChanged = true;
+ }
+
QualType Result = TL.getType();
if (getDerived().AlwaysRebuild() || ResultType != T->getReturnType() ||
T->getParamTypes() != llvm::ArrayRef(ParamTypes) || EPIChanged) {
@@ -6251,6 +6304,20 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
for (unsigned i = 0, e = NewTL.getNumParams(); i != e; ++i)
NewTL.setParam(i, ParamDecls[i]);
+#if XFORM_FALSE_FX_TO_SUGAR
+ // This creates problems because the caller assumes FunctionProtoType will
+ // be transformed to FunctionProtoType.
+ if (!TypeAttrsToAdd.empty()) {
+ for (Attr *A : TypeAttrsToAdd) {
+ QualType QT = SemaRef.Context.getAttributedType(A->getKind(), Result, Result);
+ llvm::outs() << "transformed " << Result << " -> " << QT;
+ Result = QT;
+ AttributedTypeLoc ATL = TLB.push<AttributedTypeLoc>(Result);
+ ATL.setAttr(A);
+ }
+ }
+#endif
+
return Result;
}
>From 0aa20af28bedb737c842215d56b9312f2e16106c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 23 Apr 2024 12:55:12 -0700
Subject: [PATCH 37/71] - Inline the methods on Decl. - Groundwork for a
canonical representation of "denied" effects (e.g. nonblocking(false))
---
clang/include/clang/AST/AbstractBasicReader.h | 4 +-
clang/include/clang/AST/AbstractBasicWriter.h | 4 +-
clang/include/clang/AST/Decl.h | 17 +-
clang/include/clang/AST/Type.h | 153 ++++++++------
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 31 +--
clang/include/clang/Sema/Sema.h | 9 +-
clang/lib/AST/Decl.cpp | 20 --
clang/lib/AST/Type.cpp | 198 ++++++++++--------
clang/lib/Sema/AnalysisBasedWarnings.cpp | 67 +++---
clang/lib/Sema/Sema.cpp | 12 +-
clang/lib/Sema/SemaDecl.cpp | 13 +-
clang/lib/Sema/SemaDeclCXX.cpp | 40 ++--
clang/lib/Sema/SemaType.cpp | 30 ++-
clang/lib/Sema/TreeTransform.h | 10 +-
clang/lib/Serialization/ASTReader.cpp | 2 +-
16 files changed, 304 insertions(+), 307 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index f341822278019..71bb40587bacc 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -245,10 +245,8 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
}
FunctionEffect readFunctionEffect() {
- static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
- "update this if size changes");
uint32_t value = asImpl().readUInt32();
- return FunctionEffect(static_cast<FunctionEffect::Kind>(value));
+ return FunctionEffect::fromOpaqueInt32(value);
}
FunctionEffectCondition readFunctionEffectCondition() {
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index c6a5748bfbb1b..5b5fca0fd4d97 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -223,9 +223,7 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
}
void writeFunctionEffect(FunctionEffect E) {
- static_assert(sizeof(FunctionEffect::Kind) <= sizeof(uint32_t),
- "update this if the value size changes");
- asImpl().writeUInt32(llvm::to_underlying(E.kind()));
+ asImpl().writeUInt32(E.toOpaqueInt32());
}
void writeFunctionEffectCondition(FunctionEffectCondition CE) {
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 6d4f9a3c02c1f..d097b6a92c739 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,7 +3008,15 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
- FunctionEffectsRef getFunctionEffects() const;
+ FunctionEffectsRef getFunctionEffects() const {
+ // Effects may differ between declarations, but they should be propagated
+ // from old to new on any redeclaration, so it suffices to look at
+ // getMostRecentDecl().
+ if (const auto *FPT =
+ getMostRecentDecl()->getType()->getAs<FunctionProtoType>())
+ return FPT->getFunctionEffects();
+ return {};
+ }
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
@@ -4635,7 +4643,12 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
- FunctionEffectsRef getFunctionEffects() const;
+ FunctionEffectsRef getFunctionEffects() const {
+ if (TypeSourceInfo *TSI = getSignatureAsWritten())
+ if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>())
+ return FPT->getFunctionEffects();
+ return {};
+ }
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 5f003f6e4ecc1..3003d504bdb31 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4438,34 +4438,20 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
class Decl;
class CXXMethodDecl;
+struct FunctionEffectDiff;
class FunctionEffectsRef;
class FunctionEffectSet;
-/*
- TODO: Idea about how to move most of the FunctionEffect business out of
- Type.h, thus removing these forward declarations.
-
- - Keep FunctionEffect itself here but make it more minimal. Don't define flags
- or any behaviors, just the Kind and an accessor.
- - Keep FunctionEffectCondition here.
- - Make FunctionProtoType and ExtProtoInfo use only ArrayRef<FunctionEffect>
- and ArrayRef<FunctionEffectCondition>.
- - Somewhere in Sema, define ExtFunctionEffect, which holds a FunctionEffect
- and has all the behavior-related methods.
- - There too, define the containers. FunctionEffectsRef can have a
- constructor or factory method that initializes itself from a
- FunctionProtoType.
-*/
-
/// Represents an abstract function effect, using just an enumeration describing
-/// its kind.
+/// its kind and a bool "denied", which indicates that the effect is asserted
+/// NOT to apply (preventing inference of the effect).
class FunctionEffect {
public:
/// Identifies the particular effect.
enum class Kind : uint8_t {
- None,
- NonBlocking,
- NonAllocating,
+ None = 0,
+ NonBlocking = 1,
+ NonAllocating = 2,
};
/// Flags describing some behaviors of the effect.
@@ -4483,32 +4469,38 @@ class FunctionEffect {
FE_ExcludeThreadLocalVars = 0x20
};
- /// Describes the result of effects differing between a base class's virtual
- /// method and an overriding method in a subclass.
- enum class OverrideResult {
- Ignore,
- Warn,
- Merge // Base method's effects are merged with those of the override.
- };
-
private:
- Kind FKind = Kind::None;
+ LLVM_PREFERRED_TYPE(Kind)
+ unsigned FKind : 2;
+
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned IsDenied : 1;
// Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
// then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
// be considered for uniqueness.
public:
- FunctionEffect() = default;
+ FunctionEffect() : FKind(unsigned(Kind::None)), IsDenied(false) {}
- explicit FunctionEffect(Kind K) : FKind(K) {}
+ FunctionEffect(Kind K, bool IsDenied)
+ : FKind(unsigned(K)), IsDenied(IsDenied) {}
/// The kind of the effect.
- Kind kind() const { return FKind; }
+ Kind kind() const { return Kind(FKind); }
+
+ /// Whether the effect is being denied (as opposed to asserted).
+ bool isDenied() const { return IsDenied; }
+
+ /// For serialization.
+ uint32_t toOpaqueInt32() const { return (FKind << 1) | IsDenied; }
+ static FunctionEffect fromOpaqueInt32(uint32_t Value) {
+ return FunctionEffect(Kind(Value >> 1), Value & 1);
+ }
/// Flags describing some behaviors of the effect.
Flags flags() const {
- switch (FKind) {
+ switch (kind()) {
case Kind::NonBlocking:
return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
@@ -4526,28 +4518,6 @@ class FunctionEffect {
/// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
- /// Return true if adding or removing the effect as part of a type conversion
- /// should generate a diagnostic.
- bool shouldDiagnoseConversion(bool Adding, QualType OldType,
- const FunctionEffectsRef &OldFX,
- QualType NewType,
- const FunctionEffectsRef &NewFX) const;
-
- /// Return true if adding or removing the effect in a redeclaration should
- /// generate a diagnostic.
- bool shouldDiagnoseRedeclaration(bool Adding, 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(bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionEffectsRef &OldFX,
- const CXXMethodDecl &NewMethod,
- const FunctionEffectsRef &NewFX) const;
-
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
/// This is only used if the effect has FE_InferrableOnCallees flag set.
@@ -4563,7 +4533,7 @@ class FunctionEffect {
ArrayRef<FunctionEffect> CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.FKind == RHS.FKind;
+ return LHS.FKind == RHS.FKind && LHS.IsDenied == RHS.IsDenied;
}
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return !(LHS == RHS);
@@ -4571,9 +4541,6 @@ class FunctionEffect {
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.FKind < RHS.FKind;
}
- friend bool operator>(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.FKind > RHS.FKind;
- }
};
/// Wrap a function effect's condition expression in another struct so
@@ -4600,6 +4567,55 @@ struct FunctionEffectWithCondition {
FunctionEffectCondition Cond;
};
+struct FunctionEffectDiff {
+ enum class Kind {
+ Added,
+ Removed,
+ AssertToDeny,
+ DenyToAssert,
+ 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,
+ MergeAdded // Merge an added effect
+ };
+
+ /// 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;
+};
+
/// Support iteration in parallel through a pair of FunctionEffect and
/// FunctionEffectCondition containers.
template <typename Container> class FunctionEffectIterator {
@@ -4624,7 +4640,8 @@ template <typename Container> class FunctionEffectIterator {
FunctionEffectWithCondition operator*() const {
const bool HasConds = !Outer.Conditions.empty();
return FunctionEffectWithCondition{Outer.Effects[Idx],
- HasConds ? Outer.Conditions[Idx] : FunctionEffectCondition()};
+ HasConds ? Outer.Conditions[Idx]
+ : FunctionEffectCondition()};
}
};
@@ -4632,6 +4649,8 @@ template <typename Container> class FunctionEffectIterator {
/// them. The effects and conditions reside in memory not managed by this object
/// (typically, trailing objects in FunctionProtoType, or borrowed references
/// from a FunctionEffectSet).
+///
+/// Invariant: there is never more than one instance of any given effect.
class FunctionEffectsRef {
ArrayRef<FunctionEffect> Effects;
@@ -4677,6 +4696,10 @@ class FunctionEffectsRef {
/// A mutable set of FunctionEffects and possibly conditions attached to them.
/// Used transitorily within Sema to compare and merge effects on declarations.
+///
+/// Invariant: there is never more than one instance of any given effect. This
+/// is asserted in insert and getUnion; it is the caller's responsibility to
+/// diagnose this.
class FunctionEffectSet {
SmallVector<FunctionEffect> Effects;
// The vector of conditions is either empty or has the same size
@@ -4702,6 +4725,7 @@ class FunctionEffectSet {
void dump(llvm::raw_ostream &OS) const;
// Mutators
+
void insert(FunctionEffect Effect, Expr *Cond);
void insert(const FunctionEffectsRef &Set);
void insertIgnoringConditions(const FunctionEffectsRef &Set);
@@ -4710,15 +4734,14 @@ class FunctionEffectSet {
void erase(unsigned Idx);
// Set operations
+ static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS);
+
+ using Differences = SmallVector<FunctionEffectDiff>;
- using Differences =
- SmallVector<std::pair<FunctionEffectWithCondition, /*added=*/bool>>;
/// Caller should short-circuit by checking for equality first.
static Differences differences(const FunctionEffectsRef &Old,
const FunctionEffectsRef &New);
-
- static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
- FunctionEffectsRef RHS);
};
/// Represents a prototype with parameter type info, e.g.
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index a56df9a89375f..45e90af7dc0e8 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1521,3 +1521,4 @@ def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept"
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
+
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3ce35abc088b0..5e527f397494d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10798,73 +10798,52 @@ def warn_imp_cast_drops_unaligned : Warning<
"implicit cast from type %0 to type %1 drops __unaligned qualifier">,
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
+// Function effects
def warn_func_effect_allocates : Warning<
"'%0' function must not allocate or deallocate memory">,
InGroup<FunctionEffects>;
-
def note_func_effect_allocates : Note<
"function cannot be inferred '%0' because it allocates/deallocates memory">;
-
def warn_func_effect_throws_or_catches : Warning<
"'%0' function must not throw or catch exceptions">,
InGroup<FunctionEffects>;
-
def note_func_effect_throws_or_catches : Note<
"function cannot be inferred '%0' because it throws or catches exceptions">;
-
def warn_func_effect_has_static_local : Warning<
"'%0' function must not have static locals">,
InGroup<FunctionEffects>;
-
def note_func_effect_has_static_local : Note<
"function cannot be inferred '%0' because it has a static local">;
-
def warn_func_effect_uses_thread_local : Warning<
"'%0' function must not use thread-local variables">,
InGroup<FunctionEffects>;
-
def note_func_effect_uses_thread_local : Note<
"function cannot be inferred '%0' because it uses a thread-local variable">;
-
def warn_func_effect_calls_objc : Warning<
"'%0' function must not access an ObjC method or property">,
InGroup<FunctionEffects>;
-
def note_func_effect_calls_objc : Note<
"function cannot be inferred '%0' because it accesses an ObjC method or property">;
-
def warn_func_effect_calls_func_without_effect : Warning<
"'%0' function must not call non-'%0' function '%1'">,
InGroup<FunctionEffects>;
-
def warn_func_effect_calls_expr_without_effect : Warning<
"'%0' function must not call non-'%0' expression">,
InGroup<FunctionEffects>;
-
def note_func_effect_calls_func_without_effect : Note<
"function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
-
def note_func_effect_call_extern : Note<
"function cannot be inferred '%0' because it has no definition in this translation unit">;
-
def note_func_effect_call_disallows_inference : Note<
"function does not permit inference of '%0'">;
-
def note_func_effect_call_virtual : Note<
"virtual method cannot be inferred '%0'">;
-
def note_func_effect_call_func_ptr : Note<
"function pointer cannot be inferred '%0'">;
-
def warn_perf_constraint_implies_noexcept : Warning<
"'%0' function should be declared noexcept">,
InGroup<PerfConstraintImpliesNoexcept>;
-// TODO: Not currently being generated
-def warn_func_effect_false_on_type : Warning<
- "only functions/methods/blocks may be declared %0(false)">,
- InGroup<FunctionEffects>;
-
// TODO: can the usual template expansion notes be used?
def note_func_effect_from_template : Note<
"in template expansion here">;
@@ -10873,15 +10852,9 @@ def note_func_effect_from_template : Note<
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
InGroup<FunctionEffects>;
-
-def warn_invalid_remove_func_effects : Warning<
- "attribute '%0' should not be removed via type conversion">,
- InGroup<FunctionEffects>;
-
def warn_mismatched_func_effect_override : Warning<
- "attribute '%0' on overriding function does not match base version">,
+ "attribute '%0' on overriding function does not match base declaration">,
InGroup<FunctionEffects>;
-
def warn_mismatched_func_effect_redeclaration : Warning<
"attribute '%0' on function does not match previous declaration">,
InGroup<FunctionEffects>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5b417870a9ebb..abce9e5edff24 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -421,7 +421,7 @@ enum class TemplateDeductionResult {
};
/// Used with attributes/effects with a boolean condition, e.g. `nonblocking`.
-enum class FunctionEffectMode {
+enum class FunctionEffectMode : uint8_t {
None, // effect is not present
False, // effect(false)
True, // effect(true)
@@ -964,11 +964,14 @@ class Sema final {
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
- /// Try to parse the conditional expression attached to an effect attribute
+ /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
+ void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+ /// Try to parse the conditional expression attached to an effect attribute
/// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). If RequireConstexpr,
/// then this will fail if the expression is dependent.
ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
- bool RequireConstexpr = false);
+ bool RequireConstexpr = false);
// ----- function effects --- where ?????
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 524956ea18678..131f82985e903 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4505,17 +4505,6 @@ unsigned FunctionDecl::getODRHash() {
return ODRHash;
}
-// Effects may differ between declarations, but they should be propagated from
-// old to new on any redeclaration, so it suffices to look at
-// getMostRecentDecl().
-FunctionEffectsRef FunctionDecl::getFunctionEffects() const {
- if (const auto *FPT =
- getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
- return FPT->getFunctionEffects();
- }
- return {};
-}
-
//===----------------------------------------------------------------------===//
// FieldDecl Implementation
//===----------------------------------------------------------------------===//
@@ -5240,15 +5229,6 @@ SourceRange BlockDecl::getSourceRange() const {
return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
}
-FunctionEffectsRef BlockDecl::getFunctionEffects() const {
- if (auto *TSI = getSignatureAsWritten()) {
- if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
- return FPT->getFunctionEffects();
- }
- }
- return {};
-}
-
//===----------------------------------------------------------------------===//
// Other Decl Allocation/Deallocation Method Implementations
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index bdf6d23eec269..3702163336baf 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3654,7 +3654,8 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
- assert(isCanonicalUnqualified()); // TODO: because I don't understand this yet...
+ assert(isCanonicalUnqualified()); // TODO: because I don't understand this
+ // yet...
addDependence(TypeDependence::DependentInstantiation);
}
}
@@ -5053,57 +5054,93 @@ StringRef FunctionEffect::name() const {
llvm_unreachable("unknown effect kind");
}
-bool FunctionEffect::shouldDiagnoseConversion(
- bool Adding, QualType OldType, const FunctionEffectsRef &OldFX,
- QualType NewType, const FunctionEffectsRef &NewFX) const {
+bool FunctionEffectDiff::shouldDiagnoseConversion(
+ QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
+ const FunctionEffectsRef &DstFX) const {
- switch (kind()) {
- case Kind::NonAllocating:
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
// nonallocating can't be added (spoofed) during a conversion, unless we
// have nonblocking
- if (Adding) {
- for (const auto &CFE : OldFX) {
- if (CFE.Effect.kind() == Kind::NonBlocking)
+ if (DiffKind == Kind::Added) {
+ for (const auto &CFE : SrcFX) {
+ if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
return false;
}
}
[[fallthrough]];
- case Kind::NonBlocking:
- // nonblocking can't be added (spoofed) during a conversion
- return Adding;
- case Kind::None:
+ 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::AssertToDeny:
+ // effect asserted -> denied: no diagnostic.
+ return false;
+ case Kind::DenyToAssert:
+ // effect denied -> asserted: diagnose.
+ return true;
+ case Kind::ConditionMismatch:
+ return true; // TODO: ???
+ }
+ case FunctionEffect::Kind::None:
break;
}
llvm_unreachable("unknown effect kind");
}
-bool FunctionEffect::shouldDiagnoseRedeclaration(
- bool Adding, const FunctionDecl &OldFunction,
- const FunctionEffectsRef &OldFX, const FunctionDecl &NewFunction,
- const FunctionEffectsRef &NewFX) const {
- switch (kind()) {
- case Kind::NonAllocating:
- case Kind::NonBlocking:
+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
- // adding -> false, removing -> true (diagnose)
- return !Adding;
- case Kind::None:
+ switch (DiffKind) {
+ case Kind::Added:
+ return false; // No diagnostic.
+ case Kind::Removed:
+ return true; // Issue diagnostic
+ case Kind::DenyToAssert:
+ case Kind::AssertToDeny:
+ case Kind::ConditionMismatch:
+ // All these forms of mismatches are diagnosed.
+ return true;
+ }
+ case FunctionEffect::Kind::None:
break;
}
llvm_unreachable("unknown effect kind");
}
-FunctionEffect::OverrideResult FunctionEffect::shouldDiagnoseMethodOverride(
- bool Adding, const CXXMethodDecl &OldMethod,
- const FunctionEffectsRef &OldFX, const CXXMethodDecl &NewMethod,
- const FunctionEffectsRef &NewFX) const {
- switch (kind()) {
- case Kind::NonAllocating:
- case Kind::NonBlocking:
- // if added on an override, that's fine and not diagnosed.
- // if missing from an override (removed), propagate from base to derived.
- return Adding ? OverrideResult::Ignore : OverrideResult::Merge;
- case Kind::None:
+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::MergeAdded;
+
+ // If there's a mismatch involving the effect's polarity or condition,
+ // issue a warning.
+ case Kind::DenyToAssert:
+ case Kind::AssertToDeny:
+ case Kind::ConditionMismatch:
+ return OverrideResult::Warn;
+ }
+
+ case FunctionEffect::Kind::None:
break;
}
llvm_unreachable("unknown effect kind");
@@ -5172,7 +5209,7 @@ void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddInteger(size() | (HasConds << 31u));
for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
- ID.AddInteger(llvm::to_underlying(Effects[Idx].kind()));
+ ID.AddInteger(Effects[Idx].toOpaqueInt32());
if (HasConds)
ID.AddPointer(Conditions[Idx].expr());
}
@@ -5183,12 +5220,13 @@ void FunctionEffectSet::insert(FunctionEffect Effect, Expr *Cond) {
unsigned Idx = 0;
for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
const auto &IterEffect = Effects[Idx];
- if (IterEffect == Effect) {
- // TODO: Is it okay to assume the caller has already diagnosed
- // any potential conflict with conditions here?
+ if (IterEffect.kind() == Effect.kind()) {
+ // It's possible here to have incompatible combinations of polarity
+ // (asserted/denied) and condition; for now, we keep whichever came
+ // though this should be improved.
return;
}
- if (Effect < IterEffect)
+ if (Effect.kind() < IterEffect.kind())
break;
}
@@ -5219,8 +5257,10 @@ void FunctionEffectSet::replaceCondition(unsigned Idx, Expr *Cond) {
void FunctionEffectSet::erase(unsigned Idx) {
assert(Idx < Effects.size());
Effects.erase(Effects.begin() + Idx);
- if (!Conditions.empty())
+ if (!Conditions.empty()) {
+ assert(Idx < Conditions.size());
Conditions.erase(Conditions.begin() + Idx);
+ }
}
FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
@@ -5245,19 +5285,6 @@ FunctionEffectSet::differences(const FunctionEffectsRef &Old,
FunctionEffectsRef::iterator PNew = New.begin();
FunctionEffectsRef::iterator NewEnd = New.end();
- auto compare = [](const FunctionEffectWithCondition &LHS,
- const FunctionEffectWithCondition &RHS) {
- if (LHS.Effect < RHS.Effect)
- return -1;
- if (LHS.Effect > RHS.Effect)
- return 1;
- if (LHS.Cond.expr() < RHS.Cond.expr())
- return -1;
- if (RHS.Cond.expr() < LHS.Cond.expr())
- return 1;
- return 0;
- };
-
while (true) {
int cmp = 0;
if (POld == OldEnd) {
@@ -5266,52 +5293,53 @@ FunctionEffectSet::differences(const FunctionEffectsRef &Old,
cmp = 1;
} else if (PNew == NewEnd)
cmp = -1;
- else
- cmp = compare(*POld, *PNew);
+ 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.expr() != New.Cond.expr()) {
+ // TODO: Cases where the expressions are equivalent but
+ // don't have the same identity.
+ Result.push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
+ Old, New});
+ } else if (!Old.Effect.isDenied() && New.Effect.isDenied()) {
+ Result.push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::AssertToDeny, Old,
+ New});
+ } else if (Old.Effect.isDenied() && !New.Effect.isDenied()) {
+ Result.push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::DenyToAssert, Old,
+ New});
+ }
+ }
+ }
if (cmp < 0) {
// removal
- Result.push_back({*POld, false});
+ FunctionEffectWithCondition Old = *POld;
+ Result.push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
++POld;
} else if (cmp > 0) {
// addition
- Result.push_back({*PNew, true});
+ FunctionEffectWithCondition New = *PNew;
+ Result.push_back(FunctionEffectDiff{
+ New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
++PNew;
} else {
++POld;
++PNew;
}
}
-
return Result;
}
-#if 0
-FunctionEffectSet
-FunctionEffectSet::difference(FunctionEffectsRef LHS,
- FunctionEffectsRef RHS) {
- FunctionEffectSet Result;
- std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result.Impl));
- return Result;
-}
-
-void FunctionEffectSet::insert(FunctionEffectsRef Arr) {
- // TODO: For large RHS sets, use set_union or a custom insert-in-place
- for (const auto &CFE : Arr) {
- insert(CFE);
- }
-}
-
-void FunctionEffectSet::insertIgnoringConditions(
- FunctionEffectsRef Arr) {
- // TODO: For large RHS sets, use set_union or a custom insert-in-place
- for (const auto &CFE : Arr) {
- insert(FunctionEffectWithCondition(CFE.effect().kind(), nullptr));
- }
-}
-#endif
-
LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
@@ -5321,7 +5349,7 @@ LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
else
First = false;
OS << CFE.Effect.name();
- if (Expr * E = CFE.Cond.expr()) {
+ if (Expr *E = CFE.Cond.expr()) {
OS << '(';
E->dump();
OS << ')';
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index f82b3eee055b6..7aa4acfd3bbd4 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2385,7 +2385,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
// =============================================================================
// Temporary feature enablement
-#define FX_ANALYZER_ENABLED 0
+#define FX_ANALYZER_ENABLED 1
#if FX_ANALYZER_ENABLED
@@ -2453,37 +2453,14 @@ static bool functionIsVerifiable(const FunctionDecl *FD) {
return true;
}
-#if 0
/// A mutable set of FunctionEffect, for use in places where any conditions
/// have been resolved or can be ignored.
class EffectSet {
- SmallVector<FunctionEffect, 4> Impl;
-public:
- EffectSet() = default;
-
- operator ArrayRef<FunctionEffect>() const { return Impl; }
-
- using iterator = const FunctionEffect *;
- iterator begin() const { return Impl.begin(); }
- iterator end() const { return Impl.end(); }
-
- void insert(const FunctionEffect &Effect);
- void insert(const EffectSet &Set);
- void insertIgnoringConditions(ArrayRef<FunctionEffectWithCondition> Arr);
-
- void dump(llvm::raw_ostream &OS) const;
-
- static EffectSet difference(ArrayRef<FunctionEffect> LHS, ArrayRef<FunctionEffect> RHS);
-};
-#endif
-
-/// A mutable set of FunctionEffect, for use in places where any conditions
-/// have been resolved or can be ignored.
-// (This implementation optimizes footprint. As long as FunctionEffect is only 1
-// byte, and there are only 2 possible effects, this is more than sufficient. In
-// AnalysisBasedWarnings, we hold one of these for every function visited,
-// which, due to inference, can be many more functions than have effects.)
-class EffectSet {
+ // This implementation optimizes footprint. As long as FunctionEffect is only
+ // 1 byte, and there are only 2 possible effects, this is more than
+ // sufficient. In AnalysisBasedWarnings, we hold one of these for every
+ // function visited, which, due to inference, can be many more functions than
+ // have declared effects.
template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
SizeT Count = 0;
T Items[Capacity] = {};
@@ -2592,9 +2569,10 @@ struct CondEffectSet {
CondEffectSet(Sema &SemaRef, FunctionEffectsRef FX) {
for (const auto Item : FX) {
- if (Expr *Cond = Item.Cond) {
+ if (Expr *Cond = Item.Cond.expr()) {
FunctionEffectMode Mode = FunctionEffectMode::None;
- ExprResult ER = SemaRef.ActOnEffectExpression(Cond, Mode, /*RequireConstexpr=*/true);
+ ExprResult ER = SemaRef.ActOnEffectExpression(
+ Cond, Mode, /*RequireConstexpr=*/true);
if (ER.isInvalid() || Mode == FunctionEffectMode::Dependent)
continue;
if (Mode == FunctionEffectMode::False) {
@@ -2618,7 +2596,8 @@ struct CallableInfo {
CondEffectSet Effects;
CallType CType = CallType::Unknown;
- CallableInfo(Sema &SemaRef, const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+ CallableInfo(Sema &SemaRef, const Decl &CD,
+ SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
// llvm::errs() << "CallableInfo " << name() << "\n";
FunctionEffectsRef FXRef;
@@ -2783,16 +2762,16 @@ class PendingFunctionAnalysis {
EffectSet FX;
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl) && !CInfo.Effects.EffectsExplicitlyAbsent.contains(effect)) {
+ if (effect.canInferOnFunction(*CInfo.CDecl) &&
+ !CInfo.Effects.EffectsExplicitlyAbsent.contains(effect)) {
FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
// try to infer it.
auto &diag =
InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
- diag =
- Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
- CInfo.CDecl->getLocation());
+ diag = Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
+ CInfo.CDecl->getLocation());
}
}
// FX is now the set of inferrable effects which are not prohibited
@@ -2915,7 +2894,7 @@ const Decl *CanonicalFunctionDecl(const Decl *D) {
// ==========
class Analyzer {
- constexpr static int DebugLogLevel = 3;
+ constexpr static int DebugLogLevel = 0;
// --
Sema &Sem;
@@ -3206,8 +3185,8 @@ class Analyzer {
!(Flags & FunctionEffect::FE_InferrableOnCallees)) {
if (Callee.FuncType == SpecialFuncType::None) {
PFA.checkAddDiagnostic(
- Inferring,
- {Effect, DiagnosticID::CallsDeclWithoutEffect, CallLoc, Callee.CDecl});
+ Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
+ CallLoc, Callee.CDecl});
} else {
PFA.checkAddDiagnostic(
Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
@@ -3254,7 +3233,7 @@ class Analyzer {
switch (Diag.ID) {
case DiagnosticID::None:
case DiagnosticID::DeclDisallowsInference: // shouldn't happen
- // here
+ // here
llvm_unreachable("Unexpected diagnostic kind");
break;
case DiagnosticID::AllocatesMemory:
@@ -3311,9 +3290,11 @@ class Analyzer {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
- } else if (CalleeInfo.Effects.EffectsExplicitlyAbsent.contains(Diag.Effect)) {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_disallows_inference)
- << effectName;
+ } else if (CalleeInfo.Effects.EffectsExplicitlyAbsent.contains(
+ Diag.Effect)) {
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_disallows_inference)
+ << effectName;
} else {
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
<< effectName;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 4ac0208512f18..67042492341a2 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -593,15 +593,9 @@ 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 &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
- const FunctionEffect &Effect = Item.first.Effect;
- const bool Adding = Item.second;
- if (Effect.shouldDiagnoseConversion(Adding, SrcType, SrcFX, DstType,
- DstFX)) {
- Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
- : diag::warn_invalid_remove_func_effects)
- << Effect.name();
- }
+ for (const auto &Diff : FunctionEffectSet::differences(SrcFX, DstFX)) {
+ if (Diff.shouldDiagnoseConversion(SrcType, SrcFX, DstType, DstFX))
+ Diag(Loc, diag::warn_invalid_add_func_effects) << Diff.effectName();
}
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 60c7fe7472950..8824bdc307632 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3925,14 +3925,11 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
QualType OldQTypeForComparison = OldQType;
if (OldFX != NewFX) {
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
- for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first.Effect;
- const bool Adding = Item.second;
- if (Effect.shouldDiagnoseRedeclaration(Adding, *Old, OldFX, *New,
- NewFX)) {
+ for (const auto &Diff : Diffs) {
+ if (Diff.shouldDiagnoseRedeclaration(*Old, OldFX, *New, NewFX)) {
Diag(New->getLocation(),
diag::warn_mismatched_func_effect_redeclaration)
- << Effect.name();
+ << Diff.effectName();
Diag(Old->getLocation(), diag::note_previous_declaration);
}
}
@@ -11153,6 +11150,10 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
return;
}
+ addDeclWithEffects(D, FX);
+}
+
+void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
// Ignore any conditions when building the list of effects.
AllEffectsToVerify.insertIgnoringConditions(FX);
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1e89f97bc5f20..f593251b0fd53 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18295,7 +18295,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
if (OldFT->hasExtParameterInfos()) {
for (unsigned I = 0, E = OldFT->getNumParams(); I != E; ++I)
- // A parameter of the overriding method should be annotated with noescape
+ // A parameter of the overriding method should be annotated with noescape
// if the corresponding parameter of the overridden method is annotated.
if (OldFT->getExtParameterInfo(I).isNoEscape() &&
!NewFT->getExtParameterInfo(I).isNoEscape()) {
@@ -18326,39 +18326,33 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
// Virtual overrides: check for matching effects.
const auto OldFX = Old->getFunctionEffects();
- const auto NewFX = New->getFunctionEffects();
+ const auto NewFXOrig = New->getFunctionEffects();
- if (OldFX != NewFX) {
+ if (OldFX != NewFXOrig) {
+ FunctionEffectSet NewFX(NewFXOrig);
const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
- bool AnyDiags = false;
-
- for (const auto &Item : Diffs) {
- const FunctionEffect &Effect = Item.first.Effect;
- const bool Adding = Item.second;
- switch (Effect.shouldDiagnoseMethodOverride(Adding, *Old, OldFX, *New,
- NewFX)) {
- case FunctionEffect::OverrideResult::Ignore:
+ for (const auto &Diff : Diffs) {
+ switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) {
+ case FunctionEffectDiff::OverrideResult::NoAction:
break;
- case FunctionEffect::OverrideResult::Warn:
+ case FunctionEffectDiff::OverrideResult::Warn:
Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
- << Effect.name();
- Diag(Old->getLocation(), diag::note_overridden_virtual_function);
- // TODO: It would be nice to have a FIXIT here!
- AnyDiags = true;
+ << Diff.effectName();
+ Diag(Old->getLocation(), diag::note_overridden_virtual_function)
+ << Old->getReturnTypeSourceRange();
break;
- case FunctionEffect::OverrideResult::Merge: {
- auto MergedFX = FunctionEffectSet::getUnion(OldFX, NewFX);
-
+ case FunctionEffectDiff::OverrideResult::MergeAdded: {
+ NewFX.insert(Diff.New.Effect, Diff.New.Cond.expr());
+ const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
- EPI.FunctionEffects = FunctionEffectsRef(MergedFX);
+ EPI.FunctionEffects = FunctionEffectsRef(NewFX);
QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
NewFT->getParamTypes(), EPI);
New->setType(ModQT);
- } break;
+ break;
+ }
}
}
- if (AnyDiags)
- return true;
}
CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index b7cbb2524d748..b27d85a858d89 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -218,8 +218,8 @@ namespace {
// nonallocating(cond). Manual logic for finding previous attributes would
// be more complex, unless we transformed nonblocking/nonallocating(false)
// into distinct separate attributes from the ones which are parsed.
- FunctionEffectMode parsedNonBlocking : 2;
- FunctionEffectMode parsedNonAllocating : 2;
+ FunctionEffectMode parsedNonBlocking;
+ FunctionEffectMode parsedNonAllocating;
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
@@ -354,9 +354,15 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
void setParsedNonBlocking(FunctionEffectMode v) { parsedNonBlocking = v; }
- FunctionEffectMode getParsedNonBlocking() const { return parsedNonBlocking; }
- void setParsedNonAllocating(FunctionEffectMode v) { parsedNonAllocating = v; }
- FunctionEffectMode getParsedNonAllocating() const { return parsedNonAllocating; }
+ FunctionEffectMode getParsedNonBlocking() const {
+ return parsedNonBlocking;
+ }
+ void setParsedNonAllocating(FunctionEffectMode v) {
+ parsedNonAllocating = v;
+ }
+ FunctionEffectMode getParsedNonAllocating() const {
+ return parsedNonAllocating;
+ }
~TypeProcessingState() {
if (savedAttrs.empty())
@@ -7977,8 +7983,8 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
-ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode, bool RequireConstexpr)
-{
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
+ bool RequireConstexpr) {
// see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
if (RequireConstexpr || !CondExpr->isTypeDependent()) {
ExprResult E = PerformContextuallyConvertToBool(CondExpr);
@@ -8071,8 +8077,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
};
const FunctionEffectMode PrevState = isNonBlocking
- ? TPState.getParsedNonBlocking()
- : TPState.getParsedNonAllocating();
+ ? TPState.getParsedNonBlocking()
+ : TPState.getParsedNonAllocating();
if (PrevState != FunctionEffectMode::None) {
// Only one attribute per constraint is allowed.
return incompatible(isNonBlocking, PrevState);
@@ -8112,11 +8118,13 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// FunctionEffectsRef attached to a FunctionProtoType.
const FunctionEffect NewEffect(isNonBlocking
? FunctionEffect::Kind::NonBlocking
- : FunctionEffect::Kind::NonAllocating);
+ : FunctionEffect::Kind::NonAllocating,
+ /*IsDenied=*/false);
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEffect, NewState == FunctionEffectMode::Dependent ? CondExpr : nullptr);
+ FX.insert(NewEffect,
+ NewState == FunctionEffectMode::Dependent ? CondExpr : nullptr);
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index c8816d6651e6d..13fe13d0fafae 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6239,7 +6239,7 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
// Transform any function effects with unevaluated conditions.
// Hold this set in a local for the rest of this function, since EPI
// is going to hold a FunctionEffectsRef pointing into it.
- std::optional <FunctionEffectSet> NewFX;
+ std::optional<FunctionEffectSet> NewFX;
#if XFORM_FALSE_FX_TO_SUGAR
SmallVector<Attr *> TypeAttrsToAdd;
#endif
@@ -6248,7 +6248,7 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
EnterExpressionEvaluationContext Unevaluated(
getSema(), Sema::ExpressionEvaluationContext::ConstantEvaluated);
- for (unsigned Idx = 0, Count = FXConds.size(); Idx != Count; ) {
+ for (unsigned Idx = 0, Count = FXConds.size(); Idx != Count;) {
if (Expr *CondExpr = FXConds[Idx].expr()) {
ExprResult NewExpr = getDerived().TransformExpr(CondExpr);
if (NewExpr.isInvalid())
@@ -6277,7 +6277,8 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
// If it is now a constant of 'true', we can discard it because a null
// condition in FunctionEffectSet means true. If the expression is still
// dependent or constant 'false', just propagate it into the new EPI.
- FunctionEffectCondition Cond(Mode == FunctionEffectMode::True ? nullptr : NewExpr.get());
+ FunctionEffectCondition Cond(
+ Mode == FunctionEffectMode::True ? nullptr : NewExpr.get());
NewFX->replaceCondition(Idx, Cond.expr());
#endif
}
@@ -6309,7 +6310,8 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
// be transformed to FunctionProtoType.
if (!TypeAttrsToAdd.empty()) {
for (Attr *A : TypeAttrsToAdd) {
- QualType QT = SemaRef.Context.getAttributedType(A->getKind(), Result, Result);
+ QualType QT =
+ SemaRef.Context.getAttributedType(A->getKind(), Result, Result);
llvm::outs() << "transformed " << Result << " -> " << QT;
Result = QT;
AttributedTypeLoc ATL = TLB.push<AttributedTypeLoc>(Result);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index dede16d84dc1b..94b7338bd7aa1 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8233,7 +8233,7 @@ void ASTReader::InitializeSema(Sema &S) {
FX = BD->getFunctionEffects();
}
if (!FX.empty()) {
- SemaObj->AllEffectsToVerify.insertIgnoringConditions(FX);
+ SemaObj->addDeclWithEffects(D, FX);
}
}
DeclsWithEffectsToVerify.clear();
>From ca9b8faebae8c60e60179a662c1f0160eeb4c4cb Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 24 Apr 2024 10:45:42 -0700
Subject: [PATCH 38/71] - Adapt to merge from main. - represent
blocking/allocating separately rather than "denied" variants - SemaType: use
canonical representation for "denied" - TreeTransform: adapt to canonical
representation of "denied"
---
clang/include/clang/AST/Type.h | 47 ++++-----
clang/include/clang/Basic/AttrDocs.td | 3 +-
clang/include/clang/Sema/Sema.h | 6 +-
clang/include/clang/Serialization/ASTReader.h | 2 +-
clang/lib/AST/Type.cpp | 95 ++++++++++++++-----
clang/lib/Sema/SemaDecl.cpp | 7 +-
clang/lib/Sema/SemaDeclCXX.cpp | 4 +-
clang/lib/Sema/SemaType.cpp | 13 ++-
clang/lib/Sema/TreeTransform.h | 61 ++++--------
clang/lib/Serialization/ASTReader.cpp | 15 ++-
10 files changed, 138 insertions(+), 115 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 3003d504bdb31..c08e08913750f 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4443,8 +4443,7 @@ class FunctionEffectsRef;
class FunctionEffectSet;
/// Represents an abstract function effect, using just an enumeration describing
-/// its kind and a bool "denied", which indicates that the effect is asserted
-/// NOT to apply (preventing inference of the effect).
+/// its kind.
class FunctionEffect {
public:
/// Identifies the particular effect.
@@ -4452,13 +4451,16 @@ class FunctionEffect {
None = 0,
NonBlocking = 1,
NonAllocating = 2,
+ Blocking = 3,
+ Allocating = 4
};
/// Flags describing some behaviors of the effect.
using Flags = unsigned;
enum FlagBit : Flags {
// Can verification inspect callees' implementations? (e.g. nonblocking:
- // yes, tcb+types: no)
+ // yes, tcb+types: no). This also implies the need for 2nd-pass
+ // verification.
FE_InferrableOnCallees = 0x1,
// Language constructs which effects can diagnose as disallowed.
@@ -4471,31 +4473,27 @@ class FunctionEffect {
private:
LLVM_PREFERRED_TYPE(Kind)
- unsigned FKind : 2;
-
- LLVM_PREFERRED_TYPE(bool)
- unsigned IsDenied : 1;
+ unsigned FKind : 3;
// Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
// then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
// be considered for uniqueness.
public:
- FunctionEffect() : FKind(unsigned(Kind::None)), IsDenied(false) {}
+ FunctionEffect() : FKind(unsigned(Kind::None)) {}
- FunctionEffect(Kind K, bool IsDenied)
- : FKind(unsigned(K)), IsDenied(IsDenied) {}
+ FunctionEffect(Kind K) : FKind(unsigned(K)) {}
/// The kind of the effect.
Kind kind() const { return Kind(FKind); }
- /// Whether the effect is being denied (as opposed to asserted).
- bool isDenied() const { return IsDenied; }
+ /// Return the opposite kind, for effects which have opposites.
+ Kind oppositeKind() const;
/// For serialization.
- uint32_t toOpaqueInt32() const { return (FKind << 1) | IsDenied; }
+ uint32_t toOpaqueInt32() const { return FKind; }
static FunctionEffect fromOpaqueInt32(uint32_t Value) {
- return FunctionEffect(Kind(Value >> 1), Value & 1);
+ return FunctionEffect(Kind(Value));
}
/// Flags describing some behaviors of the effect.
@@ -4509,6 +4507,9 @@ class FunctionEffect {
// Same as NonBlocking, except without FE_ExcludeStaticLocalVars
return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
+ case Kind::Blocking:
+ case Kind::Allocating:
+ return 0;
case Kind::None:
break;
}
@@ -4533,7 +4534,7 @@ class FunctionEffect {
ArrayRef<FunctionEffect> CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
- return LHS.FKind == RHS.FKind && LHS.IsDenied == RHS.IsDenied;
+ return LHS.FKind == RHS.FKind;
}
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return !(LHS == RHS);
@@ -4568,13 +4569,7 @@ struct FunctionEffectWithCondition {
};
struct FunctionEffectDiff {
- enum class Kind {
- Added,
- Removed,
- AssertToDeny,
- DenyToAssert,
- ConditionMismatch
- };
+ enum class Kind { Added, Removed, ConditionMismatch };
FunctionEffect::Kind EffectKind;
Kind DiffKind;
@@ -4592,7 +4587,7 @@ struct FunctionEffectDiff {
enum class OverrideResult {
NoAction,
Warn,
- MergeAdded // Merge an added effect
+ Merge // Merge missing effect from base to derived
};
/// Return true if adding or removing the effect as part of a type conversion
@@ -4697,9 +4692,7 @@ class FunctionEffectsRef {
/// A mutable set of FunctionEffects and possibly conditions attached to them.
/// Used transitorily within Sema to compare and merge effects on declarations.
///
-/// Invariant: there is never more than one instance of any given effect. This
-/// is asserted in insert and getUnion; it is the caller's responsibility to
-/// diagnose this.
+/// Invariant: there is never more than one instance of any given effect.
class FunctionEffectSet {
SmallVector<FunctionEffect> Effects;
// The vector of conditions is either empty or has the same size
@@ -4730,7 +4723,7 @@ class FunctionEffectSet {
void insert(const FunctionEffectsRef &Set);
void insertIgnoringConditions(const FunctionEffectsRef &Set);
- void replaceCondition(unsigned Idx, Expr *Cond);
+ void replaceItem(unsigned Idx, const FunctionEffectWithCondition &Item);
void erase(unsigned Idx);
// Set operations
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 9a3dc0ea67b9c..0b4ed5a1dc626 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1,5 +1,4 @@
-
- //==--- AttrDocs.td - Attribute documentation ----------------------------===//
+//==--- AttrDocs.td - Attribute documentation ----------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 083129b66f355..b5ce248be57c5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -776,9 +776,7 @@ class Sema final : public SemaBase {
/// Warn when implicitly casting 0 to nullptr.
void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
- // ----- function effects --- where ?????
- // Ultimately some more of the effects implementation could/should be moved
- // out of Type.h, but where to?
+ // ----- function effects ---
/// All functions/lambdas/blocks which have bodies and which have a non-empty
/// FunctionEffectsRef to be verified.
@@ -803,8 +801,6 @@ class Sema final : public SemaBase {
ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
bool RequireConstexpr = false);
- // ----- function effects --- where ?????
-
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 683f64532586d..4ac7dd76febd6 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -978,7 +978,7 @@ class ASTReader
DeclsToCheckForDeferredDiags;
/// The IDs of all decls with function effects to be checked.
- SmallVector<serialization::DeclID> DeclsWithEffectsToVerify;
+ SmallVector<serialization::GlobalDeclID, 0> DeclsWithEffectsToVerify;
private:
struct ImportedSubmodule {
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 3702163336baf..750da4f6bdc7a 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5042,12 +5042,32 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
getTypeConstraintConcept(), getTypeConstraintArguments());
}
+FunctionEffect::Kind FunctionEffect::oppositeKind() const {
+ switch (kind()) {
+ case Kind::NonBlocking:
+ return Kind::Blocking;
+ case Kind::Blocking:
+ return Kind::NonBlocking;
+ case Kind::NonAllocating:
+ return Kind::Allocating;
+ case Kind::Allocating:
+ return Kind::NonAllocating;
+ case Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
StringRef FunctionEffect::name() const {
switch (kind()) {
case Kind::NonBlocking:
return "nonblocking";
case Kind::NonAllocating:
return "nonallocating";
+ case Kind::Blocking:
+ return "blocking";
+ case Kind::Allocating:
+ return "allocating";
case Kind::None:
break;
}
@@ -5076,15 +5096,12 @@ bool FunctionEffectDiff::shouldDiagnoseConversion(
return true;
case Kind::Removed:
return false;
- case Kind::AssertToDeny:
- // effect asserted -> denied: no diagnostic.
- return false;
- case Kind::DenyToAssert:
- // effect denied -> asserted: diagnose.
- return true;
case Kind::ConditionMismatch:
return true; // TODO: ???
}
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
case FunctionEffect::Kind::None:
break;
}
@@ -5103,12 +5120,13 @@ bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
return false; // No diagnostic.
case Kind::Removed:
return true; // Issue diagnostic
- case Kind::DenyToAssert:
- case Kind::AssertToDeny:
case Kind::ConditionMismatch:
// All these forms of mismatches are diagnosed.
return true;
}
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
case FunctionEffect::Kind::None:
break;
}
@@ -5130,16 +5148,18 @@ FunctionEffectDiff::shouldDiagnoseMethodOverride(
// If missing from an override (removed), propagate from base to derived.
case Kind::Removed:
- return OverrideResult::MergeAdded;
+ return OverrideResult::Merge;
// If there's a mismatch involving the effect's polarity or condition,
// issue a warning.
- case Kind::DenyToAssert:
- case Kind::AssertToDeny:
case Kind::ConditionMismatch:
return OverrideResult::Warn;
}
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return OverrideResult::NoAction;
+
case FunctionEffect::Kind::None:
break;
}
@@ -5149,7 +5169,28 @@ FunctionEffectDiff::shouldDiagnoseMethodOverride(
bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
switch (kind()) {
case Kind::NonAllocating:
- case Kind::NonBlocking:
+ case Kind::NonBlocking: {
+ FunctionEffectsRef CalleeFX;
+ if (auto *FD = Callee.getAsFunction())
+ CalleeFX = FD->getFunctionEffects();
+ else if (auto *BD = dyn_cast<BlockDecl>(&Callee))
+ CalleeFX = BD->getFunctionEffects();
+ else
+ return false;
+ for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
+ // nonblocking/nonallocating cannot call allocating
+ if (CalleeEC.Effect.kind() == Kind::Allocating)
+ return false;
+ // nonblocking cannot call blocking
+ if (kind() == Kind::NonBlocking &&
+ CalleeEC.Effect.kind() == Kind::Blocking)
+ return false;
+ }
+ }
+ return true;
+
+#if 0
+ // This is the type-sugar implementation of "denied" effects.
// Do any of the callee's Decls have type sugar for blocking or allocating?
for (const Decl *D : Callee.redecls()) {
QualType QT;
@@ -5174,6 +5215,11 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
}
}
return true;
+#endif
+ case Kind::Allocating:
+ case Kind::Blocking:
+ return false;
+
case Kind::None:
break;
}
@@ -5196,6 +5242,9 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
}
return true; // warning
}
+ case Kind::Allocating:
+ case Kind::Blocking:
+ return false;
case Kind::None:
break;
}
@@ -5249,9 +5298,19 @@ void FunctionEffectSet::insertIgnoringConditions(
insert(Item.Effect, nullptr);
}
-void FunctionEffectSet::replaceCondition(unsigned Idx, Expr *Cond) {
+void FunctionEffectSet::replaceItem(unsigned Idx,
+ const FunctionEffectWithCondition &Item) {
assert(Idx < Conditions.size());
- Conditions[Idx] = FunctionEffectCondition(Cond);
+ Effects[Idx] = Item.Effect;
+ Conditions[Idx] = Item.Cond;
+
+ // Maintain invariant: If all conditions are null, the vector should be empty.
+ if (std::all_of(Conditions.begin(), Conditions.end(),
+ [](const FunctionEffectCondition &C) {
+ return C.expr() == nullptr;
+ })) {
+ Conditions.clear();
+ }
}
void FunctionEffectSet::erase(unsigned Idx) {
@@ -5308,14 +5367,6 @@ FunctionEffectSet::differences(const FunctionEffectsRef &Old,
Result.push_back(FunctionEffectDiff{
Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
Old, New});
- } else if (!Old.Effect.isDenied() && New.Effect.isDenied()) {
- Result.push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::AssertToDeny, Old,
- New});
- } else if (Old.Effect.isDenied() && !New.Effect.isDenied()) {
- Result.push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::DenyToAssert, Old,
- New});
}
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index e924546c30d38..cb93d5a9c0b9f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11174,7 +11174,12 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
return;
}
- addDeclWithEffects(D, FX);
+ if (std::any_of(FX.begin(), FX.end(),
+ [](const FunctionEffectWithCondition &EC) {
+ return (EC.Effect.flags() &
+ FunctionEffect::FE_InferrableOnCallees) != 0;
+ }))
+ addDeclWithEffects(D, FX);
}
void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 6b457521b8ec2..cb3ec0abb8bad 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18392,8 +18392,8 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
Diag(Old->getLocation(), diag::note_overridden_virtual_function)
<< Old->getReturnTypeSourceRange();
break;
- case FunctionEffectDiff::OverrideResult::MergeAdded: {
- NewFX.insert(Diff.New.Effect, Diff.New.Cond.expr());
+ case FunctionEffectDiff::OverrideResult::Merge: {
+ NewFX.insert(Diff.Old.Effect, Diff.Old.Cond.expr());
const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
EPI.FunctionEffects = FunctionEffectsRef(NewFX);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c7f693fde1e3b..c960935378156 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8105,6 +8105,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
TPState.setParsedNonAllocating(NewState);
}
+#if 0
+ // Old type-sugar implementation of denied effects
if (NewState == FunctionEffectMode::False) {
// blocking and allocating are represented as AttributedType sugar,
// using those attributes.
@@ -8117,13 +8119,16 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
QT = TPState.getAttributedType(A, QT, QT);
return true;
}
+#endif
// nonblocking/nonallocating(true/expr) are represented in a
// FunctionEffectsRef attached to a FunctionProtoType.
- const FunctionEffect NewEffect(isNonBlocking
- ? FunctionEffect::Kind::NonBlocking
- : FunctionEffect::Kind::NonAllocating,
- /*IsDenied=*/false);
+ const bool Denied = NewState == FunctionEffectMode::False;
+ const FunctionEffect NewEffect(
+ isNonBlocking ? (Denied ? FunctionEffect::Kind::Blocking
+ : FunctionEffect::Kind::NonBlocking)
+ : (Denied ? FunctionEffect::Kind::Allocating
+ : FunctionEffect::Kind::NonAllocating));
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 2e2d398525299..b602975f6c033 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6255,15 +6255,10 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
EPI.ExtParameterInfos = nullptr;
}
-#define XFORM_FALSE_FX_TO_SUGAR 0
-
// Transform any function effects with unevaluated conditions.
// Hold this set in a local for the rest of this function, since EPI
// is going to hold a FunctionEffectsRef pointing into it.
std::optional<FunctionEffectSet> NewFX;
-#if XFORM_FALSE_FX_TO_SUGAR
- SmallVector<Attr *> TypeAttrsToAdd;
-#endif
if (ArrayRef FXConds = EPI.FunctionEffects.conditions(); !FXConds.empty()) {
NewFX.emplace(EPI.FunctionEffects);
EnterExpressionEvaluationContext Unevaluated(
@@ -6279,29 +6274,26 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
if (NewExpr.isInvalid())
return QualType();
-#if XFORM_FALSE_FX_TO_SUGAR
- if (Mode == FunctionEffectMode::False) {
- NewFX->erase(Idx);
- --Count;
- const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
- if (Effect.kind() == FunctionEffect::Kind::NonAllocating) {
- TypeAttrsToAdd.push_back(AllocatingAttr::Create(SemaRef.Context));
- } else if (Effect.kind() == FunctionEffect::Kind::NonBlocking) {
- TypeAttrsToAdd.push_back(BlockingAttr::Create(SemaRef.Context));
- }
- continue;
- }
- assert(Mode == FunctionEffectMode::True);
- NewFX->replaceCondition(Idx, NewExpr.get());
-#else
// The condition expression has been transformed, and re-evaluated.
- // If it is now a constant of 'true', we can discard it because a null
- // condition in FunctionEffectSet means true. If the expression is still
- // dependent or constant 'false', just propagate it into the new EPI.
- FunctionEffectCondition Cond(
- Mode == FunctionEffectMode::True ? nullptr : NewExpr.get());
- NewFX->replaceCondition(Idx, Cond.expr());
-#endif
+ // It may or may not have become constant.
+ const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
+ FunctionEffectWithCondition EC;
+ switch (Mode) {
+ case FunctionEffectMode::True:
+ EC.Effect = Effect;
+ break;
+ case FunctionEffectMode::False:
+ EC.Effect = FunctionEffect(Effect.oppositeKind());
+ break;
+ case FunctionEffectMode::Dependent:
+ EC.Effect = Effect;
+ EC.Cond = FunctionEffectCondition(NewExpr.get());
+ break;
+ case FunctionEffectMode::None:
+ llvm_unreachable(
+ "FunctionEffectMode::None shouldn't be possible here");
+ }
+ NewFX->replaceItem(Idx, EC);
}
++Idx;
}
@@ -6326,21 +6318,6 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
for (unsigned i = 0, e = NewTL.getNumParams(); i != e; ++i)
NewTL.setParam(i, ParamDecls[i]);
-#if XFORM_FALSE_FX_TO_SUGAR
- // This creates problems because the caller assumes FunctionProtoType will
- // be transformed to FunctionProtoType.
- if (!TypeAttrsToAdd.empty()) {
- for (Attr *A : TypeAttrsToAdd) {
- QualType QT =
- SemaRef.Context.getAttributedType(A->getKind(), Result, Result);
- llvm::outs() << "transformed " << Result << " -> " << QT;
- Result = QT;
- AttributedTypeLoc ATL = TLB.push<AttributedTypeLoc>(Result);
- ATL.setAttr(A);
- }
- }
-#endif
-
return Result;
}
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index e83795d814b8a..907282eab5611 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3855,7 +3855,8 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
case DECLS_WITH_EFFECTS_TO_VERIFY:
for (unsigned I = 0, N = Record.size(); I != N; ++I)
- DeclsWithEffectsToVerify.push_back(getGlobalDeclID(F, Record[I]));
+ DeclsWithEffectsToVerify.push_back(
+ getGlobalDeclID(F, LocalDeclID(Record[I])));
break;
case OPENCL_EXTENSIONS:
@@ -8238,19 +8239,15 @@ void ASTReader::InitializeSema(Sema &S) {
}
if (!DeclsWithEffectsToVerify.empty()) {
- for (uint64_t ID : DeclsWithEffectsToVerify) {
+ for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
Decl *D = GetDecl(ID);
- SemaObj->DeclsWithEffectsToVerify.push_back(D);
-
FunctionEffectsRef FX;
- if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ if (auto *FD = dyn_cast<FunctionDecl>(D))
FX = FD->getFunctionEffects();
- } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ else if (auto *BD = dyn_cast<BlockDecl>(D))
FX = BD->getFunctionEffects();
- }
- if (!FX.empty()) {
+ if (!FX.empty())
SemaObj->addDeclWithEffects(D, FX);
- }
}
DeclsWithEffectsToVerify.clear();
}
>From 5143877e985815c80323ab2324ba7700f18183e4 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 24 Apr 2024 15:23:30 -0700
Subject: [PATCH 39/71] tiny cleanup
---
clang/lib/AST/TypePrinter.cpp | 8 ++------
clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 2 +-
clang/lib/Sema/TreeTransform.h | 2 +-
4 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 70fbb97093536..a7a10cbb7489c 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1944,6 +1944,8 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmPreserves:
case attr::NonBlocking:
case attr::NonAllocating:
+ case attr::Blocking:
+ case attr::Allocating:
llvm_unreachable("This attribute should have been handled already");
case attr::NSReturnsRetained:
@@ -2003,12 +2005,6 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmMveStrictPolymorphism:
OS << "__clang_arm_mve_strict_polymorphism";
break;
- case attr::Blocking:
- OS << "clang_blocking";
- break;
- case attr::Allocating:
- OS << "clang_allocating";
- break;
// Nothing to print for this attribute.
case attr::HLSLParamModifier:
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index cb3ec0abb8bad..9ccdaeea8695f 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18346,7 +18346,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
if (OldFT->hasExtParameterInfos()) {
for (unsigned I = 0, E = OldFT->getNumParams(); I != E; ++I)
- // A parameter of the overriding method should be annotated with noescape
+ // A parameter of the overriding method should be annotated with noescape
// if the corresponding parameter of the overridden method is annotated.
if (OldFT->getExtParameterInfo(I).isNoEscape() &&
!NewFT->getExtParameterInfo(I).isNoEscape()) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c960935378156..8dddc30adb505 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8121,7 +8121,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
#endif
- // nonblocking/nonallocating(true/expr) are represented in a
+ // All forms of the attributes are represented in a
// FunctionEffectsRef attached to a FunctionProtoType.
const bool Denied = NewState == FunctionEffectMode::False;
const FunctionEffect NewEffect(
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index b602975f6c033..e91f471b72adb 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6257,7 +6257,7 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
// Transform any function effects with unevaluated conditions.
// Hold this set in a local for the rest of this function, since EPI
- // is going to hold a FunctionEffectsRef pointing into it.
+ // may need to hold a FunctionEffectsRef pointing into it.
std::optional<FunctionEffectSet> NewFX;
if (ArrayRef FXConds = EPI.FunctionEffects.conditions(); !FXConds.empty()) {
NewFX.emplace(EPI.FunctionEffects);
>From 65e5b6117fb180959eaeeece92632701b35048a0 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 25 Apr 2024 09:43:11 -0700
Subject: [PATCH 40/71] Tests involving nonblocking(expression)
---
clang/include/clang/AST/Type.h | 2 +-
clang/lib/AST/Type.cpp | 2 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 88 ++++++++-----------
clang/lib/Sema/SemaDecl.cpp | 2 +-
.../Sema/attr-nonblocking-constraints.cpp | 17 +++-
5 files changed, 54 insertions(+), 57 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index c08e08913750f..805a0d51af1f7 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4482,7 +4482,7 @@ class FunctionEffect {
public:
FunctionEffect() : FKind(unsigned(Kind::None)) {}
- FunctionEffect(Kind K) : FKind(unsigned(K)) {}
+ explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {}
/// The kind of the effect.
Kind kind() const { return Kind(FKind); }
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 750da4f6bdc7a..ba91fcea2f2ad 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5053,7 +5053,7 @@ FunctionEffect::Kind FunctionEffect::oppositeKind() const {
case Kind::Allocating:
return Kind::NonAllocating;
case Kind::None:
- break;
+ return Kind::None;
}
llvm_unreachable("unknown effect kind");
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 7aa4acfd3bbd4..c6d2b1c25c1d2 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2471,6 +2471,8 @@ class EffectSet {
using const_iterator = const T *;
iterator begin() { return &Items[0]; }
iterator end() { return &Items[Count]; }
+ const_iterator begin() const { return &Items[0]; }
+ const_iterator end() const { return &Items[Count]; }
const_iterator cbegin() const { return &Items[0]; }
const_iterator cend() const { return &Items[Count]; }
@@ -2493,6 +2495,9 @@ class EffectSet {
public:
EffectSet() = default;
+ explicit EffectSet(FunctionEffectsRef FX) {
+ insert(FX);
+ }
operator ArrayRef<FunctionEffect>() const {
return ArrayRef(Impl.cbegin(), Impl.cend());
@@ -2503,10 +2508,25 @@ class EffectSet {
iterator end() const { return Impl.cend(); }
void insert(const FunctionEffect &Effect);
- void insert(const EffectSet &Set);
- void insertIgnoringConditions(const FunctionEffectsRef &FX);
- bool contains(const FunctionEffect &E) const {
- return std::find(begin(), end(), E) != end();
+ void insert(const EffectSet &Set) {
+ for (auto &Item : Set) {
+ // push_back because set is already sorted
+ Impl.push_back(Item);
+ }
+ }
+ void insert(FunctionEffectsRef FX) {
+ for (const auto &EC : FX) {
+ assert(EC.Cond.expr() == nullptr); // should be resolved by now, right?
+ // push_back because set is already sorted
+ Impl.push_back(EC.Effect);
+ }
+ }
+ bool contains(const FunctionEffect::Kind EK) const {
+ for (const FunctionEffect &E : Impl) {
+ if (E.kind() == EK)
+ return true;
+ }
+ return false;
}
void dump(llvm::raw_ostream &OS) const;
@@ -2528,16 +2548,6 @@ void EffectSet::insert(const FunctionEffect &Effect) {
Impl.insert(Iter, Effect);
}
-void EffectSet::insert(const EffectSet &Set) {
- for (auto &Item : Set)
- insert(Item);
-}
-
-void EffectSet::insertIgnoringConditions(const FunctionEffectsRef &FX) {
- for (const auto &Item : FX)
- insert(Item.Effect);
-}
-
LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
@@ -2559,32 +2569,6 @@ EffectSet EffectSet::difference(ArrayRef<FunctionEffect> LHS,
return Result;
}
-// Represent the declared effects, and absent effects (e.g. nonblocking(false)).
-// Centralize the resolution of computed effects.
-struct CondEffectSet {
- EffectSet EffectsPresent;
- EffectSet EffectsExplicitlyAbsent;
-
- CondEffectSet() = default;
-
- CondEffectSet(Sema &SemaRef, FunctionEffectsRef FX) {
- for (const auto Item : FX) {
- if (Expr *Cond = Item.Cond.expr()) {
- FunctionEffectMode Mode = FunctionEffectMode::None;
- ExprResult ER = SemaRef.ActOnEffectExpression(
- Cond, Mode, /*RequireConstexpr=*/true);
- if (ER.isInvalid() || Mode == FunctionEffectMode::Dependent)
- continue;
- if (Mode == FunctionEffectMode::False) {
- EffectsExplicitlyAbsent.insert(Item.Effect);
- continue;
- }
- }
- EffectsPresent.insert(Item.Effect);
- }
- }
-};
-
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer...
struct CallableInfo {
@@ -2593,7 +2577,7 @@ struct CallableInfo {
mutable std::optional<std::string>
MaybeName; // mutable because built on demand in const method
SpecialFuncType FuncType = SpecialFuncType::None;
- CondEffectSet Effects;
+ EffectSet Effects;
CallType CType = CallType::Unknown;
CallableInfo(Sema &SemaRef, const Decl &CD,
@@ -2621,7 +2605,7 @@ struct CallableInfo {
// ValueDecl is function, enum, or variable, so just look at its type.
FXRef = FunctionEffectsRef::get(VD->getType());
}
- Effects = CondEffectSet(SemaRef, FXRef);
+ Effects = EffectSet(FXRef);
}
bool isDirectCall() const {
@@ -2756,14 +2740,13 @@ class PendingFunctionAnalysis {
PendingFunctionAnalysis(
Sema &Sem, const CallableInfo &CInfo,
ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- DeclaredVerifiableEffects = CInfo.Effects.EffectsPresent;
+ DeclaredVerifiableEffects = CInfo.Effects;
// Check for effects we are not allowed to infer
EffectSet FX;
for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl) &&
- !CInfo.Effects.EffectsExplicitlyAbsent.contains(effect)) {
+ if (effect.canInferOnFunction(*CInfo.CDecl)) {
FX.insert(effect);
} else {
// Add a diagnostic for this effect if a caller were to
@@ -3046,7 +3029,7 @@ class Analyzer {
// If any of the Decl's declared effects forbid throwing (e.g. nonblocking)
// then the function should also be declared noexcept.
- for (const auto &Effect : CInfo.Effects.EffectsPresent) {
+ for (const auto &Effect : CInfo.Effects) {
if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
continue;
@@ -3104,7 +3087,7 @@ class Analyzer {
emitDiagnostics(*Diags, CInfo, Sem);
}
auto *CompletePtr = new CompleteFunctionAnalysis(
- Sem.getASTContext(), Pending, CInfo.Effects.EffectsPresent,
+ Sem.getASTContext(), Pending, CInfo.Effects,
AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
if constexpr (DebugLogLevel > 0) {
@@ -3143,7 +3126,7 @@ class Analyzer {
const bool DirectCall = Callee.isDirectCall();
// These will be its declared effects.
- EffectSet CalleeEffects = Callee.Effects.EffectsPresent;
+ EffectSet CalleeEffects = Callee.Effects;
bool IsInferencePossible = DirectCall;
@@ -3210,6 +3193,8 @@ class Analyzer {
// Should only be called when determined to be complete.
void emitDiagnostics(SmallVector<Diagnostic> &Diags,
const CallableInfo &CInfo, Sema &S) {
+ if (Diags.empty())
+ return;
const SourceManager &SM = S.getSourceManager();
std::sort(Diags.begin(), Diags.end(),
[&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
@@ -3290,8 +3275,8 @@ class Analyzer {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
- } else if (CalleeInfo.Effects.EffectsExplicitlyAbsent.contains(
- Diag.Effect)) {
+ } else if (CalleeInfo.Effects.contains(
+ Diag.Effect.oppositeKind())) {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_disallows_inference)
<< effectName;
@@ -3456,8 +3441,7 @@ class Analyzer {
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
EffectSet CalleeFX;
if (FPT) {
- CondEffectSet CFE(Outer.Sem, FPT->getFunctionEffects());
- CalleeFX.insert(CFE.EffectsPresent);
+ CalleeFX.insert(FPT->getFunctionEffects());
}
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 186f7a0e3a1ea..aaadb41d1beee 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11167,7 +11167,7 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
if (hasUncompilableErrorOccurred())
return;
- // For code in dependent contexts, we'll do this at instantiation time
+ // For code in dependent contexts, we'll do this at instantiation time.
// Without this check, we would analyze the function based on placeholder
// template parameters, and potentially generate spurious diagnostics.
if (cast<DeclContext>(D)->isDependentContext()) {
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index a273beac1baf7..21d14a2998c38 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -134,13 +134,26 @@ void nl10(
}
// Interactions with nonblocking(false)
-void nl11_no_inference() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
{
}
+void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+
+template <bool V>
+struct ComputedNB {
+ void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+};
void nl11() [[clang::nonblocking]]
{
- nl11_no_inference(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+
+ ComputedNB<true> CNB_true;
+ CNB_true.method();
+
+ ComputedNB<false> CNB_false;
+ CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
// Verify that when attached to a redeclaration, the attribute successfully attaches.
>From 1748edb26b5ebb5a89b449a272e2f249a4e7dfac Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Apr 2024 07:44:24 -0700
Subject: [PATCH 41/71] ASTReader: adapt to GlobalDeclID change
---
clang/include/clang/Serialization/ASTReader.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 03cc1e241b943..2fc3cee465ed2 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -970,7 +970,7 @@ class ASTReader
llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
/// The IDs of all decls with function effects to be checked.
- SmallVector<serialization::GlobalDeclID, 0> DeclsWithEffectsToVerify;
+ SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify;
private:
struct ImportedSubmodule {
>From 485c941769c8e51de12fe94672398ec9a59b0b63 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Apr 2024 07:44:45 -0700
Subject: [PATCH 42/71] warn_perf_constraint_implies_noexcept should only be
issued in C++ without extern "C"
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 50 +++++++++++++-----------
1 file changed, 27 insertions(+), 23 deletions(-)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index c6d2b1c25c1d2..c4387e8d3ec22 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2495,9 +2495,7 @@ class EffectSet {
public:
EffectSet() = default;
- explicit EffectSet(FunctionEffectsRef FX) {
- insert(FX);
- }
+ explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
operator ArrayRef<FunctionEffect>() const {
return ArrayRef(Impl.cbegin(), Impl.cend());
@@ -3026,30 +3024,36 @@ class Analyzer {
// else null. This method must not recurse.
PendingFunctionAnalysis *verifyDecl(const Decl *D) {
CallableInfo CInfo(Sem, *D);
+ bool isExternC = false;
- // If any of the Decl's declared effects forbid throwing (e.g. nonblocking)
- // then the function should also be declared noexcept.
- for (const auto &Effect : CInfo.Effects) {
- if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
- continue;
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ tmp_assert(FD->getBuiltinID() == 0);
+ isExternC = FD->isExternCContext();
+ }
- const FunctionProtoType *FPT = nullptr;
- if (auto *FD = D->getAsFunction()) {
- FPT = FD->getType()->getAs<FunctionProtoType>();
- } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
- if (auto *TSI = BD->getSignatureAsWritten()) {
- FPT = TSI->getType()->getAs<FunctionProtoType>();
+ // For C++, with non-extern "C" linkage only - if any of the Decl's declared
+ // effects forbid throwing (e.g. nonblocking) then the function should also
+ // be declared noexcept.
+ if (Sem.getLangOpts().CPlusPlus && !isExternC) {
+ for (const auto &Effect : CInfo.Effects) {
+ if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
+ continue;
+
+ const FunctionProtoType *FPT = nullptr;
+ if (auto *FD = D->getAsFunction()) {
+ FPT = FD->getType()->getAs<FunctionProtoType>();
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten()) {
+ FPT = TSI->getType()->getAs<FunctionProtoType>();
+ }
}
+ if (FPT && FPT->canThrow() != clang::CT_Cannot) {
+ Sem.Diag(D->getBeginLoc(),
+ diag::warn_perf_constraint_implies_noexcept)
+ << Effect.name();
+ }
+ break;
}
- if (FPT && FPT->canThrow() != clang::CT_Cannot) {
- Sem.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
- << Effect.name();
- }
- break;
- }
-
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- tmp_assert(FD->getBuiltinID() == 0);
}
// Build a PendingFunctionAnalysis on the stack. If it turns out to be
>From 92e4b9a4f05001b427a8024f236cd033162d4ad9 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 28 Apr 2024 11:25:45 -0700
Subject: [PATCH 43/71] Obtain extern-C-ness from the function's canonical
Decl.
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index c4387e8d3ec22..3f40a354ea521 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3028,7 +3028,7 @@ class Analyzer {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
tmp_assert(FD->getBuiltinID() == 0);
- isExternC = FD->isExternCContext();
+ isExternC = FD->getCanonicalDecl()->isExternCContext();
}
// For C++, with non-extern "C" linkage only - if any of the Decl's declared
>From c534d63c46787271245f9d164c5c77421b0efbb6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 29 Apr 2024 07:55:07 -0700
Subject: [PATCH 44/71] Fix build problem involving std::any_of and a
FunctionEffectsRef.
---
clang/lib/Sema/SemaDecl.cpp | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index aaadb41d1beee..ee89c0c326ddb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11170,16 +11170,18 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
// For code in dependent contexts, we'll do this at instantiation time.
// Without this check, we would analyze the function based on placeholder
// template parameters, and potentially generate spurious diagnostics.
- if (cast<DeclContext>(D)->isDependentContext()) {
+ if (cast<DeclContext>(D)->isDependentContext())
+ return;
+
+ // Effects which are not inferrable (e.g. nonblocking(false) don't need
+ // to be verified later in the deferred pass. (If they do need to be
+ // verified, that can happen immediately at the call site, like TCB.)
+ if (llvm::none_of(FX.effects(), [](const FunctionEffect &E) {
+ return (E.flags() & FunctionEffect::FE_InferrableOnCallees) != 0;
+ }))
return;
- }
- if (std::any_of(FX.begin(), FX.end(),
- [](const FunctionEffectWithCondition &EC) {
- return (EC.Effect.flags() &
- FunctionEffect::FE_InferrableOnCallees) != 0;
- }))
- addDeclWithEffects(D, FX);
+ addDeclWithEffects(D, FX);
}
void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
>From 5cd7bfdbe65df3b48f86a7710692d91d3b9bfd4f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 2 May 2024 08:44:38 -0700
Subject: [PATCH 45/71] Remove AnalysisBasedWarnings debug code that was
pulling in ASTMatchers.
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 90 +-----------------------
1 file changed, 3 insertions(+), 87 deletions(-)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 3f40a354ea521..b257b9d2233ba 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -27,8 +27,6 @@
#include "clang/AST/StmtObjC.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/Analyses/CalledOnceCheck.h"
#include "clang/Analysis/Analyses/Consumed.h"
@@ -2389,9 +2387,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
#if FX_ANALYZER_ENABLED
-// Temporary debugging option
-#define FX_ANALYZER_VERIFY_DECL_LIST 1
-
namespace FXAnalysis {
#define tmp_assert(x) \
@@ -2696,7 +2691,6 @@ class EffectToDiagnosticMap {
// ----------
// State pertaining to a function whose AST is walked. Since there are
// potentially a large number of these objects, it needs care about size.
-// TODO: EffectSet could be made much smaller.
class PendingFunctionAnalysis {
friend class CompleteFunctionAnalysis;
@@ -2763,23 +2757,20 @@ class PendingFunctionAnalysis {
// ones are handled differently.
void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
if (!Inferring) {
- if (DiagnosticsForExplicitFX == nullptr) {
+ if (DiagnosticsForExplicitFX == nullptr)
DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
- }
DiagnosticsForExplicitFX->push_back(NewDiag);
} else {
auto &Diag =
InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
- if (Diag.ID == DiagnosticID::None) {
+ if (Diag.ID == DiagnosticID::None)
Diag = NewDiag;
- }
}
}
void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
- if (UnverifiedDirectCalls == nullptr) {
+ if (UnverifiedDirectCalls == nullptr)
UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
- }
UnverifiedDirectCalls->emplace_back(D, CallLoc);
}
@@ -2946,9 +2937,6 @@ class Analyzer {
Analyzer(Sema &S) : Sem(S) {}
void run(const TranslationUnitDecl &TU) {
-#if FX_ANALYZER_VERIFY_DECL_LIST
- verifyRootDecls(TU);
-#endif
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
{
@@ -3700,78 +3688,6 @@ class Analyzer {
bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
};
-
-#if FX_ANALYZER_VERIFY_DECL_LIST
- // Sema has accumulated CallablesWithEffectsToVerify. As a debug check, do our
- // own AST traversal and see what we find.
-
- using MatchFinder = ast_matchers::MatchFinder;
- static constexpr StringRef Tag_Callable = "Callable";
-
- // -----
- // Called for every callable in the translation unit.
- struct CallableFinderCallback : MatchFinder::MatchCallback {
- Sema &Sem;
-
- CallableFinderCallback(Sema &S) : Sem(S) {}
-
- void run(const MatchFinder::MatchResult &Result) override {
- if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
- if (const auto FX = functionEffectsForDecl(Callable); !FX.empty()) {
- // Reuse this filtering method in Sema
- Sem.maybeAddDeclWithEffects(Callable, FX);
- }
- }
- }
-
- static FunctionEffectsRef functionEffectsForDecl(const Decl *D) {
- if (auto *FD = D->getAsFunction()) {
- return FD->getFunctionEffects();
- }
- if (auto *BD = dyn_cast<BlockDecl>(D)) {
- return BD->getFunctionEffects();
- }
- return {};
- }
-
- static void get(Sema &S, const TranslationUnitDecl &TU) {
- MatchFinder CallableFinder;
- CallableFinderCallback Callback(S);
-
- using namespace clang::ast_matchers;
-
- CallableFinder.addMatcher(
- decl(forEachDescendant(
- decl(anyOf(functionDecl(hasBody(anything())).bind(Tag_Callable),
- // objcMethodDecl(isDefinition()).bind(Tag_Callable),
- // // no, always unsafe
- blockDecl().bind(Tag_Callable))))),
- &Callback);
- // Matching LambdaExpr this way [a] doesn't seem to work (need to check
- // for Stmt?) and [b] doesn't seem necessary, since the anonymous function
- // is reached via the above.
- // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))),
- // &Callback);
-
- CallableFinder.match(TU, TU.getASTContext());
- }
- };
-
- void verifyRootDecls(const TranslationUnitDecl &TU) const {
- // If this weren't debug code, it would be good to find a way to move/swap
- // instead of copying.
- SmallVector<const Decl *> decls = Sem.DeclsWithEffectsToVerify;
- Sem.DeclsWithEffectsToVerify.clear();
-
- CallableFinderCallback::get(Sem, TU);
-
- if constexpr (DebugLogLevel > 0) {
- llvm::errs() << "\nFXAnalysis: Sema gathered " << decls.size()
- << " Decls; second AST pass found "
- << Sem.DeclsWithEffectsToVerify.size() << "\n";
- }
- }
-#endif
};
Analyzer::AnalysisMap::~AnalysisMap() {
>From c18b77459fb34482f169323de9a81142a922b6a7 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 2 May 2024 09:06:12 -0700
Subject: [PATCH 46/71] Remove the 2nd pass caller/callee analysis/verification
for now (to be a separate PR).
---
clang/include/clang/Basic/DiagnosticGroups.td | 1 -
.../clang/Basic/DiagnosticSemaKinds.td | 49 -
clang/include/clang/Sema/Sema.h | 13 -
.../include/clang/Serialization/ASTBitCodes.h | 4 -
clang/include/clang/Serialization/ASTReader.h | 3 -
clang/include/clang/Serialization/ASTWriter.h | 1 -
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1331 -----------------
clang/lib/Sema/SemaDecl.cpp | 50 -
clang/lib/Sema/SemaExpr.cpp | 6 -
clang/lib/Sema/SemaLambda.cpp | 5 -
clang/lib/Serialization/ASTReader.cpp | 20 -
clang/lib/Serialization/ASTWriter.cpp | 12 -
.../Sema/attr-nonblocking-constraints.cpp | 198 ---
.../attr-nonblocking-constraints.mm | 23 -
14 files changed, 1716 deletions(-)
delete mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
delete mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2fc8d55d67efa..bdf7e46d0fb43 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1520,7 +1520,6 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInCon
// Warnings and notes related to the function effects system underlying
// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;
-def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fc899b2d82b6a..03a8b5afddb5e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10809,55 +10809,6 @@ def warn_imp_cast_drops_unaligned : Warning<
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
// Function effects
-def warn_func_effect_allocates : Warning<
- "'%0' function must not allocate or deallocate memory">,
- InGroup<FunctionEffects>;
-def note_func_effect_allocates : Note<
- "function cannot be inferred '%0' because it allocates/deallocates memory">;
-def warn_func_effect_throws_or_catches : Warning<
- "'%0' function must not throw or catch exceptions">,
- InGroup<FunctionEffects>;
-def note_func_effect_throws_or_catches : Note<
- "function cannot be inferred '%0' because it throws or catches exceptions">;
-def warn_func_effect_has_static_local : Warning<
- "'%0' function must not have static locals">,
- InGroup<FunctionEffects>;
-def note_func_effect_has_static_local : Note<
- "function cannot be inferred '%0' because it has a static local">;
-def warn_func_effect_uses_thread_local : Warning<
- "'%0' function must not use thread-local variables">,
- InGroup<FunctionEffects>;
-def note_func_effect_uses_thread_local : Note<
- "function cannot be inferred '%0' because it uses a thread-local variable">;
-def warn_func_effect_calls_objc : Warning<
- "'%0' function must not access an ObjC method or property">,
- InGroup<FunctionEffects>;
-def note_func_effect_calls_objc : Note<
- "function cannot be inferred '%0' because it accesses an ObjC method or property">;
-def warn_func_effect_calls_func_without_effect : Warning<
- "'%0' function must not call non-'%0' function '%1'">,
- InGroup<FunctionEffects>;
-def warn_func_effect_calls_expr_without_effect : Warning<
- "'%0' function must not call non-'%0' expression">,
- InGroup<FunctionEffects>;
-def note_func_effect_calls_func_without_effect : Note<
- "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
-def note_func_effect_call_extern : Note<
- "function cannot be inferred '%0' because it has no definition in this translation unit">;
-def note_func_effect_call_disallows_inference : Note<
- "function does not permit inference of '%0'">;
-def note_func_effect_call_virtual : Note<
- "virtual method cannot be inferred '%0'">;
-def note_func_effect_call_func_ptr : Note<
- "function pointer cannot be inferred '%0'">;
-def warn_perf_constraint_implies_noexcept : Warning<
- "'%0' function should be declared noexcept">,
- InGroup<PerfConstraintImpliesNoexcept>;
-
-// TODO: can the usual template expansion notes be used?
-def note_func_effect_from_template : Note<
- "in template expansion here">;
-
// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b5ce248be57c5..d75124209b2a9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -778,23 +778,10 @@ class Sema final : public SemaBase {
// ----- function effects ---
- /// All functions/lambdas/blocks which have bodies and which have a non-empty
- /// FunctionEffectsRef to be verified.
- SmallVector<const Decl *> DeclsWithEffectsToVerify;
- /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
- /// are all null.
- FunctionEffectSet AllEffectsToVerify;
-
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
- /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
- /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
- void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
/// Try to parse the conditional expression attached to an effect attribute
/// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). If RequireConstexpr,
/// then this will fail if the expression is dependent.
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 40dc9feae9284..186c3b722ced1 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -694,10 +694,6 @@ enum ASTRecordTypes {
/// Record code for lexical and visible block for delayed namespace in
/// reduced BMI.
DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD = 68,
-
- /// Record code for Sema's vector of functions/blocks with effects to
- /// be verified.
- DECLS_WITH_EFFECTS_TO_VERIFY = 69,
};
/// 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 2fc3cee465ed2..64f1ebc117b32 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -969,9 +969,6 @@ class ASTReader
/// Sema tracks these to emit deferred diags.
llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
- /// The IDs of all decls with function effects to be checked.
- SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify;
-
private:
struct ImportedSubmodule {
serialization::SubmoduleID ID;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index bd8ae44005900..6c45b7348b855 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -572,7 +572,6 @@ class ASTWriter : public ASTDeserializationListener,
void WriteMSPointersToMembersPragmaOptions(Sema &SemaRef);
void WritePackPragmaOptions(Sema &SemaRef);
void WriteFloatControlPragmaOptions(Sema &SemaRef);
- void WriteDeclsWithEffectsToVerify(Sema &SemaRef);
void WriteModuleFileExtension(Sema &SemaRef,
ModuleFileExtensionWriter &Writer);
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index b257b9d2233ba..6992ba9ad9a75 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2380,1332 +2380,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
};
} // namespace
-// =============================================================================
-
-// Temporary feature enablement
-#define FX_ANALYZER_ENABLED 1
-
-#if FX_ANALYZER_ENABLED
-
-namespace FXAnalysis {
-
-#define tmp_assert(x) \
- do { \
- if (!(x)) \
- __builtin_trap(); \
- } while (0)
-
-enum class DiagnosticID : uint8_t {
- None = 0, // sentinel for an empty Diagnostic
- Throws,
- Catches,
- CallsObjC,
- AllocatesMemory,
- HasStaticLocal,
- AccessesThreadLocal,
-
- // These only apply to callees, where the analysis stops at the Decl
- DeclDisallowsInference,
-
- CallsDeclWithoutEffect,
- CallsExprWithoutEffect,
-};
-
-struct Diagnostic {
- FunctionEffect Effect;
- const Decl *Callee = nullptr; // only valid for Calls*
- SourceLocation Loc;
- DiagnosticID ID = DiagnosticID::None;
-
- Diagnostic() = default;
-
- Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
- const Decl *Callee = nullptr)
- : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
-};
-
-enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallType {
- Unknown,
- Function,
- Virtual,
- Block
- // unknown: probably function pointer
-};
-
-// Return whether the function CAN be verified.
-// The question of whether it SHOULD be verified is independent.
-static bool functionIsVerifiable(const FunctionDecl *FD) {
- if (!(FD->hasBody() || FD->isInlined())) {
- // externally defined; we couldn't verify if we wanted to.
- return false;
- }
- if (FD->isTrivial()) {
- // Otherwise `struct x { int a; };` would have an unverifiable default
- // constructor.
- return true;
- }
- return true;
-}
-
-/// A mutable set of FunctionEffect, for use in places where any conditions
-/// have been resolved or can be ignored.
-class EffectSet {
- // This implementation optimizes footprint. As long as FunctionEffect is only
- // 1 byte, and there are only 2 possible effects, this is more than
- // sufficient. In AnalysisBasedWarnings, we hold one of these for every
- // function visited, which, due to inference, can be many more functions than
- // have declared effects.
- template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
- SizeT Count = 0;
- T Items[Capacity] = {};
-
- using value_type = T;
-
- using iterator = T *;
- using const_iterator = const T *;
- iterator begin() { return &Items[0]; }
- iterator end() { return &Items[Count]; }
- const_iterator begin() const { return &Items[0]; }
- const_iterator end() const { return &Items[Count]; }
- const_iterator cbegin() const { return &Items[0]; }
- const_iterator cend() const { return &Items[Count]; }
-
- void insert(iterator I, const T &Value) {
- assert(Count < Capacity);
- iterator E = end();
- if (I != E)
- std::copy_backward(I, E, E + 1);
- *I = Value;
- ++Count;
- }
-
- void push_back(const T &Value) {
- assert(Count < Capacity);
- Items[Count++] = Value;
- }
- };
-
- FixedVector<FunctionEffect, uint8_t, 7> Impl;
-
-public:
- EffectSet() = default;
- explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
-
- operator ArrayRef<FunctionEffect>() const {
- return ArrayRef(Impl.cbegin(), Impl.cend());
- }
-
- using iterator = const FunctionEffect *;
- iterator begin() const { return Impl.cbegin(); }
- iterator end() const { return Impl.cend(); }
-
- void insert(const FunctionEffect &Effect);
- void insert(const EffectSet &Set) {
- for (auto &Item : Set) {
- // push_back because set is already sorted
- Impl.push_back(Item);
- }
- }
- void insert(FunctionEffectsRef FX) {
- for (const auto &EC : FX) {
- assert(EC.Cond.expr() == nullptr); // should be resolved by now, right?
- // push_back because set is already sorted
- Impl.push_back(EC.Effect);
- }
- }
- bool contains(const FunctionEffect::Kind EK) const {
- for (const FunctionEffect &E : Impl) {
- if (E.kind() == EK)
- return true;
- }
- return false;
- }
-
- void dump(llvm::raw_ostream &OS) const;
-
- static EffectSet difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS);
-};
-
-void EffectSet::insert(const FunctionEffect &Effect) {
- FunctionEffect *Iter = Impl.begin();
- FunctionEffect *End = Impl.end();
- // lower_bound is overkill for a tiny vector like this
- for (; Iter != End; ++Iter) {
- if (*Iter == Effect)
- return;
- if (Effect < *Iter)
- break;
- }
- Impl.insert(Iter, Effect);
-}
-
-LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
- OS << "Effects{";
- bool First = true;
- for (const auto &Effect : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << Effect.name();
- }
- OS << "}";
-}
-
-EffectSet EffectSet::difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS) {
- EffectSet Result;
- std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result.Impl));
- return Result;
-}
-
-// Transitory, more extended information about a callable, which can be a
-// function, block, function pointer...
-struct CallableInfo {
- // CDecl holds the function's definition, if any.
- const Decl *CDecl;
- mutable std::optional<std::string>
- MaybeName; // mutable because built on demand in const method
- SpecialFuncType FuncType = SpecialFuncType::None;
- EffectSet Effects;
- CallType CType = CallType::Unknown;
-
- CallableInfo(Sema &SemaRef, const Decl &CD,
- SpecialFuncType FT = SpecialFuncType::None)
- : CDecl(&CD), FuncType(FT) {
- // llvm::errs() << "CallableInfo " << name() << "\n";
- FunctionEffectsRef FXRef;
-
- if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
- // Use the function's definition, if any.
- if (auto *Def = FD->getDefinition()) {
- CDecl = FD = Def;
- }
- CType = CallType::Function;
- if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
- if (Method->isVirtual()) {
- CType = CallType::Virtual;
- }
- }
- FXRef = FD->getFunctionEffects();
- } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
- CType = CallType::Block;
- FXRef = BD->getFunctionEffects();
- } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
- // ValueDecl is function, enum, or variable, so just look at its type.
- FXRef = FunctionEffectsRef::get(VD->getType());
- }
- Effects = EffectSet(FXRef);
- }
-
- bool isDirectCall() const {
- return CType == CallType::Function || CType == CallType::Block;
- }
-
- bool isVerifiable() const {
- switch (CType) {
- case CallType::Unknown:
- case CallType::Virtual:
- break;
- case CallType::Block:
- return true;
- case CallType::Function:
- return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
- }
- return false;
- }
-
- /// Generate a name for logging and diagnostics.
- std::string name(Sema &Sem) const {
- if (!MaybeName) {
- std::string Name;
- llvm::raw_string_ostream OS(Name);
-
- if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
- FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
- /*Qualified=*/true);
- } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
- OS << "(block " << BD->getBlockManglingNumber() << ")";
- } else if (auto *VD = dyn_cast<NamedDecl>(CDecl)) {
- VD->printQualifiedName(OS);
- }
- MaybeName = Name;
- }
- return *MaybeName;
- }
-};
-
-// ----------
-// Map effects to single diagnostics.
-class EffectToDiagnosticMap {
- // Since we currently only have a tiny number of effects (typically no more
- // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
- // is often empty, use a unique_ptr to the SmallVector.
- using Element = std::pair<FunctionEffect, Diagnostic>;
- using ImplVec = llvm::SmallVector<Element, 1>;
- std::unique_ptr<ImplVec> Impl;
-
-public:
- Diagnostic &getOrInsertDefault(FunctionEffect Key) {
- if (Impl == nullptr) {
- Impl = std::make_unique<llvm::SmallVector<Element>>();
- auto &Item = Impl->emplace_back();
- Item.first = Key;
- return Item.second;
- }
- Element Elem(Key, {});
- auto *Iter = _find(Elem);
- if (Iter != Impl->end() && Iter->first == Key) {
- return Iter->second;
- }
- Iter = Impl->insert(Iter, Elem);
- return Iter->second;
- }
-
- const Diagnostic *lookup(FunctionEffect key) {
- if (Impl == nullptr) {
- return nullptr;
- }
- Element elem(key, {});
- auto *iter = _find(elem);
- if (iter != Impl->end() && iter->first == key) {
- return &iter->second;
- }
- return nullptr;
- }
-
- size_t size() const { return Impl ? Impl->size() : 0; }
-
-private:
- ImplVec::iterator _find(const Element &elem) {
- return std::lower_bound(Impl->begin(), Impl->end(), elem,
- [](const Element &lhs, const Element &rhs) {
- return lhs.first < rhs.first;
- });
- }
-};
-
-// ----------
-// State pertaining to a function whose AST is walked. Since there are
-// potentially a large number of these objects, it needs care about size.
-class PendingFunctionAnalysis {
- friend class CompleteFunctionAnalysis;
-
- struct DirectCall {
- // Pack a Decl* and a bool indicating whether the call was detected
- // to be recursive. Not all recursive calls are detected, just enough
- // to break cycles.
- llvm::PointerIntPair<const Decl *, 1> CalleeAndRecursed;
- SourceLocation CallLoc;
-
- DirectCall(const Decl *D, SourceLocation CallLoc)
- : CalleeAndRecursed(D), CallLoc(CallLoc) {}
-
- const Decl *callee() const { return CalleeAndRecursed.getPointer(); }
- bool recursed() const { return CalleeAndRecursed.getInt(); }
-
- void setRecursed() { CalleeAndRecursed.setInt(1); }
- };
-
-public:
- // We always have two disjoint sets of effects to verify:
- // 1. Effects declared explicitly by this function.
- // 2. All other inferrable effects needing verification.
- EffectSet DeclaredVerifiableEffects;
- EffectSet FXToInfer;
-
-private:
- // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr
- // to optimize size for the case of 0 diagnostics.
- std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
-
- // Potential diagnostics pertaining to other, non-explicit, inferrable
- // effects.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
-
- std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
-
-public:
- PendingFunctionAnalysis(
- Sema &Sem, const CallableInfo &CInfo,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- DeclaredVerifiableEffects = CInfo.Effects;
-
- // Check for effects we are not allowed to infer
- EffectSet FX;
-
- for (const auto &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl)) {
- FX.insert(effect);
- } else {
- // Add a diagnostic for this effect if a caller were to
- // try to infer it.
- auto &diag =
- InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
- diag = Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
- CInfo.CDecl->getLocation());
- }
- }
- // FX is now the set of inferrable effects which are not prohibited
- FXToInfer = EffectSet::difference(FX, DeclaredVerifiableEffects);
- }
-
- // Hide the way that diagnostics for explicitly required effects vs. inferred
- // ones are handled differently.
- void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
- if (!Inferring) {
- if (DiagnosticsForExplicitFX == nullptr)
- DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
- DiagnosticsForExplicitFX->push_back(NewDiag);
- } else {
- auto &Diag =
- InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
- if (Diag.ID == DiagnosticID::None)
- Diag = NewDiag;
- }
- }
-
- void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
- if (UnverifiedDirectCalls == nullptr)
- UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
- UnverifiedDirectCalls->emplace_back(D, CallLoc);
- }
-
- // Analysis is complete when there are no unverified direct calls.
- bool isComplete() const {
- return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
- }
-
- const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
- return InferrableEffectToFirstDiagnostic.lookup(effect);
- }
-
- SmallVector<DirectCall> &unverifiedCalls() const {
- tmp_assert(!isComplete());
- return *UnverifiedDirectCalls;
- }
-
- SmallVector<Diagnostic> *getDiagnosticsForExplicitFX() const {
- return DiagnosticsForExplicitFX.get();
- }
-
- void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
- OS << "Pending: Declared ";
- DeclaredVerifiableEffects.dump(OS);
- OS << ", "
- << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0)
- << " diags; ";
- OS << " Infer ";
- FXToInfer.dump(OS);
- OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
- if (UnverifiedDirectCalls) {
- OS << "; Calls: ";
- for (const auto &Call : *UnverifiedDirectCalls) {
- CallableInfo CI(SemaRef, *Call.callee());
- OS << " " << CI.name(SemaRef);
- }
- }
- OS << "\n";
- }
-};
-
-// ----------
-class CompleteFunctionAnalysis {
- // Current size: 2 pointers
-public:
- // Has effects which are both the declared ones -- not to be inferred -- plus
- // ones which have been successfully inferred. These are all considered
- // "verified" for the purposes of callers; any issue with verifying declared
- // effects has already been reported and is not the problem of any caller.
- EffectSet VerifiedEffects;
-
-private:
- // This is used to generate notes about failed inference.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
-
-public:
- CompleteFunctionAnalysis(
- ASTContext &Ctx, PendingFunctionAnalysis &pending,
- const EffectSet &funcFX,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- VerifiedEffects.insert(funcFX);
- for (const auto &effect : AllInferrableEffectsToVerify) {
- if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
- VerifiedEffects.insert(effect);
- }
- }
-
- InferrableEffectToFirstDiagnostic =
- std::move(pending.InferrableEffectToFirstDiagnostic);
- }
-
- const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &effect) {
- // TODO: is this correct?
- return InferrableEffectToFirstDiagnostic.lookup(effect);
- }
-
- void dump(llvm::raw_ostream &OS) const {
- OS << "Complete: Verified ";
- VerifiedEffects.dump(OS);
- OS << "; Infer ";
- OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
- }
-};
-
-const Decl *CanonicalFunctionDecl(const Decl *D) {
- if (auto *FD = dyn_cast<FunctionDecl>(D)) {
- FD = FD->getCanonicalDecl();
- tmp_assert(FD != nullptr);
- return FD;
- }
- return D;
-}
-
-// ==========
-class Analyzer {
- constexpr static int DebugLogLevel = 0;
- // --
- Sema &Sem;
-
- // used from Sema:
- // SmallVector<const Decl *> CallablesWithEffectsToVerify
-
- // Subset of Sema.AllEffectsToVerify
- EffectSet AllInferrableEffectsToVerify;
-
- using FuncAnalysisPtr =
- llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
-
- // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
- // than complete state, so use different objects to represent them.
- // The state pointers are owned by the container.
- class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
- using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
-
- public:
- ~AnalysisMap();
-
- // Use non-public inheritance in order to maintain the invariant
- // that lookups and insertions are via the canonical Decls.
-
- FuncAnalysisPtr lookup(const Decl *Key) const {
- return Base::lookup(CanonicalFunctionDecl(Key));
- }
-
- FuncAnalysisPtr &operator[](const Decl *Key) {
- return Base::operator[](CanonicalFunctionDecl(Key));
- }
-
- /// Shortcut for the case where we only care about completed analysis.
- CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
- if (auto AP = lookup(D)) {
- if (isa<CompleteFunctionAnalysis *>(AP)) {
- return AP.get<CompleteFunctionAnalysis *>();
- }
- }
- return nullptr;
- }
-
- void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
- OS << "\nAnalysisMap:\n";
- for (const auto &item : *this) {
- CallableInfo CI(SemaRef, *item.first);
- const auto AP = item.second;
- OS << item.first << " " << CI.name(SemaRef) << " : ";
- if (AP.isNull()) {
- OS << "null\n";
- } else if (isa<CompleteFunctionAnalysis *>(AP)) {
- auto *CFA = AP.get<CompleteFunctionAnalysis *>();
- OS << CFA << " ";
- CFA->dump(OS);
- } else if (isa<PendingFunctionAnalysis *>(AP)) {
- auto *PFA = AP.get<PendingFunctionAnalysis *>();
- OS << PFA << " ";
- PFA->dump(SemaRef, OS);
- } else
- llvm_unreachable("never");
- }
- OS << "---\n";
- }
- };
- AnalysisMap DeclAnalysis;
-
-public:
- Analyzer(Sema &S) : Sem(S) {}
-
- void run(const TranslationUnitDecl &TU) {
- // Gather all of the effects to be verified to see what operations need to
- // be checked, and to see which ones are inferrable.
- {
- for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
- const FunctionEffect &Effect = CFE.Effect;
- const auto Flags = Effect.flags();
- if (Flags & FunctionEffect::FE_InferrableOnCallees) {
- AllInferrableEffectsToVerify.insert(Effect);
- }
- }
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "AllInferrableEffectsToVerify: ";
- AllInferrableEffectsToVerify.dump(llvm::outs());
- llvm::outs() << "\n";
- }
- }
-
- SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithEffectsToVerify;
-
- // It's helpful to use CallablesWithEffectsToVerify as a stack for a
- // depth-first traversal rather than have a secondary container. But first,
- // reverse it, so Decls are verified in the order they are declared.
- std::reverse(verifyQueue.begin(), verifyQueue.end());
-
- while (!verifyQueue.empty()) {
- const Decl *D = verifyQueue.back();
- if (auto AP = DeclAnalysis.lookup(D)) {
- if (isa<CompleteFunctionAnalysis *>(AP)) {
- // already done
- verifyQueue.pop_back();
- continue;
- }
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // All children have been traversed; finish analysis.
- auto *pending = AP.get<PendingFunctionAnalysis *>();
- finishPendingAnalysis(D, pending);
- verifyQueue.pop_back();
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
- }
-
- auto *Pending = verifyDecl(D);
- if (Pending == nullptr) {
- // completed now
- verifyQueue.pop_back();
- continue;
- }
-
- for (auto &Call : Pending->unverifiedCalls()) {
- // This lookup could be optimized out if the results could have been
- // saved from followCall when we traversed the caller's AST. It would
- // however make the check for recursion more complex.
- auto AP = DeclAnalysis.lookup(Call.callee());
- if (AP.isNull()) {
- verifyQueue.push_back(Call.callee());
- continue;
- }
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // This indicates recursion (not necessarily direct). For the
- // purposes of effect analysis, we can just ignore it since
- // no effects forbid recursion.
- Call.setRecursed();
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
- }
- }
- }
-
-private:
- // Verify a single Decl. Return the pending structure if that was the result,
- // else null. This method must not recurse.
- PendingFunctionAnalysis *verifyDecl(const Decl *D) {
- CallableInfo CInfo(Sem, *D);
- bool isExternC = false;
-
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- tmp_assert(FD->getBuiltinID() == 0);
- isExternC = FD->getCanonicalDecl()->isExternCContext();
- }
-
- // For C++, with non-extern "C" linkage only - if any of the Decl's declared
- // effects forbid throwing (e.g. nonblocking) then the function should also
- // be declared noexcept.
- if (Sem.getLangOpts().CPlusPlus && !isExternC) {
- for (const auto &Effect : CInfo.Effects) {
- if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
- continue;
-
- const FunctionProtoType *FPT = nullptr;
- if (auto *FD = D->getAsFunction()) {
- FPT = FD->getType()->getAs<FunctionProtoType>();
- } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
- if (auto *TSI = BD->getSignatureAsWritten()) {
- FPT = TSI->getType()->getAs<FunctionProtoType>();
- }
- }
- if (FPT && FPT->canThrow() != clang::CT_Cannot) {
- Sem.Diag(D->getBeginLoc(),
- diag::warn_perf_constraint_implies_noexcept)
- << Effect.name();
- }
- break;
- }
- }
-
- // Build a PendingFunctionAnalysis on the stack. If it turns out to be
- // complete, we'll have avoided a heap allocation; if it's incomplete, it's
- // a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
-
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(Sem, llvm::outs());
- }
-
- FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
-
- Visitor.run();
- if (FAnalysis.isComplete()) {
- completeAnalysis(CInfo, FAnalysis);
- return nullptr;
- }
- // Copy the pending analysis to the heap and save it in the map.
- auto *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
- DeclAnalysis[D] = PendingPtr;
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "inserted pending " << PendingPtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
- }
- return PendingPtr;
- }
-
- // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis
- // and inserted in the container.
- void completeAnalysis(const CallableInfo &CInfo,
- PendingFunctionAnalysis &Pending) {
- if (auto *Diags = Pending.getDiagnosticsForExplicitFX()) {
- emitDiagnostics(*Diags, CInfo, Sem);
- }
- auto *CompletePtr = new CompleteFunctionAnalysis(
- Sem.getASTContext(), Pending, CInfo.Effects,
- AllInferrableEffectsToVerify);
- DeclAnalysis[CInfo.CDecl] = CompletePtr;
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "inserted complete " << CompletePtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
- }
- }
-
- // Called after all direct calls requiring inference have been found -- or
- // not. Generally replicates FunctionBodyASTVisitor::followCall() but without
- // the possibility of inference.
- void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
- CallableInfo Caller(Sem, *D);
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
- Pending->dump(Sem, llvm::outs());
- llvm::outs() << "\n";
- }
- for (const auto &Call : Pending->unverifiedCalls()) {
- if (Call.recursed())
- continue;
-
- CallableInfo Callee(Sem, *Call.callee());
- followCall(Caller, *Pending, Callee, Call.CallLoc,
- /*AssertNoFurtherInference=*/true);
- }
- completeAnalysis(Caller, *Pending);
- delete Pending;
- }
-
- // Here we have a call to a Decl, either explicitly via a CallExpr or some
- // other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
- const CallableInfo &Callee, SourceLocation CallLoc,
- bool AssertNoFurtherInference) {
- const bool DirectCall = Callee.isDirectCall();
-
- // These will be its declared effects.
- EffectSet CalleeEffects = Callee.Effects;
-
- bool IsInferencePossible = DirectCall;
-
- if (DirectCall) {
- if (auto *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) {
- tmp_assert(!IsInferencePossible);
- }
- if (!Callee.isVerifiable()) {
- IsInferencePossible = false;
- }
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
- CalleeEffects.dump(llvm::outs());
- llvm::outs() << "\n";
- llvm::outs() << " callee " << Callee.CDecl << " canonical "
- << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
- for (auto *D : Callee.CDecl->redecls()) {
- llvm::outs() << " " << D;
- }
- llvm::outs() << "\n";
- }
-
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- const auto Flags = Effect.flags();
- const bool diagnose =
- Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
- if (diagnose) {
- // If inference is not allowed, or the target is indirect (virtual
- // method/function ptr?), generate a diagnostic now.
- if (!IsInferencePossible ||
- !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
- if (Callee.FuncType == SpecialFuncType::None) {
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
- CallLoc, Callee.CDecl});
- } else {
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
- }
- } else {
- // Inference is allowed and necessary; defer it.
- PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
- }
- }
- };
-
- for (const auto &Effect : PFA.DeclaredVerifiableEffects) {
- check1Effect(Effect, false);
- }
-
- for (const auto &Effect : PFA.FXToInfer) {
- check1Effect(Effect, true);
- }
- }
-
- // Should only be called when determined to be complete.
- void emitDiagnostics(SmallVector<Diagnostic> &Diags,
- const CallableInfo &CInfo, Sema &S) {
- if (Diags.empty())
- return;
- const SourceManager &SM = S.getSourceManager();
- std::sort(Diags.begin(), Diags.end(),
- [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
- return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
- });
-
- // TODO: Can we get better template instantiation notes?
- auto checkAddTemplateNote = [&](const Decl *D) {
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- while (FD != nullptr && FD->isTemplateInstantiation()) {
- S.Diag(FD->getPointOfInstantiation(),
- diag::note_func_effect_from_template);
- FD = FD->getTemplateInstantiationPattern();
- }
- }
- };
-
- // Top-level diagnostics are warnings.
- for (const auto &Diag : Diags) {
- StringRef effectName = Diag.Effect.name();
- switch (Diag.ID) {
- case DiagnosticID::None:
- case DiagnosticID::DeclDisallowsInference: // shouldn't happen
- // here
- llvm_unreachable("Unexpected diagnostic kind");
- break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
-
- case DiagnosticID::CallsDeclWithoutEffect: {
- CallableInfo CalleeInfo(S, *Diag.Callee);
- auto CalleeName = CalleeInfo.name(S);
-
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
- << effectName << CalleeName;
- checkAddTemplateNote(CInfo.CDecl);
-
- // Emit notes explaining the transitive chain of inferences: Why isn't
- // the callee safe?
- for (const auto *Callee = Diag.Callee; Callee != nullptr;) {
- std::optional<CallableInfo> MaybeNextCallee;
- auto *Completed =
- DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
- if (Completed == nullptr) {
- // No result - could be
- // - non-inline
- // - virtual
- if (CalleeInfo.CType == CallType::Virtual) {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
- << effectName;
- } else if (CalleeInfo.CType == CallType::Unknown) {
- S.Diag(Callee->getLocation(),
- diag::note_func_effect_call_func_ptr)
- << effectName;
- } else if (CalleeInfo.Effects.contains(
- Diag.Effect.oppositeKind())) {
- S.Diag(Callee->getLocation(),
- diag::note_func_effect_call_disallows_inference)
- << effectName;
- } else {
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
- << effectName;
- }
- break;
- }
- const auto *pDiag2 = Completed->firstDiagnosticForEffect(Diag.Effect);
- if (pDiag2 == nullptr) {
- break;
- }
-
- const auto &Diag2 = *pDiag2;
- switch (Diag2.ID) {
- case DiagnosticID::None:
- llvm_unreachable("Unexpected diagnostic kind");
- break;
- case DiagnosticID::DeclDisallowsInference:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
- << effectName;
- break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
- << effectName;
- break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
- break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
- << effectName;
- break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
- << effectName;
- break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
- << effectName;
- break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
- break;
- case DiagnosticID::CallsDeclWithoutEffect:
- MaybeNextCallee.emplace(S, *Diag2.Callee);
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
- << effectName << MaybeNextCallee->name(S);
- break;
- }
- checkAddTemplateNote(Callee);
- Callee = Diag2.Callee;
- if (MaybeNextCallee) {
- CalleeInfo = *MaybeNextCallee;
- CalleeName = CalleeInfo.name(S);
- }
- }
- } break;
- }
- }
- }
-
- // ----------
- // This AST visitor is used to traverse the body of a function during effect
- // verification. This happens in 2 situations:
- // [1] The function has declared effects which need to be validated.
- // [2] The function has not explicitly declared an effect in question, and is
- // being checked for implicit conformance.
- //
- // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
- // all diagnostic output.
- //
- // TODO: Currently we create a new RecursiveASTVisitor for every function
- // analysis. Is it so lightweight that this is OK? It would appear so.
- struct FunctionBodyASTVisitor
- : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
- constexpr static bool Stop = false;
- constexpr static bool Proceed = true;
-
- Analyzer &Outer;
- PendingFunctionAnalysis &CurrentFunction;
- CallableInfo &CurrentCaller;
-
- FunctionBodyASTVisitor(Analyzer &outer,
- PendingFunctionAnalysis &CurrentFunction,
- CallableInfo &CurrentCaller)
- : Outer(outer), CurrentFunction(CurrentFunction),
- CurrentCaller(CurrentCaller) {}
-
- // -- Entry point --
- void run() {
- // The target function itself may have some implicit code paths beyond the
- // body: member and base constructors and destructors. Visit these first.
- if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
- if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
- for (const CXXCtorInitializer *Initer : Ctor->inits()) {
- if (Expr *Init = Initer->getInit()) {
- VisitStmt(Init);
- }
- }
- } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
- followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
- } else if (!FD->isTrivial() && FD->isDefaulted()) {
- // needed? maybe not
- }
- }
- // else could be BlockDecl
-
- // Do an AST traversal of the function/block body
- TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
- }
-
- // -- Methods implementing common logic --
-
- // Handle a language construct forbidden by some effects. Only effects whose
- // flags include the specified flag receive a diagnostic. \p Flag describes
- // the construct.
- void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
- SourceLocation Loc,
- const Decl *Callee = nullptr) {
- // If there are ANY declared verifiable effects holding the flag, store
- // just one diagnostic.
- for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
- if (Effect.flags() & Flag) {
- addDiagnosticInner(/*inferring=*/false, Effect, D, Loc, Callee);
- break;
- }
- }
- // For each inferred-but-not-verifiable effect holding the flag, store a
- // diagnostic, if we don't already have a diagnostic for that effect.
- for (const auto &Effect : CurrentFunction.FXToInfer) {
- if (Effect.flags() & Flag) {
- addDiagnosticInner(/*inferring=*/true, Effect, D, Loc, Callee);
- }
- }
- }
-
- void addDiagnosticInner(bool Inferring, const FunctionEffect &Effect,
- DiagnosticID D, SourceLocation Loc,
- const Decl *Callee = nullptr) {
- CurrentFunction.checkAddDiagnostic(Inferring,
- Diagnostic(Effect, D, Loc, Callee));
- }
-
- // Here we have a call to a Decl, either explicitly via a CallExpr or some
- // other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
- if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl)) {
- // Currently, built-in functions are always considered safe.
- if (FD->getBuiltinID() != 0)
- return;
- }
- Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
- /*AssertNoFurtherInference=*/false);
- }
-
- void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
- const auto CalleeType = CalleeExpr->getType();
- auto *FPT =
- CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- EffectSet CalleeFX;
- if (FPT) {
- CalleeFX.insert(FPT->getFunctionEffects());
- }
-
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
- /*direct=*/false, CalleeFX)) {
- addDiagnosticInner(Inferring, Effect,
- DiagnosticID::CallsExprWithoutEffect,
- Call->getBeginLoc());
- }
- };
-
- for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
- check1Effect(Effect, false);
- }
-
- for (const auto &Effect : CurrentFunction.FXToInfer) {
- check1Effect(Effect, true);
- }
- }
-
- // This destructor's body should be followed by the caller, but here we
- // follow the field and base destructors.
- void followDestructor(const CXXRecordDecl *Rec,
- const CXXDestructorDecl *Dtor) {
- for (const FieldDecl *Field : Rec->fields()) {
- followTypeDtor(Field->getType());
- }
-
- if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
- for (const CXXBaseSpecifier &Base : Class->bases()) {
- followTypeDtor(Base.getType());
- }
- for (const CXXBaseSpecifier &Base : Class->vbases()) {
- followTypeDtor(Base.getType());
- }
- }
- }
-
- void followTypeDtor(QualType QT) {
- const Type *Ty = QT.getTypePtr();
- while (Ty->isArrayType()) {
- const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
- QT = Arr->getElementType();
- Ty = QT.getTypePtr();
- }
-
- if (Ty->isRecordType()) {
- if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
- if (auto *Dtor = Class->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
- followCall(CI, Dtor->getLocation());
- }
- }
- return;
- }
- if (Ty->isScalarType() || Ty->isReferenceType() || Ty->isAtomicType()) {
- return;
- }
-
- llvm::errs() << "warning: " << QT << ": unknown special functions\n";
- }
-
- // -- Methods for use of RecursiveASTVisitor --
-
- bool shouldVisitImplicitCode() const { return true; }
-
- bool shouldWalkTypesOfTypeLocs() const { return false; }
-
- bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- DiagnosticID::Throws, Throw->getThrowLoc());
- return Proceed;
- }
-
- bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- DiagnosticID::Catches, Catch->getCatchLoc());
- return Proceed;
- }
-
- bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
- DiagnosticID::CallsObjC, Msg->getBeginLoc());
- return Proceed;
- }
-
- bool VisitCallExpr(CallExpr *Call) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitCallExpr : "
- << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- }
-
- Expr *CalleeExpr = Call->getCallee();
- if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
- CallableInfo CI(Outer.Sem, *Callee);
- followCall(CI, Call->getBeginLoc());
- return Proceed;
- }
-
- if (isa<CXXPseudoDestructorExpr>(CalleeExpr)) {
- // just destroying a scalar, fine.
- return Proceed;
- }
-
- checkIndirectCall(Call, CalleeExpr);
-
- // No Decl, just an Expr. It could be a cast or something; we could dig
- // into it and look for a DeclRefExpr. But for now it should suffice
- // to look at the type of the Expr. Well, unless we're in a template :-/
-
- // llvm::errs() << "CalleeExpr ";
- // CalleeExpr->dumpColor();
- // llvm::errs() <<
- // Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ":
- // warning: null callee\n";
-
- return Proceed;
- }
-
- bool VisitVarDecl(VarDecl *Var) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitVarDecl : "
- << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- }
-
- if (Var->isStaticLocal()) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
- DiagnosticID::HasStaticLocal,
- Var->getLocation());
- }
-
- const QualType::DestructionKind DK =
- Var->needsDestruction(Outer.Sem.getASTContext());
- if (DK == QualType::DK_cxx_destructor) {
- QualType QT = Var->getType();
- if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
- if (const auto *CxxRec =
- dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
- if (const auto *Dtor = CxxRec->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
- followCall(CI, Var->getLocation());
- }
- }
- }
- }
- return Proceed;
- }
-
- bool VisitCXXNewExpr(CXXNewExpr *New) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator new.
- if (auto *FD = New->getOperatorNew()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
- followCall(CI, New->getBeginLoc());
- }
-
- // It's a bit excessive to check operator delete here, since it's
- // just a fallback for operator new followed by a failed constructor.
- // We could check it via New->getOperatorDelete().
-
- // It DOES however visit the called constructor
- return Proceed;
- }
-
- bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator delete.
- if (auto *FD = Delete->getOperatorDelete()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
- followCall(CI, Delete->getBeginLoc());
- }
-
- // It DOES however visit the called destructor
-
- return Proceed;
- }
-
- bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitCXXConstructExpr : "
- << Construct->getBeginLoc().printToString(
- Outer.Sem.SourceMgr)
- << "\n";
- }
-
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to the constructor.
- const CXXConstructorDecl *Ctor = Construct->getConstructor();
- CallableInfo CI(Outer.Sem, *Ctor);
- followCall(CI, Construct->getLocation());
-
- return Proceed;
- }
-
- bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
- if (auto *Expr = DEI->getExpr()) {
- TraverseStmt(Expr);
- }
- return Proceed;
- }
-
- bool TraverseLambdaExpr(LambdaExpr *Lambda) {
- // We override this so as the be able to skip traversal of the lambda's
- // body. We have to explicitly traverse the captures.
- for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) {
- if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
- Lambda->capture_init_begin()[I]) == Stop)
- return Stop;
- }
-
- return Proceed;
- }
-
- bool TraverseBlockExpr(BlockExpr * /*unused*/) {
- // TODO: are the capture expressions (ctor call?) safe?
- return Proceed;
- }
-
- bool VisitDeclRefExpr(const DeclRefExpr *E) {
- const ValueDecl *Val = E->getDecl();
- if (isa<VarDecl>(Val)) {
- const VarDecl *Var = cast<VarDecl>(Val);
- const auto TLSK = Var->getTLSKind();
- if (TLSK != VarDecl::TLS_None) {
- // At least on macOS, thread-local variables are initialized on
- // first access, including a heap allocation.
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
- DiagnosticID::AccessesThreadLocal,
- E->getLocation());
- }
- }
- return Proceed;
- }
-
- // Unevaluated contexts: need to skip
- // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
-
- bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
- return TraverseStmt(Node->getResultExpr());
- }
- bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
- return Proceed;
- }
-
- bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
-
- bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
-
- bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
-
- bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
- };
-};
-
-Analyzer::AnalysisMap::~AnalysisMap() {
- for (const auto &Item : *this) {
- auto AP = Item.second;
- if (isa<PendingFunctionAnalysis *>(AP)) {
- delete AP.get<PendingFunctionAnalysis *>();
- } else {
- delete AP.get<CompleteFunctionAnalysis *>();
- }
- }
-}
-
-} // namespace FXAnalysis
-#endif // FX_ANALYZER_ENABLED
-
-// =============================================================================
-
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
@@ -3860,11 +2534,6 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
SourceLocation())) {
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
}
-
-#if FX_ANALYZER_ENABLED
- // TODO: skip this if the warning isn't enabled.
- FXAnalysis::Analyzer{S}.run(*TU);
-#endif
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index ee89c0c326ddb..54153d630e139 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11147,51 +11147,6 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
return nullptr;
}
-// Should only be called when getFunctionEffects() returns a non-empty set.
-// Decl should be a FunctionDecl or BlockDecl.
-void Sema::maybeAddDeclWithEffects(const Decl *D,
- const FunctionEffectsRef &FX) {
- if (!D->hasBody()) {
- if (const auto *FD = D->getAsFunction()) {
- if (!FD->willHaveBody()) {
- return;
- }
- }
- }
-
- if (Diags.getIgnoreAllWarnings() ||
- (Diags.getSuppressSystemWarnings() &&
- SourceMgr.isInSystemHeader(D->getLocation())))
- return;
-
- if (hasUncompilableErrorOccurred())
- return;
-
- // For code in dependent contexts, we'll do this at instantiation time.
- // Without this check, we would analyze the function based on placeholder
- // template parameters, and potentially generate spurious diagnostics.
- if (cast<DeclContext>(D)->isDependentContext())
- return;
-
- // Effects which are not inferrable (e.g. nonblocking(false) don't need
- // to be verified later in the deferred pass. (If they do need to be
- // verified, that can happen immediately at the call site, like TCB.)
- if (llvm::none_of(FX.effects(), [](const FunctionEffect &E) {
- return (E.flags() & FunctionEffect::FE_InferrableOnCallees) != 0;
- }))
- return;
-
- addDeclWithEffects(D, FX);
-}
-
-void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
- // Ignore any conditions when building the list of effects.
- AllEffectsToVerify.insertIgnoringConditions(FX);
-
- // Record the declaration for later analysis.
- DeclsWithEffectsToVerify.push_back(D);
-}
-
/// Determines if we can perform a correct type check for \p D as a
/// redeclaration of \p PrevDecl. If not, we can generally still perform a
/// best-effort check.
@@ -16009,11 +15964,6 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
- if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
- !FX.empty()) {
- maybeAddDeclWithEffects(FD, FX);
- }
-
return D;
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 96968d5e5ed05..5c861467bc102 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9435,8 +9435,6 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
return Sema::IncompatibleFunctionPointer;
return Sema::IncompatiblePointer;
}
- // #85415: This call to IsFunctionConversion appears to have inverted
- // arguments: ltrans -> From, rtrans -> To
if (!S.getLangOpts().CPlusPlus &&
S.IsFunctionConversion(ltrans, rtrans, ltrans))
return Sema::IncompatibleFunctionPointer;
@@ -16581,10 +16579,6 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
BlockDecl *BD = BSI->TheDecl;
- if (const auto FX = BD->getFunctionEffects(); !FX.empty()) {
- maybeAddDeclWithEffects(BD, FX);
- }
-
if (BSI->HasImplicitReturnType)
deduceClosureReturnType(*BSI);
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 2acd78ff3e617..1743afaf15287 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1890,11 +1890,6 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
ActOnFinishFunctionBody(LSI.CallOperator, Body);
-
- if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty()) {
- maybeAddDeclWithEffects(LSI.CallOperator, FX);
- }
-
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
}
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index eb9f956a272fe..c99d6ed1c36c8 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3853,12 +3853,6 @@ 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; ++I)
- DeclsWithEffectsToVerify.push_back(
- getGlobalDeclID(F, LocalDeclID(Record[I])));
- break;
-
case OPENCL_EXTENSIONS:
for (unsigned I = 0, E = Record.size(); I != E; ) {
auto Name = ReadString(Record, I);
@@ -8236,20 +8230,6 @@ void ASTReader::InitializeSema(Sema &S) {
NewOverrides.applyOverrides(SemaObj->getLangOpts());
}
- if (!DeclsWithEffectsToVerify.empty()) {
- for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
- Decl *D = GetDecl(ID);
- FunctionEffectsRef FX;
- if (auto *FD = dyn_cast<FunctionDecl>(D))
- FX = FD->getFunctionEffects();
- else if (auto *BD = dyn_cast<BlockDecl>(D))
- FX = BD->getFunctionEffects();
- if (!FX.empty())
- SemaObj->addDeclWithEffects(D, FX);
- }
- DeclsWithEffectsToVerify.clear();
- }
-
SemaObj->OpenCLFeatures = OpenCLExtensions;
UpdateSema();
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index c6ce60a6bed16..0408eeb6a95b0 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4494,17 +4494,6 @@ 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.
@@ -5399,7 +5388,6 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
}
WritePackPragmaOptions(SemaRef);
WriteFloatControlPragmaOptions(SemaRef);
- WriteDeclsWithEffectsToVerify(SemaRef);
// Some simple statistics
RecordData::value_type Record[] = {
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
deleted file mode 100644
index 21d14a2998c38..0000000000000
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ /dev/null
@@ -1,198 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
-// the AnalysisBasedWarnings pass from running at all.
-
-#if !__has_attribute(clang_nonblocking)
-#error "the 'nonblocking' attribute is not available"
-#endif
-
-// This diagnostic is re-enabled and exercised in isolation later in this file.
-#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
-
-// --- CONSTRAINTS ---
-
-void nl1() [[clang::nonblocking]]
-{
- auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
-}
-
-void nl2() [[clang::nonblocking]]
-{
- static int global; // expected-warning {{'nonblocking' function must not have static locals}}
-}
-
-void nl3() [[clang::nonblocking]]
-{
- try {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
- }
- catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
- }
-}
-
-void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-
-void nl4() [[clang::nonblocking]]
-{
- nl4_inline(); // OK
- nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-}
-
-
-struct HasVirtual {
- virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
-};
-
-void nl5() [[clang::nonblocking]]
-{
- HasVirtual hv;
- hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-}
-
-void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void nl6_transitively_unsafe()
-{
- nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
-}
-
-void nl6() [[clang::nonblocking]]
-{
- nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-}
-
-thread_local int tl_var{ 42 };
-
-bool tl_test() [[clang::nonblocking]]
-{
- return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
-}
-
-void nl7()
-{
- // Make sure we verify blocks
- auto blk = ^() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
- };
-}
-
-void nl8()
-{
- // Make sure we verify lambdas
- auto lambda = []() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
- };
-}
-
-// Make sure template expansions are found and verified.
- template <typename T>
- struct Adder {
- static T add_explicit(T x, T y) [[clang::nonblocking]]
- {
- return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
- }
- static T add_implicit(T x, T y)
- {
- return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
- }
- };
-
- struct Stringy {
- friend Stringy operator+(const Stringy& x, const Stringy& y)
- {
- // Do something inferably unsafe
- auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}}
- return {};
- }
- };
-
- struct Stringy2 {
- friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
- {
- // Do something inferably unsafe
- throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
- }
- };
-
-void nl9() [[clang::nonblocking]]
-{
- Adder<int>::add_explicit(1, 2);
- Adder<int>::add_implicit(1, 2);
-
- Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
- Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \
- expected-note {{in template expansion here}}
-}
-
-void nl10(
- void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
- void (*fp2)() [[clang::nonblocking]]
- ) [[clang::nonblocking]]
-{
- fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
- fp2();
-}
-
-// Interactions with nonblocking(false)
-void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
-{
-}
-void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
-
-template <bool V>
-struct ComputedNB {
- void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
-};
-
-void nl11() [[clang::nonblocking]]
-{
- nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
- nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-
- ComputedNB<true> CNB_true;
- CNB_true.method();
-
- ComputedNB<false> CNB_false;
- CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-}
-
-// Verify that when attached to a redeclaration, the attribute successfully attaches.
-void nl12() {
- static int x; // expected-warning {{'nonblocking' function must not have static locals}}
-}
-void nl12() [[clang::nonblocking]];
-void nl13() [[clang::nonblocking]] { nl12(); }
-
-// C++ member function pointers
-struct PTMFTester {
- typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
-
- void convert() [[clang::nonblocking]];
-
- ConvertFunction mConvertFunc;
-};
-
-void PTMFTester::convert() [[clang::nonblocking]]
-{
- (this->*mConvertFunc)();
-}
-
-// Block variables
-void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
- blk();
-}
-
-// References to blocks
-void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
-{
- auto &ref = block;
- ref();
-}
-
-
-// --- nonblocking implies noexcept ---
-#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
-
-void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
-{
-}
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
deleted file mode 100644
index aeb8b21f56e44..0000000000000
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-
-#if !__has_attribute(clang_nonblocking)
-#error "the 'nonblocking' attribute is not available"
-#endif
-
-#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
-
-// Objective-C
- at interface OCClass
-- (void)method;
- at end
-
-void nl14(OCClass *oc) [[clang::nonblocking]] {
- [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
-}
-void nl15(OCClass *oc) {
- [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
-}
-void nl16(OCClass *oc) [[clang::nonblocking]] {
- nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
-}
-
>From b6cfea80a01939ea35b8c3bbab35676472028444 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 2 May 2024 12:33:42 -0700
Subject: [PATCH 47/71] In Sema::ImpCastExprToType, the check for a nullptr
source was unnecessarily complex, and triggering an assertion failure.
---
clang/lib/Sema/Sema.cpp | 5 ++---
clang/test/Sema/attr-nonblocking-sema.cpp | 3 +++
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 8ae8874a59354..c1a4088382c12 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -684,10 +684,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
diagnoseZeroToNullptrConversion(Kind, E);
- if (!isCast(CCK) &&
- !E->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNotNull)) {
+ if (!isCast(CCK) && Kind != CK_NullToPointer &&
+ Kind != CK_NullToMemberPointer)
diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
- }
QualType ExprTy = Context.getCanonicalType(E->getType());
QualType TypeTy = Context.getCanonicalType(Ty);
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index ba86420d6c814..cf6ff5994af3a 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -42,18 +42,21 @@ void type_conversions()
// It's fine to remove a performance constraint.
void (*fp_plain)();
+ fp_plain = nullptr;
fp_plain = unannotated;
fp_plain = nonblocking;
fp_plain = nonallocating;
// Adding/spoofing nonblocking is unsafe.
void (*fp_nonblocking)() [[clang::nonblocking]];
+ fp_nonblocking = nullptr;
fp_nonblocking = nonblocking;
fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
// Adding/spoofing nonallocating is unsafe.
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 = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}}
>From 711bc562686b34858b6b9d0d36cc486c1271d756 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 2 May 2024 14:52:44 -0700
Subject: [PATCH 48/71] Better attribute documentation
---
clang/include/clang/Basic/Attr.td | 8 +--
clang/include/clang/Basic/AttrDocs.td | 72 +++++++++++++++++++++++----
2 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a218dbf54fb9e..dd2eff71e1821 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1441,7 +1441,7 @@ def NonBlocking : TypeAttr {
GNU<"clang_nonblocking">];
let Args = [ExprArgument<"Cond", /*optional*/1>];
- let Documentation = [NonBlockingNonAllocatingDocs];
+ let Documentation = [NonBlockingDocs];
}
def NonAllocating : TypeAttr {
@@ -1449,7 +1449,7 @@ def NonAllocating : TypeAttr {
C23<"clang", "nonallocating">,
GNU<"clang_nonallocating">];
let Args = [ExprArgument<"Cond", /*optional*/1>];
- let Documentation = [NonBlockingNonAllocatingDocs];
+ let Documentation = [NonAllocatingDocs];
}
def Blocking : TypeAttr {
@@ -1457,14 +1457,14 @@ def Blocking : TypeAttr {
C23<"clang", "blocking">,
GNU<"clang_blocking">];
- let Documentation = [NonBlockingNonAllocatingDocs];
+ let Documentation = [BlockingDocs];
}
def Allocating : TypeAttr {
let Spellings = [CXX11<"clang", "allocating">,
C23<"clang", "allocating">,
GNU<"clang_allocating">];
- let Documentation = [NonBlockingNonAllocatingDocs];
+ let Documentation = [AllocatingDocs];
}
// Similar to CUDA, OpenCL attributes do not receive a [[]] spelling because
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 0b4ed5a1dc626..d3c97ff304b6c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8058,19 +8058,69 @@ requirement:
}];
}
-def NonBlockingNonAllocatingDocs : Documentation {
- let Category = DocCatType;
+def DocCatNonBlockingNonAllocating : DocumentationCategory<"Performance constraint attributes"> {
+ let Content = [{
+The ``nonblocking``, ``blocking``, ``nonallocating`` and ``allocating`` attributes can be attached
+to function types, including blocks, C++ lambdas, and member functions. The attributes declare
+constraints about a function's behavior pertaining to blocking and heap memory allocation.
+
+There are several rules for function types with these attributes, enforced with
+compiler warnings:
+
+- When assigning or otherwise converting to a function pointer of ``nonblocking`` or
+ ``nonallocating`` type, the source must also be a function or function pointer of
+ that type, unless it is a null pointer, i.e. the attributes should not be "spoofed". Conversions
+ that remove the attributes are transparent and valid.
+
+- An override of a ``nonblocking`` or ``nonallocating`` virtual method must also be declared
+ with that same attribute (or a stronger one.) An overriding method may add an attribute.
+
+- 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.
+
+In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating``
+functions to other functions which lack the appropriate attribute.
+ }];
+}
+
+def NonBlockingDocs : Documentation {
+ let Category = DocCatNonBlockingNonAllocating;
+ let Heading = "nonblocking";
+ let Content = [{
+Declares that a function or function type either does or does not block in any way, according
+to the optional, compile-time constant boolean argument, which defaults to true. When the argument
+is false, the attribute is equivalent to ``blocking``.
+
+For the purposes of diagnostics, ``nonblocking`` is considered to include the
+``nonallocating`` guarantee and is therefore a "stronger" constraint or attribute.
+ }];
+}
+
+def NonAllocatingDocs : Documentation {
+ let Category = DocCatNonBlockingNonAllocating;
+ let Heading = "nonallocating";
let Content = [{
-The ``nonblocking`` and ``nonallocating`` 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.
+Declares that a function or function type either does or does not allocate heap memory, according
+to the optional, compile-time constant boolean argument, which defaults to true. When the argument
+is false, the attribute is equivalent to ``allocating``.
+ }];
+}
-Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a
-compile-time constant boolean expression. By default, the argument is true, so
-``[[clang::nonblocking(true)]]`` is equivalent to ``[[clang::nonblocking]]``, and declares
-the function type as never locking.
+def BlockingDocs : Documentation {
+ let Category = DocCatNonBlockingNonAllocating;
+ let Heading = "blocking";
+ let Content = [{
+Declares that a function potentially blocks, and prevents any potential inference of ``nonblocking``
+by the compiler.
+ }];
+}
-TODO: how much of the RFC to include here? Make it a separate page?
+def AllocatingDocs : Documentation {
+ let Category = DocCatNonBlockingNonAllocating;
+ let Heading = "allocating";
+ let Content = [{
+Declares that a function potentially allocates heap memory, and prevents any potential inference
+of ``nonallocating`` by the compiler.
}];
}
+
>From 990f5bbacf639ddae83008d6d909fc735fe693a2 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 3 May 2024 08:40:13 -0700
Subject: [PATCH 49/71] - Remove disabling of future warning - Fix
capitalization in AttrDocs.td - Clean up parsing of new attributes
---
clang/include/clang/AST/Type.h | 3 +
clang/include/clang/Basic/AttrDocs.td | 2 +-
clang/lib/AST/Type.cpp | 7 +
clang/lib/Sema/SemaType.cpp | 135 +++++++-------------
clang/test/Sema/attr-nonblocking-syntax.cpp | 1 -
5 files changed, 58 insertions(+), 90 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index c10fd0eebddc6..51386695847ed 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4776,6 +4776,9 @@ class FunctionEffectCondition {
struct FunctionEffectWithCondition {
FunctionEffect Effect;
FunctionEffectCondition Cond;
+
+ /// Return a textual description of the effect, and its condition, if any.
+ std::string description() const;
};
struct FunctionEffectDiff {
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index d3c97ff304b6c..8fc4160dc2abc 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8058,7 +8058,7 @@ requirement:
}];
}
-def DocCatNonBlockingNonAllocating : DocumentationCategory<"Performance constraint attributes"> {
+def DocCatNonBlockingNonAllocating : DocumentationCategory<"Performance Constraint Attributes"> {
let Content = [{
The ``nonblocking``, ``blocking``, ``nonallocating`` and ``allocating`` attributes can be attached
to function types, including blocks, C++ lambdas, and member functions. The attributes declare
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6b1f9b787d97d..3ac1c8ce8c4a9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5446,3 +5446,10 @@ FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
return {};
}
+
+std::string FunctionEffectWithCondition::description() const {
+ std::string Result(Effect.name().str());
+ if (Cond.expr() != nullptr)
+ Result += "(expr)";
+ return Result;
+}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 8dddc30adb505..b43bdf06e260c 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -217,19 +217,10 @@ namespace {
/// validating that noderef was used on a pointer or array.
bool parsedNoDeref;
- // Flags to diagnose illegal permutations of nonblocking(cond) and
- // nonallocating(cond). Manual logic for finding previous attributes would
- // be more complex, unless we transformed nonblocking/nonallocating(false)
- // into distinct separate attributes from the ones which are parsed.
- FunctionEffectMode parsedNonBlocking;
- FunctionEffectMode parsedNonAllocating;
-
public:
TypeProcessingState(Sema &sema, Declarator &declarator)
: sema(sema), declarator(declarator),
- chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
- parsedNonBlocking(FunctionEffectMode::None),
- parsedNonAllocating(FunctionEffectMode::None) {}
+ chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false) {}
Sema &getSema() const {
return sema;
@@ -356,17 +347,6 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
- void setParsedNonBlocking(FunctionEffectMode v) { parsedNonBlocking = v; }
- FunctionEffectMode getParsedNonBlocking() const {
- return parsedNonBlocking;
- }
- void setParsedNonAllocating(FunctionEffectMode v) {
- parsedNonAllocating = v;
- }
- FunctionEffectMode getParsedNonAllocating() const {
- return parsedNonAllocating;
- }
-
~TypeProcessingState() {
if (savedAttrs.empty())
return;
@@ -8031,12 +8011,12 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
// Parse the new attribute.
- // non/blocking or non/allocating? Or dependent?
+ // non/blocking or non/allocating? Or conditional (computed)?
const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_Blocking;
Sema &S = TPState.getSema();
- FunctionEffectMode NewState = FunctionEffectMode::None;
+ FunctionEffectMode NewMode = FunctionEffectMode::None;
Expr *CondExpr = nullptr; // only valid if dependent
if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
@@ -8044,96 +8024,75 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Parse the conditional expression, if any
if (PAttr.getNumArgs() > 0) {
CondExpr = PAttr.getArgAsExpr(0);
- ExprResult E = S.ActOnEffectExpression(CondExpr, NewState);
+ ExprResult E = S.ActOnEffectExpression(CondExpr, NewMode);
if (E.isInvalid())
return false;
- CondExpr = E.get();
+ CondExpr = NewMode == FunctionEffectMode::Dependent ? E.get() : nullptr;
} else {
- NewState = FunctionEffectMode::True;
+ NewMode = FunctionEffectMode::True;
}
} else {
// This is the `blocking` or `allocating` attribute.
- NewState = FunctionEffectMode::False;
+ NewMode = FunctionEffectMode::False;
}
- auto attrName = [](bool NB, FunctionEffectMode value) {
- switch (value) {
- case FunctionEffectMode::False:
- return NB ? "blocking" : "allocating";
- case FunctionEffectMode::True:
- return NB ? "nonblocking" : "nonallocating";
- case FunctionEffectMode::Dependent:
- return NB ? "nonblocking(expr)" : "nonallocating(expr)";
- default:
- llvm_unreachable("'FunctionEffectMode::None' shouldn't happen");
- }
- };
+ const FunctionEffect::Kind FEKind =
+ (NewMode == FunctionEffectMode::False)
+ ? (isNonBlocking ? FunctionEffect::Kind::Blocking
+ : FunctionEffect::Kind::Allocating)
+ : (isNonBlocking ? FunctionEffect::Kind::NonBlocking
+ : FunctionEffect::Kind::NonAllocating);
+ const FunctionEffectWithCondition NewEC{FunctionEffect(FEKind),
+ FunctionEffectCondition(CondExpr)};
// Diagnose the newly provided attribute as incompatible with a previous one.
- auto incompatible = [&](bool prevNB, FunctionEffectMode prevValue) {
+ auto incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
- << attrName(isNonBlocking, NewState) << attrName(prevNB, prevValue)
- << false;
+ << NewEC.description() << PrevEC.description() << false;
// we don't necessarily have the location of the previous attribute,
// so no note.
PAttr.setInvalid();
return true;
};
- const FunctionEffectMode PrevState = isNonBlocking
- ? TPState.getParsedNonBlocking()
- : TPState.getParsedNonAllocating();
- if (PrevState != FunctionEffectMode::None) {
- // Only one attribute per constraint is allowed.
- return incompatible(isNonBlocking, PrevState);
+ // Find previous attributes
+ std::optional<FunctionEffectWithCondition> PrevNonBlocking;
+ std::optional<FunctionEffectWithCondition> PrevNonAllocating;
+
+ for (const FunctionEffectWithCondition &PrevEC : FPT->getFunctionEffects()) {
+ if (PrevEC.Effect.kind() == FEKind ||
+ PrevEC.Effect.oppositeKind() == FEKind)
+ return incompatible(PrevEC);
+ switch (PrevEC.Effect.kind()) {
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::NonBlocking:
+ PrevNonBlocking = PrevEC;
+ break;
+ case FunctionEffect::Kind::Allocating:
+ case FunctionEffect::Kind::NonAllocating:
+ PrevNonAllocating = PrevEC;
+ break;
+ default:
+ break;
+ }
}
if (isNonBlocking) {
- // also check nonblocking(true) against allocating
- if (NewState == FunctionEffectMode::True &&
- TPState.getParsedNonAllocating() == FunctionEffectMode::False) {
- return incompatible(false, FunctionEffectMode::False);
- }
- TPState.setParsedNonBlocking(NewState);
+ // new nonblocking(true) is incompatible with previous allocating
+ if (NewMode == FunctionEffectMode::True && PrevNonAllocating &&
+ PrevNonAllocating->Effect.kind() == FunctionEffect::Kind::Allocating)
+ return incompatible(*PrevNonAllocating);
} else {
- // also check nonblocking(true) against allocating
- if (TPState.getParsedNonBlocking() == FunctionEffectMode::True) {
- if (NewState == FunctionEffectMode::False) {
- return incompatible(true, FunctionEffectMode::True);
- }
- }
- TPState.setParsedNonAllocating(NewState);
+ // new allocating is incompatible with previous nonblocking(true)
+ if (NewMode == FunctionEffectMode::False && PrevNonBlocking &&
+ PrevNonBlocking->Effect.kind() == FunctionEffect::Kind::NonBlocking)
+ return incompatible(*PrevNonBlocking);
}
-#if 0
- // Old type-sugar implementation of denied effects
- if (NewState == FunctionEffectMode::False) {
- // blocking and allocating are represented as AttributedType sugar,
- // using those attributes.
- Attr *A = nullptr;
- if (isNonBlocking) {
- A = BlockingAttr::Create(S.Context);
- } else {
- A = AllocatingAttr::Create(S.Context);
- }
- QT = TPState.getAttributedType(A, QT, QT);
- return true;
- }
-#endif
-
- // All forms of the attributes are represented in a
- // FunctionEffectsRef attached to a FunctionProtoType.
- const bool Denied = NewState == FunctionEffectMode::False;
- const FunctionEffect NewEffect(
- isNonBlocking ? (Denied ? FunctionEffect::Kind::Blocking
- : FunctionEffect::Kind::NonBlocking)
- : (Denied ? FunctionEffect::Kind::Allocating
- : FunctionEffect::Kind::NonAllocating));
-
+ // Add the effect to the FunctionProtoType
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEffect,
- NewState == FunctionEffectMode::Dependent ? CondExpr : nullptr);
+ FX.insert(NewEC.Effect, NewEC.Cond.expr());
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 48ca62f3401e4..5ef5d9aa18e8a 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -3,7 +3,6 @@
// 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
>From db576ef87843841a35677501d03fd8cf234d2af6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 3 May 2024 09:16:15 -0700
Subject: [PATCH 50/71] Cleanup dead code.
---
clang/lib/AST/Type.cpp | 30 ------------------------------
1 file changed, 30 deletions(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 3ac1c8ce8c4a9..49184dafd4999 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3665,9 +3665,6 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
ExtraBits.EffectsHaveConditions = true;
auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
-
- assert(isCanonicalUnqualified()); // TODO: because I don't understand this
- // yet...
addDependence(TypeDependence::DependentInstantiation);
}
}
@@ -5201,33 +5198,6 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
}
return true;
-#if 0
- // This is the type-sugar implementation of "denied" effects.
- // Do any of the callee's Decls have type sugar for blocking or allocating?
- for (const Decl *D : Callee.redecls()) {
- QualType QT;
- if (auto *FD = D->getAsFunction()) {
- QT = FD->getType();
- } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
- if (auto *TSI = BD->getSignatureAsWritten())
- QT = TSI->getType();
- else
- continue;
- } else
- continue;
-
- // c.f. Sema::getCallingConvAttributedType
- const AttributedType *AT = QT->getAs<AttributedType>();
- while (AT) {
- if (AT->getAttrKind() == attr::Allocating)
- return false;
- if (kind() == Kind::NonBlocking && AT->getAttrKind() == attr::Blocking)
- return false;
- AT = AT->getModifiedType()->getAs<AttributedType>();
- }
- }
- return true;
-#endif
case Kind::Allocating:
case Kind::Blocking:
return false;
>From b95964c2570c11d1d80ae6874be5e400f7b504ad Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 3 May 2024 15:48:51 -0700
Subject: [PATCH 51/71] - AttrDocs: add note about -W option to control
diagnostics. - Validate attribute argument counts. - Quote attribute names in
diagnostics.
---
clang/include/clang/Basic/AttrDocs.td | 2 ++
clang/lib/Sema/SemaType.cpp | 12 ++++++++++--
clang/test/Sema/attr-nonblocking-sema.cpp | 18 ++++++++++++------
3 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 8fc4160dc2abc..8ab1d74f3a6cb 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8078,6 +8078,8 @@ 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.
+
In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating``
functions to other functions which lack the appropriate attribute.
}];
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index b43bdf06e260c..3bf09eb3cb503 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8021,8 +8021,13 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_NonAllocating) {
+ if (!PAttr.checkAtMostNumArgs(S, 1)) {
+ PAttr.setInvalid();
+ return true;
+ }
+
// Parse the conditional expression, if any
- if (PAttr.getNumArgs() > 0) {
+ if (PAttr.getNumArgs() == 1) {
CondExpr = PAttr.getArgAsExpr(0);
ExprResult E = S.ActOnEffectExpression(CondExpr, NewMode);
if (E.isInvalid())
@@ -8033,6 +8038,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
} else {
// This is the `blocking` or `allocating` attribute.
+ if (S.CheckAttrNoArgs(PAttr))
+ return true;
NewMode = FunctionEffectMode::False;
}
@@ -8048,7 +8055,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Diagnose the newly provided attribute as incompatible with a previous one.
auto incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
- << NewEC.description() << PrevEC.description() << false;
+ << ("'" + NewEC.description() + "'")
+ << ("'" + PrevEC.description() + "'") << false;
// we don't necessarily have the location of the previous attribute,
// so no note.
PAttr.setInvalid();
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index cf6ff5994af3a..acb2e55f0e1c6 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -11,20 +11,26 @@ int nl_var [[clang::nonblocking]]; // expected-warning {{'nonblocking' only appl
struct nl_struct {} [[clang::nonblocking]]; // expected-warning {{attribute 'nonblocking' is ignored, place it after "struct" to apply attribute to type declaration}}
struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' attribute cannot be applied to a declaration}}
+// --- ATTRIBUTE SYNTAX: ARGUMENT COUNT ---
+void nargs_1() [[clang::nonblocking(1, 2)]]; // expected-error {{'nonblocking' attribute takes no more than 1 argument}}
+void nargs_2() [[clang::nonallocating(1, 2)]]; // expected-error {{'nonallocating' attribute takes no more than 1 argument}}
+void nargs_3() [[clang::blocking(1)]]; // expected-error {{'blocking' attribute takes no arguments}}
+void nargs_4() [[clang::allocating(1)]]; // expected-error {{'allocating' attribute takes no arguments}}
+
// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
// Check invalid combinations of nonblocking/nonallocating attributes
-void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{blocking and nonblocking attributes are not compatible}}
-void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{nonblocking and blocking attributes are not compatible}}
+void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{'blocking' and 'nonblocking' attributes are not compatible}}
+void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{'nonblocking' and 'blocking' attributes are not compatible}}
-void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{allocating and nonallocating attributes are not compatible}}
-void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{nonallocating and allocating attributes are not compatible}}
+void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonallocating' attributes are not compatible}}
+void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{'nonallocating' and 'allocating' attributes are not compatible}}
void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]];
void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]];
-void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{allocating and nonblocking attributes are not compatible}}
-void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{nonblocking and allocating attributes are not compatible}}
+void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonblocking' attributes are not compatible}}
+void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{'nonblocking' and 'allocating' attributes are not compatible}}
void nl_false_na_true_1() [[clang::blocking]] [[clang::nonallocating]];
void nl_false_na_true_2() [[clang::nonallocating]] [[clang::blocking]];
>From 46830f64d654b515a5aa3ecf24a524acbe2f5bc2 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 4 May 2024 13:28:04 -0700
Subject: [PATCH 52/71] Review feedback, notably: FunctionEffectsRef
invariants, mergeFunctionTypes().
---
clang/include/clang/AST/Type.h | 55 +++++++++++++++++------
clang/include/clang/AST/TypeProperties.td | 4 +-
clang/include/clang/Basic/AttrDocs.td | 2 +-
clang/include/clang/Sema/Sema.h | 3 +-
clang/lib/AST/ASTContext.cpp | 20 ++++++++-
clang/lib/AST/Type.cpp | 51 +++++++++++++++++++--
clang/lib/Sema/SemaOverload.cpp | 3 +-
clang/lib/Sema/SemaType.cpp | 33 +++++++-------
clang/test/Sema/attr-nonblocking-sema.cpp | 8 ----
9 files changed, 129 insertions(+), 50 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 51386695847ed..29c03c5891727 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4779,6 +4779,15 @@ struct FunctionEffectWithCondition {
/// Return a textual description of the effect, and its condition, if any.
std::string description() const;
+
+ friend bool operator<(const FunctionEffectWithCondition &LHS,
+ const FunctionEffectWithCondition &RHS) {
+ if (LHS.Effect < RHS.Effect)
+ return true;
+ if (RHS.Effect < LHS.Effect)
+ return false;
+ return LHS.Cond.expr() < RHS.Cond.expr();
+ }
};
struct FunctionEffectDiff {
@@ -4858,25 +4867,45 @@ template <typename Container> class FunctionEffectIterator {
/// (typically, trailing objects in FunctionProtoType, or borrowed references
/// from a FunctionEffectSet).
///
-/// Invariant: there is never more than one instance of any given effect.
+/// Invariants:
+/// - there is never more than one instance of any given effect.
+/// - the array of conditions is either empty or has the same size as the
+/// array of effects.
+/// - some conditions may be null expressions; each condition pertains to
+/// the effect at the same array index.
+///
+/// Also, if there are any conditions, at least one of those expressions will be
+/// dependent, but this is only asserted in the constructor of
+/// FunctionProtoType.
class FunctionEffectsRef {
- ArrayRef<FunctionEffect> Effects;
+ // Restrict classes which can call the private constructor -- these friends
+ // all maintain the required invariants. FunctionEffectSet is generally the
+ // only way in which the arrays are created; FunctionProtoType will not
+ // reorder them.
+ friend FunctionProtoType;
+ friend FunctionEffectSet;
- // The array of conditions is either empty or has the same size
- // as the array of effects.
+ ArrayRef<FunctionEffect> Effects;
ArrayRef<FunctionEffectCondition> Conditions;
+ // The arrays are expected to have been sorted by the caller, with the
+ // effects in order. The conditions array must be empty or the same size
+ // as the effects array, since the conditions are associated with the effects
+ // at the same array indices.
+ FunctionEffectsRef(ArrayRef<FunctionEffect> FX,
+ ArrayRef<FunctionEffectCondition> Conds)
+ : Effects(FX), Conditions(Conds) {}
+
public:
/// Extract the effects from a Type if it is a function, block, or member
/// function pointer, or a reference or pointer to one.
static FunctionEffectsRef get(QualType QT);
- FunctionEffectsRef() = default;
+ /// Asserts invariants.
+ static FunctionEffectsRef create(ArrayRef<FunctionEffect> FX,
+ ArrayRef<FunctionEffectCondition> Conds);
- // The arrays are expected to have been sorted by the caller.
- FunctionEffectsRef(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondition> Conds)
- : Effects(FX), Conditions(Conds) {}
+ FunctionEffectsRef() = default;
bool empty() const { return Effects.empty(); }
size_t size() const { return Effects.size(); }
@@ -4905,11 +4934,9 @@ class FunctionEffectsRef {
/// A mutable set of FunctionEffects and possibly conditions attached to them.
/// Used transitorily within Sema to compare and merge effects on declarations.
///
-/// Invariant: there is never more than one instance of any given effect.
+/// Has the same invariants as FunctionEffectsRef.
class FunctionEffectSet {
SmallVector<FunctionEffect> Effects;
- // The vector of conditions is either empty or has the same size
- // as the vector of effects.
SmallVector<FunctionEffectCondition> Conditions;
public:
@@ -4942,6 +4969,8 @@ class FunctionEffectSet {
// Set operations
static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
FunctionEffectsRef RHS);
+ static FunctionEffectSet getIntersection(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS);
using Differences = SmallVector<FunctionEffectDiff>;
@@ -5456,7 +5485,7 @@ class FunctionProtoType final
}
// For serialization.
- ArrayRef<FunctionEffect> getFunctionEffectsOnly() const {
+ ArrayRef<FunctionEffect> getFunctionEffectsWithoutConditions() const {
if (hasExtraBitfields()) {
const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
if (Bitfields->NumFunctionEffects > 0)
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index f98659dca73c6..bb565a15baf1a 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -353,7 +353,7 @@ let Class = FunctionProtoType in {
let Read = [{ node->getAArch64SMEAttributes() }];
}
def : Property<"functionEffects", Array<FunctionEffect>> {
- let Read = [{ node->getFunctionEffectsOnly() }];
+ let Read = [{ node->getFunctionEffectsWithoutConditions() }];
}
def : Property<"functionEffectConds", Array<FunctionEffectCondition>> {
let Read = [{ node->getFunctionEffectConditions() }];
@@ -374,7 +374,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
- epi.FunctionEffects = FunctionEffectsRef(functionEffects, functionEffectConds);
+ epi.FunctionEffects = FunctionEffectsRef::create(functionEffects, functionEffectConds);
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 8ab1d74f3a6cb..274eb91f8658e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8060,7 +8060,7 @@ requirement:
def DocCatNonBlockingNonAllocating : DocumentationCategory<"Performance Constraint Attributes"> {
let Content = [{
-The ``nonblocking``, ``blocking``, ``nonallocating`` and ``allocating`` attributes can be attached
+The ``nonblocking``, ``blocking``, ``nonallocating`` and ``allocating`` attributes can be attached
to function types, including blocks, C++ lambdas, and member functions. The attributes declare
constraints about a function's behavior pertaining to blocking and heap memory allocation.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 120d3d0e427e7..b4bf3e9f067b4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -785,8 +785,7 @@ class Sema final : public SemaBase {
/// Try to parse the conditional expression attached to an effect attribute
/// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). If RequireConstexpr,
/// then this will fail if the expression is dependent.
- ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
- bool RequireConstexpr = false);
+ ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode);
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 291ba6d4eba94..34ae4d88b7fd7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10497,6 +10497,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
+ std::optional<FunctionEffectSet> MergedFX;
+
if (lproto && rproto) { // two C99 style function prototypes
assert((AllowCXX ||
(!lproto->hasExceptionSpec() && !rproto->hasExceptionSpec())) &&
@@ -10512,7 +10514,19 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
if (lproto->getMethodQuals() != rproto->getMethodQuals())
return {};
- // TODO: (nonblocking) Does anything need to be done with FunctionEffects?
+ // Function effects are handled similarly to noreturn, see above.
+ FunctionEffectsRef LHSFX = lproto->getFunctionEffects();
+ FunctionEffectsRef RHSFX = rproto->getFunctionEffects();
+ if (LHSFX != RHSFX) {
+ if (IsConditionalOperator)
+ MergedFX = FunctionEffectSet::getIntersection(LHSFX, RHSFX);
+ else
+ MergedFX = FunctionEffectSet::getUnion(LHSFX, RHSFX);
+ if (*MergedFX != LHSFX)
+ allLTypes = false;
+ if (*MergedFX != RHSFX)
+ allRTypes = false;
+ }
SmallVector<FunctionProtoType::ExtParameterInfo, 4> newParamInfos;
bool canUseLeft, canUseRight;
@@ -10557,6 +10571,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
EPI.ExtInfo = einfo;
EPI.ExtParameterInfos =
newParamInfos.empty() ? nullptr : newParamInfos.data();
+ if (MergedFX)
+ EPI.FunctionEffects = *MergedFX;
return getFunctionType(retType, types, EPI);
}
@@ -10594,6 +10610,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
EPI.ExtInfo = einfo;
+ if (MergedFX)
+ EPI.FunctionEffects = *MergedFX;
return getFunctionType(retType, proto->getParamTypes(), EPI);
}
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 49184dafd4999..242b0059b50b2 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3652,19 +3652,29 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
if (!epi.FunctionEffects.empty()) {
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
- // TODO: bitfield overflow?
- ExtraBits.NumFunctionEffects = epi.FunctionEffects.size();
+ const size_t EffectsCount = epi.FunctionEffects.size();
+ ExtraBits.NumFunctionEffects = EffectsCount;
+ assert(ExtraBits.NumFunctionEffects == EffectsCount &&
+ "effect bitfield overflow");
ArrayRef<FunctionEffect> SrcFX = epi.FunctionEffects.effects();
auto *DestFX = getTrailingObjects<FunctionEffect>();
- std::copy(SrcFX.begin(), SrcFX.end(), DestFX);
+ std::uninitialized_copy(SrcFX.begin(), SrcFX.end(), DestFX);
ArrayRef<FunctionEffectCondition> SrcConds =
epi.FunctionEffects.conditions();
if (!SrcConds.empty()) {
ExtraBits.EffectsHaveConditions = true;
auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
- std::copy(SrcConds.begin(), SrcConds.end(), DestConds);
+ std::uninitialized_copy(SrcConds.begin(), SrcConds.end(), DestConds);
+ assert(std::any_of(SrcConds.begin(), SrcConds.end(),
+ [](const FunctionEffectCondition &EC) {
+ if (const Expr *E = EC.expr())
+ return E->isTypeDependent() ||
+ E->isValueDependent();
+ return false;
+ }) &&
+ "expected a dependent expression among the conditions");
addDependence(TypeDependence::DependentInstantiation);
}
}
@@ -5304,6 +5314,30 @@ void FunctionEffectSet::erase(unsigned Idx) {
}
}
+FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS,
+ FunctionEffectsRef RHS) {
+ FunctionEffectSet Result;
+
+ // We could use std::set_intersection but that would require expanding the
+ // container interface to include push_back, making it available to clients
+ // who might fail to maintain invariants.
+ auto IterA = LHS.begin(), EndA = LHS.end();
+ auto IterB = RHS.begin(), EndB = RHS.end();
+
+ while (IterA != EndA && IterB != EndB) {
+ if (*IterA < *IterB)
+ ++IterA;
+ else if (*IterB < *IterA)
+ ++IterB;
+ else {
+ Result.insert((*IterA).Effect, (*IterA).Cond.expr());
+ ++IterA;
+ ++IterB;
+ }
+ }
+ return Result;
+}
+
FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
FunctionEffectsRef RHS) {
// Optimize for either of the two sets being empty (very common).
@@ -5417,6 +5451,15 @@ FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
return {};
}
+FunctionEffectsRef
+FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
+ ArrayRef<FunctionEffectCondition> Conds) {
+ assert(std::is_sorted(FX.begin(), FX.end()) && "effects should be sorted");
+ assert((Conds.empty() || Conds.size() == FX.size()) &&
+ "effects size should match conditions size");
+ return FunctionEffectsRef(FX, Conds);
+}
+
std::string FunctionEffectWithCondition::description() const {
std::string Result(Effect.name().str());
if (Cond.expr() != nullptr)
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 2edbfa9c7d757..b83bd75bc0985 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1876,8 +1876,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
// like dropping effects will fail. In C++ however we do want to
// alter FromFn. TODO: Is this correct?
if (getLangOpts().CPlusPlus) {
- FromFPT =
- dyn_cast<FunctionProtoType>(FromFn); // in case FromFn changed above
+ FromFPT = cast<FunctionProtoType>(FromFn); // in case FromFn changed above
// Transparently add/drop effects; here we are concerned with
// language rules/canonicalization. Adding/dropping effects is a warning.
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3bf09eb3cb503..ac139ff5d91fc 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7967,19 +7967,18 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
-ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode,
- bool RequireConstexpr) {
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr,
+ FunctionEffectMode &Mode) {
// see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
- if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ if (!CondExpr->isTypeDependent()) {
ExprResult E = PerformContextuallyConvertToBool(CondExpr);
if (E.isInvalid())
return E;
CondExpr = E.get();
- if (RequireConstexpr || !CondExpr->isValueDependent()) {
+ if (!CondExpr->isValueDependent()) {
llvm::APSInt CondInt;
E = VerifyIntegerConstantExpression(
E.get(), &CondInt,
- // TODO: have our own diagnostic
diag::err_constexpr_if_condition_expression_is_not_constant);
if (E.isInvalid()) {
return E;
@@ -8012,7 +8011,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Parse the new attribute.
// non/blocking or non/allocating? Or conditional (computed)?
- const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ const bool IsNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_Blocking;
Sema &S = TPState.getSema();
@@ -8026,7 +8025,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
return true;
}
- // Parse the conditional expression, if any
+ // Parse the condition, if any
if (PAttr.getNumArgs() == 1) {
CondExpr = PAttr.getArgAsExpr(0);
ExprResult E = S.ActOnEffectExpression(CondExpr, NewMode);
@@ -8045,15 +8044,15 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
const FunctionEffect::Kind FEKind =
(NewMode == FunctionEffectMode::False)
- ? (isNonBlocking ? FunctionEffect::Kind::Blocking
+ ? (IsNonBlocking ? FunctionEffect::Kind::Blocking
: FunctionEffect::Kind::Allocating)
- : (isNonBlocking ? FunctionEffect::Kind::NonBlocking
+ : (IsNonBlocking ? FunctionEffect::Kind::NonBlocking
: FunctionEffect::Kind::NonAllocating);
const FunctionEffectWithCondition NewEC{FunctionEffect(FEKind),
FunctionEffectCondition(CondExpr)};
- // Diagnose the newly provided attribute as incompatible with a previous one.
- auto incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
+ // Diagnose the newly parsed attribute as incompatible with a previous one.
+ auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
<< ("'" + NewEC.description() + "'")
<< ("'" + PrevEC.description() + "'") << false;
@@ -8070,7 +8069,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
for (const FunctionEffectWithCondition &PrevEC : FPT->getFunctionEffects()) {
if (PrevEC.Effect.kind() == FEKind ||
PrevEC.Effect.oppositeKind() == FEKind)
- return incompatible(PrevEC);
+ return Incompatible(PrevEC);
switch (PrevEC.Effect.kind()) {
case FunctionEffect::Kind::Blocking:
case FunctionEffect::Kind::NonBlocking:
@@ -8085,16 +8084,16 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
}
- if (isNonBlocking) {
+ if (IsNonBlocking) {
// new nonblocking(true) is incompatible with previous allocating
if (NewMode == FunctionEffectMode::True && PrevNonAllocating &&
PrevNonAllocating->Effect.kind() == FunctionEffect::Kind::Allocating)
- return incompatible(*PrevNonAllocating);
+ return Incompatible(*PrevNonAllocating);
} else {
// new allocating is incompatible with previous nonblocking(true)
if (NewMode == FunctionEffectMode::False && PrevNonBlocking &&
PrevNonBlocking->Effect.kind() == FunctionEffect::Kind::NonBlocking)
- return incompatible(*PrevNonBlocking);
+ return Incompatible(*PrevNonBlocking);
}
// Add the effect to the FunctionProtoType
@@ -8103,9 +8102,9 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
FX.insert(NewEC.Effect, NewEC.Cond.expr());
EPI.FunctionEffects = FunctionEffectsRef(FX);
- QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
+ QualType NewType = S.Context.getFunctionType(FPT->getReturnType(),
FPT->getParamTypes(), EPI);
- QT = Unwrapped.wrap(S, newtype->getAs<FunctionType>());
+ QT = Unwrapped.wrap(S, NewType->getAs<FunctionType>());
return true;
}
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index acb2e55f0e1c6..82970dddd1a6f 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -104,17 +104,9 @@ struct Derived : public Base {
// --- REDECLARATIONS ---
-#ifdef __cplusplus
-// In C++, the third declaration gets seen as a redeclaration of the second.
void f2();
void f2() [[clang::nonblocking]]; // expected-note {{previous declaration is here}}
void f2(); // expected-warning {{attribute 'nonblocking' on function does not match previous declaration}}
-#else
-// In C, the third declaration is redeclaration of the first (?).
-void f2();
-void f2() [[clang::nonblocking]];
-void f2();
-#endif
// Note: we verify that the attribute is actually seen during the constraints tests.
// --- OVERLOADS ---
>From d918424e0d1df2bb9bb38d62d8380eaf95d5c876 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 4 May 2024 13:51:37 -0700
Subject: [PATCH 53/71] Review feedback: - Simplify FunctionEffectsRef::get() -
Move FunctionEffectDiff from AST/Type to Sema
---
clang/include/clang/AST/Type.h | 60 +-----------
clang/include/clang/Sema/Sema.h | 49 ++++++++++
clang/lib/AST/Type.cpp | 168 +-------------------------------
clang/lib/Sema/Sema.cpp | 152 ++++++++++++++++++++++++++++-
clang/lib/Sema/SemaDecl.cpp | 2 +-
clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
6 files changed, 208 insertions(+), 225 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 29c03c5891727..276b7aa61cf28 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -118,6 +118,7 @@ class EnumDecl;
class Expr;
class ExtQualsTypeCommonBase;
class FunctionDecl;
+class FunctionEffectSet;
class IdentifierInfo;
class NamedDecl;
class ObjCInterfaceDecl;
@@ -4646,12 +4647,6 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
// ------------------------------------------------------------------------------
-class Decl;
-class CXXMethodDecl;
-struct FunctionEffectDiff;
-class FunctionEffectsRef;
-class FunctionEffectSet;
-
/// Represents an abstract function effect, using just an enumeration describing
/// its kind.
class FunctionEffect {
@@ -4790,49 +4785,6 @@ struct FunctionEffectWithCondition {
}
};
-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;
-};
-
/// Support iteration in parallel through a pair of FunctionEffect and
/// FunctionEffectCondition containers.
template <typename Container> class FunctionEffectIterator {
@@ -4877,6 +4829,8 @@ template <typename Container> class FunctionEffectIterator {
/// Also, if there are any conditions, at least one of those expressions will be
/// dependent, but this is only asserted in the constructor of
/// FunctionProtoType.
+///
+/// See also FunctionEffectSet, in Sema, which provides a mutable set.
class FunctionEffectsRef {
// Restrict classes which can call the private constructor -- these friends
// all maintain the required invariants. FunctionEffectSet is generally the
@@ -4932,7 +4886,7 @@ class FunctionEffectsRef {
};
/// A mutable set of FunctionEffects and possibly conditions attached to them.
-/// Used transitorily within Sema to compare and merge effects on declarations.
+/// Used to compare and merge effects on declarations.
///
/// Has the same invariants as FunctionEffectsRef.
class FunctionEffectSet {
@@ -4971,12 +4925,6 @@ class FunctionEffectSet {
FunctionEffectsRef RHS);
static FunctionEffectSet getIntersection(FunctionEffectsRef LHS,
FunctionEffectsRef RHS);
-
- using Differences = SmallVector<FunctionEffectDiff>;
-
- /// Caller should short-circuit by checking for equality first.
- static Differences differences(const FunctionEffectsRef &Old,
- const FunctionEffectsRef &New);
};
/// Represents a prototype with parameter type info, e.g.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b4bf3e9f067b4..a8d668b8d5f0f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -460,6 +460,55 @@ 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<FunctionEffectDiff> {
+ /// 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 {
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 242b0059b50b2..5affe7c251ebf 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5093,98 +5093,6 @@ StringRef FunctionEffect::name() const {
llvm_unreachable("unknown effect kind");
}
-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:
- return true; // TODO: ???
- }
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return false;
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
-
-bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
- const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
- const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
- switch (EffectKind) {
- case FunctionEffect::Kind::NonAllocating:
- case FunctionEffect::Kind::NonBlocking:
- // nonblocking/nonallocating can't be removed in a redeclaration
- switch (DiffKind) {
- case Kind::Added:
- return false; // No diagnostic.
- case Kind::Removed:
- return true; // Issue diagnostic
- case Kind::ConditionMismatch:
- // All these forms of mismatches are diagnosed.
- return true;
- }
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return false;
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
-
-FunctionEffectDiff::OverrideResult
-FunctionEffectDiff::shouldDiagnoseMethodOverride(
- const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
- const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
- switch (EffectKind) {
- case FunctionEffect::Kind::NonAllocating:
- case FunctionEffect::Kind::NonBlocking:
- switch (DiffKind) {
-
- // If added on an override, that's fine and not diagnosed.
- case Kind::Added:
- return OverrideResult::NoAction;
-
- // If missing from an override (removed), propagate from base to derived.
- case Kind::Removed:
- return OverrideResult::Merge;
-
- // If there's a mismatch involving the effect's polarity or condition,
- // issue a warning.
- case Kind::ConditionMismatch:
- return OverrideResult::Warn;
- }
-
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return OverrideResult::NoAction;
-
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
-
bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
switch (kind()) {
case Kind::NonAllocating:
@@ -5349,64 +5257,6 @@ FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
return Result;
}
-FunctionEffectSet::Differences
-FunctionEffectSet::differences(const FunctionEffectsRef &Old,
- const FunctionEffectsRef &New) {
-
- FunctionEffectSet::Differences Result;
-
- 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.expr() != New.Cond.expr()) {
- // TODO: Cases where the expressions are equivalent but
- // don't have the same identity.
- Result.push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
- Old, New});
- }
- }
- }
-
- if (cmp < 0) {
- // removal
- FunctionEffectWithCondition Old = *POld;
- Result.push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
- ++POld;
- } else if (cmp > 0) {
- // addition
- FunctionEffectWithCondition New = *PNew;
- Result.push_back(FunctionEffectDiff{
- New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
- ++PNew;
- } else {
- ++POld;
- ++PNew;
- }
- }
- return Result;
-}
-
LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
bool First = true;
@@ -5429,25 +5279,11 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
FunctionEffectsRef(*this).dump(OS);
}
-// TODO: inline?
FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
- if (QT->isReferenceType())
- QT = QT.getNonReferenceType();
- if (QT->isPointerType())
- QT = QT->getPointeeType();
-
- // TODO: Why aren't these included in isPointerType()?
- if (const auto *BT = QT->getAs<BlockPointerType>()) {
- QT = BT->getPointeeType();
- } else if (const auto *MP = QT->getAs<MemberPointerType>()) {
- if (MP->isMemberFunctionPointer()) {
- QT = MP->getPointeeType();
- }
- }
-
+ if (QualType Pointee = QT->getPointeeType(); !Pointee.isNull())
+ QT = Pointee;
if (const auto *FPT = QT->getAs<FunctionProtoType>())
return FPT->getFunctionEffects();
-
return {};
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index c1a4088382c12..0ba62c3e59739 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -600,7 +600,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 : FunctionEffectSet::differences(SrcFX, DstFX)) {
+ for (const auto &Diff : FunctionEffectDifferences(SrcFX, DstFX)) {
if (Diff.shouldDiagnoseConversion(SrcType, SrcFX, DstType, DstFX))
Diag(Loc, diag::warn_invalid_add_func_effects) << Diff.effectName();
}
@@ -2759,3 +2759,153 @@ 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.expr() != New.Cond.expr()) {
+ // TODO: 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:
+ // TODO: Condition mismatches are too coarse right now -- expressions
+ // which are equivalent but don't have the same identity are detected as
+ // mismatches. We're going to diagnose those anyhow until expression
+ // matching is better.
+ return true;
+ }
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
+ const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
+ const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
+ case FunctionEffect::Kind::NonBlocking:
+ // nonblocking/nonallocating can't be removed in a redeclaration
+ switch (DiffKind) {
+ case Kind::Added:
+ return false; // No diagnostic.
+ case Kind::Removed:
+ return true; // Issue diagnostic
+ case Kind::ConditionMismatch:
+ // All these forms of mismatches are diagnosed.
+ return true;
+ }
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
+FunctionEffectDiff::OverrideResult
+FunctionEffectDiff::shouldDiagnoseMethodOverride(
+ const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+ const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
+ case FunctionEffect::Kind::NonBlocking:
+ switch (DiffKind) {
+
+ // If added on an override, that's fine and not diagnosed.
+ case Kind::Added:
+ return OverrideResult::NoAction;
+
+ // If missing from an override (removed), propagate from base to derived.
+ case Kind::Removed:
+ return OverrideResult::Merge;
+
+ // If there's a mismatch involving the effect's polarity or condition,
+ // issue a warning.
+ case Kind::ConditionMismatch:
+ return OverrideResult::Warn;
+ }
+
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return OverrideResult::NoAction;
+
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 0fcfe38b28a8d..3f6d33ff8845c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3946,7 +3946,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
const auto NewFX = New->getFunctionEffects();
QualType OldQTypeForComparison = OldQType;
if (OldFX != NewFX) {
- const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionEffectDifferences(OldFX, NewFX);
for (const auto &Diff : Diffs) {
if (Diff.shouldDiagnoseRedeclaration(*Old, OldFX, *New, NewFX)) {
Diag(New->getLocation(),
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index d6bf212b13ec8..d16a6c9df65aa 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18389,7 +18389,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
if (OldFX != NewFXOrig) {
FunctionEffectSet NewFX(NewFXOrig);
- const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+ const auto Diffs = FunctionEffectDifferences(OldFX, NewFX);
for (const auto &Diff : Diffs) {
switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) {
case FunctionEffectDiff::OverrideResult::NoAction:
>From 86795b4ee05baf4fe5fad59bfdc6c1ffc4a38b00 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 5 May 2024 11:46:33 -0700
Subject: [PATCH 54/71] Detect and diagnose conflicts when merging effects
between declarations.
---
clang/include/clang/AST/Type.h | 23 +++++--
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/include/clang/Sema/Sema.h | 5 ++
clang/lib/AST/ASTContext.cpp | 9 ++-
clang/lib/AST/Type.cpp | 64 ++++++++++++-------
clang/lib/Sema/SemaDecl.cpp | 19 +++++-
clang/lib/Sema/SemaDeclCXX.cpp | 6 +-
clang/lib/Sema/SemaType.cpp | 4 +-
clang/test/Sema/attr-nonblocking-sema.cpp | 7 +-
9 files changed, 107 insertions(+), 33 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 276b7aa61cf28..205f5f2f6a6c5 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4772,6 +4772,11 @@ struct FunctionEffectWithCondition {
FunctionEffect Effect;
FunctionEffectCondition Cond;
+ FunctionEffectWithCondition() = default;
+ FunctionEffectWithCondition(const FunctionEffect &E,
+ const FunctionEffectCondition &C)
+ : Effect(E), Cond(C) {}
+
/// Return a textual description of the effect, and its condition, if any.
std::string description() const;
@@ -4913,16 +4918,26 @@ class FunctionEffectSet {
// Mutators
- void insert(FunctionEffect Effect, Expr *Cond);
- void insert(const FunctionEffectsRef &Set);
- void insertIgnoringConditions(const FunctionEffectsRef &Set);
+ // On insertion, a conflict occurs when attempting to insert an
+ // effect which is opposite an effect already in the set, or attempting
+ // to insert an effect which is already in the set but with a condition
+ // which is not identical.
+ struct Conflict {
+ FunctionEffectWithCondition Kept;
+ FunctionEffectWithCondition Rejected;
+ };
+ using Conflicts = SmallVector<Conflict>;
+
+ void insert(const FunctionEffectWithCondition &NewEC, Conflicts &Errs);
+ void insert(const FunctionEffectsRef &Set, Conflicts &Errs);
+ void insertIgnoringConditions(const FunctionEffectsRef &Set, Conflicts &Errs);
void replaceItem(unsigned Idx, const FunctionEffectWithCondition &Item);
void erase(unsigned Idx);
// Set operations
static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
- FunctionEffectsRef RHS);
+ FunctionEffectsRef RHS, Conflicts &Errs);
static FunctionEffectSet getIntersection(FunctionEffectsRef LHS,
FunctionEffectsRef RHS);
};
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index bf4cbaa26d6cf..7a5a7b965beb1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10830,6 +10830,9 @@ def warn_mismatched_func_effect_override : Warning<
def warn_mismatched_func_effect_redeclaration : Warning<
"attribute '%0' on function does not match previous declaration">,
InGroup<FunctionEffects>;
+def warn_conflicting_func_effects : Warning<
+ "effects conflict when merging declarations; kept '%0', discarded '%1'">,
+ InGroup<FunctionEffects>;
} // end of sema category
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a8d668b8d5f0f..b28d6d27a97c4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -831,6 +831,11 @@ class Sema final : public SemaBase {
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
+ 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). If RequireConstexpr,
/// then this will fail if the expression is dependent.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 34ae4d88b7fd7..4e3b8590fc551 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10520,8 +10520,13 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
if (LHSFX != RHSFX) {
if (IsConditionalOperator)
MergedFX = FunctionEffectSet::getIntersection(LHSFX, RHSFX);
- else
- MergedFX = FunctionEffectSet::getUnion(LHSFX, RHSFX);
+ else {
+ FunctionEffectSet::Conflicts Errs;
+ MergedFX = FunctionEffectSet::getUnion(LHSFX, RHSFX, Errs);
+ // Here we're discarding a possible error due to conflicts in the effect
+ // sets. But we're not in a context where we can report it. The
+ // operation does however guarantee maintenance of invariants.
+ }
if (*MergedFX != LHSFX)
allLTypes = false;
if (*MergedFX != RHSFX)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 5affe7c251ebf..a36e9c3b6174e 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5164,38 +5164,49 @@ void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
}
}
-void FunctionEffectSet::insert(FunctionEffect Effect, Expr *Cond) {
- // lower_bound would be overkill
+void FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
+ Conflicts &Errs) {
+ const FunctionEffect::Kind NewOppositeKind = NewEC.Effect.oppositeKind();
+
+ // The index at which insertion will take place; default is at end
+ // but we might find an earlier insertion point.
+ unsigned InsertIdx = Effects.size();
unsigned Idx = 0;
- for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
- const auto &IterEffect = Effects[Idx];
- if (IterEffect.kind() == Effect.kind()) {
- // It's possible here to have incompatible combinations of polarity
- // (asserted/denied) and condition; for now, we keep whichever came
- // though this should be improved.
+ for (const FunctionEffectWithCondition &EC : *this) {
+ if (EC.Effect.kind() == NewEC.Effect.kind()) {
+ if (Conditions[Idx].expr() != NewEC.Cond.expr())
+ Errs.push_back({EC, NewEC});
return;
}
- if (Effect.kind() < IterEffect.kind())
- break;
+
+ if (EC.Effect.kind() == NewOppositeKind) {
+ Errs.push_back({EC, NewEC});
+ return;
+ }
+
+ if (NewEC.Effect.kind() < EC.Effect.kind() && InsertIdx > Idx)
+ InsertIdx = Idx;
+
+ ++Idx;
}
- if (Cond) {
+ if (NewEC.Cond.expr()) {
if (Conditions.empty() && !Effects.empty())
Conditions.resize(Effects.size());
- Conditions.insert(Conditions.begin() + Idx, Cond);
+ Conditions.insert(Conditions.begin() + InsertIdx, NewEC.Cond.expr());
}
- Effects.insert(Effects.begin() + Idx, Effect);
+ Effects.insert(Effects.begin() + InsertIdx, NewEC.Effect);
}
-void FunctionEffectSet::insert(const FunctionEffectsRef &Set) {
+void FunctionEffectSet::insert(const FunctionEffectsRef &Set, Conflicts &Errs) {
for (const auto &Item : Set)
- insert(Item.Effect, Item.Cond.expr());
+ insert(Item, Errs);
}
-void FunctionEffectSet::insertIgnoringConditions(
- const FunctionEffectsRef &Set) {
+void FunctionEffectSet::insertIgnoringConditions(const FunctionEffectsRef &Set,
+ Conflicts &Errs) {
for (const auto &Item : Set)
- insert(Item.Effect, nullptr);
+ insert(FunctionEffectWithCondition(Item.Effect, {}), Errs);
}
void FunctionEffectSet::replaceItem(unsigned Idx,
@@ -5225,6 +5236,7 @@ void FunctionEffectSet::erase(unsigned Idx) {
FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS,
FunctionEffectsRef RHS) {
FunctionEffectSet Result;
+ FunctionEffectSet::Conflicts Errs;
// We could use std::set_intersection but that would require expanding the
// container interface to include push_back, making it available to clients
@@ -5238,23 +5250,29 @@ FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS,
else if (*IterB < *IterA)
++IterB;
else {
- Result.insert((*IterA).Effect, (*IterA).Cond.expr());
+ Result.insert(*IterA, Errs);
++IterA;
++IterB;
}
}
+
+ // Insertion shouldn't be able to fail; that would mean both input
+ // sets contained conflicts.
+ assert(Errs.empty() && "conflict shouldn't be possible in getIntersection");
+
return Result;
}
FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
- FunctionEffectsRef RHS) {
+ FunctionEffectsRef RHS,
+ Conflicts &Errs) {
// Optimize for either of the two sets being empty (very common).
if (LHS.empty())
return FunctionEffectSet(RHS);
- FunctionEffectSet Result(LHS);
- Result.insert(RHS);
- return Result;
+ FunctionEffectSet Combined(LHS);
+ Combined.insert(RHS, Errs);
+ return Combined;
}
LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 3f6d33ff8845c..09ffa27cc421b 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3959,7 +3959,12 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// declaration, but that would trigger an additional "conflicting types"
// error.
if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
- auto MergedFX = FunctionEffectSet::getUnion(OldFX, NewFX);
+ FunctionEffectSet::Conflicts MergeErrs;
+ FunctionEffectSet MergedFX =
+ FunctionEffectSet::getUnion(OldFX, NewFX, MergeErrs);
+ if (!MergeErrs.empty())
+ diagnoseFunctionEffectMergeConflicts(MergeErrs, New->getLocation(),
+ Old->getLocation());
FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
EPI.FunctionEffects = FunctionEffectsRef(MergedFX);
@@ -20788,3 +20793,15 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) {
return LangOpts.CUDA && !LangOpts.CUDAIsDevice &&
CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global;
}
+
+// Report a failure to merge function effects between declarations due to a
+// conflict.
+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);
+ }
+}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index d16a6c9df65aa..2c3c743e6527f 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18401,7 +18401,11 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
<< Old->getReturnTypeSourceRange();
break;
case FunctionEffectDiff::OverrideResult::Merge: {
- NewFX.insert(Diff.Old.Effect, Diff.Old.Cond.expr());
+ FunctionEffectSet::Conflicts Errs;
+ NewFX.insert(Diff.Old, Errs);
+ if (!Errs.empty())
+ diagnoseFunctionEffectMergeConflicts(Errs, New->getLocation(),
+ Old->getLocation());
const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
EPI.FunctionEffects = FunctionEffectsRef(NewFX);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index ac139ff5d91fc..9d3c78f057517 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8099,7 +8099,9 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Add the effect to the FunctionProtoType
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
- FX.insert(NewEC.Effect, NewEC.Cond.expr());
+ FunctionEffectSet::Conflicts Errs;
+ FX.insert(NewEC, Errs);
+ assert(Errs.empty() && "effect conflicts should have been diagnosed above");
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType NewType = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 82970dddd1a6f..19eecc42ed9e9 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -86,19 +86,21 @@ void type_conversions_2()
#endif
// --- VIRTUAL METHODS ---
-// Attributes propagate to overridden methods, so no diagnostics.
+// Attributes propagate to overridden methods, so no diagnostics except for conflicts.
// Check this in the syntax tests too.
#ifdef __cplusplus
struct Base {
virtual void f1();
virtual void nonblocking() noexcept [[clang::nonblocking]];
virtual void nonallocating() noexcept [[clang::nonallocating]];
+ virtual void f2() [[clang::nonallocating]]; // expected-note {{previous declaration is here}}
};
struct Derived : public Base {
void f1() [[clang::nonblocking]] override;
void nonblocking() noexcept override;
void nonallocating() noexcept override;
+ void f2() [[clang::allocating]] override; // expected-warning {{effects conflict when merging declarations; kept 'allocating', discarded 'nonallocating'}}
};
#endif // __cplusplus
@@ -109,6 +111,9 @@ void f2() [[clang::nonblocking]]; // expected-note {{previous declaration is her
void f2(); // expected-warning {{attribute 'nonblocking' on function does not match previous declaration}}
// Note: we verify that the attribute is actually seen during the constraints tests.
+void f3() [[clang::blocking]]; // expected-note {{previous declaration is here}}
+void f3() [[clang::nonblocking]]; // expected-warning {{effects conflict when merging declarations; kept 'blocking', discarded 'nonblocking'}}
+
// --- OVERLOADS ---
#ifdef __cplusplus
struct S {
>From 8fdea5b30e71d80ba0c54b62f61db82ceaca2f18 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 6 May 2024 07:28:27 -0700
Subject: [PATCH 55/71] In CheckOverridingFunctionAttributes, gather and report
effect conflicts just once.
---
clang/lib/Sema/SemaDeclCXX.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2c3c743e6527f..8cffa984f53ff 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18390,6 +18390,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
if (OldFX != NewFXOrig) {
FunctionEffectSet NewFX(NewFXOrig);
const auto Diffs = FunctionEffectDifferences(OldFX, NewFX);
+ FunctionEffectSet::Conflicts Errs;
for (const auto &Diff : Diffs) {
switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) {
case FunctionEffectDiff::OverrideResult::NoAction:
@@ -18401,11 +18402,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
<< Old->getReturnTypeSourceRange();
break;
case FunctionEffectDiff::OverrideResult::Merge: {
- FunctionEffectSet::Conflicts Errs;
NewFX.insert(Diff.Old, Errs);
- if (!Errs.empty())
- diagnoseFunctionEffectMergeConflicts(Errs, New->getLocation(),
- Old->getLocation());
const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
EPI.FunctionEffects = FunctionEffectsRef(NewFX);
@@ -18416,6 +18413,9 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
}
}
}
+ if (!Errs.empty())
+ diagnoseFunctionEffectMergeConflicts(Errs, New->getLocation(),
+ Old->getLocation());
}
CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
>From f4e299f764a22b2e47ebd8c390b8f89849def444 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 6 May 2024 09:06:47 -0700
Subject: [PATCH 56/71] nonblocking(expr): Simplify ActOnEffectExpression, add
tests.
---
clang/include/clang/Sema/Sema.h | 6 +--
clang/lib/Sema/SemaType.cpp | 53 +++++++++---------
clang/lib/Sema/TreeTransform.h | 5 +-
clang/test/Sema/attr-nonblocking-syntax.cpp | 60 +++++++++++++++++++++
4 files changed, 92 insertions(+), 32 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b28d6d27a97c4..67345635d36dc 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -837,9 +837,9 @@ class Sema final : public SemaBase {
SourceLocation OldLoc);
/// Try to parse the conditional expression attached to an effect attribute
- /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). If RequireConstexpr,
- /// then this will fail if the expression is dependent.
- ExprResult ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode &Mode);
+ /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec).
+ ExprResult ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
+ FunctionEffectMode &Mode);
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 9d3c78f057517..a790df8d471fc 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -31,6 +31,7 @@
#include "clang/Sema/DeclSpec.h"
#include "clang/Sema/DelayedDiagnostic.h"
#include "clang/Sema/Lookup.h"
+#include "clang/Sema/ParsedAttr.h"
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/SemaCUDA.h"
@@ -7967,31 +7968,24 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
-ExprResult Sema::ActOnEffectExpression(Expr *CondExpr,
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
FunctionEffectMode &Mode) {
- // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
- if (!CondExpr->isTypeDependent()) {
- ExprResult E = PerformContextuallyConvertToBool(CondExpr);
- if (E.isInvalid())
- return E;
- CondExpr = E.get();
- if (!CondExpr->isValueDependent()) {
- llvm::APSInt CondInt;
- E = VerifyIntegerConstantExpression(
- E.get(), &CondInt,
- diag::err_constexpr_if_condition_expression_is_not_constant);
- if (E.isInvalid()) {
- return E;
- }
- Mode =
- (CondInt != 0) ? FunctionEffectMode::True : FunctionEffectMode::False;
- } else {
- Mode = FunctionEffectMode::Dependent;
- }
- } else {
+ if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
Mode = FunctionEffectMode::Dependent;
+ return CondExpr;
+ }
+
+ std::optional<llvm::APSInt> ConditionValue =
+ CondExpr->getIntegerConstantExpr(Context);
+ if (!ConditionValue) {
+ Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
+ << AttributeName << AANT_ArgumentIntegerConstant
+ << CondExpr->getSourceRange();
+ return ExprResult(/*invalid=*/true);
}
- return CondExpr;
+ Mode = ConditionValue->getExtValue() ? FunctionEffectMode::True
+ : FunctionEffectMode::False;
+ return ExprResult(static_cast<Expr *>(nullptr));
}
static bool
@@ -8006,7 +8000,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
auto *FPT = Unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
// TODO: special diagnostic?
- return false;
+ return true;
}
// Parse the new attribute.
@@ -8028,17 +8022,22 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Parse the condition, if any
if (PAttr.getNumArgs() == 1) {
CondExpr = PAttr.getArgAsExpr(0);
- ExprResult E = S.ActOnEffectExpression(CondExpr, NewMode);
- if (E.isInvalid())
- return false;
+ ExprResult E = S.ActOnEffectExpression(
+ CondExpr, PAttr.getAttrName()->getName(), NewMode);
+ if (E.isInvalid()) {
+ PAttr.setInvalid();
+ return true;
+ }
CondExpr = NewMode == FunctionEffectMode::Dependent ? E.get() : nullptr;
} else {
NewMode = FunctionEffectMode::True;
}
} else {
// This is the `blocking` or `allocating` attribute.
- if (S.CheckAttrNoArgs(PAttr))
+ if (S.CheckAttrNoArgs(PAttr)) {
+ // The attribute has been marked invalid.
return true;
+ }
NewMode = FunctionEffectMode::False;
}
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index c8a366fe3cd28..98f576658b056 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6277,14 +6277,15 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
ExprResult NewExpr = getDerived().TransformExpr(CondExpr);
if (NewExpr.isInvalid())
return QualType();
+ const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
FunctionEffectMode Mode = FunctionEffectMode::None;
- NewExpr = SemaRef.ActOnEffectExpression(NewExpr.get(), Mode);
+ NewExpr =
+ SemaRef.ActOnEffectExpression(NewExpr.get(), Effect.name(), Mode);
if (NewExpr.isInvalid())
return QualType();
// The condition expression has been transformed, and re-evaluated.
// It may or may not have become constant.
- const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
FunctionEffectWithCondition EC;
switch (Mode) {
case FunctionEffectMode::True:
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 5ef5d9aa18e8a..401821cfaab04 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -122,3 +122,63 @@ __attribute__((clang_nonblocking)) void (*nl_func_b)();
} // namespace gnu_style
// TODO: Duplicate the above for nonallocating
+
+
+
+
+// =========================================================================================
+// Non-blocking with an expression parameter
+
+void t0() [[clang::nonblocking(1 - 1)]];
+// CHECK: FunctionDecl {{.*}} t0 'void () __attribute__((clang_blocking))'
+void t1() [[clang::nonblocking(1 + 1)]];
+// CHECK: FunctionDecl {{.*}} t1 'void () __attribute__((clang_nonblocking))'
+
+template <bool V>
+struct ValueDependent {
+ void nb_method() [[clang::nonblocking(V)]];
+};
+
+void t3() [[clang::nonblocking]]
+{
+ ValueDependent<false> x1;
+ x1.nb_method();
+// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
+// CHECK: TemplateArgument integral 0
+// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_blocking))'
+
+ ValueDependent<true> x2;
+ x2.nb_method();
+// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
+// CHECK: TemplateArgument integral 1
+// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
+}
+
+template <typename X>
+struct TypeDependent {
+ void td_method() [[clang::nonblocking(X::is_nb)]];
+};
+
+struct NBPolicyTrue {
+ static constexpr bool is_nb = true;
+};
+
+struct NBPolicyFalse {
+ static constexpr bool is_nb = false;
+};
+
+void t4()
+{
+ TypeDependent<NBPolicyFalse> x1;
+ x1.td_method();
+// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
+// CHECK: TemplateArgument type 'NBPolicyFalse'
+// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_blocking))'
+
+ TypeDependent<NBPolicyTrue> x2;
+ x2.td_method();
+// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
+// CHECK: TemplateArgument type 'NBPolicyTrue'
+// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_nonblocking))'
+}
+
>From cad5d0d89e022d4d1a882516497c69371c1d1616 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 6 May 2024 09:27:24 -0700
Subject: [PATCH 57/71] - Require function prototypes in conjunction with the
new attributes. - Diagnose unexpanded parameter packs in the condition
expression.
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 ++
clang/lib/Sema/SemaType.cpp | 20 ++++++++++++++-----
clang/test/Sema/attr-nonblocking-sema.c | 14 +++++++++++++
clang/test/Sema/attr-nonblocking-sema.cpp | 11 ++++++++++
4 files changed, 42 insertions(+), 5 deletions(-)
create mode 100644 clang/test/Sema/attr-nonblocking-sema.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7a5a7b965beb1..f116a78df0931 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10833,6 +10833,8 @@ def warn_mismatched_func_effect_redeclaration : Warning<
def warn_conflicting_func_effects : Warning<
"effects conflict when merging declarations; kept '%0', discarded '%1'">,
InGroup<FunctionEffects>;
+def err_func_with_effects_no_prototype : Error<
+ "'%0' function must have a prototype">;
} // end of sema category
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a790df8d471fc..8f1fc97c79b29 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7970,7 +7970,17 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
FunctionEffectMode &Mode) {
+ auto BadExpr = [&]() {
+ Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
+ << ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
+ << CondExpr->getSourceRange();
+ };
+
if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
+ if (CondExpr->containsUnexpandedParameterPack()) {
+ BadExpr();
+ return ExprResult(/*invalid=*/true);
+ }
Mode = FunctionEffectMode::Dependent;
return CondExpr;
}
@@ -7978,9 +7988,7 @@ ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
std::optional<llvm::APSInt> ConditionValue =
CondExpr->getIntegerConstantExpr(Context);
if (!ConditionValue) {
- Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
- << AttributeName << AANT_ArgumentIntegerConstant
- << CondExpr->getSourceRange();
+ BadExpr();
return ExprResult(/*invalid=*/true);
}
Mode = ConditionValue->getExtValue() ? FunctionEffectMode::True
@@ -7996,10 +8004,13 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
if (!Unwrapped.isFunctionType())
return false;
+ Sema &S = TPState.getSema();
+
// Require FunctionProtoType
auto *FPT = Unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
- // TODO: special diagnostic?
+ S.Diag(PAttr.getLoc(), diag::err_func_with_effects_no_prototype)
+ << PAttr.getAttrName()->getName();
return true;
}
@@ -8007,7 +8018,6 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// non/blocking or non/allocating? Or conditional (computed)?
const bool IsNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
PAttr.getKind() == ParsedAttr::AT_Blocking;
- Sema &S = TPState.getSema();
FunctionEffectMode NewMode = FunctionEffectMode::None;
Expr *CondExpr = nullptr; // only valid if dependent
diff --git a/clang/test/Sema/attr-nonblocking-sema.c b/clang/test/Sema/attr-nonblocking-sema.c
new file mode 100644
index 0000000000000..199b13b1b33ff
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-sema.c
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -x c -std=c11 %s
+
+// Tests for a few cases involving C functions without prototypes.
+
+void noproto() __attribute__((clang_nonblocking)) // expected-error {{'clang_nonblocking' function must have a prototype}}
+{
+}
+
+// This will succeed
+void noproto(void) __attribute__((clang_blocking));
+
+// A redeclaration isn't any different - a prototype is required.
+void f1(void);
+void f1() __attribute__((clang_nonblocking)); // expected-error {{'clang_nonblocking' function must have a prototype}}
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 19eecc42ed9e9..c3e15e5ab6cf5 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -121,3 +121,14 @@ struct S {
void foo(); // expected-error {{class member cannot be redeclared}}
};
#endif // __cplusplus
+
+// --- COMPUTED NONBLOCKING ---
+void f4() [[clang::nonblocking(__builtin_memset)]] {} // expected-error {{'nonblocking' attribute requires an integer constant}}
+
+#ifdef __cplusplus
+// Unexpanded parameter pack
+template <bool ...val>
+void f5() [[clang::nonblocking(val /* NO ... here */)]] {} // expected-error {{'nonblocking' attribute requires an integer constant}}
+
+void f6() { f5<true, false>(); }
+#endif // __cplusplus
>From f4112ff449ad8e12738bd8d965bfa038b9a1650c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 6 May 2024 14:33:08 -0700
Subject: [PATCH 58/71] tighten up ActOnEffectExpression
---
clang/include/clang/Sema/Sema.h | 7 ++++---
clang/lib/Sema/SemaType.cpp | 33 +++++++++++++++------------------
clang/lib/Sema/TreeTransform.h | 9 ++++-----
3 files changed, 23 insertions(+), 26 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 67345635d36dc..716baaca8af53 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -837,9 +837,10 @@ class Sema final : public SemaBase {
SourceLocation OldLoc);
/// Try to parse the conditional expression attached to an effect attribute
- /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec).
- ExprResult ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
- FunctionEffectMode &Mode);
+ /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
+ /// optional on error.
+ std::optional<FunctionEffectMode>
+ ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName);
bool makeUnavailableInSystemHeader(SourceLocation loc,
UnavailableAttr::ImplicitReason reason);
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 8f1fc97c79b29..ae1ba0ff1121b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7968,32 +7968,27 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
llvm_unreachable("unexpected attribute kind!");
}
-ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName,
- FunctionEffectMode &Mode) {
+std::optional<FunctionEffectMode>
+Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
auto BadExpr = [&]() {
Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
<< ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
<< CondExpr->getSourceRange();
+ return std::nullopt;
};
if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
- if (CondExpr->containsUnexpandedParameterPack()) {
- BadExpr();
- return ExprResult(/*invalid=*/true);
- }
- Mode = FunctionEffectMode::Dependent;
- return CondExpr;
+ if (CondExpr->containsUnexpandedParameterPack())
+ return BadExpr();
+ return FunctionEffectMode::Dependent;
}
std::optional<llvm::APSInt> ConditionValue =
CondExpr->getIntegerConstantExpr(Context);
- if (!ConditionValue) {
- BadExpr();
- return ExprResult(/*invalid=*/true);
- }
- Mode = ConditionValue->getExtValue() ? FunctionEffectMode::True
+ if (!ConditionValue)
+ return BadExpr();
+ return ConditionValue->getExtValue() ? FunctionEffectMode::True
: FunctionEffectMode::False;
- return ExprResult(static_cast<Expr *>(nullptr));
}
static bool
@@ -8032,13 +8027,15 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Parse the condition, if any
if (PAttr.getNumArgs() == 1) {
CondExpr = PAttr.getArgAsExpr(0);
- ExprResult E = S.ActOnEffectExpression(
- CondExpr, PAttr.getAttrName()->getName(), NewMode);
- if (E.isInvalid()) {
+ std::optional<FunctionEffectMode> MaybeMode =
+ S.ActOnEffectExpression(CondExpr, PAttr.getAttrName()->getName());
+ if (!MaybeMode) {
PAttr.setInvalid();
return true;
}
- CondExpr = NewMode == FunctionEffectMode::Dependent ? E.get() : nullptr;
+ NewMode = *MaybeMode;
+ if (NewMode != FunctionEffectMode::Dependent)
+ CondExpr = nullptr;
} else {
NewMode = FunctionEffectMode::True;
}
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 98f576658b056..aad429d7fa47f 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6278,16 +6278,15 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
if (NewExpr.isInvalid())
return QualType();
const FunctionEffect Effect(EPI.FunctionEffects.effects()[Idx]);
- FunctionEffectMode Mode = FunctionEffectMode::None;
- NewExpr =
- SemaRef.ActOnEffectExpression(NewExpr.get(), Effect.name(), Mode);
- if (NewExpr.isInvalid())
+ std::optional<FunctionEffectMode> Mode =
+ SemaRef.ActOnEffectExpression(NewExpr.get(), Effect.name());
+ if (!Mode)
return QualType();
// The condition expression has been transformed, and re-evaluated.
// It may or may not have become constant.
FunctionEffectWithCondition EC;
- switch (Mode) {
+ switch (*Mode) {
case FunctionEffectMode::True:
EC.Effect = Effect;
break;
>From c2848f84a633427230e0fa0aa646e2c7421ce517 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 7 May 2024 07:55:59 -0700
Subject: [PATCH 59/71] More syntax tests.
---
clang/test/Sema/attr-nonblocking-sema.cpp | 84 +++++++++---------
clang/test/Sema/attr-nonblocking-syntax.cpp | 98 ++++++++++++++-------
2 files changed, 107 insertions(+), 75 deletions(-)
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index c3e15e5ab6cf5..59cd38966e886 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -45,43 +45,43 @@ void nonblocking() [[clang::nonblocking]];
void nonallocating() [[clang::nonallocating]];
void type_conversions()
{
- // It's fine to remove a performance constraint.
- void (*fp_plain)();
-
- fp_plain = nullptr;
- fp_plain = unannotated;
- fp_plain = nonblocking;
- fp_plain = nonallocating;
-
- // Adding/spoofing nonblocking is unsafe.
- void (*fp_nonblocking)() [[clang::nonblocking]];
- fp_nonblocking = nullptr;
- fp_nonblocking = nonblocking;
- fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
- fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
-
- // Adding/spoofing nonallocating is unsafe.
- 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 = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}}
+ // It's fine to remove a performance constraint.
+ void (*fp_plain)();
+
+ fp_plain = nullptr;
+ fp_plain = unannotated;
+ fp_plain = nonblocking;
+ fp_plain = nonallocating;
+
+ // Adding/spoofing nonblocking is unsafe.
+ void (*fp_nonblocking)() [[clang::nonblocking]];
+ fp_nonblocking = nullptr;
+ fp_nonblocking = nonblocking;
+ fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
+ fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}}
+
+ // Adding/spoofing nonallocating is unsafe.
+ 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 = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}}
}
#ifdef __cplusplus
-// There was a bug: noexcept and nonblocking could be individually removed in conversion, but not both
+// There was a bug: noexcept and nonblocking could be individually removed in conversion, but not both
void type_conversions_2()
{
- auto receives_fp = [](void (*fp)()) {
- };
-
- auto ne = +[]() noexcept {};
- auto nl = +[]() [[clang::nonblocking]] {};
- auto nl_ne = +[]() noexcept [[clang::nonblocking]] {};
-
- receives_fp(ne);
- receives_fp(nl);
- receives_fp(nl_ne);
+ auto receives_fp = [](void (*fp)()) {
+ };
+
+ auto ne = +[]() noexcept {};
+ auto nl = +[]() [[clang::nonblocking]] {};
+ auto nl_ne = +[]() noexcept [[clang::nonblocking]] {};
+
+ receives_fp(ne);
+ receives_fp(nl);
+ receives_fp(nl_ne);
}
#endif
@@ -90,17 +90,17 @@ void type_conversions_2()
// Check this in the syntax tests too.
#ifdef __cplusplus
struct Base {
- virtual void f1();
- virtual void nonblocking() noexcept [[clang::nonblocking]];
- virtual void nonallocating() noexcept [[clang::nonallocating]];
- virtual void f2() [[clang::nonallocating]]; // expected-note {{previous declaration is here}}
+ virtual void f1();
+ virtual void nonblocking() noexcept [[clang::nonblocking]];
+ virtual void nonallocating() noexcept [[clang::nonallocating]];
+ virtual void f2() [[clang::nonallocating]]; // expected-note {{previous declaration is here}}
};
struct Derived : public Base {
- void f1() [[clang::nonblocking]] override;
- void nonblocking() noexcept override;
- void nonallocating() noexcept override;
- void f2() [[clang::allocating]] override; // expected-warning {{effects conflict when merging declarations; kept 'allocating', discarded 'nonallocating'}}
+ void f1() [[clang::nonblocking]] override;
+ void nonblocking() noexcept override;
+ void nonallocating() noexcept override;
+ void f2() [[clang::allocating]] override; // expected-warning {{effects conflict when merging declarations; kept 'allocating', discarded 'nonallocating'}}
};
#endif // __cplusplus
@@ -117,8 +117,8 @@ void f3() [[clang::nonblocking]]; // expected-warning {{effects conflict when me
// --- OVERLOADS ---
#ifdef __cplusplus
struct S {
- void foo(); // expected-note {{previous declaration is here}}
- void foo(); // expected-error {{class member cannot be redeclared}}
+ void foo(); // expected-note {{previous declaration is here}}
+ void foo(); // expected-error {{class member cannot be redeclared}}
};
#endif // __cplusplus
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 401821cfaab04..6d8082c0e1e0b 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -9,33 +9,33 @@
namespace square_brackets {
-// On the type of the FunctionDecl
+// 1. On the type of the FunctionDecl
void nl_function() [[clang::nonblocking]];
// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
-// On the type of the VarDecl holding a function pointer
+// 2. On the type of the VarDecl holding a function pointer
void (*nl_func_a)() [[clang::nonblocking]];
// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
-// On the type of the ParmVarDecl of a function parameter
+// 3. On the type of the ParmVarDecl of a function parameter
static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
-// As an AttributedType within the nested types of a typedef
+// 4. As an AttributedType within the nested types of a typedef
typedef void (*nl_fp_type)() [[clang::nonblocking]];
// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nonblocking))'
using nl_fp_talias = void (*)() [[clang::nonblocking]];
// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nonblocking))'
-// From a typedef or typealias, on a VarDecl
+// 5. From a typedef or typealias, on a VarDecl
nl_fp_type nl_fp_var1;
// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nonblocking))'
nl_fp_talias nl_fp_var2;
// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nonblocking))'
-// On type of a FieldDecl
+// 6. On type of a FieldDecl
struct Struct {
- void (*nl_func_field)() [[clang::nonblocking]];
+ void (*nl_func_field)() [[clang::nonblocking]];
// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
};
@@ -51,18 +51,18 @@ decltype(nl1) nl3;
// Attribute propagates from base class virtual method to overrides.
struct Base {
- virtual void nb_method() [[clang::nonblocking]];
+ virtual void nb_method() [[clang::nonblocking]];
};
struct Derived : public Base {
- void nb_method() override;
- // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
+ void nb_method() override;
+ // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
};
// Dependent expression
template <bool V>
struct Dependent {
- void nb_method2() [[clang::nonblocking(V)]];
- // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((clang_nonblocking(V)))'
+ void nb_method2() [[clang::nonblocking(V)]];
+ // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((clang_nonblocking(V)))'
};
// --- Blocks ---
@@ -90,40 +90,72 @@ auto nl_lambda = []() [[clang::nonblocking]] {};
void nl_func_false() [[clang::blocking]];
// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_blocking))'
-// TODO: This exposes a bug where a type attribute is lost when inferring a lambda's
-// return type.
auto nl_lambda_false = []() [[clang::blocking]] {};
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_blocking))'
} // namespace square_brackets
// =========================================================================================
// GNU-style attribute, true
-// TODO: Duplicate more of the above for GNU-style attribute
-
namespace gnu_style {
-// On the type of the FunctionDecl
+// 1. On the type of the FunctionDecl
void nl_function() __attribute__((clang_nonblocking));
// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
-// Alternate placement on the FunctionDecl
+// 1a. Alternate placement on the FunctionDecl
__attribute__((clang_nonblocking)) void nl_function();
// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
-// On the type of the VarDecl holding a function pointer
+// 2. On the type of the VarDecl holding a function pointer
void (*nl_func_a)() __attribute__((clang_nonblocking));
// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
-// Alternate attribute placement on VarDecl
+// 2a. Alternate attribute placement on VarDecl
__attribute__((clang_nonblocking)) void (*nl_func_b)();
// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nonblocking))'
+// 3. On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() __attribute__((clang_nonblocking)));
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
+
+// 4. As an AttributedType within the nested types of a typedef
+// Note different placement from square brackets for the typealias.
+typedef void (*nl_fp_type)() __attribute__((clang_nonblocking));
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nonblocking))'
+using nl_fp_talias = __attribute__((clang_nonblocking)) void (*)();
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nonblocking))'
+
+// 5. From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nonblocking))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nonblocking))'
+
+// 6. On type of a FieldDecl
+struct Struct {
+ void (*nl_func_field)() __attribute__((clang_nonblocking));
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
+};
+
} // namespace gnu_style
-// TODO: Duplicate the above for nonallocating
+// =========================================================================================
+// nonallocating and allocating - quick checks because the code paths are generally
+// identical after parsing.
+
+void na_function() [[clang::nonallocating]];
+// CHECK: FunctionDecl {{.*}} na_function 'void () __attribute__((clang_nonallocating))'
+
+void na_true_function() [[clang::nonallocating(true)]];
+// CHECK: FunctionDecl {{.*}} na_true_function 'void () __attribute__((clang_nonallocating))'
+void na_false_function() [[clang::nonallocating(false)]];
+// CHECK: FunctionDecl {{.*}} na_false_function 'void () __attribute__((clang_allocating))'
+void alloc_function() [[clang::allocating]];
+// CHECK: FunctionDecl {{.*}} alloc_function 'void () __attribute__((clang_allocating))'
// =========================================================================================
@@ -136,19 +168,19 @@ void t1() [[clang::nonblocking(1 + 1)]];
template <bool V>
struct ValueDependent {
- void nb_method() [[clang::nonblocking(V)]];
+ void nb_method() [[clang::nonblocking(V)]];
};
void t3() [[clang::nonblocking]]
{
- ValueDependent<false> x1;
- x1.nb_method();
+ ValueDependent<false> x1;
+ x1.nb_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
// CHECK: TemplateArgument integral 0
// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_blocking))'
- ValueDependent<true> x2;
- x2.nb_method();
+ ValueDependent<true> x2;
+ x2.nb_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
// CHECK: TemplateArgument integral 1
// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
@@ -156,27 +188,27 @@ void t3() [[clang::nonblocking]]
template <typename X>
struct TypeDependent {
- void td_method() [[clang::nonblocking(X::is_nb)]];
+ void td_method() [[clang::nonblocking(X::is_nb)]];
};
struct NBPolicyTrue {
- static constexpr bool is_nb = true;
+ static constexpr bool is_nb = true;
};
struct NBPolicyFalse {
- static constexpr bool is_nb = false;
+ static constexpr bool is_nb = false;
};
void t4()
{
- TypeDependent<NBPolicyFalse> x1;
- x1.td_method();
+ TypeDependent<NBPolicyFalse> x1;
+ x1.td_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
// CHECK: TemplateArgument type 'NBPolicyFalse'
// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_blocking))'
- TypeDependent<NBPolicyTrue> x2;
- x2.td_method();
+ TypeDependent<NBPolicyTrue> x2;
+ x2.td_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
// CHECK: TemplateArgument type 'NBPolicyTrue'
// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_nonblocking))'
>From 1fef4b16d8b4715285f3fc27f2fd0526895aadb8 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Thu, 16 May 2024 11:21:53 -0400
Subject: [PATCH 60/71] Apply suggestions from code review
Co-authored-by: Aaron Ballman <aaron at aaronballman.com>
---
clang/include/clang/AST/Decl.h | 4 ++--
clang/include/clang/AST/Type.h | 6 +-----
clang/lib/AST/Type.cpp | 4 ++--
clang/lib/Sema/Sema.cpp | 8 ++++----
clang/lib/Sema/SemaOverload.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 4 ++--
clang/test/Sema/attr-nonblocking-sema.c | 2 +-
7 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 1b5b9874fea93..770d1deddf1df 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -4680,8 +4680,8 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
FunctionEffectsRef getFunctionEffects() const {
- if (TypeSourceInfo *TSI = getSignatureAsWritten())
- if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>())
+ if (const TypeSourceInfo *TSI = getSignatureAsWritten())
+ if (const auto *FPT = TSI->getType()->getAs<FunctionProtoType>())
return FPT->getFunctionEffects();
return {};
}
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 205f5f2f6a6c5..325770dade802 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4782,11 +4782,7 @@ struct FunctionEffectWithCondition {
friend bool operator<(const FunctionEffectWithCondition &LHS,
const FunctionEffectWithCondition &RHS) {
- if (LHS.Effect < RHS.Effect)
- return true;
- if (RHS.Effect < LHS.Effect)
- return false;
- return LHS.Cond.expr() < RHS.Cond.expr();
+ return std::tie(LHS.Effect, LHS.Cond.expr()) < std::tie(RHS.Effect, RHS.Cond.expr());
}
};
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index a36e9c3b6174e..1236bcd1dfd1b 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5113,8 +5113,8 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
CalleeEC.Effect.kind() == Kind::Blocking)
return false;
}
- }
return true;
+ }
case Kind::Allocating:
case Kind::Blocking:
@@ -5216,7 +5216,7 @@ void FunctionEffectSet::replaceItem(unsigned Idx,
Conditions[Idx] = Item.Cond;
// Maintain invariant: If all conditions are null, the vector should be empty.
- if (std::all_of(Conditions.begin(), Conditions.end(),
+ if (llvm::all_of(Conditions,
[](const FunctionEffectCondition &C) {
return C.expr() == nullptr;
})) {
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 0ba62c3e59739..ae704b51af9e7 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2821,7 +2821,7 @@ bool FunctionEffectDiff::shouldDiagnoseConversion(
switch (EffectKind) {
case FunctionEffect::Kind::NonAllocating:
// nonallocating can't be added (spoofed) during a conversion, unless we
- // have nonblocking
+ // have nonblocking.
if (DiffKind == Kind::Added) {
for (const auto &CFE : SrcFX) {
if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
@@ -2837,7 +2837,7 @@ bool FunctionEffectDiff::shouldDiagnoseConversion(
case Kind::Removed:
return false;
case Kind::ConditionMismatch:
- // TODO: Condition mismatches are too coarse right now -- expressions
+ // 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.
@@ -2858,12 +2858,12 @@ bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
switch (EffectKind) {
case FunctionEffect::Kind::NonAllocating:
case FunctionEffect::Kind::NonBlocking:
- // nonblocking/nonallocating can't be removed in a redeclaration
+ // 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
+ return true; // Issue diagnostic.
case Kind::ConditionMismatch:
// All these forms of mismatches are diagnosed.
return true;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index b83bd75bc0985..fbc34c9fb5a87 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1872,7 +1872,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
}
// For C, when called from checkPointerTypesForAssignment,
- // we need not to alter FromFn, or else even an innocuous cast
+ // we need to not alter FromFn, or else even an innocuous cast
// like dropping effects will fail. In C++ however we do want to
// alter FromFn. TODO: Is this correct?
if (getLangOpts().CPlusPlus) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index ae1ba0ff1121b..03efc47a274cd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8001,7 +8001,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
Sema &S = TPState.getSema();
- // Require FunctionProtoType
+ // Require FunctionProtoType.
auto *FPT = Unwrapped.get()->getAs<FunctionProtoType>();
if (FPT == nullptr) {
S.Diag(PAttr.getLoc(), diag::err_func_with_effects_no_prototype)
@@ -8068,7 +8068,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
return true;
};
- // Find previous attributes
+ // Find previous attributes.
std::optional<FunctionEffectWithCondition> PrevNonBlocking;
std::optional<FunctionEffectWithCondition> PrevNonAllocating;
diff --git a/clang/test/Sema/attr-nonblocking-sema.c b/clang/test/Sema/attr-nonblocking-sema.c
index 199b13b1b33ff..41cff4580a17c 100644
--- a/clang/test/Sema/attr-nonblocking-sema.c
+++ b/clang/test/Sema/attr-nonblocking-sema.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -x c -std=c11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 %s
// Tests for a few cases involving C functions without prototypes.
>From ba7f53ffefeb014047fcada895f8baa80d4b8119 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 16 May 2024 12:58:49 -0400
Subject: [PATCH 61/71] - review feedback: getCondition(),
default-constructable iterator. - Remove
FunctionEffectSet::insertIgnoringConditions; insert() returns a bool.
---
clang/include/clang/AST/AbstractBasicWriter.h | 2 +-
clang/include/clang/AST/Type.h | 39 +++++++++++------
clang/lib/AST/Type.cpp | 43 +++++++------------
clang/lib/Sema/Sema.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 4 +-
clang/lib/Sema/TreeTransform.h | 2 +-
6 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index b4ec219c1ebb3..69c4fdc4f49d8 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -227,7 +227,7 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
}
void writeFunctionEffectCondition(FunctionEffectCondition CE) {
- asImpl().writeExprRef(CE.expr());
+ asImpl().writeExprRef(CE.getCondition());
}
void writeNestedNameSpecifier(NestedNameSpecifier *NNS) {
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 325770dade802..89ba7a52a26ca 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4758,7 +4758,7 @@ class FunctionEffectCondition {
FunctionEffectCondition() = default;
FunctionEffectCondition(Expr *E) : Cond(E) {} // implicit OK
- Expr *expr() const { return Cond; }
+ Expr *getCondition() const { return Cond; }
bool operator==(const FunctionEffectCondition &RHS) const {
return Cond == RHS.Cond;
@@ -4782,18 +4782,21 @@ struct FunctionEffectWithCondition {
friend bool operator<(const FunctionEffectWithCondition &LHS,
const FunctionEffectWithCondition &RHS) {
- return std::tie(LHS.Effect, LHS.Cond.expr()) < std::tie(RHS.Effect, RHS.Cond.expr());
+ return std::tuple(LHS.Effect, uintptr_t(LHS.Cond.getCondition())) < std::tuple(RHS.Effect, uintptr_t(RHS.Cond.getCondition()));
}
};
/// Support iteration in parallel through a pair of FunctionEffect and
/// FunctionEffectCondition containers.
template <typename Container> class FunctionEffectIterator {
- const Container &Outer;
- size_t Idx;
+ friend Container;
+
+ const Container *Outer = nullptr;
+ size_t Idx = 0;
public:
- FunctionEffectIterator(const Container &O, size_t I) : Outer(O), Idx(I) {}
+ FunctionEffectIterator();
+ FunctionEffectIterator(const Container &O, size_t I) : Outer(&O), Idx(I) {}
bool operator==(const FunctionEffectIterator &Other) const {
return Idx == Other.Idx;
}
@@ -4807,10 +4810,12 @@ template <typename Container> class FunctionEffectIterator {
return *this;
}
- FunctionEffectWithCondition operator*() const {
- const bool HasConds = !Outer.Conditions.empty();
- return FunctionEffectWithCondition{Outer.Effects[Idx],
- HasConds ? Outer.Conditions[Idx]
+ const FunctionEffectWithCondition operator*() const {
+ // Returns a const struct because storing into it would not accomplish anything.
+ assert(Outer != nullptr && "invalid FunctionEffectIterator");
+ bool HasConds = !Outer->Conditions.empty();
+ return FunctionEffectWithCondition{Outer->Effects[Idx],
+ HasConds ? Outer->Conditions[Idx]
: FunctionEffectCondition()};
}
};
@@ -4908,6 +4913,13 @@ class FunctionEffectSet {
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, size()); }
+ const FunctionEffectWithCondition operator[](size_t Idx) {
+ // Returns a const struct because storing into it would not accomplish anything;
+ // see replaceItem().
+ assert(Idx < size() && "FunctionEffectSet index out of bounds");
+ return *iterator(*this, Idx);
+ }
+
operator FunctionEffectsRef() const { return {Effects, Conditions}; }
void dump(llvm::raw_ostream &OS) const;
@@ -4924,12 +4936,13 @@ class FunctionEffectSet {
};
using Conflicts = SmallVector<Conflict>;
- void insert(const FunctionEffectWithCondition &NewEC, Conflicts &Errs);
- void insert(const FunctionEffectsRef &Set, Conflicts &Errs);
- void insertIgnoringConditions(const FunctionEffectsRef &Set, Conflicts &Errs);
+ // Returns true for success (obviating a check of Errs.empty()).
+ bool insert(const FunctionEffectWithCondition &NewEC, Conflicts &Errs);
+
+ // Returns true for success (obviating a check of Errs.empty()).
+ bool insert(const FunctionEffectsRef &Set, Conflicts &Errs);
void replaceItem(unsigned Idx, const FunctionEffectWithCondition &Item);
- void erase(unsigned Idx);
// Set operations
static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 1236bcd1dfd1b..a949f821b4672 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3669,7 +3669,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
std::uninitialized_copy(SrcConds.begin(), SrcConds.end(), DestConds);
assert(std::any_of(SrcConds.begin(), SrcConds.end(),
[](const FunctionEffectCondition &EC) {
- if (const Expr *E = EC.expr())
+ if (const Expr *E = EC.getCondition())
return E->isTypeDependent() ||
E->isValueDependent();
return false;
@@ -5160,13 +5160,13 @@ void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
ID.AddInteger(Effects[Idx].toOpaqueInt32());
if (HasConds)
- ID.AddPointer(Conditions[Idx].expr());
+ ID.AddPointer(Conditions[Idx].getCondition());
}
}
-void FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
+bool FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
Conflicts &Errs) {
- const FunctionEffect::Kind NewOppositeKind = NewEC.Effect.oppositeKind();
+ FunctionEffect::Kind NewOppositeKind = NewEC.Effect.oppositeKind();
// The index at which insertion will take place; default is at end
// but we might find an earlier insertion point.
@@ -5174,14 +5174,14 @@ void FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
unsigned Idx = 0;
for (const FunctionEffectWithCondition &EC : *this) {
if (EC.Effect.kind() == NewEC.Effect.kind()) {
- if (Conditions[Idx].expr() != NewEC.Cond.expr())
+ if (Conditions[Idx].getCondition() != NewEC.Cond.getCondition())
Errs.push_back({EC, NewEC});
- return;
+ return false;
}
if (EC.Effect.kind() == NewOppositeKind) {
Errs.push_back({EC, NewEC});
- return;
+ return false;
}
if (NewEC.Effect.kind() < EC.Effect.kind() && InsertIdx > Idx)
@@ -5190,23 +5190,19 @@ void FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
++Idx;
}
- if (NewEC.Cond.expr()) {
+ if (NewEC.Cond.getCondition()) {
if (Conditions.empty() && !Effects.empty())
Conditions.resize(Effects.size());
- Conditions.insert(Conditions.begin() + InsertIdx, NewEC.Cond.expr());
+ Conditions.insert(Conditions.begin() + InsertIdx, NewEC.Cond.getCondition());
}
Effects.insert(Effects.begin() + InsertIdx, NewEC.Effect);
+ return true;
}
-void FunctionEffectSet::insert(const FunctionEffectsRef &Set, Conflicts &Errs) {
+bool FunctionEffectSet::insert(const FunctionEffectsRef &Set, Conflicts &Errs) {
for (const auto &Item : Set)
insert(Item, Errs);
-}
-
-void FunctionEffectSet::insertIgnoringConditions(const FunctionEffectsRef &Set,
- Conflicts &Errs) {
- for (const auto &Item : Set)
- insert(FunctionEffectWithCondition(Item.Effect, {}), Errs);
+ return Errs.empty();
}
void FunctionEffectSet::replaceItem(unsigned Idx,
@@ -5218,21 +5214,12 @@ void FunctionEffectSet::replaceItem(unsigned Idx,
// Maintain invariant: If all conditions are null, the vector should be empty.
if (llvm::all_of(Conditions,
[](const FunctionEffectCondition &C) {
- return C.expr() == nullptr;
+ return C.getCondition() == nullptr;
})) {
Conditions.clear();
}
}
-void FunctionEffectSet::erase(unsigned Idx) {
- assert(Idx < Effects.size());
- Effects.erase(Effects.begin() + Idx);
- if (!Conditions.empty()) {
- assert(Idx < Conditions.size());
- Conditions.erase(Conditions.begin() + Idx);
- }
-}
-
FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS,
FunctionEffectsRef RHS) {
FunctionEffectSet Result;
@@ -5284,7 +5271,7 @@ LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
else
First = false;
OS << CFE.Effect.name();
- if (Expr *E = CFE.Cond.expr()) {
+ if (Expr *E = CFE.Cond.getCondition()) {
OS << '(';
E->dump();
OS << ')';
@@ -5316,7 +5303,7 @@ FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
std::string FunctionEffectWithCondition::description() const {
std::string Result(Effect.name().str());
- if (Cond.expr() != nullptr)
+ if (Cond.getCondition() != nullptr)
Result += "(expr)";
return Result;
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index ae704b51af9e7..6e05ddb315fab 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2785,7 +2785,7 @@ FunctionEffectDifferences::FunctionEffectDifferences(
cmp = 1;
else {
cmp = 0;
- if (Old.Cond.expr() != New.Cond.expr()) {
+ if (Old.Cond.getCondition() != New.Cond.getCondition()) {
// TODO: Cases where the expressions are equivalent but
// don't have the same identity.
push_back(FunctionEffectDiff{
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 03efc47a274cd..1b70b8a11f077 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8106,8 +8106,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
FunctionEffectSet::Conflicts Errs;
- FX.insert(NewEC, Errs);
- assert(Errs.empty() && "effect conflicts should have been diagnosed above");
+ bool Success = FX.insert(NewEC, Errs);
+ assert(Success && "effect conflicts should have been diagnosed above");
EPI.FunctionEffects = FunctionEffectsRef(FX);
QualType NewType = S.Context.getFunctionType(FPT->getReturnType(),
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index aad429d7fa47f..296b8e52af3ef 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6273,7 +6273,7 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
getSema(), Sema::ExpressionEvaluationContext::ConstantEvaluated);
for (unsigned Idx = 0, Count = FXConds.size(); Idx != Count;) {
- if (Expr *CondExpr = FXConds[Idx].expr()) {
+ if (Expr *CondExpr = FXConds[Idx].getCondition()) {
ExprResult NewExpr = getDerived().TransformExpr(CondExpr);
if (NewExpr.isInvalid())
return QualType();
>From 173d36bb00a01800e6137456cbcdf40ad80a2590 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 16 May 2024 13:07:19 -0400
Subject: [PATCH 62/71] Remove the "clang_" prefix from the attribute
spellings.
---
clang/include/clang/Basic/Attr.td | 18 +---
clang/lib/AST/TypePrinter.cpp | 4 +-
clang/test/Sema/attr-nonblocking-sema.c | 6 +-
clang/test/Sema/attr-nonblocking-sema.cpp | 2 +-
clang/test/Sema/attr-nonblocking-syntax.cpp | 94 ++++++++++-----------
5 files changed, 57 insertions(+), 67 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index dd2eff71e1821..d0612ca46ba30 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1436,34 +1436,24 @@ def CXX11NoReturn : InheritableAttr {
}
def NonBlocking : TypeAttr {
- let Spellings = [CXX11<"clang", "nonblocking">,
- C23<"clang", "nonblocking">,
- GNU<"clang_nonblocking">];
-
+ let Spellings = [Clang<"nonblocking">];
let Args = [ExprArgument<"Cond", /*optional*/1>];
let Documentation = [NonBlockingDocs];
}
def NonAllocating : TypeAttr {
- let Spellings = [CXX11<"clang", "nonallocating">,
- C23<"clang", "nonallocating">,
- GNU<"clang_nonallocating">];
+ let Spellings = [Clang<"nonallocating">];
let Args = [ExprArgument<"Cond", /*optional*/1>];
let Documentation = [NonAllocatingDocs];
}
def Blocking : TypeAttr {
- let Spellings = [CXX11<"clang", "blocking">,
- C23<"clang", "blocking">,
- GNU<"clang_blocking">];
-
+ let Spellings = [Clang<"blocking">];
let Documentation = [BlockingDocs];
}
def Allocating : TypeAttr {
- let Spellings = [CXX11<"clang", "allocating">,
- C23<"clang", "allocating">,
- GNU<"clang_allocating">];
+ let Spellings = [Clang<"allocating">];
let Documentation = [AllocatingDocs];
}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index a7a10cbb7489c..9b93971e01122 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1000,8 +1000,8 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
const FunctionEffectsRef FX = T->getFunctionEffects();
for (const auto &CFE : FX) {
- OS << " __attribute__((clang_" << CFE.Effect.name();
- if (const Expr *E = CFE.Cond.expr()) {
+ OS << " __attribute__((" << CFE.Effect.name();
+ if (const Expr *E = CFE.Cond.getCondition()) {
OS << '(';
E->printPretty(OS, nullptr, Policy);
OS << ')';
diff --git a/clang/test/Sema/attr-nonblocking-sema.c b/clang/test/Sema/attr-nonblocking-sema.c
index 41cff4580a17c..0647e47febef2 100644
--- a/clang/test/Sema/attr-nonblocking-sema.c
+++ b/clang/test/Sema/attr-nonblocking-sema.c
@@ -2,13 +2,13 @@
// Tests for a few cases involving C functions without prototypes.
-void noproto() __attribute__((clang_nonblocking)) // expected-error {{'clang_nonblocking' function must have a prototype}}
+void noproto() __attribute__((nonblocking)) // expected-error {{'nonblocking' function must have a prototype}}
{
}
// This will succeed
-void noproto(void) __attribute__((clang_blocking));
+void noproto(void) __attribute__((blocking));
// A redeclaration isn't any different - a prototype is required.
void f1(void);
-void f1() __attribute__((clang_nonblocking)); // expected-error {{'clang_nonblocking' function must have a prototype}}
+void f1() __attribute__((nonblocking)); // expected-error {{'nonblocking' function must have a prototype}}
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 59cd38966e886..b2096381837ea 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
-#if !__has_attribute(clang_nonblocking)
+#if !__has_attribute(nonblocking)
#error "the 'nonblocking' attribute is not available"
#endif
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 6d8082c0e1e0b..b0823dff9463e 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -11,43 +11,43 @@ namespace square_brackets {
// 1. On the type of the FunctionDecl
void nl_function() [[clang::nonblocking]];
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))'
// 2. On the type of the VarDecl holding a function pointer
void (*nl_func_a)() [[clang::nonblocking]];
-// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((nonblocking))'
// 3. On the type of the ParmVarDecl of a function parameter
static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
-// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((nonblocking))'
// 4. As an AttributedType within the nested types of a typedef
typedef void (*nl_fp_type)() [[clang::nonblocking]];
-// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((nonblocking))'
using nl_fp_talias = void (*)() [[clang::nonblocking]];
-// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((nonblocking))'
// 5. From a typedef or typealias, on a VarDecl
nl_fp_type nl_fp_var1;
-// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((nonblocking))'
nl_fp_talias nl_fp_var2;
-// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((nonblocking))'
// 6. On type of a FieldDecl
struct Struct {
void (*nl_func_field)() [[clang::nonblocking]];
-// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((nonblocking))'
};
// nonallocating should NOT be subsumed into nonblocking
void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
-// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((nonblocking)) __attribute__((nonallocating))'
void nl2() [[clang::nonallocating]] [[clang::nonblocking]];
-// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
+// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((nonblocking)) __attribute__((nonallocating))'
decltype(nl1) nl3;
-// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nonblocking)) __attribute__((clang_nonallocating))'
+// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((nonblocking)) __attribute__((nonallocating))'
// Attribute propagates from base class virtual method to overrides.
struct Base {
@@ -55,43 +55,43 @@ struct Base {
};
struct Derived : public Base {
void nb_method() override;
- // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
+ // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((nonblocking))'
};
// Dependent expression
template <bool V>
struct Dependent {
void nb_method2() [[clang::nonblocking(V)]];
- // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((clang_nonblocking(V)))'
+ // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((nonblocking(V)))'
};
// --- Blocks ---
// On the type of the VarDecl holding a BlockDecl
void (^nl_block1)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] {};
-// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((nonblocking))'
int (^nl_block2)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] { return 0; };
-// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((nonblocking))'
// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
static void blockCaller() { nl_block1(); }
-// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nonblocking))'
+// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((nonblocking))'
// --- Lambdas ---
// On the operator() of a lambda's CXXMethodDecl
auto nl_lambda = []() [[clang::nonblocking]] {};
-// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_nonblocking))' inline
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((nonblocking))' inline
// =========================================================================================
// Square brackets, false
void nl_func_false() [[clang::blocking]];
-// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_blocking))'
+// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((blocking))'
auto nl_lambda_false = []() [[clang::blocking]] {};
-// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_blocking))'
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((blocking))'
} // namespace square_brackets
@@ -101,42 +101,42 @@ auto nl_lambda_false = []() [[clang::blocking]] {};
namespace gnu_style {
// 1. On the type of the FunctionDecl
-void nl_function() __attribute__((clang_nonblocking));
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
+void nl_function() __attribute__((nonblocking));
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))'
// 1a. Alternate placement on the FunctionDecl
-__attribute__((clang_nonblocking)) void nl_function();
-// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nonblocking))'
+__attribute__((nonblocking)) void nl_function();
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))'
// 2. On the type of the VarDecl holding a function pointer
-void (*nl_func_a)() __attribute__((clang_nonblocking));
-// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nonblocking))'
+void (*nl_func_a)() __attribute__((nonblocking));
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((nonblocking))'
// 2a. Alternate attribute placement on VarDecl
-__attribute__((clang_nonblocking)) void (*nl_func_b)();
-// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nonblocking))'
+__attribute__((nonblocking)) void (*nl_func_b)();
+// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((nonblocking))'
// 3. On the type of the ParmVarDecl of a function parameter
-static void nlReceiver(void (*nl_func)() __attribute__((clang_nonblocking)));
-// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nonblocking))'
+static void nlReceiver(void (*nl_func)() __attribute__((nonblocking)));
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((nonblocking))'
// 4. As an AttributedType within the nested types of a typedef
// Note different placement from square brackets for the typealias.
-typedef void (*nl_fp_type)() __attribute__((clang_nonblocking));
-// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nonblocking))'
-using nl_fp_talias = __attribute__((clang_nonblocking)) void (*)();
-// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nonblocking))'
+typedef void (*nl_fp_type)() __attribute__((nonblocking));
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((nonblocking))'
+using nl_fp_talias = __attribute__((nonblocking)) void (*)();
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((nonblocking))'
// 5. From a typedef or typealias, on a VarDecl
nl_fp_type nl_fp_var1;
-// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((nonblocking))'
nl_fp_talias nl_fp_var2;
-// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nonblocking))'
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((nonblocking))'
// 6. On type of a FieldDecl
struct Struct {
- void (*nl_func_field)() __attribute__((clang_nonblocking));
-// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nonblocking))'
+ void (*nl_func_field)() __attribute__((nonblocking));
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((nonblocking))'
};
} // namespace gnu_style
@@ -146,25 +146,25 @@ struct Struct {
// identical after parsing.
void na_function() [[clang::nonallocating]];
-// CHECK: FunctionDecl {{.*}} na_function 'void () __attribute__((clang_nonallocating))'
+// CHECK: FunctionDecl {{.*}} na_function 'void () __attribute__((nonallocating))'
void na_true_function() [[clang::nonallocating(true)]];
-// CHECK: FunctionDecl {{.*}} na_true_function 'void () __attribute__((clang_nonallocating))'
+// CHECK: FunctionDecl {{.*}} na_true_function 'void () __attribute__((nonallocating))'
void na_false_function() [[clang::nonallocating(false)]];
-// CHECK: FunctionDecl {{.*}} na_false_function 'void () __attribute__((clang_allocating))'
+// CHECK: FunctionDecl {{.*}} na_false_function 'void () __attribute__((allocating))'
void alloc_function() [[clang::allocating]];
-// CHECK: FunctionDecl {{.*}} alloc_function 'void () __attribute__((clang_allocating))'
+// CHECK: FunctionDecl {{.*}} alloc_function 'void () __attribute__((allocating))'
// =========================================================================================
// Non-blocking with an expression parameter
void t0() [[clang::nonblocking(1 - 1)]];
-// CHECK: FunctionDecl {{.*}} t0 'void () __attribute__((clang_blocking))'
+// CHECK: FunctionDecl {{.*}} t0 'void () __attribute__((blocking))'
void t1() [[clang::nonblocking(1 + 1)]];
-// CHECK: FunctionDecl {{.*}} t1 'void () __attribute__((clang_nonblocking))'
+// CHECK: FunctionDecl {{.*}} t1 'void () __attribute__((nonblocking))'
template <bool V>
struct ValueDependent {
@@ -177,13 +177,13 @@ void t3() [[clang::nonblocking]]
x1.nb_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
// CHECK: TemplateArgument integral 0
-// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_blocking))'
+// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((blocking))'
ValueDependent<true> x2;
x2.nb_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent
// CHECK: TemplateArgument integral 1
-// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((clang_nonblocking))'
+// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((nonblocking))'
}
template <typename X>
@@ -205,12 +205,12 @@ void t4()
x1.td_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
// CHECK: TemplateArgument type 'NBPolicyFalse'
-// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_blocking))'
+// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((blocking))'
TypeDependent<NBPolicyTrue> x2;
x2.td_method();
// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent
// CHECK: TemplateArgument type 'NBPolicyTrue'
-// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((clang_nonblocking))'
+// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((nonblocking))'
}
>From 5438e963adf30df3c59cc21362a3bd3fac684661 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 16 May 2024 13:12:27 -0400
Subject: [PATCH 63/71] More review feedback.
---
clang/include/clang/AST/Type.h | 12 ++++--------
clang/lib/AST/Type.cpp | 32 +++++++++++++++++++++-----------
clang/lib/Sema/SemaType.cpp | 4 ++--
3 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 89ba7a52a26ca..b3db510944a8f 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4779,11 +4779,6 @@ struct FunctionEffectWithCondition {
/// Return a textual description of the effect, and its condition, if any.
std::string description() const;
-
- friend bool operator<(const FunctionEffectWithCondition &LHS,
- const FunctionEffectWithCondition &RHS) {
- return std::tuple(LHS.Effect, uintptr_t(LHS.Cond.getCondition())) < std::tuple(RHS.Effect, uintptr_t(RHS.Cond.getCondition()));
- }
};
/// Support iteration in parallel through a pair of FunctionEffect and
@@ -4811,7 +4806,8 @@ template <typename Container> class FunctionEffectIterator {
}
const FunctionEffectWithCondition operator*() const {
- // Returns a const struct because storing into it would not accomplish anything.
+ // Returns a const struct because storing into it would not accomplish
+ // anything.
assert(Outer != nullptr && "invalid FunctionEffectIterator");
bool HasConds = !Outer->Conditions.empty();
return FunctionEffectWithCondition{Outer->Effects[Idx],
@@ -4914,8 +4910,8 @@ class FunctionEffectSet {
iterator end() const { return iterator(*this, size()); }
const FunctionEffectWithCondition operator[](size_t Idx) {
- // Returns a const struct because storing into it would not accomplish anything;
- // see replaceItem().
+ // Returns a const struct because storing into it would not accomplish
+ // anything; see replaceItem().
assert(Idx < size() && "FunctionEffectSet index out of bounds");
return *iterator(*this, Idx);
}
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index a949f821b4672..b99ffb5000e47 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3652,7 +3652,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
if (!epi.FunctionEffects.empty()) {
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
- const size_t EffectsCount = epi.FunctionEffects.size();
+ size_t EffectsCount = epi.FunctionEffects.size();
ExtraBits.NumFunctionEffects = EffectsCount;
assert(ExtraBits.NumFunctionEffects == EffectsCount &&
"effect bitfield overflow");
@@ -5088,7 +5088,7 @@ StringRef FunctionEffect::name() const {
case Kind::Allocating:
return "allocating";
case Kind::None:
- break;
+ return "(none)";
}
llvm_unreachable("unknown effect kind");
}
@@ -5121,6 +5121,7 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
return false;
case Kind::None:
+ assert(0 && "canInferOnFunction with None");
break;
}
llvm_unreachable("unknown effect kind");
@@ -5146,6 +5147,7 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
case Kind::Blocking:
return false;
case Kind::None:
+ assert(0 && "shouldDiagnoseFunctionCall with None");
break;
}
llvm_unreachable("unknown effect kind");
@@ -5154,7 +5156,7 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
// =====
void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
- const bool HasConds = !Conditions.empty();
+ bool HasConds = !Conditions.empty();
ID.AddInteger(size() | (HasConds << 31u));
for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
@@ -5193,7 +5195,8 @@ bool FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
if (NewEC.Cond.getCondition()) {
if (Conditions.empty() && !Effects.empty())
Conditions.resize(Effects.size());
- Conditions.insert(Conditions.begin() + InsertIdx, NewEC.Cond.getCondition());
+ Conditions.insert(Conditions.begin() + InsertIdx,
+ NewEC.Cond.getCondition());
}
Effects.insert(Effects.begin() + InsertIdx, NewEC.Effect);
return true;
@@ -5212,10 +5215,9 @@ void FunctionEffectSet::replaceItem(unsigned Idx,
Conditions[Idx] = Item.Cond;
// Maintain invariant: If all conditions are null, the vector should be empty.
- if (llvm::all_of(Conditions,
- [](const FunctionEffectCondition &C) {
- return C.getCondition() == nullptr;
- })) {
+ if (llvm::all_of(Conditions, [](const FunctionEffectCondition &C) {
+ return C.getCondition() == nullptr;
+ })) {
Conditions.clear();
}
}
@@ -5231,13 +5233,21 @@ FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS,
auto IterA = LHS.begin(), EndA = LHS.end();
auto IterB = RHS.begin(), EndB = RHS.end();
+ auto FEWCLess = [](const FunctionEffectWithCondition &LHS,
+ const FunctionEffectWithCondition &RHS) {
+ return std::tuple(LHS.Effect, uintptr_t(LHS.Cond.getCondition())) <
+ std::tuple(RHS.Effect, uintptr_t(RHS.Cond.getCondition()));
+ };
+
while (IterA != EndA && IterB != EndB) {
- if (*IterA < *IterB)
+ FunctionEffectWithCondition A = *IterA;
+ FunctionEffectWithCondition B = *IterB;
+ if (FEWCLess(A, B))
++IterA;
- else if (*IterB < *IterA)
+ else if (FEWCLess(B, A))
++IterB;
else {
- Result.insert(*IterA, Errs);
+ Result.insert(A, Errs);
++IterA;
++IterB;
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 1b70b8a11f077..141eb9183e7ed 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8011,8 +8011,8 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
// Parse the new attribute.
// non/blocking or non/allocating? Or conditional (computed)?
- const bool IsNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
- PAttr.getKind() == ParsedAttr::AT_Blocking;
+ bool IsNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ PAttr.getKind() == ParsedAttr::AT_Blocking;
FunctionEffectMode NewMode = FunctionEffectMode::None;
Expr *CondExpr = nullptr; // only valid if dependent
>From 9e4782c9ff1413be5d50bcf6f5fcf7427bf0f52d Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 17 May 2024 08:39:53 -0400
Subject: [PATCH 64/71] - Clean up comments (trailing period). - Hide
removeItem, make TreeTransform a friend.
---
clang/include/clang/AST/Type.h | 25 ++++++++++++-------------
clang/include/clang/Sema/Sema.h | 14 +++++++-------
clang/lib/AST/Type.cpp | 4 ++--
clang/lib/Sema/Sema.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 10 +++++-----
5 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index b3db510944a8f..58e07a682ed8c 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -132,6 +132,7 @@ class TemplateArgument;
class TemplateArgumentListInfo;
class TemplateArgumentLoc;
class TemplateTypeParmDecl;
+template <typename> struct TreeTransform;
class TypedefNameDecl;
class UnresolvedUsingTypenameDecl;
class UsingShadowDecl;
@@ -4709,7 +4710,7 @@ class FunctionEffect {
FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
FE_ExcludeThreadLocalVars;
case Kind::NonAllocating:
- // Same as NonBlocking, except without FE_ExcludeStaticLocalVars
+ // Same as NonBlocking, except without FE_ExcludeStaticLocalVars.
return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
case Kind::Blocking:
@@ -4752,11 +4753,11 @@ class FunctionEffect {
/// Wrap a function effect's condition expression in another struct so
/// that FunctionProtoType's TrailingObjects can treat it separately.
class FunctionEffectCondition {
- Expr *Cond = nullptr; // if null, unconditional
+ Expr *Cond = nullptr; // if null, unconditional.
public:
FunctionEffectCondition() = default;
- FunctionEffectCondition(Expr *E) : Cond(E) {} // implicit OK
+ FunctionEffectCondition(Expr *E) : Cond(E) {} // non-explicit is OK.
Expr *getCondition() const { return Cond; }
@@ -4799,7 +4800,6 @@ template <typename Container> class FunctionEffectIterator {
return Idx != Other.Idx;
}
- // prefix increment
FunctionEffectIterator operator++() {
++Idx;
return *this;
@@ -4892,6 +4892,9 @@ class FunctionEffectsRef {
///
/// Has the same invariants as FunctionEffectsRef.
class FunctionEffectSet {
+ // TreeTransform is the only user of replaceItem.
+ template <typename> friend struct TreeTransform;
+
SmallVector<FunctionEffect> Effects;
SmallVector<FunctionEffectCondition> Conditions;
@@ -4909,13 +4912,6 @@ class FunctionEffectSet {
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, size()); }
- const FunctionEffectWithCondition operator[](size_t Idx) {
- // Returns a const struct because storing into it would not accomplish
- // anything; see replaceItem().
- assert(Idx < size() && "FunctionEffectSet index out of bounds");
- return *iterator(*this, Idx);
- }
-
operator FunctionEffectsRef() const { return {Effects, Conditions}; }
void dump(llvm::raw_ostream &OS) const;
@@ -4938,13 +4934,16 @@ class FunctionEffectSet {
// Returns true for success (obviating a check of Errs.empty()).
bool insert(const FunctionEffectsRef &Set, Conflicts &Errs);
- void replaceItem(unsigned Idx, const FunctionEffectWithCondition &Item);
-
// Set operations
+
static FunctionEffectSet getUnion(FunctionEffectsRef LHS,
FunctionEffectsRef RHS, Conflicts &Errs);
static FunctionEffectSet getIntersection(FunctionEffectsRef LHS,
FunctionEffectsRef RHS);
+
+private:
+ // For the use of TreeTransform.
+ void replaceItem(unsigned Idx, const FunctionEffectWithCondition &Item);
};
/// Represents a prototype with parameter type info, e.g.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 716baaca8af53..dba3eeb9fc610 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -454,10 +454,10 @@ enum class CheckedConversionKind {
/// Used with attributes/effects with a boolean condition, e.g. `nonblocking`.
enum class FunctionEffectMode : uint8_t {
- None, // effect is not present
- False, // effect(false)
- True, // effect(true)
- Dependent // effect(expr) where expr is dependent
+ None, // effect is not present.
+ False, // effect(false).
+ True, // effect(true).
+ Dependent // effect(expr) where expr is dependent.
};
struct FunctionEffectDiff {
@@ -465,8 +465,8 @@ struct FunctionEffectDiff {
FunctionEffect::Kind EffectKind;
Kind DiffKind;
- FunctionEffectWithCondition Old; // invalid when Added
- FunctionEffectWithCondition New; // invalid when Removed
+ FunctionEffectWithCondition Old; // invalid when Added.
+ FunctionEffectWithCondition New; // invalid when Removed.
StringRef effectName() const {
if (Old.Effect.kind() != FunctionEffect::Kind::None)
@@ -479,7 +479,7 @@ struct FunctionEffectDiff {
enum class OverrideResult {
NoAction,
Warn,
- Merge // Merge missing effect from base to derived
+ Merge // Merge missing effect from base to derived.
};
/// Return true if adding or removing the effect as part of a type conversion
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index b99ffb5000e47..300ea41df2844 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5105,10 +5105,10 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
else
return false;
for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
- // nonblocking/nonallocating cannot call allocating
+ // nonblocking/nonallocating cannot call allocating.
if (CalleeEC.Effect.kind() == Kind::Allocating)
return false;
- // nonblocking cannot call blocking
+ // nonblocking cannot call blocking.
if (kind() == Kind::NonBlocking &&
CalleeEC.Effect.kind() == Kind::Blocking)
return false;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 6e05ddb315fab..60b723a7d9fb0 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2786,7 +2786,7 @@ FunctionEffectDifferences::FunctionEffectDifferences(
else {
cmp = 0;
if (Old.Cond.getCondition() != New.Cond.getCondition()) {
- // TODO: Cases where the expressions are equivalent but
+ // FIXME: Cases where the expressions are equivalent but
// don't have the same identity.
push_back(FunctionEffectDiff{
Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 141eb9183e7ed..623156b77bb21 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8024,7 +8024,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
return true;
}
- // Parse the condition, if any
+ // Parse the condition, if any.
if (PAttr.getNumArgs() == 1) {
CondExpr = PAttr.getArgAsExpr(0);
std::optional<FunctionEffectMode> MaybeMode =
@@ -8062,7 +8062,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
<< ("'" + NewEC.description() + "'")
<< ("'" + PrevEC.description() + "'") << false;
- // we don't necessarily have the location of the previous attribute,
+ // We don't necessarily have the location of the previous attribute,
// so no note.
PAttr.setInvalid();
return true;
@@ -8091,18 +8091,18 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
}
if (IsNonBlocking) {
- // new nonblocking(true) is incompatible with previous allocating
+ // A new nonblocking(true) is incompatible with a previous allocating.
if (NewMode == FunctionEffectMode::True && PrevNonAllocating &&
PrevNonAllocating->Effect.kind() == FunctionEffect::Kind::Allocating)
return Incompatible(*PrevNonAllocating);
} else {
- // new allocating is incompatible with previous nonblocking(true)
+ // A new allocating is incompatible with a previous nonblocking(true).
if (NewMode == FunctionEffectMode::False && PrevNonBlocking &&
PrevNonBlocking->Effect.kind() == FunctionEffect::Kind::NonBlocking)
return Incompatible(*PrevNonBlocking);
}
- // Add the effect to the FunctionProtoType
+ // Add the effect to the FunctionProtoType.
FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
FunctionEffectSet FX(EPI.FunctionEffects);
FunctionEffectSet::Conflicts Errs;
>From 60bf34c59b3b53e621dd004fd48595fbde0c74ec Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 21 May 2024 09:48:27 -0700
Subject: [PATCH 65/71] TreeTransform is a class not a struct.
---
clang/include/clang/AST/Type.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 58e07a682ed8c..dd67978e3346f 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -132,7 +132,7 @@ class TemplateArgument;
class TemplateArgumentListInfo;
class TemplateArgumentLoc;
class TemplateTypeParmDecl;
-template <typename> struct TreeTransform;
+template <typename> class TreeTransform;
class TypedefNameDecl;
class UnresolvedUsingTypenameDecl;
class UsingShadowDecl;
@@ -4893,7 +4893,7 @@ class FunctionEffectsRef {
/// Has the same invariants as FunctionEffectsRef.
class FunctionEffectSet {
// TreeTransform is the only user of replaceItem.
- template <typename> friend struct TreeTransform;
+ template <typename> friend class TreeTransform;
SmallVector<FunctionEffect> Effects;
SmallVector<FunctionEffectCondition> Conditions;
>From 877dec82627024844b0c3186614ef421dfedb1f6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 22 May 2024 08:03:55 -0700
Subject: [PATCH 66/71] Add a positive test case per Aaron's feedback
---
clang/test/Sema/attr-nonblocking-sema.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index b2096381837ea..9d32b361d9eca 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -11,6 +11,13 @@ int nl_var [[clang::nonblocking]]; // expected-warning {{'nonblocking' only appl
struct nl_struct {} [[clang::nonblocking]]; // expected-warning {{attribute 'nonblocking' is ignored, place it after "struct" to apply attribute to type declaration}}
struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' attribute cannot be applied to a declaration}}
+// Positive case
+typedef void (*fo)() [[clang::nonblocking]];
+void (*read_me_and_weep(
+ int val, void (*func)(int) [[clang::nonblocking]])
+ [[clang::nonblocking]]) (int)
+ [[clang::nonblocking]];
+
// --- ATTRIBUTE SYNTAX: ARGUMENT COUNT ---
void nargs_1() [[clang::nonblocking(1, 2)]]; // expected-error {{'nonblocking' attribute takes no more than 1 argument}}
void nargs_2() [[clang::nonallocating(1, 2)]]; // expected-error {{'nonallocating' attribute takes no more than 1 argument}}
>From 8bef15bec6a43a485cc5e24a4c483088fb4872f7 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 23 May 2024 17:50:21 -0700
Subject: [PATCH 67/71] Fix: read past end of Conditions array in
FunctionEffectSet::insert.
---
clang/lib/AST/Type.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6bc2cc0c20778..f72705f5e8e94 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5188,7 +5188,9 @@ bool FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC,
unsigned Idx = 0;
for (const FunctionEffectWithCondition &EC : *this) {
if (EC.Effect.kind() == NewEC.Effect.kind()) {
- if (Conditions[Idx].getCondition() != NewEC.Cond.getCondition())
+ const Expr *PrevCond =
+ Conditions.empty() ? nullptr : Conditions[Idx].getCondition();
+ if (PrevCond != NewEC.Cond.getCondition())
Errs.push_back({EC, NewEC});
return false;
}
>From f015a2440a8186999d10f2fc99f514e6e8601603 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Fri, 24 May 2024 07:29:35 -0700
Subject: [PATCH 68/71] Apply suggestions from code review
Co-authored-by: Sirraide <aeternalmail at gmail.com>
---
clang/lib/Sema/SemaType.cpp | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 918ef95ebdcd2..0573b12cedc73 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7532,17 +7532,16 @@ Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
return std::nullopt;
};
- if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
- if (CondExpr->containsUnexpandedParameterPack())
- return BadExpr();
+ if (DiagnoseUnexpandedParameterPack(CondExpr))
+ return std::nullopt;
+ if (CondExpr->isTypeDependent() || CondExpr->isValueDependent())
return FunctionEffectMode::Dependent;
- }
std::optional<llvm::APSInt> ConditionValue =
CondExpr->getIntegerConstantExpr(Context);
if (!ConditionValue)
return BadExpr();
- return ConditionValue->getExtValue() ? FunctionEffectMode::True
+ return !ConditionValue->isZero() ? FunctionEffectMode::True
: FunctionEffectMode::False;
}
>From d8d34be3b0eddfbacb88f53ef0c7d96546c779ec Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 24 May 2024 07:31:28 -0700
Subject: [PATCH 69/71] FunctionEffectCondition => EffectConditionExpr, more
minor review feedback
---
clang/include/clang/AST/AbstractBasicReader.h | 4 +-
clang/include/clang/AST/AbstractBasicWriter.h | 2 +-
clang/include/clang/AST/PropertiesBase.td | 2 +-
clang/include/clang/AST/Type.h | 43 +++++++++----------
clang/include/clang/AST/TypeProperties.td | 2 +-
clang/lib/AST/ASTContext.cpp | 2 +-
clang/lib/AST/Type.cpp | 11 +++--
clang/lib/Sema/SemaOverload.cpp | 2 +-
clang/lib/Sema/SemaType.cpp | 21 ++++-----
clang/lib/Sema/TreeTransform.h | 2 +-
clang/test/Sema/attr-nonblocking-sema.cpp | 2 +-
11 files changed, 43 insertions(+), 50 deletions(-)
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index ba97601ac2774..4b627c65e276b 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -249,8 +249,8 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
return FunctionEffect::fromOpaqueInt32(value);
}
- FunctionEffectCondition readFunctionEffectCondition() {
- return FunctionEffectCondition{asImpl().readExprRef()};
+ EffectConditionExpr readEffectConditionExpr() {
+ return EffectConditionExpr{asImpl().readExprRef()};
}
NestedNameSpecifier *readNestedNameSpecifier() {
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 69c4fdc4f49d8..b941add8bde88 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -226,7 +226,7 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
asImpl().writeUInt32(E.toOpaqueInt32());
}
- void writeFunctionEffectCondition(FunctionEffectCondition CE) {
+ void writeEffectConditionExpr(EffectConditionExpr CE) {
asImpl().writeExprRef(CE.getCondition());
}
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index aa25a1c7c3acf..5f7d619518762 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -118,7 +118,7 @@ def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> {
let PassByReference = 1;
}
def FunctionEffect : PropertyType<"FunctionEffect">;
-def FunctionEffectCondition : PropertyType<"FunctionEffectCondition">;
+def EffectConditionExpr : PropertyType<"EffectConditionExpr">;
def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; }
def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">;
def LValuePathSerializationHelper :
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e7485514d23b4..8661565c868bd 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4728,7 +4728,6 @@ class FunctionEffect {
/// Return true if the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl.
- /// This is only used if the effect has FE_InferrableOnCallees flag set.
/// Example: This allows nonblocking(false) to prevent inference for the
/// function.
bool canInferOnFunction(const Decl &Callee) const;
@@ -4753,16 +4752,16 @@ class FunctionEffect {
/// Wrap a function effect's condition expression in another struct so
/// that FunctionProtoType's TrailingObjects can treat it separately.
-class FunctionEffectCondition {
+class EffectConditionExpr {
Expr *Cond = nullptr; // if null, unconditional.
public:
- FunctionEffectCondition() = default;
- FunctionEffectCondition(Expr *E) : Cond(E) {} // non-explicit is OK.
+ EffectConditionExpr() = default;
+ EffectConditionExpr(Expr *E) : Cond(E) {}
Expr *getCondition() const { return Cond; }
- bool operator==(const FunctionEffectCondition &RHS) const {
+ bool operator==(const EffectConditionExpr &RHS) const {
return Cond == RHS.Cond;
}
};
@@ -4772,11 +4771,11 @@ class FunctionEffectCondition {
/// expression when present, is dependent.
struct FunctionEffectWithCondition {
FunctionEffect Effect;
- FunctionEffectCondition Cond;
+ EffectConditionExpr Cond;
FunctionEffectWithCondition() = default;
FunctionEffectWithCondition(const FunctionEffect &E,
- const FunctionEffectCondition &C)
+ const EffectConditionExpr &C)
: Effect(E), Cond(C) {}
/// Return a textual description of the effect, and its condition, if any.
@@ -4784,7 +4783,7 @@ struct FunctionEffectWithCondition {
};
/// Support iteration in parallel through a pair of FunctionEffect and
-/// FunctionEffectCondition containers.
+/// EffectConditionExpr containers.
template <typename Container> class FunctionEffectIterator {
friend Container;
@@ -4806,14 +4805,12 @@ template <typename Container> class FunctionEffectIterator {
return *this;
}
- const FunctionEffectWithCondition operator*() const {
- // Returns a const struct because storing into it would not accomplish
- // anything.
+ FunctionEffectWithCondition operator*() const {
assert(Outer != nullptr && "invalid FunctionEffectIterator");
bool HasConds = !Outer->Conditions.empty();
return FunctionEffectWithCondition{Outer->Effects[Idx],
HasConds ? Outer->Conditions[Idx]
- : FunctionEffectCondition()};
+ : EffectConditionExpr()};
}
};
@@ -4843,14 +4840,14 @@ class FunctionEffectsRef {
friend FunctionEffectSet;
ArrayRef<FunctionEffect> Effects;
- ArrayRef<FunctionEffectCondition> Conditions;
+ ArrayRef<EffectConditionExpr> Conditions;
// The arrays are expected to have been sorted by the caller, with the
// effects in order. The conditions array must be empty or the same size
// as the effects array, since the conditions are associated with the effects
// at the same array indices.
FunctionEffectsRef(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondition> Conds)
+ ArrayRef<EffectConditionExpr> Conds)
: Effects(FX), Conditions(Conds) {}
public:
@@ -4860,7 +4857,7 @@ class FunctionEffectsRef {
/// Asserts invariants.
static FunctionEffectsRef create(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondition> Conds);
+ ArrayRef<EffectConditionExpr> Conds);
FunctionEffectsRef() = default;
@@ -4868,7 +4865,7 @@ class FunctionEffectsRef {
size_t size() const { return Effects.size(); }
ArrayRef<FunctionEffect> effects() const { return Effects; }
- ArrayRef<FunctionEffectCondition> conditions() const { return Conditions; }
+ ArrayRef<EffectConditionExpr> conditions() const { return Conditions; }
using iterator = FunctionEffectIterator<FunctionEffectsRef>;
friend iterator;
@@ -4897,7 +4894,7 @@ class FunctionEffectSet {
template <typename> friend class TreeTransform;
SmallVector<FunctionEffect> Effects;
- SmallVector<FunctionEffectCondition> Conditions;
+ SmallVector<EffectConditionExpr> Conditions;
public:
FunctionEffectSet() = default;
@@ -4962,7 +4959,7 @@ class FunctionProtoType final
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
- FunctionEffect, FunctionEffectCondition, Qualifiers> {
+ FunctionEffect, EffectConditionExpr, Qualifiers> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -4996,7 +4993,7 @@ class FunctionProtoType final
// * Optionally, an array of getNumFunctionEffects() FunctionEffect.
// Present only when getNumFunctionEffects() > 0
//
- // * Optionally, an array of getNumFunctionEffects() FunctionEffectCondition.
+ // * Optionally, an array of getNumFunctionEffects() EffectConditionExpr.
// Present only when getNumFunctionEffectConditions() > 0.
//
// * Optionally a Qualifiers object to represent extra qualifiers that can't
@@ -5128,7 +5125,7 @@ class FunctionProtoType final
return getNumFunctionEffects();
}
- unsigned numTrailingObjects(OverloadToken<FunctionEffectCondition>) const {
+ unsigned numTrailingObjects(OverloadToken<EffectConditionExpr>) const {
return getNumFunctionEffectConditions();
}
@@ -5473,11 +5470,11 @@ class FunctionProtoType final
}
// For serialization.
- ArrayRef<FunctionEffectCondition> getFunctionEffectConditions() const {
+ ArrayRef<EffectConditionExpr> getFunctionEffectConditions() const {
if (hasExtraBitfields()) {
const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>();
if (Bitfields->EffectsHaveConditions)
- return {getTrailingObjects<FunctionEffectCondition>(),
+ return {getTrailingObjects<EffectConditionExpr>(),
Bitfields->NumFunctionEffects};
}
return {};
@@ -5494,7 +5491,7 @@ class FunctionProtoType final
return FunctionEffectsRef(
{getTrailingObjects<FunctionEffect>(),
Bitfields->NumFunctionEffects},
- {NumConds ? getTrailingObjects<FunctionEffectCondition>() : nullptr,
+ {NumConds ? getTrailingObjects<EffectConditionExpr>() : nullptr,
NumConds});
}
}
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index bb565a15baf1a..add745b3e332d 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -355,7 +355,7 @@ let Class = FunctionProtoType in {
def : Property<"functionEffects", Array<FunctionEffect>> {
let Read = [{ node->getFunctionEffectsWithoutConditions() }];
}
- def : Property<"functionEffectConds", Array<FunctionEffectCondition>> {
+ def : Property<"functionEffectConds", Array<EffectConditionExpr>> {
let Read = [{ node->getFunctionEffectConditions() }];
}
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 058940aa769c7..7912f8cadcd82 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4562,7 +4562,7 @@ QualType ASTContext::getFunctionTypeInternal(
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
- FunctionEffect, FunctionEffectCondition, Qualifiers>(
+ FunctionEffect, EffectConditionExpr, Qualifiers>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index f72705f5e8e94..6e375fc3dc4ab 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3672,14 +3672,13 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
auto *DestFX = getTrailingObjects<FunctionEffect>();
std::uninitialized_copy(SrcFX.begin(), SrcFX.end(), DestFX);
- ArrayRef<FunctionEffectCondition> SrcConds =
- epi.FunctionEffects.conditions();
+ ArrayRef<EffectConditionExpr> SrcConds = epi.FunctionEffects.conditions();
if (!SrcConds.empty()) {
ExtraBits.EffectsHaveConditions = true;
- auto *DestConds = getTrailingObjects<FunctionEffectCondition>();
+ auto *DestConds = getTrailingObjects<EffectConditionExpr>();
std::uninitialized_copy(SrcConds.begin(), SrcConds.end(), DestConds);
assert(std::any_of(SrcConds.begin(), SrcConds.end(),
- [](const FunctionEffectCondition &EC) {
+ [](const EffectConditionExpr &EC) {
if (const Expr *E = EC.getCondition())
return E->isTypeDependent() ||
E->isValueDependent();
@@ -5229,7 +5228,7 @@ void FunctionEffectSet::replaceItem(unsigned Idx,
Conditions[Idx] = Item.Cond;
// Maintain invariant: If all conditions are null, the vector should be empty.
- if (llvm::all_of(Conditions, [](const FunctionEffectCondition &C) {
+ if (llvm::all_of(Conditions, [](const EffectConditionExpr &C) {
return C.getCondition() == nullptr;
})) {
Conditions.clear();
@@ -5318,7 +5317,7 @@ FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
FunctionEffectsRef
FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
- ArrayRef<FunctionEffectCondition> Conds) {
+ ArrayRef<EffectConditionExpr> Conds) {
assert(std::is_sorted(FX.begin(), FX.end()) && "effects should be sorted");
assert((Conds.empty() || Conds.size() == FX.size()) &&
"effects size should match conditions size");
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c9e7ede493b5b..e170a88ad7519 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1880,7 +1880,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
// For C, when called from checkPointerTypesForAssignment,
// we need to not alter FromFn, or else even an innocuous cast
// like dropping effects will fail. In C++ however we do want to
- // alter FromFn. TODO: Is this correct?
+ // alter FromFn (because of the way PerformImplicitConversion works).
if (getLangOpts().CPlusPlus) {
FromFPT = cast<FunctionProtoType>(FromFn); // in case FromFn changed above
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 0573b12cedc73..3dd1d1a4406d8 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7525,24 +7525,21 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
std::optional<FunctionEffectMode>
Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
- auto BadExpr = [&]() {
- Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
- << ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
- << CondExpr->getSourceRange();
- return std::nullopt;
- };
-
if (DiagnoseUnexpandedParameterPack(CondExpr))
- return std::nullopt;
+ return std::nullopt;
if (CondExpr->isTypeDependent() || CondExpr->isValueDependent())
return FunctionEffectMode::Dependent;
std::optional<llvm::APSInt> ConditionValue =
CondExpr->getIntegerConstantExpr(Context);
- if (!ConditionValue)
- return BadExpr();
+ if (!ConditionValue) {
+ Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
+ << ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
+ << CondExpr->getSourceRange();
+ return std::nullopt;
+ }
return !ConditionValue->isZero() ? FunctionEffectMode::True
- : FunctionEffectMode::False;
+ : FunctionEffectMode::False;
}
static bool
@@ -7609,7 +7606,7 @@ handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState,
: (IsNonBlocking ? FunctionEffect::Kind::NonBlocking
: FunctionEffect::Kind::NonAllocating);
const FunctionEffectWithCondition NewEC{FunctionEffect(FEKind),
- FunctionEffectCondition(CondExpr)};
+ EffectConditionExpr(CondExpr)};
// Diagnose the newly parsed attribute as incompatible with a previous one.
auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 564b1dcd6ca81..3e73673ee42b0 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -6291,7 +6291,7 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType(
break;
case FunctionEffectMode::Dependent:
EC.Effect = Effect;
- EC.Cond = FunctionEffectCondition(NewExpr.get());
+ EC.Cond = EffectConditionExpr(NewExpr.get());
break;
case FunctionEffectMode::None:
llvm_unreachable(
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 9d32b361d9eca..c63ae1ba4ec4a 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -135,7 +135,7 @@ void f4() [[clang::nonblocking(__builtin_memset)]] {} // expected-error {{'nonbl
#ifdef __cplusplus
// Unexpanded parameter pack
template <bool ...val>
-void f5() [[clang::nonblocking(val /* NO ... here */)]] {} // expected-error {{'nonblocking' attribute requires an integer constant}}
+void f5() [[clang::nonblocking(val /* NO ... here */)]] {} // expected-error {{expression contains unexpanded parameter pack 'val'}}
void f6() { f5<true, false>(); }
#endif // __cplusplus
>From 29d2c3d78a8f4de5dbc857e0030d4088c78f923b Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 24 May 2024 16:07:42 -0700
Subject: [PATCH 70/71] In FunctionEffectsRef::get(QualType QT), handle case of
multiple levels of indirection. Test which exposed this involved a reference
to a block.
---
clang/lib/AST/Type.cpp | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6e375fc3dc4ab..cf9e0b3c87df5 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5308,8 +5308,12 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
}
FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
- if (QualType Pointee = QT->getPointeeType(); !Pointee.isNull())
+ while (true) {
+ QualType Pointee = QT->getPointeeType();
+ if (Pointee.isNull())
+ break;
QT = Pointee;
+ }
if (const auto *FPT = QT->getAs<FunctionProtoType>())
return FPT->getFunctionEffects();
return {};
>From 3ac9bcb97bb2ec8d42597313790c1c234588236e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 27 May 2024 10:29:28 -0700
Subject: [PATCH 71/71] Review feedback: - DiagnoseUnexpandedParameterPack()
now handled generically elsewhere - err_attribute_argument_type: the name of
the attribute shouldn't be manually quoted; there is an overall consistency
problem to be solved later.
---
clang/lib/Sema/SemaType.cpp | 6 +++---
clang/test/Sema/attr-nonblocking-sema.cpp | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 9e590aafb3e2b..72a992edc343b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7525,16 +7525,16 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
std::optional<FunctionEffectMode>
Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
- if (DiagnoseUnexpandedParameterPack(CondExpr))
- return std::nullopt;
if (CondExpr->isTypeDependent() || CondExpr->isValueDependent())
return FunctionEffectMode::Dependent;
std::optional<llvm::APSInt> ConditionValue =
CondExpr->getIntegerConstantExpr(Context);
if (!ConditionValue) {
+ // FIXME: err_attribute_argument_type doesn't quote the attribute
+ // name but needs to; users are inconsistent.
Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
- << ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
+ << AttributeName << AANT_ArgumentIntegerConstant
<< CondExpr->getSourceRange();
return std::nullopt;
}
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index c63ae1ba4ec4a..8e7bbd0b01649 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -130,7 +130,7 @@ struct S {
#endif // __cplusplus
// --- COMPUTED NONBLOCKING ---
-void f4() [[clang::nonblocking(__builtin_memset)]] {} // expected-error {{'nonblocking' attribute requires an integer constant}}
+void f4() [[clang::nonblocking(__builtin_memset)]] {} // expected-error {{nonblocking attribute requires an integer constant}}
#ifdef __cplusplus
// Unexpanded parameter pack
More information about the cfe-commits
mailing list