[clang] nolock/noalloc attributes (PR #84983)
Doug Wyatt via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 12 14:41:36 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 1/4] 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 a5879591f4c659..0460f30ce8a8b4 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,6 +3008,13 @@ class FunctionDecl : public DeclaratorDecl,
/// computed and stored.
unsigned getODRHash() const;
+ FunctionEffectSet getFunctionEffects() const {
+ const auto *FPT = getType()->getAs<FunctionProtoType>();
+ if (FPT)
+ return FPT->getFunctionEffects();
+ return {};
+ }
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) {
@@ -4633,6 +4640,8 @@ class BlockDecl : public Decl, public DeclContext {
SourceRange getSourceRange() const override LLVM_READONLY;
+ FunctionEffectSet getFunctionEffects() const;
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Block; }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1942b0e67f65a3..41c60a6e221d4e 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 0ba172a4035fdb..1d5d8f696977e7 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -326,6 +326,10 @@ let Class = FunctionProtoType in {
def : Property<"AArch64SMEAttributes", UInt32> {
let Read = [{ node->getAArch64SMEAttributes() }];
}
+ /* TODO: How to serialize FunctionEffect / FunctionEffectSet?
+ def : Property<"functionEffects", FunctionEffectSet> {
+ let Read = [{ node->getFunctionEffects() }];
+ }*/
def : Creator<[{
auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
@@ -342,6 +346,7 @@ let Class = FunctionProtoType in {
epi.ExtParameterInfos =
extParameterInfo.empty() ? nullptr : extParameterInfo.data();
epi.AArch64SMEAttributes = AArch64SMEAttributes;
+ //epi.FunctionEffects = functionEffects;
return ctx.getFunctionType(returnType, parameters, epi);
}]>;
}
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fd7970d0451acd..78bbe5185741b4 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 2c07cd09b0d5b7..bb897c708ebbea 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7973,3 +7973,20 @@ requirement:
}
}];
}
+
+def NoLockNoAllocDocs : Documentation {
+ let Category = DocCatType;
+ let Content = [{
+The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks,
+function pointers, lambdas, and member functions. The attributes identify code
+which must not allocate memory or lock, and the compiler uses the attributes to
+verify these requirements.
+
+Like ``noexcept``, ``nolock`` and ``noalloc`` have an optional argument, a
+compile-time constant boolean expression. By default, the argument is true, so
+``[[clang::nolock(true)]]`` is equivalent to ``[[clang::nolock]]``, and declares
+the function type as never locking.
+
+TODO: how much of the RFC to include here? Make it a separate page?
+ }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3f14167d6b8469..0d6e9f0289f6bc 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1507,3 +1507,7 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>;
+
+// Warnings and notes related to the function effects system underlying
+// the nolock and noalloc attributes.
+def FunctionEffects : DiagGroup<"function-effects">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c54105507753eb..5777f7dfbbcad1 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 267c79cc057cba..2772f882a5c09d 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 5a8fae76a43a4d..43fccb117a897d 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 8626f04012f7d4..596f4ea2e67176 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 22666184c56ccf..ce209be2c3a079 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 7dcc4348f8e036..560f2faeb9e503 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 6992ba9ad9a756..a0d813fa4d3d14 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 720d5fd5f0428d..6022e4a838e8aa 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 1f4a041e88dfff..77fdffef6382e0 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 c00120b59d396e..12f7869441c9be 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 e258a4f7c89415..dac5d5f3e5c1e6 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 93f82e68ab6440..643f8e7f444884 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 c34a40fa7c81ac..50007d4e1de119 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 5b95bae567b721..0e1a0f8dfdbabc 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 b0c693f078efe2..8f1563e7884429 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 3148299f6467af..2bc6b5b3880a8e 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 00000000000000..3f9938a2e54601
--- /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 00000000000000..9c494dcdd581db
--- /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 00000000000000..cfe52da16447c1
--- /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 2/4] 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 41c60a6e221d4e..de21561ce5dcad 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 78bbe5185741b4..4033b9efb86f39 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 5777f7dfbbcad1..df553a47a8dbe7 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 596f4ea2e67176..502c978d5b78e4 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 ce209be2c3a079..998360db3c5dd0 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 560f2faeb9e503..252a5dfbc952b0 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 a0d813fa4d3d14..69417b10049e97 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 6022e4a838e8aa..39d9eeac43429e 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 77fdffef6382e0..f437db5661b00d 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 12f7869441c9be..fd27ccd5c1dd1d 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 dac5d5f3e5c1e6..7533d3f9054a27 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 2bc6b5b3880a8e..c4f87be129fdca 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 00000000000000..a63750f5ca18ef
--- /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 9c494dcdd581db..d2a35453b08bb7 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 cfe52da16447c1..65d610f796f87f 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 3/4] 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 998360db3c5dd0..8cc4bc42210f99 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 39d9eeac43429e..d3b9b048230f9c 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 8f1563e7884429..80a2e6a6456d0a 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 a63750f5ca18ef..c30d0f42e37aee 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 4/4] 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 de21561ce5dcad..779c72ab63648d 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 43fccb117a897d..68f2ac3e1329f8 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 69417b10049e97..bdc3aa906541be 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 d3b9b048230f9c..b120ed22246961 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 f437db5661b00d..d407018adfde60 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 fd27ccd5c1dd1d..7805922c66b57a 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 7533d3f9054a27..2f9a4a14155fb4 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 50007d4e1de119..e698139e55a239 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 80a2e6a6456d0a..e34aa7e970d5dc 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;
More information about the cfe-commits
mailing list