[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)
Doug Wyatt via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 5 06:56:09 PDT 2024
https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/99656
>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 01/44] nonblocking/nonallocating attributes: 2nd pass
caller/callee analysis/verification
---
clang/include/clang/AST/Type.h | 2 +-
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticSemaKinds.td | 49 +
clang/include/clang/Sema/Sema.h | 13 +
.../include/clang/Serialization/ASTBitCodes.h | 4 +
clang/include/clang/Serialization/ASTReader.h | 3 +
clang/include/clang/Serialization/ASTWriter.h | 1 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1259 +++++++++++++++++
clang/lib/Sema/SemaDecl.cpp | 56 +
clang/lib/Sema/SemaExpr.cpp | 4 +
clang/lib/Sema/SemaLambda.cpp | 5 +
clang/lib/Serialization/ASTReader.cpp | 19 +
clang/lib/Serialization/ASTWriter.cpp | 12 +
.../Sema/attr-nonblocking-constraints.cpp | 194 +++
clang/test/Sema/attr-nonblocking-syntax.cpp | 1 +
.../attr-nonblocking-constraints.mm | 23 +
16 files changed, 1645 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc2..08141f75de8dbc 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,7 +4699,7 @@ class FunctionEffect {
private:
LLVM_PREFERRED_TYPE(Kind)
- unsigned FKind : 3;
+ uint8_t FKind : 3;
// Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
// then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e0433496..55d9442a939dae 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInCon
// Warnings and notes related to the function effects system underlying
// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d60f32674ca3a6..ec02c02d158c89 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
// Function effects
+def warn_func_effect_allocates : Warning<
+ "'%0' function must not allocate or deallocate memory">,
+ InGroup<FunctionEffects>;
+def note_func_effect_allocates : Note<
+ "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+ "'%0' function must not throw or catch exceptions">,
+ InGroup<FunctionEffects>;
+def note_func_effect_throws_or_catches : Note<
+ "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+ "'%0' function must not have static locals">,
+ InGroup<FunctionEffects>;
+def note_func_effect_has_static_local : Note<
+ "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+ "'%0' function must not use thread-local variables">,
+ InGroup<FunctionEffects>;
+def note_func_effect_uses_thread_local : Note<
+ "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+ "'%0' function must not access an ObjC method or property">,
+ InGroup<FunctionEffects>;
+def note_func_effect_calls_objc : Note<
+ "function cannot be inferred '%0' because it accesses an ObjC method or property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+ "'%0' function must not call non-'%0' function '%1'">,
+ InGroup<FunctionEffects>;
+def warn_func_effect_calls_expr_without_effect : Warning<
+ "'%0' function must not call non-'%0' expression">,
+ InGroup<FunctionEffects>;
+def note_func_effect_calls_func_without_effect : Note<
+ "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+def note_func_effect_call_extern : Note<
+ "function cannot be inferred '%0' because it has no definition in this translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+ "function does not permit inference of '%0'">;
+def note_func_effect_call_virtual : Note<
+ "virtual method cannot be inferred '%0'">;
+def note_func_effect_call_func_ptr : Note<
+ "function pointer cannot be inferred '%0'">;
+def warn_perf_constraint_implies_noexcept : Warning<
+ "'%0' function should be declared noexcept">,
+ InGroup<PerfConstraintImpliesNoexcept>;
+
+// FIXME: It would be nice if we could provide fuller template expansion notes.
+def note_func_effect_from_template : Note<
+ "in template expansion here">;
+
// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d638d31e050dc6..e1867348497da8 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -875,6 +875,13 @@ class Sema final : public SemaBase {
// ----- function effects ---
+ /// All functions/lambdas/blocks which have bodies and which have a non-empty
+ /// FunctionEffectsRef to be verified.
+ SmallVector<const Decl *> DeclsWithEffectsToVerify;
+ /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
+ /// are all null.
+ FunctionEffectSet AllEffectsToVerify;
+
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
@@ -891,6 +898,12 @@ class Sema final : public SemaBase {
SourceLocation NewLoc,
SourceLocation OldLoc);
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+ /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
+ void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
/// Try to parse the conditional expression attached to an effect attribute
/// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
/// optional on error.
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 5dd0ba33f8a9c2..b975db88dbaae6 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -721,6 +721,10 @@ enum ASTRecordTypes {
/// Record code for \#pragma clang unsafe_buffer_usage begin/end
PP_UNSAFE_BUFFER_USAGE = 69,
+
+ /// Record code for Sema's vector of functions/blocks with effects to
+ /// be verified.
+ DECLS_WITH_EFFECTS_TO_VERIFY = 70,
};
/// Record types used within a source manager block.
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 76e51ac7ab9792..1d8985602146ba 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -948,6 +948,9 @@ class ASTReader
/// Sema tracks these to emit deferred diags.
llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
+ /// The IDs of all decls with function effects to be checked.
+ SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify;
+
private:
struct ImportedSubmodule {
serialization::SubmoduleID ID;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index a0e475ec9f862c..4eaf77e8cb8d99 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -592,6 +592,7 @@ class ASTWriter : public ASTDeserializationListener,
void WriteMSPointersToMembersPragmaOptions(Sema &SemaRef);
void WritePackPragmaOptions(Sema &SemaRef);
void WriteFloatControlPragmaOptions(Sema &SemaRef);
+ void WriteDeclsWithEffectsToVerify(Sema &SemaRef);
void WriteModuleFileExtension(Sema &SemaRef,
ModuleFileExtensionWriter &Writer);
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f604c61fa3af9..3909d5b44a32e7 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
};
} // namespace
+// =============================================================================
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+ None = 0, // sentinel for an empty Diagnostic
+ Throws,
+ Catches,
+ CallsObjC,
+ AllocatesMemory,
+ HasStaticLocal,
+ AccessesThreadLocal,
+
+ // These only apply to callees, where the analysis stops at the Decl
+ DeclDisallowsInference,
+
+ CallsDeclWithoutEffect,
+ CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has been
+// made unsafe by a callee.
+struct Diagnostic {
+ FunctionEffect Effect;
+ DiagnosticID ID = DiagnosticID::None;
+ SourceLocation Loc;
+ const Decl *Callee = nullptr; // only valid for Calls*
+
+ Diagnostic() = default;
+
+ Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+ : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+ // unknown: probably function pointer
+ Unknown,
+ Function,
+ Virtual,
+ Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+ if (!(FD->hasBody() || FD->isInlined())) {
+ // externally defined; we couldn't verify if we wanted to.
+ return false;
+ }
+ if (FD->isTrivial()) {
+ // Otherwise `struct x { int a; };` would have an unverifiable default
+ // constructor.
+ return true;
+ }
+ return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+ // This implementation optimizes footprint, since we hold one of these for
+ // every function visited, which, due to inference, can be many more functions
+ // than have declared effects.
+
+ template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
+ SizeT Count = 0;
+ T Items[Capacity] = {};
+
+ using value_type = T;
+
+ using iterator = T *;
+ using const_iterator = const T *;
+ iterator begin() { return &Items[0]; }
+ iterator end() { return &Items[Count]; }
+ const_iterator begin() const { return &Items[0]; }
+ const_iterator end() const { return &Items[Count]; }
+ const_iterator cbegin() const { return &Items[0]; }
+ const_iterator cend() const { return &Items[Count]; }
+
+ void insert(iterator I, const T &Value) {
+ assert(Count < Capacity);
+ iterator E = end();
+ if (I != E)
+ std::copy_backward(I, E, E + 1);
+ *I = Value;
+ ++Count;
+ }
+
+ void push_back(const T &Value) {
+ assert(Count < Capacity);
+ Items[Count++] = Value;
+ }
+ };
+
+ // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+ // effects, this fixed-size vector with a capacity of 7 is more than
+ // sufficient and is only 8 bytes.
+ FixedVector<FunctionEffect, uint8_t, 7> Impl;
+
+public:
+ EffectSet() = default;
+ explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+ operator ArrayRef<FunctionEffect>() const {
+ return ArrayRef(Impl.cbegin(), Impl.cend());
+ }
+
+ using iterator = const FunctionEffect *;
+ iterator begin() const { return Impl.cbegin(); }
+ iterator end() const { return Impl.cend(); }
+
+ void insert(const FunctionEffect &Effect) {
+ FunctionEffect *Iter = Impl.begin();
+ FunctionEffect *End = Impl.end();
+ // linear search; lower_bound is overkill for a tiny vector like this
+ for (; Iter != End; ++Iter) {
+ if (*Iter == Effect)
+ return;
+ if (Effect < *Iter)
+ break;
+ }
+ Impl.insert(Iter, Effect);
+ }
+ void insert(const EffectSet &Set) {
+ for (const FunctionEffect &Item : Set) {
+ // push_back because set is already sorted
+ Impl.push_back(Item);
+ }
+ }
+ void insert(FunctionEffectsRef FX) {
+ for (const FunctionEffectWithCondition &EC : FX) {
+ assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+ // push_back because set is already sorted
+ Impl.push_back(EC.Effect);
+ }
+ }
+ bool contains(const FunctionEffect::Kind EK) const {
+ for (const FunctionEffect &E : Impl)
+ if (E.kind() == EK)
+ return true;
+ return false;
+ }
+
+ void dump(llvm::raw_ostream &OS) const;
+
+ static EffectSet difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS) {
+ EffectSet Result;
+ std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Result.Impl));
+ return Result;
+ }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
+ OS << "Effects{";
+ bool First = true;
+ for (const FunctionEffect &Effect : *this) {
+ if (!First)
+ OS << ", ";
+ else
+ First = false;
+ OS << Effect.name();
+ }
+ OS << "}";
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer, etc.
+struct CallableInfo {
+ // CDecl holds the function's definition, if any.
+ // FunctionDecl if CallType::Function or Virtual
+ // BlockDecl if CallType::Block
+ const Decl *CDecl;
+ mutable std::optional<std::string> MaybeName;
+ SpecialFuncType FuncType = SpecialFuncType::None;
+ EffectSet Effects;
+ CallType CType = CallType::Unknown;
+
+ CallableInfo(Sema &SemaRef, const Decl &CD,
+ SpecialFuncType FT = SpecialFuncType::None)
+ : CDecl(&CD), FuncType(FT) {
+ FunctionEffectsRef FXRef;
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+ // Use the function's definition, if any.
+ if (const FunctionDecl *Def = FD->getDefinition())
+ CDecl = FD = Def;
+ CType = CallType::Function;
+ if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
+ Method && Method->isVirtual())
+ CType = CallType::Virtual;
+ FXRef = FD->getFunctionEffects();
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+ CType = CallType::Block;
+ FXRef = BD->getFunctionEffects();
+ } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
+ // ValueDecl is function, enum, or variable, so just look at its type.
+ FXRef = FunctionEffectsRef::get(VD->getType());
+ }
+ Effects = EffectSet(FXRef);
+ }
+
+ bool isDirectCall() const {
+ return CType == CallType::Function || CType == CallType::Block;
+ }
+
+ bool isVerifiable() const {
+ switch (CType) {
+ case CallType::Unknown:
+ case CallType::Virtual:
+ break;
+ case CallType::Block:
+ return true;
+ case CallType::Function:
+ return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+ }
+ return false;
+ }
+
+ /// Generate a name for logging and diagnostics.
+ std::string name(Sema &Sem) const {
+ if (!MaybeName) {
+ std::string Name;
+ llvm::raw_string_ostream OS(Name);
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+ FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+ else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
+ OS << "(block " << BD->getBlockManglingNumber() << ")";
+ else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
+ VD->printQualifiedName(OS);
+ MaybeName = Name;
+ }
+ return *MaybeName;
+ }
+};
+
+// ----------
+// Map effects to single diagnostics, to hold the first (of potentially many)
+// diagnostics pertaining to an effect, per function.
+class EffectToDiagnosticMap {
+ // Since we currently only have a tiny number of effects (typically no more
+ // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+ // is often empty, use a unique_ptr to the SmallVector.
+ // Note that Diagnostic itself contains a FunctionEffect which is the key.
+ using ImplVec = llvm::SmallVector<Diagnostic, 1>;
+ std::unique_ptr<ImplVec> Impl;
+
+public:
+ // Insert a new diagnostic if we do not already have one for its effect.
+ void maybeInsert(const Diagnostic &Diag) {
+ if (Impl == nullptr)
+ Impl = std::make_unique<ImplVec>();
+ auto *Iter = _find(Diag.Effect);
+ if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
+ return;
+
+ Impl->insert(Iter, Diag);
+ }
+
+ const Diagnostic *lookup(FunctionEffect Key) {
+ if (Impl == nullptr)
+ return nullptr;
+
+ auto *Iter = _find(Key);
+ if (Iter != Impl->end() && Iter->Effect == Key)
+ return &*Iter;
+
+ return nullptr;
+ }
+
+ size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+ ImplVec::iterator _find(const FunctionEffect &key) {
+ // A linear search suffices for a tiny number of possible effects.
+ auto *End = Impl->end();
+ for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
+ if (!(Iter->Effect < key))
+ return Iter;
+ return End;
+ }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked and whose effect analysis
+// is dependent on a subsequent analysis of other functions.
+class PendingFunctionAnalysis {
+ friend class CompleteFunctionAnalysis;
+
+public:
+ struct DirectCall {
+ const Decl *Callee;
+ SourceLocation CallLoc;
+ // Not all recursive calls are detected, just enough
+ // to break cycles.
+ bool Recursed = false;
+
+ DirectCall(const Decl *D, SourceLocation CallLoc)
+ : Callee(D), CallLoc(CallLoc) {}
+ };
+
+ // We always have two disjoint sets of effects to verify:
+ // 1. Effects declared explicitly by this function.
+ // 2. All other inferrable effects needing verification.
+ EffectSet DeclaredVerifiableEffects;
+ EffectSet FXToInfer;
+
+private:
+ // Diagnostics pertaining to the function's explicit effects.
+ SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
+
+ // Diagnostics pertaining to other, non-explicit, inferrable effects.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+ // These unverified direct calls are what keeps the analysis "pending",
+ // until the callees can be verified.
+ SmallVector<DirectCall, 0> UnverifiedDirectCalls;
+
+public:
+ PendingFunctionAnalysis(
+ Sema &Sem, const CallableInfo &CInfo,
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+ DeclaredVerifiableEffects = CInfo.Effects;
+
+ // Check for effects we are not allowed to infer
+ EffectSet InferrableFX;
+
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+ if (effect.canInferOnFunction(*CInfo.CDecl))
+ InferrableFX.insert(effect);
+ else {
+ // Add a diagnostic for this effect if a caller were to
+ // try to infer it.
+ InferrableEffectToFirstDiagnostic.maybeInsert(
+ Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
+ CInfo.CDecl->getLocation()));
+ }
+ }
+ // InferrableFX is now the set of inferrable effects which are not
+ // prohibited
+ FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
+ }
+
+ // Hide the way that diagnostics for explicitly required effects vs. inferred
+ // ones are handled differently.
+ void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+ if (!Inferring)
+ DiagnosticsForExplicitFX.push_back(NewDiag);
+ else
+ InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
+ }
+
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
+ UnverifiedDirectCalls.emplace_back(D, CallLoc);
+ }
+
+ // Analysis is complete when there are no unverified direct calls.
+ bool isComplete() const { return UnverifiedDirectCalls.empty(); }
+
+ const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ SmallVector<DirectCall, 0> &unverifiedCalls() {
+ assert(!isComplete());
+ return UnverifiedDirectCalls;
+ }
+
+ SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
+ return DiagnosticsForExplicitFX;
+ }
+
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
+ OS << "Pending: Declared ";
+ DeclaredVerifiableEffects.dump(OS);
+ OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
+ OS << " Infer ";
+ FXToInfer.dump(OS);
+ OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+ if (!UnverifiedDirectCalls.empty()) {
+ OS << "; Calls: ";
+ for (const DirectCall &Call : UnverifiedDirectCalls) {
+ CallableInfo CI(SemaRef, *Call.Callee);
+ OS << " " << CI.name(SemaRef);
+ }
+ }
+ OS << "\n";
+ }
+};
+
+// ----------
+class CompleteFunctionAnalysis {
+ // Current size: 2 pointers
+public:
+ // Has effects which are both the declared ones -- not to be inferred -- plus
+ // ones which have been successfully inferred. These are all considered
+ // "verified" for the purposes of callers; any issue with verifying declared
+ // effects has already been reported and is not the problem of any caller.
+ EffectSet VerifiedEffects;
+
+private:
+ // This is used to generate notes about failed inference.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+ // The incoming Pending analysis is consumed (member(s) are moved-from).
+ CompleteFunctionAnalysis(
+ ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+ const EffectSet &DeclaredEffects,
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+ VerifiedEffects.insert(DeclaredEffects);
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+ if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
+ VerifiedEffects.insert(effect);
+
+ InferrableEffectToFirstDiagnostic =
+ std::move(Pending.InferrableEffectToFirstDiagnostic);
+ }
+
+ const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
+ return InferrableEffectToFirstDiagnostic.lookup(Effect);
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "Complete: Verified ";
+ VerifiedEffects.dump(OS);
+ OS << "; Infer ";
+ OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ }
+};
+
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+ if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ FD = FD->getCanonicalDecl();
+ assert(FD != nullptr);
+ return FD;
+ }
+ return D;
+}
+
+// ==========
+class Analyzer {
+ constexpr static int DebugLogLevel = 0;
+ // --
+ Sema &Sem;
+
+ // Subset of Sema.AllEffectsToVerify
+ EffectSet AllInferrableEffectsToVerify;
+
+ using FuncAnalysisPtr =
+ llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
+
+ // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+ // than complete state, so use different objects to represent them.
+ // The state pointers are owned by the container.
+ class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
+
+ public:
+ ~AnalysisMap();
+
+ // Use non-public inheritance in order to maintain the invariant
+ // that lookups and insertions are via the canonical Decls.
+
+ FuncAnalysisPtr lookup(const Decl *Key) const {
+ return Base::lookup(CanonicalFunctionDecl(Key));
+ }
+
+ FuncAnalysisPtr &operator[](const Decl *Key) {
+ return Base::operator[](CanonicalFunctionDecl(Key));
+ }
+
+ /// Shortcut for the case where we only care about completed analysis.
+ CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
+ if (FuncAnalysisPtr AP = lookup(D);
+ isa_and_nonnull<CompleteFunctionAnalysis *>(AP))
+ return AP.get<CompleteFunctionAnalysis *>();
+ return nullptr;
+ }
+
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
+ OS << "\nAnalysisMap:\n";
+ for (const auto &item : *this) {
+ CallableInfo CI(SemaRef, *item.first);
+ const auto AP = item.second;
+ OS << item.first << " " << CI.name(SemaRef) << " : ";
+ if (AP.isNull())
+ OS << "null\n";
+ else if (isa<CompleteFunctionAnalysis *>(AP)) {
+ auto *CFA = AP.get<CompleteFunctionAnalysis *>();
+ OS << CFA << " ";
+ CFA->dump(OS);
+ } else if (isa<PendingFunctionAnalysis *>(AP)) {
+ auto *PFA = AP.get<PendingFunctionAnalysis *>();
+ OS << PFA << " ";
+ PFA->dump(SemaRef, OS);
+ } else
+ llvm_unreachable("never");
+ }
+ OS << "---\n";
+ }
+ };
+ AnalysisMap DeclAnalysis;
+
+public:
+ Analyzer(Sema &S) : Sem(S) {}
+
+ void run(const TranslationUnitDecl &TU) {
+ // Gather all of the effects to be verified to see what operations need to
+ // be checked, and to see which ones are inferrable.
+ for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
+ const FunctionEffect &Effect = CFE.Effect;
+ const FunctionEffect::Flags Flags = Effect.flags();
+ if (Flags & FunctionEffect::FE_InferrableOnCallees)
+ AllInferrableEffectsToVerify.insert(Effect);
+ }
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::outs());
+ llvm::outs() << "\n";
+ }
+
+ // We can use DeclsWithEffectsToVerify as a stack for a
+ // depth-first traversal; there's no need for a second container. But first,
+ // reverse it, so when working from the end, Decls are verified in the order
+ // they are declared.
+ SmallVector<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify;
+ std::reverse(VerificationQueue.begin(), VerificationQueue.end());
+
+ while (!VerificationQueue.empty()) {
+ const Decl *D = VerificationQueue.back();
+ if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ // already done
+ VerificationQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto *Pending = AP.get<PendingFunctionAnalysis *>();
+ finishPendingAnalysis(D, Pending);
+ VerificationQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+
+ // Not previously visited; begin a new analysis for this Decl.
+ PendingFunctionAnalysis *Pending = verifyDecl(D);
+ if (Pending == nullptr) {
+ // completed now
+ VerificationQueue.pop_back();
+ continue;
+ }
+
+ // Analysis remains pending because there are direct callees to be
+ // verified first. Push them onto the queue.
+ for (PendingFunctionAnalysis::DirectCall &Call :
+ Pending->unverifiedCalls()) {
+ FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee);
+ if (AP.isNull()) {
+ VerificationQueue.push_back(Call.Callee);
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // This indicates recursion (not necessarily direct). For the
+ // purposes of effect analysis, we can just ignore it since
+ // no effects forbid recursion.
+ Call.Recursed = true;
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+ }
+ }
+
+private:
+ // Verify a single Decl. Return the pending structure if that was the result,
+ // else null. This method must not recurse.
+ PendingFunctionAnalysis *verifyDecl(const Decl *D) {
+ CallableInfo CInfo(Sem, *D);
+ bool isExternC = false;
+
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ assert(FD->getBuiltinID() == 0);
+ isExternC = FD->getCanonicalDecl()->isExternCContext();
+ }
+
+ // For C++, with non-extern "C" linkage only - if any of the Decl's declared
+ // effects forbid throwing (e.g. nonblocking) then the function should also
+ // be declared noexcept.
+ if (Sem.getLangOpts().CPlusPlus && !isExternC) {
+ for (const FunctionEffect &Effect : CInfo.Effects) {
+ if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
+ continue;
+
+ bool IsNoexcept = false;
+ if (auto *FD = D->getAsFunction()) {
+ IsNoexcept = isNoexcept(FD);
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten()) {
+ auto *FPT = TSI->getType()->getAs<FunctionProtoType>();
+ IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
+ }
+ }
+ if (!IsNoexcept)
+ Sem.Diag(D->getBeginLoc(),
+ diag::warn_perf_constraint_implies_noexcept)
+ << Effect.name();
+ break;
+ }
+ }
+
+ // Build a PendingFunctionAnalysis on the stack. If it turns out to be
+ // complete, we'll have avoided a heap allocation; if it's incomplete, it's
+ // a fairly trivial move to a heap-allocated object.
+ PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
+
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
+ FAnalysis.dump(Sem, llvm::outs());
+ }
+
+ FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
+
+ Visitor.run();
+ if (FAnalysis.isComplete()) {
+ completeAnalysis(CInfo, FAnalysis);
+ return nullptr;
+ }
+ // Move the pending analysis to the heap and save it in the map.
+ PendingFunctionAnalysis *PendingPtr =
+ new PendingFunctionAnalysis(std::move(FAnalysis));
+ DeclAnalysis[D] = PendingPtr;
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "inserted pending " << PendingPtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ }
+ return PendingPtr;
+ }
+
+ // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
+ // inserted in the container.
+ void completeAnalysis(const CallableInfo &CInfo,
+ PendingFunctionAnalysis &Pending) {
+ if (SmallVector<Diagnostic, 0> &Diags =
+ Pending.getDiagnosticsForExplicitFX();
+ !Diags.empty())
+ emitDiagnostics(Diags, CInfo, Sem);
+
+ CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+ Sem.getASTContext(), Pending, CInfo.Effects,
+ AllInferrableEffectsToVerify);
+ DeclAnalysis[CInfo.CDecl] = CompletePtr;
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::outs());
+ }
+ }
+
+ // Called after all direct calls requiring inference have been found -- or
+ // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
+ // the possibility of inference. Deletes Pending.
+ void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+ CallableInfo Caller(Sem, *D);
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+ Pending->dump(Sem, llvm::outs());
+ llvm::outs() << "\n";
+ }
+ for (const PendingFunctionAnalysis::DirectCall &Call :
+ Pending->unverifiedCalls()) {
+ if (Call.Recursed)
+ continue;
+
+ CallableInfo Callee(Sem, *Call.Callee);
+ followCall(Caller, *Pending, Callee, Call.CallLoc,
+ /*AssertNoFurtherInference=*/true);
+ }
+ completeAnalysis(Caller, *Pending);
+ delete Pending;
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. PFA pertains to the caller.
+ void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
+ const CallableInfo &Callee, SourceLocation CallLoc,
+ bool AssertNoFurtherInference) {
+ const bool DirectCall = Callee.isDirectCall();
+
+ // Initially, the declared effects; inferred effects will be added.
+ EffectSet CalleeEffects = Callee.Effects;
+
+ bool IsInferencePossible = DirectCall;
+
+ if (DirectCall) {
+ if (CompleteFunctionAnalysis *CFA =
+ DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+ // Combine declared effects with those which may have been inferred.
+ CalleeEffects.insert(CFA->VerifiedEffects);
+ IsInferencePossible = false; // we've already traversed it
+ }
+ }
+
+ if (AssertNoFurtherInference) {
+ assert(!IsInferencePossible);
+ }
+
+ if (!Callee.isVerifiable())
+ IsInferencePossible = false;
+
+ if constexpr (DebugLogLevel > 0) {
+ llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+ << Callee.name(Sem)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::outs());
+ llvm::outs() << "\n";
+ llvm::outs() << " callee " << Callee.CDecl << " canonical "
+ << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+ for (Decl *D : Callee.CDecl->redecls())
+ llvm::outs() << " " << D;
+
+ llvm::outs() << "\n";
+ }
+
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ FunctionEffect::Flags Flags = Effect.flags();
+ bool Diagnose =
+ Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
+ if (Diagnose) {
+ // If inference is not allowed, or the target is indirect (virtual
+ // method/function ptr?), generate a diagnostic now.
+ if (!IsInferencePossible ||
+ !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
+ if (Callee.FuncType == SpecialFuncType::None)
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
+ CallLoc, Callee.CDecl});
+ else
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ }
+ }
+ };
+
+ for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : PFA.FXToInfer)
+ check1Effect(Effect, true);
+ }
+
+ // Should only be called when determined to be complete.
+ void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
+ const CallableInfo &CInfo, Sema &S) {
+ if (Diags.empty())
+ return;
+ const SourceManager &SM = S.getSourceManager();
+ std::sort(Diags.begin(), Diags.end(),
+ [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+ return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+ });
+
+ auto checkAddTemplateNote = [&](const Decl *D) {
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ while (FD != nullptr && FD->isTemplateInstantiation()) {
+ S.Diag(FD->getPointOfInstantiation(),
+ diag::note_func_effect_from_template);
+ FD = FD->getTemplateInstantiationPattern();
+ }
+ }
+ };
+
+ // Top-level diagnostics are warnings.
+ for (const Diagnostic &Diag : Diags) {
+ StringRef effectName = Diag.Effect.name();
+ switch (Diag.ID) {
+ case DiagnosticID::None:
+ case DiagnosticID::DeclDisallowsInference: // shouldn't happen
+ // here
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::CallsExprWithoutEffect:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+
+ case DiagnosticID::CallsDeclWithoutEffect: {
+ CallableInfo CalleeInfo(S, *Diag.Callee);
+ std::string CalleeName = CalleeInfo.name(S);
+
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
+ << effectName << CalleeName;
+ checkAddTemplateNote(CInfo.CDecl);
+
+ // Emit notes explaining the transitive chain of inferences: Why isn't
+ // the callee safe?
+ for (const Decl *Callee = Diag.Callee; Callee != nullptr;) {
+ std::optional<CallableInfo> MaybeNextCallee;
+ CompleteFunctionAnalysis *Completed =
+ DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+ if (Completed == nullptr) {
+ // No result - could be
+ // - non-inline
+ // - indirect (virtual or through function pointer)
+ // - effect has been explicitly disclaimed (e.g. "blocking")
+ if (CalleeInfo.CType == CallType::Virtual)
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
+ << effectName;
+ else if (CalleeInfo.CType == CallType::Unknown)
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_func_ptr)
+ << effectName;
+ else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_disallows_inference)
+ << effectName;
+ else
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+ << effectName;
+
+ break;
+ }
+ const Diagnostic *PtrDiag2 =
+ Completed->firstDiagnosticForEffect(Diag.Effect);
+ if (PtrDiag2 == nullptr)
+ break;
+
+ const Diagnostic &Diag2 = *PtrDiag2;
+ switch (Diag2.ID) {
+ case DiagnosticID::None:
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::DeclDisallowsInference:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
+ << effectName;
+ break;
+ case DiagnosticID::CallsExprWithoutEffect:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+ << effectName;
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+ << effectName;
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+ << effectName;
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+ << effectName;
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
+ break;
+ case DiagnosticID::CallsDeclWithoutEffect:
+ MaybeNextCallee.emplace(S, *Diag2.Callee);
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
+ << effectName << MaybeNextCallee->name(S);
+ break;
+ }
+ checkAddTemplateNote(Callee);
+ Callee = Diag2.Callee;
+ if (MaybeNextCallee) {
+ CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(S);
+ }
+ }
+ } break;
+ }
+ }
+ }
+
+ // ----------
+ // This AST visitor is used to traverse the body of a function during effect
+ // verification. This happens in 2 situations:
+ // [1] The function has declared effects which need to be validated.
+ // [2] The function has not explicitly declared an effect in question, and is
+ // being checked for implicit conformance.
+ //
+ // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
+ // all diagnostic output.
+ //
+ // Q: Currently we create a new RecursiveASTVisitor for every function
+ // analysis. Is it so lightweight that this is OK? It would appear so.
+ struct FunctionBodyASTVisitor
+ : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ // The meanings of the boolean values returned by the Visit methods can be
+ // difficult to remember.
+ constexpr static bool Stop = false;
+ constexpr static bool Proceed = true;
+
+ Analyzer &Outer;
+ PendingFunctionAnalysis &CurrentFunction;
+ CallableInfo &CurrentCaller;
+
+ FunctionBodyASTVisitor(Analyzer &outer,
+ PendingFunctionAnalysis &CurrentFunction,
+ CallableInfo &CurrentCaller)
+ : Outer(outer), CurrentFunction(CurrentFunction),
+ CurrentCaller(CurrentCaller) {}
+
+ // -- Entry point --
+ void run() {
+ // The target function itself may have some implicit code paths beyond the
+ // body: member and base constructors and destructors. Visit these first.
+ if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
+ if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+ for (const CXXCtorInitializer *Initer : Ctor->inits())
+ if (Expr *Init = Initer->getInit())
+ VisitStmt(Init);
+ } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD))
+ followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+ }
+ // else could be BlockDecl
+
+ // Do an AST traversal of the function/block body
+ TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
+ }
+
+ // -- Methods implementing common logic --
+
+ // Handle a language construct forbidden by some effects. Only effects whose
+ // flags include the specified flag receive a diagnostic. \p Flag describes
+ // the construct.
+ void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+ SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ // If there are any declared verifiable effects which forbid the construct
+ // represented by the flag, store just one diagnostic.
+ for (const FunctionEffect &Effect :
+ CurrentFunction.DeclaredVerifiableEffects) {
+ if (Effect.flags() & Flag) {
+ addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
+ break;
+ }
+ }
+ // For each inferred effect which forbids the construct, store a
+ // diagnostic, if we don't already have a diagnostic for that effect.
+ for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ if (Effect.flags() & Flag)
+ addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
+ }
+
+ void addDiagnostic(bool Inferring, const FunctionEffect &Effect,
+ DiagnosticID D, SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ CurrentFunction.checkAddDiagnostic(Inferring,
+ Diagnostic(Effect, D, Loc, Callee));
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. CallableInfo pertains to the callee.
+ void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+ // Currently, built-in functions are always considered safe.
+ // FIXME: Some are not.
+ if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
+ FD && FD->getBuiltinID() != 0)
+ return;
+
+ Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+ /*AssertNoFurtherInference=*/false);
+ }
+
+ void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
+ const QualType CalleeType = CalleeExpr->getType();
+ auto *FPT =
+ CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+ EffectSet CalleeFX;
+ if (FPT)
+ CalleeFX.insert(FPT->getFunctionEffects());
+
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+ /*direct=*/false, CalleeFX))
+ addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
+ Call->getBeginLoc());
+ };
+
+ for (const FunctionEffect &Effect :
+ CurrentFunction.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ check1Effect(Effect, true);
+ }
+
+ // This destructor's body should be followed by the caller, but here we
+ // follow the field and base destructors.
+ void followDestructor(const CXXRecordDecl *Rec,
+ const CXXDestructorDecl *Dtor) {
+ for (const FieldDecl *Field : Rec->fields())
+ followTypeDtor(Field->getType());
+
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ for (const CXXBaseSpecifier &Base : Class->bases())
+ followTypeDtor(Base.getType());
+
+ for (const CXXBaseSpecifier &Base : Class->vbases())
+ followTypeDtor(Base.getType());
+ }
+ }
+
+ void followTypeDtor(QualType QT) {
+ const Type *Ty = QT.getTypePtr();
+ while (Ty->isArrayType()) {
+ const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
+ QT = Arr->getElementType();
+ Ty = QT.getTypePtr();
+ }
+
+ if (Ty->isRecordType()) {
+ if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
+ if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
+ CallableInfo CI(Outer.Sem, *Dtor);
+ followCall(CI, Dtor->getLocation());
+ }
+ }
+ }
+ }
+
+ // -- Methods for use of RecursiveASTVisitor --
+
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+ bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ DiagnosticID::Throws, Throw->getThrowLoc());
+ return Proceed;
+ }
+
+ bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ DiagnosticID::Catches, Catch->getCatchLoc());
+ return Proceed;
+ }
+
+ bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+ DiagnosticID::CallsObjC, Msg->getBeginLoc());
+ return Proceed;
+ }
+
+ bool VisitCallExpr(CallExpr *Call) {
+ if constexpr (DebugLogLevel > 2) {
+ llvm::errs() << "VisitCallExpr : "
+ << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
+ }
+
+ Expr *CalleeExpr = Call->getCallee();
+ if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+ CallableInfo CI(Outer.Sem, *Callee);
+ followCall(CI, Call->getBeginLoc());
+ return Proceed;
+ }
+
+ if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
+ // just destroying a scalar, fine.
+ return Proceed;
+
+ // No Decl, just an Expr. Just check based on its type.
+ checkIndirectCall(Call, CalleeExpr);
+
+ return Proceed;
+ }
+
+ bool VisitVarDecl(VarDecl *Var) {
+ if constexpr (DebugLogLevel > 2) {
+ llvm::errs() << "VisitVarDecl : "
+ << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
+ }
+
+ if (Var->isStaticLocal())
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
+ DiagnosticID::HasStaticLocal,
+ Var->getLocation());
+
+ const QualType::DestructionKind DK =
+ Var->needsDestruction(Outer.Sem.getASTContext());
+ if (DK == QualType::DK_cxx_destructor) {
+ QualType QT = Var->getType();
+ if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+ if (const auto *CxxRec =
+ dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+ if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
+ CallableInfo CI(Outer.Sem, *Dtor);
+ followCall(CI, Var->getLocation());
+ }
+ }
+ }
+ }
+ return Proceed;
+ }
+
+ bool VisitCXXNewExpr(CXXNewExpr *New) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator new.
+ if (FunctionDecl *FD = New->getOperatorNew()) {
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
+ followCall(CI, New->getBeginLoc());
+ }
+
+ // It's a bit excessive to check operator delete here, since it's
+ // just a fallback for operator new followed by a failed constructor.
+ // We could check it via New->getOperatorDelete().
+
+ // It DOES however visit the called constructor
+ return Proceed;
+ }
+
+ bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator delete.
+ if (FunctionDecl *FD = Delete->getOperatorDelete()) {
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
+ followCall(CI, Delete->getBeginLoc());
+ }
+
+ // It DOES however visit the called destructor
+
+ return Proceed;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
+ if constexpr (DebugLogLevel > 2) {
+ llvm::errs() << "VisitCXXConstructExpr : "
+ << Construct->getBeginLoc().printToString(
+ Outer.Sem.SourceMgr)
+ << "\n";
+ }
+
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to the constructor.
+ const CXXConstructorDecl *Ctor = Construct->getConstructor();
+ CallableInfo CI(Outer.Sem, *Ctor);
+ followCall(CI, Construct->getLocation());
+
+ return Proceed;
+ }
+
+ bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+ if (Expr *E = DEI->getExpr())
+ TraverseStmt(E);
+
+ return Proceed;
+ }
+
+ bool TraverseLambdaExpr(LambdaExpr *Lambda) {
+ // We override this so as the be able to skip traversal of the lambda's
+ // body. We have to explicitly traverse the captures.
+ for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
+ if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+ Lambda->capture_init_begin()[I]) == Stop)
+ return Stop;
+
+ return Proceed;
+ }
+
+ bool TraverseBlockExpr(BlockExpr * /*unused*/) {
+ // TODO: are the capture expressions (ctor call?) safe?
+ return Proceed;
+ }
+
+ bool VisitDeclRefExpr(const DeclRefExpr *E) {
+ const ValueDecl *Val = E->getDecl();
+ if (isa<VarDecl>(Val)) {
+ const VarDecl *Var = cast<VarDecl>(Val);
+ VarDecl::TLSKind TLSK = Var->getTLSKind();
+ if (TLSK != VarDecl::TLS_None) {
+ // At least on macOS, thread-local variables are initialized on
+ // first access, including a heap allocation.
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
+ DiagnosticID::AccessesThreadLocal,
+ E->getLocation());
+ }
+ }
+ return Proceed;
+ }
+
+ // Unevaluated contexts: need to skip
+ // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
+
+ bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+ return TraverseStmt(Node->getResultExpr());
+ }
+ bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+ return Proceed;
+ }
+
+ bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
+
+ bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
+
+ bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
+
+ bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
+ };
+};
+
+Analyzer::AnalysisMap::~AnalysisMap() {
+ for (const auto &Item : *this) {
+ FuncAnalysisPtr AP = Item.second;
+ if (isa<PendingFunctionAnalysis *>(AP))
+ delete AP.get<PendingFunctionAnalysis *>();
+ else
+ delete AP.get<CompleteFunctionAnalysis *>();
+ }
+}
+
+} // namespace FXAnalysis
+
+// =============================================================================
+
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
@@ -2551,6 +3807,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
SourceLocation())) {
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
}
+
+ if (S.Context.hasAnyFunctionEffects())
+ FXAnalysis::Analyzer{S}.run(*TU);
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index bb25a0b3a45ae9..adb4a8f4de6853 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3854,6 +3854,11 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
OldQTypeForComparison = Context.getFunctionType(
OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
}
+ if (OldFX.empty()) {
+ // A redeclaration may add the attribute to a previously seen function
+ // body which needs to be verified.
+ maybeAddDeclWithEffects(Old, MergedFX);
+ }
}
}
}
@@ -10897,6 +10902,53 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
return nullptr;
}
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+ const FunctionEffectsRef &FX) {
+ if (!D->hasBody()) {
+ if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody())
+ return;
+ }
+
+ if (Diags.getIgnoreAllWarnings() ||
+ (Diags.getSuppressSystemWarnings() &&
+ SourceMgr.isInSystemHeader(D->getLocation())))
+ return;
+
+ if (hasUncompilableErrorOccurred())
+ return;
+
+ // For code in dependent contexts, we'll do this at instantiation time.
+ // Without this check, we would analyze the function based on placeholder
+ // template parameters, and potentially generate spurious diagnostics.
+ if (cast<DeclContext>(D)->isDependentContext())
+ return;
+
+ addDeclWithEffects(D, FX);
+}
+
+void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
+ FunctionEffectSet::Conflicts Errs;
+
+ // To avoid the possibility of conflict, don't add effects which are
+ // not FE_InferrableOnCallees and therefore not verified; this removes
+ // blocking/allocating but keeps nonblocking/nonallocating.
+ // Also, ignore any conditions when building the list of effects.
+ bool AnyVerifiable = false;
+ for (const FunctionEffectWithCondition &EC : FX)
+ if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
+ AllEffectsToVerify.insert(FunctionEffectWithCondition(EC.Effect, nullptr),
+ Errs);
+ AnyVerifiable = true;
+ }
+ assert(Errs.empty() && "effects conflicts should not be possible here");
+
+ // Record the declaration for later analysis.
+ if (AnyVerifiable)
+ DeclsWithEffectsToVerify.push_back(D);
+}
+
bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD,
QualType NewT, QualType OldT) {
if (!NewD->getLexicalDeclContext()->isDependentContext())
@@ -15609,6 +15661,10 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
+ if (Context.hasAnyFunctionEffects())
+ if (const auto FX = FD->getFunctionEffects(); !FX.empty())
+ maybeAddDeclWithEffects(FD, FX);
+
return D;
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 8d24e34520e778..f9b04f5361f337 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16065,6 +16065,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
BlockDecl *BD = BSI->TheDecl;
+ if (Context.hasAnyFunctionEffects())
+ if (const auto FX = BD->getFunctionEffects(); !FX.empty())
+ maybeAddDeclWithEffects(BD, FX);
+
if (BSI->HasImplicitReturnType)
deduceClosureReturnType(*BSI);
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 601077e9f3334d..e3bf582db30279 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1947,6 +1947,11 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+ if (Context.hasAnyFunctionEffects())
+ if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty())
+ maybeAddDeclWithEffects(LSI.CallOperator, FX);
+
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
}
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 3cb96df12e4da0..5d81d921d0fffd 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3874,6 +3874,11 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
FPPragmaOptions.swap(Record);
break;
+ case DECLS_WITH_EFFECTS_TO_VERIFY:
+ for (unsigned I = 0, N = Record.size(); I != N; /*in loop*/)
+ DeclsWithEffectsToVerify.push_back(ReadDeclID(F, Record, I));
+ break;
+
case OPENCL_EXTENSIONS:
for (unsigned I = 0, E = Record.size(); I != E; ) {
auto Name = ReadString(Record, I);
@@ -8279,6 +8284,20 @@ void ASTReader::InitializeSema(Sema &S) {
NewOverrides.applyOverrides(SemaObj->getLangOpts());
}
+ if (!DeclsWithEffectsToVerify.empty()) {
+ for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
+ Decl *D = GetDecl(ID);
+ FunctionEffectsRef FX;
+ if (auto *FD = dyn_cast<FunctionDecl>(D))
+ FX = FD->getFunctionEffects();
+ else if (auto *BD = dyn_cast<BlockDecl>(D))
+ FX = BD->getFunctionEffects();
+ if (!FX.empty())
+ SemaObj->addDeclWithEffects(D, FX);
+ }
+ DeclsWithEffectsToVerify.clear();
+ }
+
SemaObj->OpenCLFeatures = OpenCLExtensions;
UpdateSema();
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index c78d8943d6d92e..faf9b9490bafc2 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4627,6 +4627,17 @@ void ASTWriter::WriteFloatControlPragmaOptions(Sema &SemaRef) {
Stream.EmitRecord(FLOAT_CONTROL_PRAGMA_OPTIONS, Record);
}
+/// Write Sema's collected list of declarations with unverified effects.
+void ASTWriter::WriteDeclsWithEffectsToVerify(Sema &SemaRef) {
+ if (SemaRef.DeclsWithEffectsToVerify.empty())
+ return;
+ RecordData Record;
+ for (const auto *D : SemaRef.DeclsWithEffectsToVerify) {
+ AddDeclRef(D, Record);
+ }
+ Stream.EmitRecord(DECLS_WITH_EFFECTS_TO_VERIFY, Record);
+}
+
void ASTWriter::WriteModuleFileExtension(Sema &SemaRef,
ModuleFileExtensionWriter &Writer) {
// Enter the extension block.
@@ -5564,6 +5575,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
}
WritePackPragmaOptions(SemaRef);
WriteFloatControlPragmaOptions(SemaRef);
+ WriteDeclsWithEffectsToVerify(SemaRef);
// Some simple statistics
RecordData::value_type Record[] = {
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
new file mode 100644
index 00000000000000..c248293cf76342
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
+// the AnalysisBasedWarnings pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nl1() [[clang::nonblocking]]
+{
+ auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nonblocking]]
+{
+ static int global; // expected-warning {{'nonblocking' function must not have static locals}}
+}
+
+void nl3() [[clang::nonblocking]]
+{
+ try {
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ }
+ catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ }
+}
+
+void nl4_inline() {}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+void nl4() [[clang::nonblocking]]
+{
+ nl4_inline(); // OK
+ nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+
+struct HasVirtual {
+ virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
+};
+
+void nl5() [[clang::nonblocking]]
+{
+ HasVirtual hv;
+ hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nl6_transitively_unsafe()
+{
+ nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+}
+
+void nl6() [[clang::nonblocking]]
+{
+ nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+thread_local int tl_var{ 42 };
+
+bool tl_test() [[clang::nonblocking]]
+{
+ return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
+}
+
+void nl7()
+{
+ // Make sure we verify blocks
+ auto blk = ^() [[clang::nonblocking]] {
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ };
+}
+
+void nl8()
+{
+ // Make sure we verify lambdas
+ auto lambda = []() [[clang::nonblocking]] {
+ throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ };
+}
+
+// Make sure template expansions are found and verified.
+ template <typename T>
+ struct Adder {
+ static T add_explicit(T x, T y) [[clang::nonblocking]]
+ {
+ return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ }
+ static T add_implicit(T x, T y)
+ {
+ return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+ }
+ };
+
+ struct Stringy {
+ friend Stringy operator+(const Stringy& x, const Stringy& y)
+ {
+ // Do something inferably unsafe
+ auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}}
+ return {};
+ }
+ };
+
+ struct Stringy2 {
+ friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
+ {
+ // Do something inferably unsafe
+ throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+ }
+ };
+
+void nl9() [[clang::nonblocking]]
+{
+ Adder<int>::add_explicit(1, 2);
+ Adder<int>::add_implicit(1, 2);
+
+ Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
+ Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \
+ expected-note {{in template expansion here}}
+}
+
+void nl10(
+ void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
+ void (*fp2)() [[clang::nonblocking]]
+ ) [[clang::nonblocking]]
+{
+ fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ fp2();
+}
+
+// Interactions with nonblocking(false)
+void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+{
+}
+void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+
+template <bool V>
+struct ComputedNB {
+ void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+};
+
+void nl11() [[clang::nonblocking]]
+{
+ nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+
+ ComputedNB<true> CNB_true;
+ CNB_true.method();
+
+ ComputedNB<false> CNB_false;
+ CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+// Verify that when attached to a redeclaration, the attribute successfully attaches.
+void nl12() {
+ static int x; // expected-warning {{'nonblocking' function must not have static locals}}
+}
+void nl12() [[clang::nonblocking]];
+void nl13() [[clang::nonblocking]] { nl12(); }
+
+// C++ member function pointers
+struct PTMFTester {
+ typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
+
+ void convert() [[clang::nonblocking]];
+
+ ConvertFunction mConvertFunc;
+};
+
+void PTMFTester::convert() [[clang::nonblocking]]
+{
+ (this->*mConvertFunc)();
+}
+
+// Block variables
+void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
+ blk();
+}
+
+// References to blocks
+void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
+{
+ auto &ref = block;
+ ref();
+}
+
+
+// --- nonblocking implies noexcept ---
+#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
+
+void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+{
+}
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 644ed754b04daa..90d074d01708f4 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -3,6 +3,7 @@
// Make sure that the attribute gets parsed and attached to the correct AST elements.
#pragma clang diagnostic ignored "-Wunused-variable"
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
// =========================================================================================
// Square brackets, true
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
new file mode 100644
index 00000000000000..aeb8b21f56e442
--- /dev/null
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
+#endif
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+ [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
+}
+void nl15(OCClass *oc) {
+ [oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
+}
+void nl16(OCClass *oc) [[clang::nonblocking]] {
+ nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
+}
+
>From 95b7a00467423e1a0e320a2fe45811739ce4d61e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 20 Jul 2024 08:57:16 -0700
Subject: [PATCH 02/44] - Sema.h: Move function decls to be in the correct
per-source-file sections. - Fix ObjC++ test which was using the attribute's
old name.
---
clang/include/clang/Sema/Sema.h | 50 +++++++++----------
.../attr-nonblocking-constraints.mm | 4 --
2 files changed, 24 insertions(+), 30 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e1867348497da8..e3259f147ec884 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -873,8 +873,6 @@ class Sema final : public SemaBase {
/// Warn when implicitly casting 0 to nullptr.
void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
- // ----- function effects ---
-
/// All functions/lambdas/blocks which have bodies and which have a non-empty
/// FunctionEffectsRef to be verified.
SmallVector<const Decl *> DeclsWithEffectsToVerify;
@@ -886,30 +884,6 @@ class Sema final : public SemaBase {
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
- /// Warn and return true if adding an effect to a set would create a conflict.
- bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
- const FunctionEffectWithCondition &EC,
- SourceLocation NewAttrLoc);
-
- // Report a failure to merge function effects between declarations due to a
- // conflict.
- void
- diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
- SourceLocation NewLoc,
- SourceLocation OldLoc);
-
- /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
- /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
- void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
- /// Try to parse the conditional expression attached to an effect attribute
- /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
- /// optional on error.
- std::optional<FunctionEffectMode>
- ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName);
-
/// makeUnavailableInSystemHeader - There is an error in the current
/// context. If we're still in a system header, and we can plausibly
/// make the relevant declaration unavailable instead of erroring, do
@@ -4343,6 +4317,24 @@ class Sema final : public SemaBase {
// Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
+ /// Warn and return true if adding a function effect to a set would create a conflict.
+ bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
+ const FunctionEffectWithCondition &EC,
+ SourceLocation NewAttrLoc);
+
+ // Report a failure to merge function effects between declarations due to a
+ // conflict.
+ void
+ diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
+ SourceLocation NewLoc,
+ SourceLocation OldLoc);
+
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+ /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
+ void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
private:
/// Function or variable declarations to be checked for whether the deferred
/// diagnostics should be emitted.
@@ -15015,6 +15007,12 @@ class Sema final : public SemaBase {
return hasAcceptableDefinition(D, &Hidden, Kind);
}
+ /// Try to parse the conditional expression attached to an effect attribute
+ /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
+ /// optional on error.
+ std::optional<FunctionEffectMode>
+ ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName);
+
private:
/// The implementation of RequireCompleteType
bool RequireCompleteTypeImpl(SourceLocation Loc, QualType T,
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index aeb8b21f56e442..0600062e89c045 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -1,9 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-#if !__has_attribute(clang_nonblocking)
-#error "the 'nonblocking' attribute is not available"
-#endif
-
#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
// Objective-C
>From 21c780aaeea3deb35c3ffc972eb126e02b48ec7a Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 20 Jul 2024 09:08:16 -0700
Subject: [PATCH 03/44] clang-format
---
clang/include/clang/Sema/Sema.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e3259f147ec884..a42f6af3c70e48 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4317,7 +4317,8 @@ class Sema final : public SemaBase {
// Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
- /// Warn and return true if adding a function effect to a set would create a conflict.
+ /// Warn and return true if adding a function effect to a set would create a
+ /// conflict.
bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
const FunctionEffectWithCondition &EC,
SourceLocation NewAttrLoc);
>From ff104137caa083888b51a78da5d439d45db04a1f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 07:40:33 -0700
Subject: [PATCH 04/44] - Detect ObjC @throw and @catch, diagnose identically
to their C++ counterparts. - Tweak test cases.
---
clang/lib/Sema/AnalysisBasedWarnings.cpp | 12 ++++++++++++
.../test/Sema/attr-nonblocking-constraints.cpp | 5 +++--
.../SemaObjCXX/attr-nonblocking-constraints.mm | 17 ++++++++++++-----
3 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 3909d5b44a32e7..f26468cfcdad54 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3475,6 +3475,18 @@ class Analyzer {
return Proceed;
}
+ bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ DiagnosticID::Throws, Throw->getThrowLoc());
+ return Proceed;
+ }
+
+ bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ DiagnosticID::Catches, Catch->getAtCatchLoc());
+ return Proceed;
+ }
+
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
DiagnosticID::CallsObjC, Msg->getBeginLoc());
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index c248293cf76342..e50c5b436daafc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -1,6 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
-// the AnalysisBasedWarnings pass from running at all.
+// the FXAnalysis pass from running at all.
// This diagnostic is re-enabled and exercised in isolation later in this file.
#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
@@ -9,7 +9,8 @@
void nl1() [[clang::nonblocking]]
{
- auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+ int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+ delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
}
void nl2() [[clang::nonblocking]]
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index 0600062e89c045..ff5873c11c4fe7 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify %s
#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
@@ -7,13 +7,20 @@ @interface OCClass
- (void)method;
@end
-void nl14(OCClass *oc) [[clang::nonblocking]] {
+void nb1(OCClass *oc) [[clang::nonblocking]] {
[oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
}
-void nl15(OCClass *oc) {
+void nb2(OCClass *oc) {
[oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
}
-void nl16(OCClass *oc) [[clang::nonblocking]] {
- nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
+void nb3(OCClass *oc) [[clang::nonblocking]] {
+ nb2(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}}
}
+void nb4() [[clang::nonblocking]] {
+ @try {
+ @throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ }
+ @catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ }
+}
>From d472964eb11d063b6d0afc433c69f6369d1ae1ae Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 05/44] FunctionEffect constructors: cast Kind to uint8_t Make
FunctionEffect just have a Kind instead of a bitfield
(from PR #100753)
---
clang/include/clang/AST/Type.h | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 08141f75de8dbc..509f0d95643b0b 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4698,26 +4698,25 @@ class FunctionEffect {
};
private:
- LLVM_PREFERRED_TYPE(Kind)
- uint8_t FKind : 3;
+ Kind FKind;
// Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
// then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
// be considered for uniqueness.
public:
- FunctionEffect() : FKind(unsigned(Kind::None)) {}
+ FunctionEffect() : FKind(Kind::None) {}
- explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {}
+ explicit FunctionEffect(Kind K) : FKind(K) {}
/// The kind of the effect.
- Kind kind() const { return Kind(FKind); }
+ Kind kind() const { return FKind; }
/// Return the opposite kind, for effects which have opposites.
Kind oppositeKind() const;
/// For serialization.
- uint32_t toOpaqueInt32() const { return FKind; }
+ uint32_t toOpaqueInt32() const { return uint32_t(FKind); }
static FunctionEffect fromOpaqueInt32(uint32_t Value) {
return FunctionEffect(Kind(Value));
}
>From f1142db51dff9794be2f63c2a2aca585760b5620 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 12:48:59 -0700
Subject: [PATCH 06/44] New file: EFfectAnalysis.cpp. Use LLVM_DEBUG for debug
logging. Remove unneeded isInline() check in functionIsVerifiable
---
clang/include/clang/Sema/Sema.h | 4 +
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1269 ---------------------
clang/lib/Sema/CMakeLists.txt | 1 +
clang/lib/Sema/EffectAnalysis.cpp | 1289 ++++++++++++++++++++++
clang/lib/Sema/Sema.cpp | 3 +
5 files changed, 1297 insertions(+), 1269 deletions(-)
create mode 100644 clang/lib/Sema/EffectAnalysis.cpp
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a42f6af3c70e48..fae0a2f4f46dee 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,6 +530,10 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
const FunctionEffectsRef &New);
};
+// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move
+// more of the effects implementation into that file?
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
+
/// Sema - This implements semantic analysis and AST building for C.
/// \nosubgrouping
class Sema final : public SemaBase {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index f26468cfcdad54..8268117a0addab 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2399,1272 +2399,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
// =============================================================================
-namespace FXAnalysis {
-
-enum class DiagnosticID : uint8_t {
- None = 0, // sentinel for an empty Diagnostic
- Throws,
- Catches,
- CallsObjC,
- AllocatesMemory,
- HasStaticLocal,
- AccessesThreadLocal,
-
- // These only apply to callees, where the analysis stops at the Decl
- DeclDisallowsInference,
-
- CallsDeclWithoutEffect,
- CallsExprWithoutEffect,
-};
-
-// Holds an effect diagnosis, potentially for the entire duration of the
-// analysis phase, in order to refer to it when explaining why a caller has been
-// made unsafe by a callee.
-struct Diagnostic {
- FunctionEffect Effect;
- DiagnosticID ID = DiagnosticID::None;
- SourceLocation Loc;
- const Decl *Callee = nullptr; // only valid for Calls*
-
- Diagnostic() = default;
-
- Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
- const Decl *Callee = nullptr)
- : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
-};
-
-enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallType {
- // unknown: probably function pointer
- Unknown,
- Function,
- Virtual,
- Block
-};
-
-// Return whether a function's effects CAN be verified.
-// The question of whether it SHOULD be verified is independent.
-static bool functionIsVerifiable(const FunctionDecl *FD) {
- if (!(FD->hasBody() || FD->isInlined())) {
- // externally defined; we couldn't verify if we wanted to.
- return false;
- }
- if (FD->isTrivial()) {
- // Otherwise `struct x { int a; };` would have an unverifiable default
- // constructor.
- return true;
- }
- return true;
-}
-
-/// A mutable set of FunctionEffect, for use in places where any conditions
-/// have been resolved or can be ignored.
-class EffectSet {
- // This implementation optimizes footprint, since we hold one of these for
- // every function visited, which, due to inference, can be many more functions
- // than have declared effects.
-
- template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
- SizeT Count = 0;
- T Items[Capacity] = {};
-
- using value_type = T;
-
- using iterator = T *;
- using const_iterator = const T *;
- iterator begin() { return &Items[0]; }
- iterator end() { return &Items[Count]; }
- const_iterator begin() const { return &Items[0]; }
- const_iterator end() const { return &Items[Count]; }
- const_iterator cbegin() const { return &Items[0]; }
- const_iterator cend() const { return &Items[Count]; }
-
- void insert(iterator I, const T &Value) {
- assert(Count < Capacity);
- iterator E = end();
- if (I != E)
- std::copy_backward(I, E, E + 1);
- *I = Value;
- ++Count;
- }
-
- void push_back(const T &Value) {
- assert(Count < Capacity);
- Items[Count++] = Value;
- }
- };
-
- // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
- // effects, this fixed-size vector with a capacity of 7 is more than
- // sufficient and is only 8 bytes.
- FixedVector<FunctionEffect, uint8_t, 7> Impl;
-
-public:
- EffectSet() = default;
- explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
-
- operator ArrayRef<FunctionEffect>() const {
- return ArrayRef(Impl.cbegin(), Impl.cend());
- }
-
- using iterator = const FunctionEffect *;
- iterator begin() const { return Impl.cbegin(); }
- iterator end() const { return Impl.cend(); }
-
- void insert(const FunctionEffect &Effect) {
- FunctionEffect *Iter = Impl.begin();
- FunctionEffect *End = Impl.end();
- // linear search; lower_bound is overkill for a tiny vector like this
- for (; Iter != End; ++Iter) {
- if (*Iter == Effect)
- return;
- if (Effect < *Iter)
- break;
- }
- Impl.insert(Iter, Effect);
- }
- void insert(const EffectSet &Set) {
- for (const FunctionEffect &Item : Set) {
- // push_back because set is already sorted
- Impl.push_back(Item);
- }
- }
- void insert(FunctionEffectsRef FX) {
- for (const FunctionEffectWithCondition &EC : FX) {
- assert(EC.Cond.getCondition() ==
- nullptr); // should be resolved by now, right?
- // push_back because set is already sorted
- Impl.push_back(EC.Effect);
- }
- }
- bool contains(const FunctionEffect::Kind EK) const {
- for (const FunctionEffect &E : Impl)
- if (E.kind() == EK)
- return true;
- return false;
- }
-
- void dump(llvm::raw_ostream &OS) const;
-
- static EffectSet difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS) {
- EffectSet Result;
- std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result.Impl));
- return Result;
- }
-};
-
-LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
- OS << "Effects{";
- bool First = true;
- for (const FunctionEffect &Effect : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << Effect.name();
- }
- OS << "}";
-}
-
-// Transitory, more extended information about a callable, which can be a
-// function, block, function pointer, etc.
-struct CallableInfo {
- // CDecl holds the function's definition, if any.
- // FunctionDecl if CallType::Function or Virtual
- // BlockDecl if CallType::Block
- const Decl *CDecl;
- mutable std::optional<std::string> MaybeName;
- SpecialFuncType FuncType = SpecialFuncType::None;
- EffectSet Effects;
- CallType CType = CallType::Unknown;
-
- CallableInfo(Sema &SemaRef, const Decl &CD,
- SpecialFuncType FT = SpecialFuncType::None)
- : CDecl(&CD), FuncType(FT) {
- FunctionEffectsRef FXRef;
-
- if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
- // Use the function's definition, if any.
- if (const FunctionDecl *Def = FD->getDefinition())
- CDecl = FD = Def;
- CType = CallType::Function;
- if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
- Method && Method->isVirtual())
- CType = CallType::Virtual;
- FXRef = FD->getFunctionEffects();
- } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
- CType = CallType::Block;
- FXRef = BD->getFunctionEffects();
- } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
- // ValueDecl is function, enum, or variable, so just look at its type.
- FXRef = FunctionEffectsRef::get(VD->getType());
- }
- Effects = EffectSet(FXRef);
- }
-
- bool isDirectCall() const {
- return CType == CallType::Function || CType == CallType::Block;
- }
-
- bool isVerifiable() const {
- switch (CType) {
- case CallType::Unknown:
- case CallType::Virtual:
- break;
- case CallType::Block:
- return true;
- case CallType::Function:
- return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
- }
- return false;
- }
-
- /// Generate a name for logging and diagnostics.
- std::string name(Sema &Sem) const {
- if (!MaybeName) {
- std::string Name;
- llvm::raw_string_ostream OS(Name);
-
- if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
- FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
- /*Qualified=*/true);
- else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
- OS << "(block " << BD->getBlockManglingNumber() << ")";
- else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
- VD->printQualifiedName(OS);
- MaybeName = Name;
- }
- return *MaybeName;
- }
-};
-
-// ----------
-// Map effects to single diagnostics, to hold the first (of potentially many)
-// diagnostics pertaining to an effect, per function.
-class EffectToDiagnosticMap {
- // Since we currently only have a tiny number of effects (typically no more
- // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
- // is often empty, use a unique_ptr to the SmallVector.
- // Note that Diagnostic itself contains a FunctionEffect which is the key.
- using ImplVec = llvm::SmallVector<Diagnostic, 1>;
- std::unique_ptr<ImplVec> Impl;
-
-public:
- // Insert a new diagnostic if we do not already have one for its effect.
- void maybeInsert(const Diagnostic &Diag) {
- if (Impl == nullptr)
- Impl = std::make_unique<ImplVec>();
- auto *Iter = _find(Diag.Effect);
- if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
- return;
-
- Impl->insert(Iter, Diag);
- }
-
- const Diagnostic *lookup(FunctionEffect Key) {
- if (Impl == nullptr)
- return nullptr;
-
- auto *Iter = _find(Key);
- if (Iter != Impl->end() && Iter->Effect == Key)
- return &*Iter;
-
- return nullptr;
- }
-
- size_t size() const { return Impl ? Impl->size() : 0; }
-
-private:
- ImplVec::iterator _find(const FunctionEffect &key) {
- // A linear search suffices for a tiny number of possible effects.
- auto *End = Impl->end();
- for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
- if (!(Iter->Effect < key))
- return Iter;
- return End;
- }
-};
-
-// ----------
-// State pertaining to a function whose AST is walked and whose effect analysis
-// is dependent on a subsequent analysis of other functions.
-class PendingFunctionAnalysis {
- friend class CompleteFunctionAnalysis;
-
-public:
- struct DirectCall {
- const Decl *Callee;
- SourceLocation CallLoc;
- // Not all recursive calls are detected, just enough
- // to break cycles.
- bool Recursed = false;
-
- DirectCall(const Decl *D, SourceLocation CallLoc)
- : Callee(D), CallLoc(CallLoc) {}
- };
-
- // We always have two disjoint sets of effects to verify:
- // 1. Effects declared explicitly by this function.
- // 2. All other inferrable effects needing verification.
- EffectSet DeclaredVerifiableEffects;
- EffectSet FXToInfer;
-
-private:
- // Diagnostics pertaining to the function's explicit effects.
- SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
-
- // Diagnostics pertaining to other, non-explicit, inferrable effects.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
-
- // These unverified direct calls are what keeps the analysis "pending",
- // until the callees can be verified.
- SmallVector<DirectCall, 0> UnverifiedDirectCalls;
-
-public:
- PendingFunctionAnalysis(
- Sema &Sem, const CallableInfo &CInfo,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- DeclaredVerifiableEffects = CInfo.Effects;
-
- // Check for effects we are not allowed to infer
- EffectSet InferrableFX;
-
- for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl))
- InferrableFX.insert(effect);
- else {
- // Add a diagnostic for this effect if a caller were to
- // try to infer it.
- InferrableEffectToFirstDiagnostic.maybeInsert(
- Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
- CInfo.CDecl->getLocation()));
- }
- }
- // InferrableFX is now the set of inferrable effects which are not
- // prohibited
- FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
- }
-
- // Hide the way that diagnostics for explicitly required effects vs. inferred
- // ones are handled differently.
- void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
- if (!Inferring)
- DiagnosticsForExplicitFX.push_back(NewDiag);
- else
- InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
- }
-
- void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
- UnverifiedDirectCalls.emplace_back(D, CallLoc);
- }
-
- // Analysis is complete when there are no unverified direct calls.
- bool isComplete() const { return UnverifiedDirectCalls.empty(); }
-
- const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
- return InferrableEffectToFirstDiagnostic.lookup(effect);
- }
-
- SmallVector<DirectCall, 0> &unverifiedCalls() {
- assert(!isComplete());
- return UnverifiedDirectCalls;
- }
-
- SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
- return DiagnosticsForExplicitFX;
- }
-
- void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
- OS << "Pending: Declared ";
- DeclaredVerifiableEffects.dump(OS);
- OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
- OS << " Infer ";
- FXToInfer.dump(OS);
- OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
- if (!UnverifiedDirectCalls.empty()) {
- OS << "; Calls: ";
- for (const DirectCall &Call : UnverifiedDirectCalls) {
- CallableInfo CI(SemaRef, *Call.Callee);
- OS << " " << CI.name(SemaRef);
- }
- }
- OS << "\n";
- }
-};
-
-// ----------
-class CompleteFunctionAnalysis {
- // Current size: 2 pointers
-public:
- // Has effects which are both the declared ones -- not to be inferred -- plus
- // ones which have been successfully inferred. These are all considered
- // "verified" for the purposes of callers; any issue with verifying declared
- // effects has already been reported and is not the problem of any caller.
- EffectSet VerifiedEffects;
-
-private:
- // This is used to generate notes about failed inference.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
-
-public:
- // The incoming Pending analysis is consumed (member(s) are moved-from).
- CompleteFunctionAnalysis(
- ASTContext &Ctx, PendingFunctionAnalysis &Pending,
- const EffectSet &DeclaredEffects,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- VerifiedEffects.insert(DeclaredEffects);
- for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
- if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
- VerifiedEffects.insert(effect);
-
- InferrableEffectToFirstDiagnostic =
- std::move(Pending.InferrableEffectToFirstDiagnostic);
- }
-
- const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
- return InferrableEffectToFirstDiagnostic.lookup(Effect);
- }
-
- void dump(llvm::raw_ostream &OS) const {
- OS << "Complete: Verified ";
- VerifiedEffects.dump(OS);
- OS << "; Infer ";
- OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
- }
-};
-
-const Decl *CanonicalFunctionDecl(const Decl *D) {
- if (auto *FD = dyn_cast<FunctionDecl>(D)) {
- FD = FD->getCanonicalDecl();
- assert(FD != nullptr);
- return FD;
- }
- return D;
-}
-
-// ==========
-class Analyzer {
- constexpr static int DebugLogLevel = 0;
- // --
- Sema &Sem;
-
- // Subset of Sema.AllEffectsToVerify
- EffectSet AllInferrableEffectsToVerify;
-
- using FuncAnalysisPtr =
- llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
-
- // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
- // than complete state, so use different objects to represent them.
- // The state pointers are owned by the container.
- class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
- using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
-
- public:
- ~AnalysisMap();
-
- // Use non-public inheritance in order to maintain the invariant
- // that lookups and insertions are via the canonical Decls.
-
- FuncAnalysisPtr lookup(const Decl *Key) const {
- return Base::lookup(CanonicalFunctionDecl(Key));
- }
-
- FuncAnalysisPtr &operator[](const Decl *Key) {
- return Base::operator[](CanonicalFunctionDecl(Key));
- }
-
- /// Shortcut for the case where we only care about completed analysis.
- CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
- if (FuncAnalysisPtr AP = lookup(D);
- isa_and_nonnull<CompleteFunctionAnalysis *>(AP))
- return AP.get<CompleteFunctionAnalysis *>();
- return nullptr;
- }
-
- void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
- OS << "\nAnalysisMap:\n";
- for (const auto &item : *this) {
- CallableInfo CI(SemaRef, *item.first);
- const auto AP = item.second;
- OS << item.first << " " << CI.name(SemaRef) << " : ";
- if (AP.isNull())
- OS << "null\n";
- else if (isa<CompleteFunctionAnalysis *>(AP)) {
- auto *CFA = AP.get<CompleteFunctionAnalysis *>();
- OS << CFA << " ";
- CFA->dump(OS);
- } else if (isa<PendingFunctionAnalysis *>(AP)) {
- auto *PFA = AP.get<PendingFunctionAnalysis *>();
- OS << PFA << " ";
- PFA->dump(SemaRef, OS);
- } else
- llvm_unreachable("never");
- }
- OS << "---\n";
- }
- };
- AnalysisMap DeclAnalysis;
-
-public:
- Analyzer(Sema &S) : Sem(S) {}
-
- void run(const TranslationUnitDecl &TU) {
- // Gather all of the effects to be verified to see what operations need to
- // be checked, and to see which ones are inferrable.
- for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
- const FunctionEffect &Effect = CFE.Effect;
- const FunctionEffect::Flags Flags = Effect.flags();
- if (Flags & FunctionEffect::FE_InferrableOnCallees)
- AllInferrableEffectsToVerify.insert(Effect);
- }
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "AllInferrableEffectsToVerify: ";
- AllInferrableEffectsToVerify.dump(llvm::outs());
- llvm::outs() << "\n";
- }
-
- // We can use DeclsWithEffectsToVerify as a stack for a
- // depth-first traversal; there's no need for a second container. But first,
- // reverse it, so when working from the end, Decls are verified in the order
- // they are declared.
- SmallVector<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify;
- std::reverse(VerificationQueue.begin(), VerificationQueue.end());
-
- while (!VerificationQueue.empty()) {
- const Decl *D = VerificationQueue.back();
- if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
- if (isa<CompleteFunctionAnalysis *>(AP)) {
- // already done
- VerificationQueue.pop_back();
- continue;
- }
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // All children have been traversed; finish analysis.
- auto *Pending = AP.get<PendingFunctionAnalysis *>();
- finishPendingAnalysis(D, Pending);
- VerificationQueue.pop_back();
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
- }
-
- // Not previously visited; begin a new analysis for this Decl.
- PendingFunctionAnalysis *Pending = verifyDecl(D);
- if (Pending == nullptr) {
- // completed now
- VerificationQueue.pop_back();
- continue;
- }
-
- // Analysis remains pending because there are direct callees to be
- // verified first. Push them onto the queue.
- for (PendingFunctionAnalysis::DirectCall &Call :
- Pending->unverifiedCalls()) {
- FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee);
- if (AP.isNull()) {
- VerificationQueue.push_back(Call.Callee);
- continue;
- }
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // This indicates recursion (not necessarily direct). For the
- // purposes of effect analysis, we can just ignore it since
- // no effects forbid recursion.
- Call.Recursed = true;
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
- }
- }
- }
-
-private:
- // Verify a single Decl. Return the pending structure if that was the result,
- // else null. This method must not recurse.
- PendingFunctionAnalysis *verifyDecl(const Decl *D) {
- CallableInfo CInfo(Sem, *D);
- bool isExternC = false;
-
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- assert(FD->getBuiltinID() == 0);
- isExternC = FD->getCanonicalDecl()->isExternCContext();
- }
-
- // For C++, with non-extern "C" linkage only - if any of the Decl's declared
- // effects forbid throwing (e.g. nonblocking) then the function should also
- // be declared noexcept.
- if (Sem.getLangOpts().CPlusPlus && !isExternC) {
- for (const FunctionEffect &Effect : CInfo.Effects) {
- if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
- continue;
-
- bool IsNoexcept = false;
- if (auto *FD = D->getAsFunction()) {
- IsNoexcept = isNoexcept(FD);
- } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
- if (auto *TSI = BD->getSignatureAsWritten()) {
- auto *FPT = TSI->getType()->getAs<FunctionProtoType>();
- IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
- }
- }
- if (!IsNoexcept)
- Sem.Diag(D->getBeginLoc(),
- diag::warn_perf_constraint_implies_noexcept)
- << Effect.name();
- break;
- }
- }
-
- // Build a PendingFunctionAnalysis on the stack. If it turns out to be
- // complete, we'll have avoided a heap allocation; if it's incomplete, it's
- // a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
-
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(Sem, llvm::outs());
- }
-
- FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
-
- Visitor.run();
- if (FAnalysis.isComplete()) {
- completeAnalysis(CInfo, FAnalysis);
- return nullptr;
- }
- // Move the pending analysis to the heap and save it in the map.
- PendingFunctionAnalysis *PendingPtr =
- new PendingFunctionAnalysis(std::move(FAnalysis));
- DeclAnalysis[D] = PendingPtr;
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "inserted pending " << PendingPtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
- }
- return PendingPtr;
- }
-
- // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
- // inserted in the container.
- void completeAnalysis(const CallableInfo &CInfo,
- PendingFunctionAnalysis &Pending) {
- if (SmallVector<Diagnostic, 0> &Diags =
- Pending.getDiagnosticsForExplicitFX();
- !Diags.empty())
- emitDiagnostics(Diags, CInfo, Sem);
-
- CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
- Sem.getASTContext(), Pending, CInfo.Effects,
- AllInferrableEffectsToVerify);
- DeclAnalysis[CInfo.CDecl] = CompletePtr;
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "inserted complete " << CompletePtr << "\n";
- DeclAnalysis.dump(Sem, llvm::outs());
- }
- }
-
- // Called after all direct calls requiring inference have been found -- or
- // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
- // the possibility of inference. Deletes Pending.
- void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
- CallableInfo Caller(Sem, *D);
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
- Pending->dump(Sem, llvm::outs());
- llvm::outs() << "\n";
- }
- for (const PendingFunctionAnalysis::DirectCall &Call :
- Pending->unverifiedCalls()) {
- if (Call.Recursed)
- continue;
-
- CallableInfo Callee(Sem, *Call.Callee);
- followCall(Caller, *Pending, Callee, Call.CallLoc,
- /*AssertNoFurtherInference=*/true);
- }
- completeAnalysis(Caller, *Pending);
- delete Pending;
- }
-
- // Here we have a call to a Decl, either explicitly via a CallExpr or some
- // other AST construct. PFA pertains to the caller.
- void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
- const CallableInfo &Callee, SourceLocation CallLoc,
- bool AssertNoFurtherInference) {
- const bool DirectCall = Callee.isDirectCall();
-
- // Initially, the declared effects; inferred effects will be added.
- EffectSet CalleeEffects = Callee.Effects;
-
- bool IsInferencePossible = DirectCall;
-
- if (DirectCall) {
- if (CompleteFunctionAnalysis *CFA =
- DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
- // Combine declared effects with those which may have been inferred.
- CalleeEffects.insert(CFA->VerifiedEffects);
- IsInferencePossible = false; // we've already traversed it
- }
- }
-
- if (AssertNoFurtherInference) {
- assert(!IsInferencePossible);
- }
-
- if (!Callee.isVerifiable())
- IsInferencePossible = false;
-
- if constexpr (DebugLogLevel > 0) {
- llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
- CalleeEffects.dump(llvm::outs());
- llvm::outs() << "\n";
- llvm::outs() << " callee " << Callee.CDecl << " canonical "
- << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
- for (Decl *D : Callee.CDecl->redecls())
- llvm::outs() << " " << D;
-
- llvm::outs() << "\n";
- }
-
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- FunctionEffect::Flags Flags = Effect.flags();
- bool Diagnose =
- Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
- if (Diagnose) {
- // If inference is not allowed, or the target is indirect (virtual
- // method/function ptr?), generate a diagnostic now.
- if (!IsInferencePossible ||
- !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
- if (Callee.FuncType == SpecialFuncType::None)
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
- CallLoc, Callee.CDecl});
- else
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
- } else {
- // Inference is allowed and necessary; defer it.
- PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
- }
- }
- };
-
- for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
- check1Effect(Effect, false);
-
- for (const FunctionEffect &Effect : PFA.FXToInfer)
- check1Effect(Effect, true);
- }
-
- // Should only be called when determined to be complete.
- void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
- const CallableInfo &CInfo, Sema &S) {
- if (Diags.empty())
- return;
- const SourceManager &SM = S.getSourceManager();
- std::sort(Diags.begin(), Diags.end(),
- [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
- return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
- });
-
- auto checkAddTemplateNote = [&](const Decl *D) {
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- while (FD != nullptr && FD->isTemplateInstantiation()) {
- S.Diag(FD->getPointOfInstantiation(),
- diag::note_func_effect_from_template);
- FD = FD->getTemplateInstantiationPattern();
- }
- }
- };
-
- // Top-level diagnostics are warnings.
- for (const Diagnostic &Diag : Diags) {
- StringRef effectName = Diag.Effect.name();
- switch (Diag.ID) {
- case DiagnosticID::None:
- case DiagnosticID::DeclDisallowsInference: // shouldn't happen
- // here
- llvm_unreachable("Unexpected diagnostic kind");
- break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
- << effectName;
- checkAddTemplateNote(CInfo.CDecl);
- break;
-
- case DiagnosticID::CallsDeclWithoutEffect: {
- CallableInfo CalleeInfo(S, *Diag.Callee);
- std::string CalleeName = CalleeInfo.name(S);
-
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
- << effectName << CalleeName;
- checkAddTemplateNote(CInfo.CDecl);
-
- // Emit notes explaining the transitive chain of inferences: Why isn't
- // the callee safe?
- for (const Decl *Callee = Diag.Callee; Callee != nullptr;) {
- std::optional<CallableInfo> MaybeNextCallee;
- CompleteFunctionAnalysis *Completed =
- DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
- if (Completed == nullptr) {
- // No result - could be
- // - non-inline
- // - indirect (virtual or through function pointer)
- // - effect has been explicitly disclaimed (e.g. "blocking")
- if (CalleeInfo.CType == CallType::Virtual)
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
- << effectName;
- else if (CalleeInfo.CType == CallType::Unknown)
- S.Diag(Callee->getLocation(),
- diag::note_func_effect_call_func_ptr)
- << effectName;
- else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
- S.Diag(Callee->getLocation(),
- diag::note_func_effect_call_disallows_inference)
- << effectName;
- else
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
- << effectName;
-
- break;
- }
- const Diagnostic *PtrDiag2 =
- Completed->firstDiagnosticForEffect(Diag.Effect);
- if (PtrDiag2 == nullptr)
- break;
-
- const Diagnostic &Diag2 = *PtrDiag2;
- switch (Diag2.ID) {
- case DiagnosticID::None:
- llvm_unreachable("Unexpected diagnostic kind");
- break;
- case DiagnosticID::DeclDisallowsInference:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
- << effectName;
- break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
- << effectName;
- break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
- break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
- << effectName;
- break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
- << effectName;
- break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
- << effectName;
- break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
- break;
- case DiagnosticID::CallsDeclWithoutEffect:
- MaybeNextCallee.emplace(S, *Diag2.Callee);
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
- << effectName << MaybeNextCallee->name(S);
- break;
- }
- checkAddTemplateNote(Callee);
- Callee = Diag2.Callee;
- if (MaybeNextCallee) {
- CalleeInfo = *MaybeNextCallee;
- CalleeName = CalleeInfo.name(S);
- }
- }
- } break;
- }
- }
- }
-
- // ----------
- // This AST visitor is used to traverse the body of a function during effect
- // verification. This happens in 2 situations:
- // [1] The function has declared effects which need to be validated.
- // [2] The function has not explicitly declared an effect in question, and is
- // being checked for implicit conformance.
- //
- // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
- // all diagnostic output.
- //
- // Q: Currently we create a new RecursiveASTVisitor for every function
- // analysis. Is it so lightweight that this is OK? It would appear so.
- struct FunctionBodyASTVisitor
- : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
- // The meanings of the boolean values returned by the Visit methods can be
- // difficult to remember.
- constexpr static bool Stop = false;
- constexpr static bool Proceed = true;
-
- Analyzer &Outer;
- PendingFunctionAnalysis &CurrentFunction;
- CallableInfo &CurrentCaller;
-
- FunctionBodyASTVisitor(Analyzer &outer,
- PendingFunctionAnalysis &CurrentFunction,
- CallableInfo &CurrentCaller)
- : Outer(outer), CurrentFunction(CurrentFunction),
- CurrentCaller(CurrentCaller) {}
-
- // -- Entry point --
- void run() {
- // The target function itself may have some implicit code paths beyond the
- // body: member and base constructors and destructors. Visit these first.
- if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
- if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
- for (const CXXCtorInitializer *Initer : Ctor->inits())
- if (Expr *Init = Initer->getInit())
- VisitStmt(Init);
- } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD))
- followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
- }
- // else could be BlockDecl
-
- // Do an AST traversal of the function/block body
- TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
- }
-
- // -- Methods implementing common logic --
-
- // Handle a language construct forbidden by some effects. Only effects whose
- // flags include the specified flag receive a diagnostic. \p Flag describes
- // the construct.
- void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
- SourceLocation Loc,
- const Decl *Callee = nullptr) {
- // If there are any declared verifiable effects which forbid the construct
- // represented by the flag, store just one diagnostic.
- for (const FunctionEffect &Effect :
- CurrentFunction.DeclaredVerifiableEffects) {
- if (Effect.flags() & Flag) {
- addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
- break;
- }
- }
- // For each inferred effect which forbids the construct, store a
- // diagnostic, if we don't already have a diagnostic for that effect.
- for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
- if (Effect.flags() & Flag)
- addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
- }
-
- void addDiagnostic(bool Inferring, const FunctionEffect &Effect,
- DiagnosticID D, SourceLocation Loc,
- const Decl *Callee = nullptr) {
- CurrentFunction.checkAddDiagnostic(Inferring,
- Diagnostic(Effect, D, Loc, Callee));
- }
-
- // Here we have a call to a Decl, either explicitly via a CallExpr or some
- // other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
- // Currently, built-in functions are always considered safe.
- // FIXME: Some are not.
- if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
- FD && FD->getBuiltinID() != 0)
- return;
-
- Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
- /*AssertNoFurtherInference=*/false);
- }
-
- void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
- const QualType CalleeType = CalleeExpr->getType();
- auto *FPT =
- CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- EffectSet CalleeFX;
- if (FPT)
- CalleeFX.insert(FPT->getFunctionEffects());
-
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
- /*direct=*/false, CalleeFX))
- addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
- Call->getBeginLoc());
- };
-
- for (const FunctionEffect &Effect :
- CurrentFunction.DeclaredVerifiableEffects)
- check1Effect(Effect, false);
-
- for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
- check1Effect(Effect, true);
- }
-
- // This destructor's body should be followed by the caller, but here we
- // follow the field and base destructors.
- void followDestructor(const CXXRecordDecl *Rec,
- const CXXDestructorDecl *Dtor) {
- for (const FieldDecl *Field : Rec->fields())
- followTypeDtor(Field->getType());
-
- if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
- for (const CXXBaseSpecifier &Base : Class->bases())
- followTypeDtor(Base.getType());
-
- for (const CXXBaseSpecifier &Base : Class->vbases())
- followTypeDtor(Base.getType());
- }
- }
-
- void followTypeDtor(QualType QT) {
- const Type *Ty = QT.getTypePtr();
- while (Ty->isArrayType()) {
- const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
- QT = Arr->getElementType();
- Ty = QT.getTypePtr();
- }
-
- if (Ty->isRecordType()) {
- if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
- if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
- followCall(CI, Dtor->getLocation());
- }
- }
- }
- }
-
- // -- Methods for use of RecursiveASTVisitor --
-
- bool shouldVisitImplicitCode() const { return true; }
-
- bool shouldWalkTypesOfTypeLocs() const { return false; }
-
- bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- DiagnosticID::Throws, Throw->getThrowLoc());
- return Proceed;
- }
-
- bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- DiagnosticID::Catches, Catch->getCatchLoc());
- return Proceed;
- }
-
- bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- DiagnosticID::Throws, Throw->getThrowLoc());
- return Proceed;
- }
-
- bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- DiagnosticID::Catches, Catch->getAtCatchLoc());
- return Proceed;
- }
-
- bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
- DiagnosticID::CallsObjC, Msg->getBeginLoc());
- return Proceed;
- }
-
- bool VisitCallExpr(CallExpr *Call) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitCallExpr : "
- << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- }
-
- Expr *CalleeExpr = Call->getCallee();
- if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
- CallableInfo CI(Outer.Sem, *Callee);
- followCall(CI, Call->getBeginLoc());
- return Proceed;
- }
-
- if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
- // just destroying a scalar, fine.
- return Proceed;
-
- // No Decl, just an Expr. Just check based on its type.
- checkIndirectCall(Call, CalleeExpr);
-
- return Proceed;
- }
-
- bool VisitVarDecl(VarDecl *Var) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitVarDecl : "
- << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- }
-
- if (Var->isStaticLocal())
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
- DiagnosticID::HasStaticLocal,
- Var->getLocation());
-
- const QualType::DestructionKind DK =
- Var->needsDestruction(Outer.Sem.getASTContext());
- if (DK == QualType::DK_cxx_destructor) {
- QualType QT = Var->getType();
- if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
- if (const auto *CxxRec =
- dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
- if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
- followCall(CI, Var->getLocation());
- }
- }
- }
- }
- return Proceed;
- }
-
- bool VisitCXXNewExpr(CXXNewExpr *New) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator new.
- if (FunctionDecl *FD = New->getOperatorNew()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
- followCall(CI, New->getBeginLoc());
- }
-
- // It's a bit excessive to check operator delete here, since it's
- // just a fallback for operator new followed by a failed constructor.
- // We could check it via New->getOperatorDelete().
-
- // It DOES however visit the called constructor
- return Proceed;
- }
-
- bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator delete.
- if (FunctionDecl *FD = Delete->getOperatorDelete()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
- followCall(CI, Delete->getBeginLoc());
- }
-
- // It DOES however visit the called destructor
-
- return Proceed;
- }
-
- bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
- if constexpr (DebugLogLevel > 2) {
- llvm::errs() << "VisitCXXConstructExpr : "
- << Construct->getBeginLoc().printToString(
- Outer.Sem.SourceMgr)
- << "\n";
- }
-
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to the constructor.
- const CXXConstructorDecl *Ctor = Construct->getConstructor();
- CallableInfo CI(Outer.Sem, *Ctor);
- followCall(CI, Construct->getLocation());
-
- return Proceed;
- }
-
- bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
- if (Expr *E = DEI->getExpr())
- TraverseStmt(E);
-
- return Proceed;
- }
-
- bool TraverseLambdaExpr(LambdaExpr *Lambda) {
- // We override this so as the be able to skip traversal of the lambda's
- // body. We have to explicitly traverse the captures.
- for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
- if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
- Lambda->capture_init_begin()[I]) == Stop)
- return Stop;
-
- return Proceed;
- }
-
- bool TraverseBlockExpr(BlockExpr * /*unused*/) {
- // TODO: are the capture expressions (ctor call?) safe?
- return Proceed;
- }
-
- bool VisitDeclRefExpr(const DeclRefExpr *E) {
- const ValueDecl *Val = E->getDecl();
- if (isa<VarDecl>(Val)) {
- const VarDecl *Var = cast<VarDecl>(Val);
- VarDecl::TLSKind TLSK = Var->getTLSKind();
- if (TLSK != VarDecl::TLS_None) {
- // At least on macOS, thread-local variables are initialized on
- // first access, including a heap allocation.
- diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
- DiagnosticID::AccessesThreadLocal,
- E->getLocation());
- }
- }
- return Proceed;
- }
-
- // Unevaluated contexts: need to skip
- // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
-
- bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
- return TraverseStmt(Node->getResultExpr());
- }
- bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
- return Proceed;
- }
-
- bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
-
- bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
-
- bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
-
- bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
- };
-};
-
-Analyzer::AnalysisMap::~AnalysisMap() {
- for (const auto &Item : *this) {
- FuncAnalysisPtr AP = Item.second;
- if (isa<PendingFunctionAnalysis *>(AP))
- delete AP.get<PendingFunctionAnalysis *>();
- else
- delete AP.get<CompleteFunctionAnalysis *>();
- }
-}
-
-} // namespace FXAnalysis
-
-// =============================================================================
-
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
@@ -3819,9 +2553,6 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
SourceLocation())) {
CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
}
-
- if (S.Context.hasAnyFunctionEffects())
- FXAnalysis::Analyzer{S}.run(*TU);
}
void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 2cee4f5ef6e99c..ea827323395d74 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -19,6 +19,7 @@ add_clang_library(clangSema
CodeCompleteConsumer.cpp
DeclSpec.cpp
DelayedDiagnostic.cpp
+ EffectAnalysis.cpp
HLSLExternalSemaSource.cpp
IdentifierResolver.cpp
JumpDiagnostics.cpp
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
new file mode 100644
index 00000000000000..ca1213658f4596
--- /dev/null
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -0,0 +1,1289 @@
+//=== EffectAnalysis.cpp - Sema warnings for function effects -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements caller/callee analysis for function effects.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Sema/SemaInternal.h"
+
+#define DEBUG_TYPE "fxanalysis"
+
+using namespace clang;
+
+namespace {
+
+enum class DiagnosticID : uint8_t {
+ None = 0, // sentinel for an empty Diagnostic
+ Throws,
+ Catches,
+ CallsObjC,
+ AllocatesMemory,
+ HasStaticLocal,
+ AccessesThreadLocal,
+
+ // These only apply to callees, where the analysis stops at the Decl
+ DeclDisallowsInference,
+
+ CallsDeclWithoutEffect,
+ CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has been
+// made unsafe by a callee.
+struct Diagnostic {
+ FunctionEffect Effect;
+ DiagnosticID ID = DiagnosticID::None;
+ SourceLocation Loc;
+ const Decl *Callee = nullptr; // only valid for Calls*
+
+ Diagnostic() = default;
+
+ Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+ : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+ // unknown: probably function pointer
+ Unknown,
+ Function,
+ Virtual,
+ Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+ if (FD->isTrivial()) {
+ // Otherwise `struct x { int a; };` would have an unverifiable default
+ // constructor.
+ return true;
+ }
+ return FD->hasBody();
+}
+
+static bool isNoexcept(const FunctionDecl *FD) {
+ const auto *FPT = FD->getType()->castAs<FunctionProtoType>();
+ if (FPT->isNothrow() || FD->hasAttr<NoThrowAttr>())
+ return true;
+ return false;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+ // This implementation optimizes footprint, since we hold one of these for
+ // every function visited, which, due to inference, can be many more functions
+ // than have declared effects.
+
+ template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
+ SizeT Count = 0;
+ T Items[Capacity] = {};
+
+ using value_type = T;
+
+ using iterator = T *;
+ using const_iterator = const T *;
+ iterator begin() { return &Items[0]; }
+ iterator end() { return &Items[Count]; }
+ const_iterator begin() const { return &Items[0]; }
+ const_iterator end() const { return &Items[Count]; }
+ const_iterator cbegin() const { return &Items[0]; }
+ const_iterator cend() const { return &Items[Count]; }
+
+ void insert(iterator I, const T &Value) {
+ assert(Count < Capacity);
+ iterator E = end();
+ if (I != E)
+ std::copy_backward(I, E, E + 1);
+ *I = Value;
+ ++Count;
+ }
+
+ void push_back(const T &Value) {
+ assert(Count < Capacity);
+ Items[Count++] = Value;
+ }
+ };
+
+ // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+ // effects, this fixed-size vector with a capacity of 7 is more than
+ // sufficient and is only 8 bytes.
+ FixedVector<FunctionEffect, uint8_t, 7> Impl;
+
+public:
+ EffectSet() = default;
+ explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+ operator ArrayRef<FunctionEffect>() const {
+ return ArrayRef(Impl.cbegin(), Impl.cend());
+ }
+
+ using iterator = const FunctionEffect *;
+ iterator begin() const { return Impl.cbegin(); }
+ iterator end() const { return Impl.cend(); }
+
+ void insert(const FunctionEffect &Effect) {
+ FunctionEffect *Iter = Impl.begin();
+ FunctionEffect *End = Impl.end();
+ // linear search; lower_bound is overkill for a tiny vector like this
+ for (; Iter != End; ++Iter) {
+ if (*Iter == Effect)
+ return;
+ if (Effect < *Iter)
+ break;
+ }
+ Impl.insert(Iter, Effect);
+ }
+ void insert(const EffectSet &Set) {
+ for (const FunctionEffect &Item : Set) {
+ // push_back because set is already sorted
+ Impl.push_back(Item);
+ }
+ }
+ void insert(FunctionEffectsRef FX) {
+ for (const FunctionEffectWithCondition &EC : FX) {
+ assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+ // push_back because set is already sorted
+ Impl.push_back(EC.Effect);
+ }
+ }
+ bool contains(const FunctionEffect::Kind EK) const {
+ for (const FunctionEffect &E : Impl)
+ if (E.kind() == EK)
+ return true;
+ return false;
+ }
+
+ void dump(llvm::raw_ostream &OS) const;
+
+ static EffectSet difference(ArrayRef<FunctionEffect> LHS,
+ ArrayRef<FunctionEffect> RHS) {
+ EffectSet Result;
+ std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Result.Impl));
+ return Result;
+ }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
+ OS << "Effects{";
+ bool First = true;
+ for (const FunctionEffect &Effect : *this) {
+ if (!First)
+ OS << ", ";
+ else
+ First = false;
+ OS << Effect.name();
+ }
+ OS << "}";
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer, etc.
+struct CallableInfo {
+ // CDecl holds the function's definition, if any.
+ // FunctionDecl if CallType::Function or Virtual
+ // BlockDecl if CallType::Block
+ const Decl *CDecl;
+ mutable std::optional<std::string> MaybeName;
+ SpecialFuncType FuncType = SpecialFuncType::None;
+ EffectSet Effects;
+ CallType CType = CallType::Unknown;
+
+ CallableInfo(Sema &SemaRef, const Decl &CD,
+ SpecialFuncType FT = SpecialFuncType::None)
+ : CDecl(&CD), FuncType(FT) {
+ FunctionEffectsRef FXRef;
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+ // Use the function's definition, if any.
+ if (const FunctionDecl *Def = FD->getDefinition())
+ CDecl = FD = Def;
+ CType = CallType::Function;
+ if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
+ Method && Method->isVirtual())
+ CType = CallType::Virtual;
+ FXRef = FD->getFunctionEffects();
+ } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+ CType = CallType::Block;
+ FXRef = BD->getFunctionEffects();
+ } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
+ // ValueDecl is function, enum, or variable, so just look at its type.
+ FXRef = FunctionEffectsRef::get(VD->getType());
+ }
+ Effects = EffectSet(FXRef);
+ }
+
+ bool isDirectCall() const {
+ return CType == CallType::Function || CType == CallType::Block;
+ }
+
+ bool isVerifiable() const {
+ switch (CType) {
+ case CallType::Unknown:
+ case CallType::Virtual:
+ return false;
+ case CallType::Block:
+ return true;
+ case CallType::Function:
+ return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+ }
+ llvm_unreachable("undefined CallType");
+ }
+
+ /// Generate a name for logging and diagnostics.
+ std::string name(Sema &Sem) const {
+ if (!MaybeName) {
+ std::string Name;
+ llvm::raw_string_ostream OS(Name);
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+ FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+ else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
+ OS << "(block " << BD->getBlockManglingNumber() << ")";
+ else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
+ VD->printQualifiedName(OS);
+ MaybeName = Name;
+ }
+ return *MaybeName;
+ }
+};
+
+// ----------
+// Map effects to single diagnostics, to hold the first (of potentially many)
+// diagnostics pertaining to an effect, per function.
+class EffectToDiagnosticMap {
+ // Since we currently only have a tiny number of effects (typically no more
+ // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+ // is often empty, use a unique_ptr to the SmallVector.
+ // Note that Diagnostic itself contains a FunctionEffect which is the key.
+ using ImplVec = llvm::SmallVector<Diagnostic, 1>;
+ std::unique_ptr<ImplVec> Impl;
+
+public:
+ // Insert a new diagnostic if we do not already have one for its effect.
+ void maybeInsert(const Diagnostic &Diag) {
+ if (Impl == nullptr)
+ Impl = std::make_unique<ImplVec>();
+ auto *Iter = _find(Diag.Effect);
+ if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
+ return;
+
+ Impl->insert(Iter, Diag);
+ }
+
+ const Diagnostic *lookup(FunctionEffect Key) {
+ if (Impl == nullptr)
+ return nullptr;
+
+ auto *Iter = _find(Key);
+ if (Iter != Impl->end() && Iter->Effect == Key)
+ return &*Iter;
+
+ return nullptr;
+ }
+
+ size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+ ImplVec::iterator _find(const FunctionEffect &key) {
+ // A linear search suffices for a tiny number of possible effects.
+ auto *End = Impl->end();
+ for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
+ if (!(Iter->Effect < key))
+ return Iter;
+ return End;
+ }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked and whose effect analysis
+// is dependent on a subsequent analysis of other functions.
+class PendingFunctionAnalysis {
+ friend class CompleteFunctionAnalysis;
+
+public:
+ struct DirectCall {
+ const Decl *Callee;
+ SourceLocation CallLoc;
+ // Not all recursive calls are detected, just enough
+ // to break cycles.
+ bool Recursed = false;
+
+ DirectCall(const Decl *D, SourceLocation CallLoc)
+ : Callee(D), CallLoc(CallLoc) {}
+ };
+
+ // We always have two disjoint sets of effects to verify:
+ // 1. Effects declared explicitly by this function.
+ // 2. All other inferrable effects needing verification.
+ EffectSet DeclaredVerifiableEffects;
+ EffectSet FXToInfer;
+
+private:
+ // Diagnostics pertaining to the function's explicit effects.
+ SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
+
+ // Diagnostics pertaining to other, non-explicit, inferrable effects.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+ // These unverified direct calls are what keeps the analysis "pending",
+ // until the callees can be verified.
+ SmallVector<DirectCall, 0> UnverifiedDirectCalls;
+
+public:
+ PendingFunctionAnalysis(
+ Sema &Sem, const CallableInfo &CInfo,
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+ DeclaredVerifiableEffects = CInfo.Effects;
+
+ // Check for effects we are not allowed to infer
+ EffectSet InferrableFX;
+
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+ if (effect.canInferOnFunction(*CInfo.CDecl))
+ InferrableFX.insert(effect);
+ else {
+ // Add a diagnostic for this effect if a caller were to
+ // try to infer it.
+ InferrableEffectToFirstDiagnostic.maybeInsert(
+ Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
+ CInfo.CDecl->getLocation()));
+ }
+ }
+ // InferrableFX is now the set of inferrable effects which are not
+ // prohibited
+ FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
+ }
+
+ // Hide the way that diagnostics for explicitly required effects vs. inferred
+ // ones are handled differently.
+ void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+ if (!Inferring)
+ DiagnosticsForExplicitFX.push_back(NewDiag);
+ else
+ InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
+ }
+
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
+ UnverifiedDirectCalls.emplace_back(D, CallLoc);
+ }
+
+ // Analysis is complete when there are no unverified direct calls.
+ bool isComplete() const { return UnverifiedDirectCalls.empty(); }
+
+ const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
+ return InferrableEffectToFirstDiagnostic.lookup(effect);
+ }
+
+ SmallVector<DirectCall, 0> &unverifiedCalls() {
+ assert(!isComplete());
+ return UnverifiedDirectCalls;
+ }
+
+ SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
+ return DiagnosticsForExplicitFX;
+ }
+
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
+ OS << "Pending: Declared ";
+ DeclaredVerifiableEffects.dump(OS);
+ OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
+ OS << " Infer ";
+ FXToInfer.dump(OS);
+ OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+ if (!UnverifiedDirectCalls.empty()) {
+ OS << "; Calls: ";
+ for (const DirectCall &Call : UnverifiedDirectCalls) {
+ CallableInfo CI(SemaRef, *Call.Callee);
+ OS << " " << CI.name(SemaRef);
+ }
+ }
+ OS << "\n";
+ }
+};
+
+// ----------
+class CompleteFunctionAnalysis {
+ // Current size: 2 pointers
+public:
+ // Has effects which are both the declared ones -- not to be inferred -- plus
+ // ones which have been successfully inferred. These are all considered
+ // "verified" for the purposes of callers; any issue with verifying declared
+ // effects has already been reported and is not the problem of any caller.
+ EffectSet VerifiedEffects;
+
+private:
+ // This is used to generate notes about failed inference.
+ EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+ // The incoming Pending analysis is consumed (member(s) are moved-from).
+ CompleteFunctionAnalysis(
+ ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+ const EffectSet &DeclaredEffects,
+ ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+ VerifiedEffects.insert(DeclaredEffects);
+ for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+ if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
+ VerifiedEffects.insert(effect);
+
+ InferrableEffectToFirstDiagnostic =
+ std::move(Pending.InferrableEffectToFirstDiagnostic);
+ }
+
+ const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
+ return InferrableEffectToFirstDiagnostic.lookup(Effect);
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "Complete: Verified ";
+ VerifiedEffects.dump(OS);
+ OS << "; Infer ";
+ OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ }
+};
+
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+ if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+ FD = FD->getCanonicalDecl();
+ assert(FD != nullptr);
+ return FD;
+ }
+ return D;
+}
+
+// ==========
+class Analyzer {
+ Sema &Sem;
+
+ // Subset of Sema.AllEffectsToVerify
+ EffectSet AllInferrableEffectsToVerify;
+
+ using FuncAnalysisPtr =
+ llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
+
+ // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+ // than complete state, so use different objects to represent them.
+ // The state pointers are owned by the container.
+ class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
+
+ public:
+ ~AnalysisMap();
+
+ // Use non-public inheritance in order to maintain the invariant
+ // that lookups and insertions are via the canonical Decls.
+
+ FuncAnalysisPtr lookup(const Decl *Key) const {
+ return Base::lookup(CanonicalFunctionDecl(Key));
+ }
+
+ FuncAnalysisPtr &operator[](const Decl *Key) {
+ return Base::operator[](CanonicalFunctionDecl(Key));
+ }
+
+ /// Shortcut for the case where we only care about completed analysis.
+ CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
+ if (FuncAnalysisPtr AP = lookup(D);
+ isa_and_nonnull<CompleteFunctionAnalysis *>(AP))
+ return AP.get<CompleteFunctionAnalysis *>();
+ return nullptr;
+ }
+
+ void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
+ OS << "\nAnalysisMap:\n";
+ for (const auto &item : *this) {
+ CallableInfo CI(SemaRef, *item.first);
+ const auto AP = item.second;
+ OS << item.first << " " << CI.name(SemaRef) << " : ";
+ if (AP.isNull())
+ OS << "null\n";
+ else if (isa<CompleteFunctionAnalysis *>(AP)) {
+ auto *CFA = AP.get<CompleteFunctionAnalysis *>();
+ OS << CFA << " ";
+ CFA->dump(OS);
+ } else if (isa<PendingFunctionAnalysis *>(AP)) {
+ auto *PFA = AP.get<PendingFunctionAnalysis *>();
+ OS << PFA << " ";
+ PFA->dump(SemaRef, OS);
+ } else
+ llvm_unreachable("never");
+ }
+ OS << "---\n";
+ }
+ };
+ AnalysisMap DeclAnalysis;
+
+public:
+ Analyzer(Sema &S) : Sem(S) {}
+
+ void run(const TranslationUnitDecl &TU) {
+ // Gather all of the effects to be verified to see what operations need to
+ // be checked, and to see which ones are inferrable.
+ for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
+ const FunctionEffect &Effect = CFE.Effect;
+ const FunctionEffect::Flags Flags = Effect.flags();
+ if (Flags & FunctionEffect::FE_InferrableOnCallees)
+ AllInferrableEffectsToVerify.insert(Effect);
+ }
+ LLVM_DEBUG(
+ llvm::dbgs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::dbgs());
+ llvm::dbgs() << "\n";
+ );
+
+ // We can use DeclsWithEffectsToVerify as a stack for a
+ // depth-first traversal; there's no need for a second container. But first,
+ // reverse it, so when working from the end, Decls are verified in the order
+ // they are declared.
+ SmallVector<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify;
+ std::reverse(VerificationQueue.begin(), VerificationQueue.end());
+
+ while (!VerificationQueue.empty()) {
+ const Decl *D = VerificationQueue.back();
+ if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
+ if (isa<CompleteFunctionAnalysis *>(AP)) {
+ // already done
+ VerificationQueue.pop_back();
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // All children have been traversed; finish analysis.
+ auto *Pending = AP.get<PendingFunctionAnalysis *>();
+ finishPendingAnalysis(D, Pending);
+ VerificationQueue.pop_back();
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+
+ // Not previously visited; begin a new analysis for this Decl.
+ PendingFunctionAnalysis *Pending = verifyDecl(D);
+ if (Pending == nullptr) {
+ // completed now
+ VerificationQueue.pop_back();
+ continue;
+ }
+
+ // Analysis remains pending because there are direct callees to be
+ // verified first. Push them onto the queue.
+ for (PendingFunctionAnalysis::DirectCall &Call :
+ Pending->unverifiedCalls()) {
+ FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee);
+ if (AP.isNull()) {
+ VerificationQueue.push_back(Call.Callee);
+ continue;
+ }
+ if (isa<PendingFunctionAnalysis *>(AP)) {
+ // This indicates recursion (not necessarily direct). For the
+ // purposes of effect analysis, we can just ignore it since
+ // no effects forbid recursion.
+ Call.Recursed = true;
+ continue;
+ }
+ llvm_unreachable("unexpected DeclAnalysis item");
+ }
+ }
+ }
+
+private:
+ // Verify a single Decl. Return the pending structure if that was the result,
+ // else null. This method must not recurse.
+ PendingFunctionAnalysis *verifyDecl(const Decl *D) {
+ CallableInfo CInfo(Sem, *D);
+ bool isExternC = false;
+
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ assert(FD->getBuiltinID() == 0);
+ isExternC = FD->getCanonicalDecl()->isExternCContext();
+ }
+
+ // For C++, with non-extern "C" linkage only - if any of the Decl's declared
+ // effects forbid throwing (e.g. nonblocking) then the function should also
+ // be declared noexcept.
+ if (Sem.getLangOpts().CPlusPlus && !isExternC) {
+ for (const FunctionEffect &Effect : CInfo.Effects) {
+ if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
+ continue;
+
+ bool IsNoexcept = false;
+ if (auto *FD = D->getAsFunction()) {
+ IsNoexcept = isNoexcept(FD);
+ } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+ if (auto *TSI = BD->getSignatureAsWritten()) {
+ auto *FPT = TSI->getType()->getAs<FunctionProtoType>();
+ IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
+ }
+ }
+ if (!IsNoexcept)
+ Sem.Diag(D->getBeginLoc(),
+ diag::warn_perf_constraint_implies_noexcept)
+ << Effect.name();
+ break;
+ }
+ }
+
+ // Build a PendingFunctionAnalysis on the stack. If it turns out to be
+ // complete, we'll have avoided a heap allocation; if it's incomplete, it's
+ // a fairly trivial move to a heap-allocated object.
+ PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
+
+ LLVM_DEBUG(
+ llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
+ FAnalysis.dump(Sem, llvm::dbgs());
+ );
+
+ FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
+
+ Visitor.run();
+ if (FAnalysis.isComplete()) {
+ completeAnalysis(CInfo, FAnalysis);
+ return nullptr;
+ }
+ // Move the pending analysis to the heap and save it in the map.
+ PendingFunctionAnalysis *PendingPtr =
+ new PendingFunctionAnalysis(std::move(FAnalysis));
+ DeclAnalysis[D] = PendingPtr;
+ LLVM_DEBUG(
+ llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::dbgs());
+ );
+ return PendingPtr;
+ }
+
+ // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
+ // inserted in the container.
+ void completeAnalysis(const CallableInfo &CInfo,
+ PendingFunctionAnalysis &Pending) {
+ if (SmallVector<Diagnostic, 0> &Diags =
+ Pending.getDiagnosticsForExplicitFX();
+ !Diags.empty())
+ emitDiagnostics(Diags, CInfo, Sem);
+
+ CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+ Sem.getASTContext(), Pending, CInfo.Effects,
+ AllInferrableEffectsToVerify);
+ DeclAnalysis[CInfo.CDecl] = CompletePtr;
+ LLVM_DEBUG(
+ llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::dbgs());
+ );
+ }
+
+ // Called after all direct calls requiring inference have been found -- or
+ // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
+ // the possibility of inference. Deletes Pending.
+ void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+ CallableInfo Caller(Sem, *D);
+ LLVM_DEBUG(
+ llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+ Pending->dump(Sem, llvm::dbgs());
+ llvm::dbgs() << "\n";
+ );
+ for (const PendingFunctionAnalysis::DirectCall &Call :
+ Pending->unverifiedCalls()) {
+ if (Call.Recursed)
+ continue;
+
+ CallableInfo Callee(Sem, *Call.Callee);
+ followCall(Caller, *Pending, Callee, Call.CallLoc,
+ /*AssertNoFurtherInference=*/true);
+ }
+ completeAnalysis(Caller, *Pending);
+ delete Pending;
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. PFA pertains to the caller.
+ void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
+ const CallableInfo &Callee, SourceLocation CallLoc,
+ bool AssertNoFurtherInference) {
+ const bool DirectCall = Callee.isDirectCall();
+
+ // Initially, the declared effects; inferred effects will be added.
+ EffectSet CalleeEffects = Callee.Effects;
+
+ bool IsInferencePossible = DirectCall;
+
+ if (DirectCall) {
+ if (CompleteFunctionAnalysis *CFA =
+ DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+ // Combine declared effects with those which may have been inferred.
+ CalleeEffects.insert(CFA->VerifiedEffects);
+ IsInferencePossible = false; // we've already traversed it
+ }
+ }
+
+ if (AssertNoFurtherInference) {
+ assert(!IsInferencePossible);
+ }
+
+ if (!Callee.isVerifiable())
+ IsInferencePossible = false;
+
+ LLVM_DEBUG(
+ llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
+ << Callee.name(Sem)
+ << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::dbgs());
+ llvm::dbgs() << "\n";
+ llvm::dbgs() << " callee " << Callee.CDecl << " canonical "
+ << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+ for (Decl *D : Callee.CDecl->redecls())
+ llvm::dbgs() << " " << D;
+
+ llvm::dbgs() << "\n";
+ );
+
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ FunctionEffect::Flags Flags = Effect.flags();
+ bool Diagnose =
+ Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
+ if (Diagnose) {
+ // If inference is not allowed, or the target is indirect (virtual
+ // method/function ptr?), generate a diagnostic now.
+ if (!IsInferencePossible ||
+ !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
+ if (Callee.FuncType == SpecialFuncType::None)
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
+ CallLoc, Callee.CDecl});
+ else
+ PFA.checkAddDiagnostic(
+ Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ }
+ }
+ };
+
+ for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : PFA.FXToInfer)
+ check1Effect(Effect, true);
+ }
+
+ // Should only be called when determined to be complete.
+ void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
+ const CallableInfo &CInfo, Sema &S) {
+ if (Diags.empty())
+ return;
+ const SourceManager &SM = S.getSourceManager();
+ std::sort(Diags.begin(), Diags.end(),
+ [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+ return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+ });
+
+ auto checkAddTemplateNote = [&](const Decl *D) {
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ while (FD != nullptr && FD->isTemplateInstantiation()) {
+ S.Diag(FD->getPointOfInstantiation(),
+ diag::note_func_effect_from_template);
+ FD = FD->getTemplateInstantiationPattern();
+ }
+ }
+ };
+
+ // Top-level diagnostics are warnings.
+ for (const Diagnostic &Diag : Diags) {
+ StringRef effectName = Diag.Effect.name();
+ switch (Diag.ID) {
+ case DiagnosticID::None:
+ case DiagnosticID::DeclDisallowsInference: // shouldn't happen
+ // here
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+ case DiagnosticID::CallsExprWithoutEffect:
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
+ << effectName;
+ checkAddTemplateNote(CInfo.CDecl);
+ break;
+
+ case DiagnosticID::CallsDeclWithoutEffect: {
+ CallableInfo CalleeInfo(S, *Diag.Callee);
+ std::string CalleeName = CalleeInfo.name(S);
+
+ S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
+ << effectName << CalleeName;
+ checkAddTemplateNote(CInfo.CDecl);
+
+ // Emit notes explaining the transitive chain of inferences: Why isn't
+ // the callee safe?
+ for (const Decl *Callee = Diag.Callee; Callee != nullptr;) {
+ std::optional<CallableInfo> MaybeNextCallee;
+ CompleteFunctionAnalysis *Completed =
+ DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+ if (Completed == nullptr) {
+ // No result - could be
+ // - non-inline
+ // - indirect (virtual or through function pointer)
+ // - effect has been explicitly disclaimed (e.g. "blocking")
+ if (CalleeInfo.CType == CallType::Virtual)
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
+ << effectName;
+ else if (CalleeInfo.CType == CallType::Unknown)
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_func_ptr)
+ << effectName;
+ else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_disallows_inference)
+ << effectName;
+ else
+ S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+ << effectName;
+
+ break;
+ }
+ const Diagnostic *PtrDiag2 =
+ Completed->firstDiagnosticForEffect(Diag.Effect);
+ if (PtrDiag2 == nullptr)
+ break;
+
+ const Diagnostic &Diag2 = *PtrDiag2;
+ switch (Diag2.ID) {
+ case DiagnosticID::None:
+ llvm_unreachable("Unexpected diagnostic kind");
+ break;
+ case DiagnosticID::DeclDisallowsInference:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
+ << effectName;
+ break;
+ case DiagnosticID::CallsExprWithoutEffect:
+ S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+ << effectName;
+ break;
+ case DiagnosticID::AllocatesMemory:
+ S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
+ break;
+ case DiagnosticID::Throws:
+ case DiagnosticID::Catches:
+ S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+ << effectName;
+ break;
+ case DiagnosticID::HasStaticLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+ << effectName;
+ break;
+ case DiagnosticID::AccessesThreadLocal:
+ S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+ << effectName;
+ break;
+ case DiagnosticID::CallsObjC:
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
+ break;
+ case DiagnosticID::CallsDeclWithoutEffect:
+ MaybeNextCallee.emplace(S, *Diag2.Callee);
+ S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
+ << effectName << MaybeNextCallee->name(S);
+ break;
+ }
+ checkAddTemplateNote(Callee);
+ Callee = Diag2.Callee;
+ if (MaybeNextCallee) {
+ CalleeInfo = *MaybeNextCallee;
+ CalleeName = CalleeInfo.name(S);
+ }
+ }
+ } break;
+ }
+ }
+ }
+
+ // ----------
+ // This AST visitor is used to traverse the body of a function during effect
+ // verification. This happens in 2 situations:
+ // [1] The function has declared effects which need to be validated.
+ // [2] The function has not explicitly declared an effect in question, and is
+ // being checked for implicit conformance.
+ //
+ // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
+ // all diagnostic output.
+ struct FunctionBodyASTVisitor
+ : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+
+ Analyzer &Outer;
+ PendingFunctionAnalysis &CurrentFunction;
+ CallableInfo &CurrentCaller;
+
+ FunctionBodyASTVisitor(Analyzer &outer,
+ PendingFunctionAnalysis &CurrentFunction,
+ CallableInfo &CurrentCaller)
+ : Outer(outer), CurrentFunction(CurrentFunction),
+ CurrentCaller(CurrentCaller) {}
+
+ // -- Entry point --
+ void run() {
+ // The target function itself may have some implicit code paths beyond the
+ // body: member and base constructors and destructors. Visit these first.
+ if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
+ if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+ for (const CXXCtorInitializer *Initer : Ctor->inits())
+ if (Expr *Init = Initer->getInit())
+ VisitStmt(Init);
+ } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD))
+ followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+ }
+ // else could be BlockDecl
+
+ // Do an AST traversal of the function/block body
+ TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
+ }
+
+ // -- Methods implementing common logic --
+
+ // Handle a language construct forbidden by some effects. Only effects whose
+ // flags include the specified flag receive a diagnostic. \p Flag describes
+ // the construct.
+ void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+ SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ // If there are any declared verifiable effects which forbid the construct
+ // represented by the flag, store just one diagnostic.
+ for (const FunctionEffect &Effect :
+ CurrentFunction.DeclaredVerifiableEffects) {
+ if (Effect.flags() & Flag) {
+ addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
+ break;
+ }
+ }
+ // For each inferred effect which forbids the construct, store a
+ // diagnostic, if we don't already have a diagnostic for that effect.
+ for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ if (Effect.flags() & Flag)
+ addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
+ }
+
+ void addDiagnostic(bool Inferring, const FunctionEffect &Effect,
+ DiagnosticID D, SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ CurrentFunction.checkAddDiagnostic(Inferring,
+ Diagnostic(Effect, D, Loc, Callee));
+ }
+
+ // Here we have a call to a Decl, either explicitly via a CallExpr or some
+ // other AST construct. CallableInfo pertains to the callee.
+ void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+ // Currently, built-in functions are always considered safe.
+ // FIXME: Some are not.
+ if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
+ FD && FD->getBuiltinID() != 0)
+ return;
+
+ Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+ /*AssertNoFurtherInference=*/false);
+ }
+
+ void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
+ const QualType CalleeType = CalleeExpr->getType();
+ auto *FPT =
+ CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+ EffectSet CalleeFX;
+ if (FPT)
+ CalleeFX.insert(FPT->getFunctionEffects());
+
+ auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+ /*direct=*/false, CalleeFX))
+ addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
+ Call->getBeginLoc());
+ };
+
+ for (const FunctionEffect &Effect :
+ CurrentFunction.DeclaredVerifiableEffects)
+ check1Effect(Effect, false);
+
+ for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ check1Effect(Effect, true);
+ }
+
+ // This destructor's body should be followed by the caller, but here we
+ // follow the field and base destructors.
+ void followDestructor(const CXXRecordDecl *Rec,
+ const CXXDestructorDecl *Dtor) {
+ for (const FieldDecl *Field : Rec->fields())
+ followTypeDtor(Field->getType());
+
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ for (const CXXBaseSpecifier &Base : Class->bases())
+ followTypeDtor(Base.getType());
+
+ for (const CXXBaseSpecifier &Base : Class->vbases())
+ followTypeDtor(Base.getType());
+ }
+ }
+
+ void followTypeDtor(QualType QT) {
+ const Type *Ty = QT.getTypePtr();
+ while (Ty->isArrayType()) {
+ const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
+ QT = Arr->getElementType();
+ Ty = QT.getTypePtr();
+ }
+
+ if (Ty->isRecordType()) {
+ if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
+ if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
+ CallableInfo CI(Outer.Sem, *Dtor);
+ followCall(CI, Dtor->getLocation());
+ }
+ }
+ }
+ }
+
+ // -- Methods for use of RecursiveASTVisitor --
+
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+ bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ DiagnosticID::Throws, Throw->getThrowLoc());
+ return true;
+ }
+
+ bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ DiagnosticID::Catches, Catch->getCatchLoc());
+ return true;
+ }
+
+ bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+ DiagnosticID::Throws, Throw->getThrowLoc());
+ return true;
+ }
+
+ bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ DiagnosticID::Catches, Catch->getAtCatchLoc());
+ return true;
+ }
+
+ bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+ DiagnosticID::CallsObjC, Msg->getBeginLoc());
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr *Call) {
+ LLVM_DEBUG(
+ llvm::dbgs() << "VisitCallExpr : "
+ << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
+ );
+
+ Expr *CalleeExpr = Call->getCallee();
+ if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+ CallableInfo CI(Outer.Sem, *Callee);
+ followCall(CI, Call->getBeginLoc());
+ return true;
+ }
+
+ if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
+ // just destroying a scalar, fine.
+ return true;
+
+ // No Decl, just an Expr. Just check based on its type.
+ checkIndirectCall(Call, CalleeExpr);
+
+ return true;
+ }
+
+ bool VisitVarDecl(VarDecl *Var) {
+ LLVM_DEBUG(
+ llvm::dbgs() << "VisitVarDecl : "
+ << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << "\n";
+ );
+
+ if (Var->isStaticLocal())
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
+ DiagnosticID::HasStaticLocal,
+ Var->getLocation());
+
+ const QualType::DestructionKind DK =
+ Var->needsDestruction(Outer.Sem.getASTContext());
+ if (DK == QualType::DK_cxx_destructor) {
+ QualType QT = Var->getType();
+ if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+ if (const auto *CxxRec =
+ dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+ if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
+ CallableInfo CI(Outer.Sem, *Dtor);
+ followCall(CI, Var->getLocation());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ bool VisitCXXNewExpr(CXXNewExpr *New) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator new.
+ if (FunctionDecl *FD = New->getOperatorNew()) {
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
+ followCall(CI, New->getBeginLoc());
+ }
+
+ // It's a bit excessive to check operator delete here, since it's
+ // just a fallback for operator new followed by a failed constructor.
+ // We could check it via New->getOperatorDelete().
+
+ // It DOES however visit the called constructor
+ return true;
+ }
+
+ bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to operator delete.
+ if (FunctionDecl *FD = Delete->getOperatorDelete()) {
+ CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
+ followCall(CI, Delete->getBeginLoc());
+ }
+
+ // It DOES however visit the called destructor
+
+ return true;
+ }
+
+ bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
+ LLVM_DEBUG(
+ llvm::dbgs() << "VisitCXXConstructExpr : "
+ << Construct->getBeginLoc().printToString(
+ Outer.Sem.SourceMgr)
+ << "\n";
+ );
+
+ // BUG? It seems incorrect that RecursiveASTVisitor does not
+ // visit the call to the constructor.
+ const CXXConstructorDecl *Ctor = Construct->getConstructor();
+ CallableInfo CI(Outer.Sem, *Ctor);
+ followCall(CI, Construct->getLocation());
+
+ return true;
+ }
+
+ bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+ if (Expr *E = DEI->getExpr())
+ TraverseStmt(E);
+
+ return true;
+ }
+
+ 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)
+ TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+ Lambda->capture_init_begin()[I]);
+
+ return true;
+ }
+
+ bool TraverseBlockExpr(BlockExpr * /*unused*/) {
+ // TODO: are the capture expressions (ctor call?) safe?
+ return true;
+ }
+
+ bool VisitDeclRefExpr(const DeclRefExpr *E) {
+ const ValueDecl *Val = E->getDecl();
+ if (isa<VarDecl>(Val)) {
+ const VarDecl *Var = cast<VarDecl>(Val);
+ VarDecl::TLSKind TLSK = Var->getTLSKind();
+ if (TLSK != VarDecl::TLS_None) {
+ // At least on macOS, thread-local variables are initialized on
+ // first access, including a heap allocation.
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
+ DiagnosticID::AccessesThreadLocal,
+ E->getLocation());
+ }
+ }
+ return true;
+ }
+
+ bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+ return TraverseStmt(Node->getResultExpr());
+ }
+ bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+ return true;
+ }
+
+ bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return true; }
+
+ bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return true; }
+
+ bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return true; }
+
+ bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; }
+ };
+};
+
+Analyzer::AnalysisMap::~AnalysisMap() {
+ for (const auto &Item : *this) {
+ FuncAnalysisPtr AP = Item.second;
+ if (isa<PendingFunctionAnalysis *>(AP))
+ delete AP.get<PendingFunctionAnalysis *>();
+ else
+ delete AP.get<CompleteFunctionAnalysis *>();
+ }
+}
+
+} // anonymous namespace
+
+namespace clang {
+
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU)
+{
+ if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+ // exit if having uncompilable errors or ignoring all warnings:
+ return;
+ if (TU == nullptr)
+ return;
+ Analyzer{S}.run(*TU);
+}
+
+} // namespace clang
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2e989f0ba6fe45..35ba1da9770c4e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1509,6 +1509,9 @@ void Sema::ActOnEndOfTranslationUnit() {
AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
+ if (Context.hasAnyFunctionEffects())
+ performEffectAnalysis(*this, Context.getTranslationUnitDecl());
+
// Check we've noticed that we're no longer parsing the initializer for every
// variable. If we miss cases, then at best we have a performance issue and
// at worst a rejects-valid bug.
>From efe1b93804ed91634bd89d1ea9b64947c5af79d4 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 14:29:42 -0700
Subject: [PATCH 07/44] CallableInfo doesn't need to cache the name. Clean up
traversal of constructor initializers and implicit destructors (with test).
---
clang/lib/Sema/EffectAnalysis.cpp | 51 ++++++-------
.../Sema/attr-nonblocking-constraints.cpp | 71 +++++++++++--------
2 files changed, 62 insertions(+), 60 deletions(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index ca1213658f4596..862cb17b8bd783 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -199,7 +199,6 @@ struct CallableInfo {
// FunctionDecl if CallType::Function or Virtual
// BlockDecl if CallType::Block
const Decl *CDecl;
- mutable std::optional<std::string> MaybeName;
SpecialFuncType FuncType = SpecialFuncType::None;
EffectSet Effects;
CallType CType = CallType::Unknown;
@@ -247,20 +246,17 @@ struct CallableInfo {
/// Generate a name for logging and diagnostics.
std::string name(Sema &Sem) const {
- if (!MaybeName) {
- std::string Name;
- llvm::raw_string_ostream OS(Name);
-
- if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
- FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
- /*Qualified=*/true);
- else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
- OS << "(block " << BD->getBlockManglingNumber() << ")";
- else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
- VD->printQualifiedName(OS);
- MaybeName = Name;
- }
- return *MaybeName;
+ std::string Name;
+ llvm::raw_string_ostream OS(Name);
+
+ if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+ FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+ else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
+ OS << "(block " << BD->getBlockManglingNumber() << ")";
+ else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
+ VD->printQualifiedName(OS);
+ return Name;
}
};
@@ -955,17 +951,10 @@ class Analyzer {
// -- Entry point --
void run() {
- // The target function itself may have some implicit code paths beyond the
- // body: member and base constructors and destructors. Visit these first.
- if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
- if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
- for (const CXXCtorInitializer *Initer : Ctor->inits())
- if (Expr *Init = Initer->getInit())
- VisitStmt(Init);
- } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD))
- followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
- }
- // else could be BlockDecl
+ // The target function may have implicit code paths beyond the
+ // body: member and base destructors. Visit these first.
+ if (auto *Dtor = dyn_cast<CXXDestructorDecl>(CurrentCaller.CDecl))
+ followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
// Do an AST traversal of the function/block body
TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
@@ -1043,18 +1032,18 @@ class Analyzer {
void followDestructor(const CXXRecordDecl *Rec,
const CXXDestructorDecl *Dtor) {
for (const FieldDecl *Field : Rec->fields())
- followTypeDtor(Field->getType());
+ followTypeDtor(Field->getType(), Dtor);
if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
for (const CXXBaseSpecifier &Base : Class->bases())
- followTypeDtor(Base.getType());
+ followTypeDtor(Base.getType(), Dtor);
for (const CXXBaseSpecifier &Base : Class->vbases())
- followTypeDtor(Base.getType());
+ followTypeDtor(Base.getType(), Dtor);
}
}
- void followTypeDtor(QualType QT) {
+ void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) {
const Type *Ty = QT.getTypePtr();
while (Ty->isArrayType()) {
const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
@@ -1066,7 +1055,7 @@ class Analyzer {
if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
CallableInfo CI(Outer.Sem, *Dtor);
- followCall(CI, Dtor->getLocation());
+ followCall(CI, OuterDtor->getLocation());
}
}
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index e50c5b436daafc..7c7e322df80964 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -7,18 +7,18 @@
// --- CONSTRAINTS ---
-void nl1() [[clang::nonblocking]]
+void nb1() [[clang::nonblocking]]
{
int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
}
-void nl2() [[clang::nonblocking]]
+void nb2() [[clang::nonblocking]]
{
static int global; // expected-warning {{'nonblocking' function must not have static locals}}
}
-void nl3() [[clang::nonblocking]]
+void nb3() [[clang::nonblocking]]
{
try {
throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
@@ -27,13 +27,13 @@ void nl3() [[clang::nonblocking]]
}
}
-void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb4_inline() {}
+void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void nl4() [[clang::nonblocking]]
+void nb4() [[clang::nonblocking]]
{
- nl4_inline(); // OK
- nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb4_inline(); // OK
+ nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
@@ -41,21 +41,21 @@ struct HasVirtual {
virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
};
-void nl5() [[clang::nonblocking]]
+void nb5() [[clang::nonblocking]]
{
HasVirtual hv;
hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
-void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void nl6_transitively_unsafe()
+void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb6_transitively_unsafe()
{
- nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+ nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
}
-void nl6() [[clang::nonblocking]]
+void nb6() [[clang::nonblocking]]
{
- nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
thread_local int tl_var{ 42 };
@@ -65,7 +65,7 @@ bool tl_test() [[clang::nonblocking]]
return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
}
-void nl7()
+void nb7()
{
// Make sure we verify blocks
auto blk = ^() [[clang::nonblocking]] {
@@ -73,7 +73,7 @@ void nl7()
};
}
-void nl8()
+void nb8()
{
// Make sure we verify lambdas
auto lambda = []() [[clang::nonblocking]] {
@@ -111,7 +111,7 @@ void nl8()
}
};
-void nl9() [[clang::nonblocking]]
+void nb9() [[clang::nonblocking]]
{
Adder<int>::add_explicit(1, 2);
Adder<int>::add_implicit(1, 2);
@@ -121,7 +121,7 @@ void nl9() [[clang::nonblocking]]
expected-note {{in template expansion here}}
}
-void nl10(
+void nb10(
void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
void (*fp2)() [[clang::nonblocking]]
) [[clang::nonblocking]]
@@ -131,20 +131,20 @@ void nl10(
}
// Interactions with nonblocking(false)
-void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+void nb11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
{
}
-void nl11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
template <bool V>
struct ComputedNB {
void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
};
-void nl11() [[clang::nonblocking]]
+void nb11() [[clang::nonblocking]]
{
- nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
- nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
ComputedNB<true> CNB_true;
CNB_true.method();
@@ -154,11 +154,11 @@ void nl11() [[clang::nonblocking]]
}
// Verify that when attached to a redeclaration, the attribute successfully attaches.
-void nl12() {
+void nb12() {
static int x; // expected-warning {{'nonblocking' function must not have static locals}}
}
-void nl12() [[clang::nonblocking]];
-void nl13() [[clang::nonblocking]] { nl12(); }
+void nb12() [[clang::nonblocking]];
+void nb13() [[clang::nonblocking]] { nb12(); }
// C++ member function pointers
struct PTMFTester {
@@ -175,21 +175,34 @@ void PTMFTester::convert() [[clang::nonblocking]]
}
// Block variables
-void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
+void nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
blk();
}
// References to blocks
-void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
+void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
{
auto &ref = block;
ref();
}
+// Verify traversal of implicit code paths - constructors and destructors.
+struct Unsafe {
+ static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+ Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
+ ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+};
+
+struct DerivedFromUnsafe : public Unsafe {
+ DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+ ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
+};
// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
-void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
{
}
>From 7ffdbefa56e25f8398b32104c4616690ab076afe Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 1 Aug 2024 18:09:59 -0700
Subject: [PATCH 08/44] Simpler bitmap implementation of FunctionEffectKindSet
---
clang/include/clang/AST/Type.h | 75 +++++++++++++++-
clang/include/clang/Sema/Sema.h | 6 +-
clang/lib/AST/Type.cpp | 17 +++-
clang/lib/Sema/EffectAnalysis.cpp | 137 +++++++++++++-----------------
clang/lib/Sema/SemaDecl.cpp | 6 +-
5 files changed, 153 insertions(+), 88 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 89a74ff1fb285d..f4e6c8f04b8be8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -119,6 +119,7 @@ class Expr;
class ExtQualsTypeCommonBase;
class FunctionDecl;
class FunctionEffectSet;
+class FunctionEffectKindSet;
class IdentifierInfo;
class NamedDecl;
class ObjCInterfaceDecl;
@@ -4755,7 +4756,7 @@ class FunctionEffect {
// diagnostic. Caller should be assumed to have the effect (it may not have it
// explicitly when inferring).
bool shouldDiagnoseFunctionCall(bool Direct,
- ArrayRef<FunctionEffect> CalleeFX) const;
+ const FunctionEffectKindSet &CalleeFX) const;
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
return LHS.FKind == RHS.FKind;
@@ -4902,6 +4903,78 @@ class FunctionEffectsRef {
void dump(llvm::raw_ostream &OS) const;
};
+/// A mutable set of FunctionEffect::Kind.
+class FunctionEffectKindSet {
+ // For now this only needs to be a bitmap.
+ using KindBitsT = uint8_t;
+ constexpr static size_t EndBitPos = 8;
+
+ KindBitsT KindBits = 0;
+
+ static KindBitsT kindToBit(FunctionEffect::Kind K) {
+ return 1u << KindBitsT(K);
+ }
+
+ explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
+
+public:
+ FunctionEffectKindSet() = default;
+ explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
+
+ class iterator {
+ const FunctionEffectKindSet *Outer = nullptr;
+ size_t Idx = 0;
+
+ // If Idx does not reference a set bit, advance it until it does,
+ // or until it reaches EndBitPos.
+ void advanceIdx() {
+ while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx)))
+ ++Idx;
+ }
+
+ public:
+ iterator();
+ iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
+ advanceIdx();
+ }
+ bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
+ bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
+
+ iterator operator++() {
+ ++Idx;
+ advanceIdx();
+ return *this;
+ }
+
+ FunctionEffect operator*() const {
+ assert(Idx < EndBitPos);
+ return FunctionEffect(FunctionEffect::Kind(Idx));
+ }
+ };
+
+ iterator begin() const { return iterator(*this, 0); }
+ iterator end() const { return iterator(*this, EndBitPos); }
+
+ void insert(const FunctionEffect &Effect) {
+ KindBits |= kindToBit(Effect.kind());
+ }
+ void insert(FunctionEffectsRef FX) {
+ for (const FunctionEffect &Item : FX.effects())
+ insert(Item);
+ }
+ void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; }
+
+ bool contains(const FunctionEffect::Kind EK) const {
+ return (KindBits & kindToBit(EK)) != 0;
+ }
+ void dump(llvm::raw_ostream &OS) const;
+
+ static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS,
+ const FunctionEffectKindSet &RHS) {
+ return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits);
+ }
+};
+
/// A mutable set of FunctionEffects and possibly conditions attached to them.
/// Used to compare and merge effects on declarations.
///
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 49315edcca7464..b166825bef4a5f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,8 +530,8 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
const FunctionEffectsRef &New);
};
-// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move
-// more of the effects implementation into that file?
+// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and
+// move more of the effects implementation into that file?
void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
/// Sema - This implements semantic analysis and AST building for C.
@@ -882,7 +882,7 @@ class Sema final : public SemaBase {
SmallVector<const Decl *> DeclsWithEffectsToVerify;
/// The union of all effects present on DeclsWithEffectsToVerify. Conditions
/// are all null.
- FunctionEffectSet AllEffectsToVerify;
+ FunctionEffectKindSet AllEffectsToVerify;
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index fdaab8e4345936..6152375eb091aa 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5163,12 +5163,12 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
}
bool FunctionEffect::shouldDiagnoseFunctionCall(
- bool Direct, ArrayRef<FunctionEffect> CalleeFX) const {
+ bool Direct, const FunctionEffectKindSet &CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
const Kind CallerKind = kind();
- for (const auto &Effect : CalleeFX) {
+ for (const FunctionEffect &Effect : CalleeFX) {
const Kind EK = Effect.kind();
// Does callee have same or stronger constraint?
if (EK == CallerKind ||
@@ -5311,6 +5311,19 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
FunctionEffectsRef(*this).dump(OS);
}
+LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const {
+ OS << "Effects{";
+ bool First = true;
+ for (const auto &Effect : *this) {
+ if (!First)
+ OS << ", ";
+ else
+ First = false;
+ OS << Effect.name();
+ }
+ OS << "}";
+}
+
FunctionEffectsRef
FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
ArrayRef<EffectConditionExpr> Conds) {
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 862cb17b8bd783..83c3a07b2c66c9 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -11,8 +11,8 @@
//===----------------------------------------------------------------------===//
#include "clang/AST/Decl.h"
-#include "clang/AST/Type.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Type.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/SemaInternal.h"
@@ -81,6 +81,7 @@ static bool isNoexcept(const FunctionDecl *FD) {
return false;
}
+#if 0
/// A mutable set of FunctionEffect, for use in places where any conditions
/// have been resolved or can be ignored.
class EffectSet {
@@ -191,6 +192,7 @@ LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
}
OS << "}";
}
+#endif
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer, etc.
@@ -200,7 +202,7 @@ struct CallableInfo {
// BlockDecl if CallType::Block
const Decl *CDecl;
SpecialFuncType FuncType = SpecialFuncType::None;
- EffectSet Effects;
+ FunctionEffectKindSet Effects;
CallType CType = CallType::Unknown;
CallableInfo(Sema &SemaRef, const Decl &CD,
@@ -224,7 +226,7 @@ struct CallableInfo {
// ValueDecl is function, enum, or variable, so just look at its type.
FXRef = FunctionEffectsRef::get(VD->getType());
}
- Effects = EffectSet(FXRef);
+ Effects = FunctionEffectKindSet(FXRef);
}
bool isDirectCall() const {
@@ -251,7 +253,7 @@ struct CallableInfo {
if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
- /*Qualified=*/true);
+ /*Qualified=*/true);
else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
OS << "(block " << BD->getBlockManglingNumber() << ")";
else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
@@ -328,8 +330,8 @@ class PendingFunctionAnalysis {
// We always have two disjoint sets of effects to verify:
// 1. Effects declared explicitly by this function.
// 2. All other inferrable effects needing verification.
- EffectSet DeclaredVerifiableEffects;
- EffectSet FXToInfer;
+ FunctionEffectKindSet DeclaredVerifiableEffects;
+ FunctionEffectKindSet FXToInfer;
private:
// Diagnostics pertaining to the function's explicit effects.
@@ -345,11 +347,10 @@ class PendingFunctionAnalysis {
public:
PendingFunctionAnalysis(
Sema &Sem, const CallableInfo &CInfo,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- DeclaredVerifiableEffects = CInfo.Effects;
-
+ const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ : DeclaredVerifiableEffects(CInfo.Effects) {
// Check for effects we are not allowed to infer
- EffectSet InferrableFX;
+ FunctionEffectKindSet InferrableFX;
for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
if (effect.canInferOnFunction(*CInfo.CDecl))
@@ -364,7 +365,8 @@ class PendingFunctionAnalysis {
}
// InferrableFX is now the set of inferrable effects which are not
// prohibited
- FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
+ FXToInfer = FunctionEffectKindSet::difference(InferrableFX,
+ DeclaredVerifiableEffects);
}
// Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -422,7 +424,7 @@ class CompleteFunctionAnalysis {
// ones which have been successfully inferred. These are all considered
// "verified" for the purposes of callers; any issue with verifying declared
// effects has already been reported and is not the problem of any caller.
- EffectSet VerifiedEffects;
+ FunctionEffectKindSet VerifiedEffects;
private:
// This is used to generate notes about failed inference.
@@ -432,9 +434,9 @@ class CompleteFunctionAnalysis {
// The incoming Pending analysis is consumed (member(s) are moved-from).
CompleteFunctionAnalysis(
ASTContext &Ctx, PendingFunctionAnalysis &Pending,
- const EffectSet &DeclaredEffects,
- ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
- VerifiedEffects.insert(DeclaredEffects);
+ const FunctionEffectKindSet &DeclaredEffects,
+ const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ : VerifiedEffects(DeclaredEffects) {
for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
VerifiedEffects.insert(effect);
@@ -469,7 +471,7 @@ class Analyzer {
Sema &Sem;
// Subset of Sema.AllEffectsToVerify
- EffectSet AllInferrableEffectsToVerify;
+ FunctionEffectKindSet AllInferrableEffectsToVerify;
using FuncAnalysisPtr =
llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
@@ -532,17 +534,14 @@ class Analyzer {
void run(const TranslationUnitDecl &TU) {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
- for (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
- const FunctionEffect &Effect = CFE.Effect;
+ for (const FunctionEffect &Effect : Sem.AllEffectsToVerify) {
const FunctionEffect::Flags Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees)
AllInferrableEffectsToVerify.insert(Effect);
}
- LLVM_DEBUG(
- llvm::dbgs() << "AllInferrableEffectsToVerify: ";
- AllInferrableEffectsToVerify.dump(llvm::dbgs());
- llvm::dbgs() << "\n";
- );
+ LLVM_DEBUG(llvm::dbgs() << "AllInferrableEffectsToVerify: ";
+ AllInferrableEffectsToVerify.dump(llvm::dbgs());
+ llvm::dbgs() << "\n";);
// We can use DeclsWithEffectsToVerify as a stack for a
// depth-first traversal; there's no need for a second container. But first,
@@ -640,10 +639,8 @@ class Analyzer {
// a fairly trivial move to a heap-allocated object.
PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
- LLVM_DEBUG(
- llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(Sem, llvm::dbgs());
- );
+ LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
+ FAnalysis.dump(Sem, llvm::dbgs()););
FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
@@ -656,10 +653,8 @@ class Analyzer {
PendingFunctionAnalysis *PendingPtr =
new PendingFunctionAnalysis(std::move(FAnalysis));
DeclAnalysis[D] = PendingPtr;
- LLVM_DEBUG(
- llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
- DeclAnalysis.dump(Sem, llvm::dbgs());
- );
+ LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::dbgs()););
return PendingPtr;
}
@@ -676,10 +671,8 @@ class Analyzer {
Sem.getASTContext(), Pending, CInfo.Effects,
AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
- LLVM_DEBUG(
- llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
- DeclAnalysis.dump(Sem, llvm::dbgs());
- );
+ LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+ DeclAnalysis.dump(Sem, llvm::dbgs()););
}
// Called after all direct calls requiring inference have been found -- or
@@ -687,11 +680,9 @@ class Analyzer {
// the possibility of inference. Deletes Pending.
void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
CallableInfo Caller(Sem, *D);
- LLVM_DEBUG(
- llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
- Pending->dump(Sem, llvm::dbgs());
- llvm::dbgs() << "\n";
- );
+ LLVM_DEBUG(llvm::dbgs()
+ << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+ Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
for (const PendingFunctionAnalysis::DirectCall &Call :
Pending->unverifiedCalls()) {
if (Call.Recursed)
@@ -713,7 +704,7 @@ class Analyzer {
const bool DirectCall = Callee.isDirectCall();
// Initially, the declared effects; inferred effects will be added.
- EffectSet CalleeEffects = Callee.Effects;
+ FunctionEffectKindSet CalleeEffects = Callee.Effects;
bool IsInferencePossible = DirectCall;
@@ -733,19 +724,16 @@ class Analyzer {
if (!Callee.isVerifiable())
IsInferencePossible = false;
- LLVM_DEBUG(
- llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem)
- << "; verifiable: " << Callee.isVerifiable() << "; callee ";
- CalleeEffects.dump(llvm::dbgs());
- llvm::dbgs() << "\n";
- llvm::dbgs() << " callee " << Callee.CDecl << " canonical "
- << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
- for (Decl *D : Callee.CDecl->redecls())
- llvm::dbgs() << " " << D;
+ LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
+ << Callee.name(Sem) << "; verifiable: "
+ << Callee.isVerifiable() << "; callee ";
+ CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
+ llvm::dbgs()
+ << " callee " << Callee.CDecl << " canonical "
+ << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+ for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
- llvm::dbgs() << "\n";
- );
+ llvm::dbgs() << "\n";);
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
FunctionEffect::Flags Flags = Effect.flags();
@@ -1008,7 +996,7 @@ class Analyzer {
const QualType CalleeType = CalleeExpr->getType();
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- EffectSet CalleeFX;
+ FunctionEffectKindSet CalleeFX;
if (FPT)
CalleeFX.insert(FPT->getFunctionEffects());
@@ -1098,11 +1086,10 @@ class Analyzer {
}
bool VisitCallExpr(CallExpr *Call) {
- LLVM_DEBUG(
- llvm::dbgs() << "VisitCallExpr : "
+ LLVM_DEBUG(llvm::dbgs()
+ << "VisitCallExpr : "
<< Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- );
+ << "\n";);
Expr *CalleeExpr = Call->getCallee();
if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
@@ -1122,11 +1109,10 @@ class Analyzer {
}
bool VisitVarDecl(VarDecl *Var) {
- LLVM_DEBUG(
- llvm::dbgs() << "VisitVarDecl : "
+ LLVM_DEBUG(llvm::dbgs()
+ << "VisitVarDecl : "
<< Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
- << "\n";
- );
+ << "\n";);
if (Var->isStaticLocal())
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
@@ -1180,12 +1166,10 @@ class Analyzer {
}
bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
- LLVM_DEBUG(
- llvm::dbgs() << "VisitCXXConstructExpr : "
- << Construct->getBeginLoc().printToString(
- Outer.Sem.SourceMgr)
- << "\n";
- );
+ LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
+ << Construct->getBeginLoc().printToString(
+ Outer.Sem.SourceMgr)
+ << "\n";);
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to the constructor.
@@ -1208,7 +1192,7 @@ class Analyzer {
// body. We have to explicitly traverse the captures.
for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
- Lambda->capture_init_begin()[I]);
+ Lambda->capture_init_begin()[I]);
return true;
}
@@ -1265,14 +1249,13 @@ Analyzer::AnalysisMap::~AnalysisMap() {
namespace clang {
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU)
-{
- if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
- // exit if having uncompilable errors or ignoring all warnings:
- return;
- if (TU == nullptr)
- return;
- Analyzer{S}.run(*TU);
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
+ if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+ // exit if having uncompilable errors or ignoring all warnings:
+ return;
+ if (TU == nullptr)
+ return;
+ Analyzer{S}.run(*TU);
}
} // namespace clang
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b54da058c43c5e..ef27d42b92ce69 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10929,8 +10929,6 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
}
void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
- FunctionEffectSet::Conflicts Errs;
-
// To avoid the possibility of conflict, don't add effects which are
// not FE_InferrableOnCallees and therefore not verified; this removes
// blocking/allocating but keeps nonblocking/nonallocating.
@@ -10938,11 +10936,9 @@ void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
bool AnyVerifiable = false;
for (const FunctionEffectWithCondition &EC : FX)
if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
- AllEffectsToVerify.insert(FunctionEffectWithCondition(EC.Effect, nullptr),
- Errs);
+ AllEffectsToVerify.insert(EC.Effect);
AnyVerifiable = true;
}
- assert(Errs.empty() && "effects conflicts should not be possible here");
// Record the declaration for later analysis.
if (AnyVerifiable)
>From c39e28e78f882a148914e7c17c60c01e461bea07 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 1 Aug 2024 21:36:11 -0700
Subject: [PATCH 09/44] - function does not permit inference of '%0': include
the name of the effect preventing inference. - EffectAnalysis: Rename
Diagnostic to Violation to clarify that it is an abstraction which can be
reported as either a warning or a note depending on context.
---
clang/include/clang/AST/Type.h | 13 +-
.../clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/lib/AST/Type.cpp | 21 +-
clang/lib/Sema/EffectAnalysis.cpp | 296 +++++++++---------
.../Sema/attr-nonblocking-constraints.cpp | 2 +-
5 files changed, 172 insertions(+), 162 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index f4e6c8f04b8be8..b13ba7d784b3a9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -118,8 +118,9 @@ class EnumDecl;
class Expr;
class ExtQualsTypeCommonBase;
class FunctionDecl;
-class FunctionEffectSet;
+class FunctionEffectsRef;
class FunctionEffectKindSet;
+class FunctionEffectSet;
class IdentifierInfo;
class NamedDecl;
class ObjCInterfaceDecl;
@@ -4745,11 +4746,15 @@ class FunctionEffect {
/// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
- /// Return true if the effect is allowed to be inferred on the callee,
- /// which is either a FunctionDecl or BlockDecl.
+ /// Determine whether the effect is allowed to be inferred on the callee,
+ /// which is either a FunctionDecl or BlockDecl. If the returned optional
+ /// is empty, inference is permitted; otherwise it holds the effect which
+ /// blocked inference.
/// Example: This allows nonblocking(false) to prevent inference for the
/// function.
- bool canInferOnFunction(const Decl &Callee) const;
+ std::optional<FunctionEffect>
+ effectProhibitingInference(const Decl &Callee,
+ const FunctionEffectsRef &CalleeFX) const;
// Return false for success. When true is returned for a direct call, then the
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 709535dace2cf8..eedc2722a47993 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10969,7 +10969,7 @@ def note_func_effect_calls_func_without_effect : Note<
def note_func_effect_call_extern : Note<
"function cannot be inferred '%0' because it has no definition in this translation unit">;
def note_func_effect_call_disallows_inference : Note<
- "function does not permit inference of '%0'">;
+ "function does not permit inference of '%0' because it is declared '%1'">;
def note_func_effect_call_virtual : Note<
"virtual method cannot be inferred '%0'">;
def note_func_effect_call_func_ptr : Note<
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6152375eb091aa..06f6bb03d1d9f1 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5128,35 +5128,30 @@ StringRef FunctionEffect::name() const {
llvm_unreachable("unknown effect kind");
}
-bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
+std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
+ const Decl &Callee, const FunctionEffectsRef &CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
- FunctionEffectsRef CalleeFX;
- if (auto *FD = Callee.getAsFunction())
- CalleeFX = FD->getFunctionEffects();
- else if (auto *BD = dyn_cast<BlockDecl>(&Callee))
- CalleeFX = BD->getFunctionEffects();
- else
- return false;
for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
// nonblocking/nonallocating cannot call allocating.
if (CalleeEC.Effect.kind() == Kind::Allocating)
- return false;
+ return CalleeEC.Effect;
// nonblocking cannot call blocking.
if (kind() == Kind::NonBlocking &&
CalleeEC.Effect.kind() == Kind::Blocking)
- return false;
+ return CalleeEC.Effect;
}
- return true;
+ return std::nullopt;
}
case Kind::Allocating:
case Kind::Blocking:
- return false;
+ assert(0 && "effectProhibitingInference with non-inferable effect kind");
+ break;
case Kind::None:
- assert(0 && "canInferOnFunction with None");
+ assert(0 && "effectProhibitingInference with None");
break;
}
llvm_unreachable("unknown effect kind");
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 83c3a07b2c66c9..a8bbb73faf9fd8 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -22,8 +22,8 @@ using namespace clang;
namespace {
-enum class DiagnosticID : uint8_t {
- None = 0, // sentinel for an empty Diagnostic
+enum class ViolationID : uint8_t {
+ None = 0, // sentinel for an empty Violation
Throws,
Catches,
CallsObjC,
@@ -38,20 +38,28 @@ enum class DiagnosticID : uint8_t {
CallsExprWithoutEffect,
};
-// Holds an effect diagnosis, potentially for the entire duration of the
-// analysis phase, in order to refer to it when explaining why a caller has been
-// made unsafe by a callee.
-struct Diagnostic {
+// Represents a violation of the rules, potentially for the entire duration of
+// the analysis phase, in order to refer to it when explaining why a caller has
+// been made unsafe by a callee. Can be transformed into either a Diagnostic
+// (warning or a note), depending on whether the violation pertains to a
+// function failing to be verifed as holding an effect vs. a function failing to
+// be inferred as holding that effect.
+struct Violation {
FunctionEffect Effect;
- DiagnosticID ID = DiagnosticID::None;
+ FunctionEffect CalleeEffectPreventingInference; // only for certain IDs
+ ViolationID ID = ViolationID::None;
SourceLocation Loc;
const Decl *Callee = nullptr; // only valid for Calls*
- Diagnostic() = default;
+ Violation() = default;
- Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
- const Decl *Callee = nullptr)
- : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+ Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr,
+ const FunctionEffect *CalleeEffect = nullptr)
+ : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
+ if (CalleeEffect != nullptr)
+ CalleeEffectPreventingInference = *CalleeEffect;
+ }
};
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
@@ -202,13 +210,13 @@ struct CallableInfo {
// BlockDecl if CallType::Block
const Decl *CDecl;
SpecialFuncType FuncType = SpecialFuncType::None;
+ FunctionEffectsRef DeclEffects;
FunctionEffectKindSet Effects;
CallType CType = CallType::Unknown;
CallableInfo(Sema &SemaRef, const Decl &CD,
SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
- FunctionEffectsRef FXRef;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
@@ -218,15 +226,15 @@ struct CallableInfo {
if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->isVirtual())
CType = CallType::Virtual;
- FXRef = FD->getFunctionEffects();
+ DeclEffects = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
CType = CallType::Block;
- FXRef = BD->getFunctionEffects();
+ DeclEffects = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
- FXRef = FunctionEffectsRef::get(VD->getType());
+ DeclEffects = FunctionEffectsRef::get(VD->getType());
}
- Effects = FunctionEffectKindSet(FXRef);
+ Effects = FunctionEffectKindSet(DeclEffects);
}
bool isDirectCall() const {
@@ -263,29 +271,29 @@ struct CallableInfo {
};
// ----------
-// Map effects to single diagnostics, to hold the first (of potentially many)
-// diagnostics pertaining to an effect, per function.
-class EffectToDiagnosticMap {
+// Map effects to single Violations, to hold the first (of potentially many)
+// violations pertaining to an effect, per function.
+class EffectToViolationMap {
// Since we currently only have a tiny number of effects (typically no more
// than 1), use a sorted SmallVector with an inline capacity of 1. Since it
// is often empty, use a unique_ptr to the SmallVector.
- // Note that Diagnostic itself contains a FunctionEffect which is the key.
- using ImplVec = llvm::SmallVector<Diagnostic, 1>;
+ // Note that Violation itself contains a FunctionEffect which is the key.
+ using ImplVec = llvm::SmallVector<Violation, 1>;
std::unique_ptr<ImplVec> Impl;
public:
- // Insert a new diagnostic if we do not already have one for its effect.
- void maybeInsert(const Diagnostic &Diag) {
+ // Insert a new Violation if we do not already have one for its effect.
+ void maybeInsert(const Violation &Viol) {
if (Impl == nullptr)
Impl = std::make_unique<ImplVec>();
- auto *Iter = _find(Diag.Effect);
- if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
+ auto *Iter = _find(Viol.Effect);
+ if (Iter != Impl->end() && Iter->Effect == Viol.Effect)
return;
- Impl->insert(Iter, Diag);
+ Impl->insert(Iter, Viol);
}
- const Diagnostic *lookup(FunctionEffect Key) {
+ const Violation *lookup(FunctionEffect Key) {
if (Impl == nullptr)
return nullptr;
@@ -334,11 +342,11 @@ class PendingFunctionAnalysis {
FunctionEffectKindSet FXToInfer;
private:
- // Diagnostics pertaining to the function's explicit effects.
- SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
+ // Violations pertaining to the function's explicit effects.
+ SmallVector<Violation, 0> ViolationsForExplicitFX;
- // Diagnostics pertaining to other, non-explicit, inferrable effects.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+ // Violations pertaining to other, non-explicit, inferrable effects.
+ EffectToViolationMap InferrableEffectToFirstViolation;
// These unverified direct calls are what keeps the analysis "pending",
// until the callees can be verified.
@@ -353,14 +361,16 @@ class PendingFunctionAnalysis {
FunctionEffectKindSet InferrableFX;
for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
- if (effect.canInferOnFunction(*CInfo.CDecl))
+ std::optional<FunctionEffect> ProblemCalleeEffect =
+ effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects);
+ if (!ProblemCalleeEffect)
InferrableFX.insert(effect);
else {
- // Add a diagnostic for this effect if a caller were to
+ // Add a Violation for this effect if a caller were to
// try to infer it.
- InferrableEffectToFirstDiagnostic.maybeInsert(
- Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
- CInfo.CDecl->getLocation()));
+ InferrableEffectToFirstViolation.maybeInsert(Violation(
+ effect, ViolationID::DeclDisallowsInference,
+ CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
}
}
// InferrableFX is now the set of inferrable effects which are not
@@ -369,13 +379,13 @@ class PendingFunctionAnalysis {
DeclaredVerifiableEffects);
}
- // Hide the way that diagnostics for explicitly required effects vs. inferred
+ // Hide the way that Violations for explicitly required effects vs. inferred
// ones are handled differently.
- void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+ void checkAddViolation(bool Inferring, const Violation &NewViol) {
if (!Inferring)
- DiagnosticsForExplicitFX.push_back(NewDiag);
+ ViolationsForExplicitFX.push_back(NewViol);
else
- InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
+ InferrableEffectToFirstViolation.maybeInsert(NewViol);
}
void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
@@ -385,8 +395,8 @@ class PendingFunctionAnalysis {
// Analysis is complete when there are no unverified direct calls.
bool isComplete() const { return UnverifiedDirectCalls.empty(); }
- const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
- return InferrableEffectToFirstDiagnostic.lookup(effect);
+ const Violation *violationForInferrableEffect(FunctionEffect effect) {
+ return InferrableEffectToFirstViolation.lookup(effect);
}
SmallVector<DirectCall, 0> &unverifiedCalls() {
@@ -394,17 +404,17 @@ class PendingFunctionAnalysis {
return UnverifiedDirectCalls;
}
- SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
- return DiagnosticsForExplicitFX;
+ SmallVector<Violation, 0> &getViolationsForExplicitFX() {
+ return ViolationsForExplicitFX;
}
void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
OS << "Pending: Declared ";
DeclaredVerifiableEffects.dump(OS);
- OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
+ OS << ", " << ViolationsForExplicitFX.size() << " violations; ";
OS << " Infer ";
FXToInfer.dump(OS);
- OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+ OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
if (!UnverifiedDirectCalls.empty()) {
OS << "; Calls: ";
for (const DirectCall &Call : UnverifiedDirectCalls) {
@@ -428,7 +438,7 @@ class CompleteFunctionAnalysis {
private:
// This is used to generate notes about failed inference.
- EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+ EffectToViolationMap InferrableEffectToFirstViolation;
public:
// The incoming Pending analysis is consumed (member(s) are moved-from).
@@ -438,22 +448,22 @@ class CompleteFunctionAnalysis {
const FunctionEffectKindSet &AllInferrableEffectsToVerify)
: VerifiedEffects(DeclaredEffects) {
for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
- if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
+ if (Pending.violationForInferrableEffect(effect) == nullptr)
VerifiedEffects.insert(effect);
- InferrableEffectToFirstDiagnostic =
- std::move(Pending.InferrableEffectToFirstDiagnostic);
+ InferrableEffectToFirstViolation =
+ std::move(Pending.InferrableEffectToFirstViolation);
}
- const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
- return InferrableEffectToFirstDiagnostic.lookup(Effect);
+ const Violation *firstViolationForEffect(const FunctionEffect &Effect) {
+ return InferrableEffectToFirstViolation.lookup(Effect);
}
void dump(llvm::raw_ostream &OS) const {
OS << "Complete: Verified ";
VerifiedEffects.dump(OS);
OS << "; Infer ";
- OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+ OS << InferrableEffectToFirstViolation.size() << " violations\n";
}
};
@@ -662,10 +672,9 @@ class Analyzer {
// inserted in the container.
void completeAnalysis(const CallableInfo &CInfo,
PendingFunctionAnalysis &Pending) {
- if (SmallVector<Diagnostic, 0> &Diags =
- Pending.getDiagnosticsForExplicitFX();
- !Diags.empty())
- emitDiagnostics(Diags, CInfo, Sem);
+ if (SmallVector<Violation, 0> &Viols = Pending.getViolationsForExplicitFX();
+ !Viols.empty())
+ emitDiagnostics(Viols, CInfo, Sem);
CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
Sem.getASTContext(), Pending, CInfo.Effects,
@@ -741,16 +750,16 @@ class Analyzer {
Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
if (Diagnose) {
// If inference is not allowed, or the target is indirect (virtual
- // method/function ptr?), generate a diagnostic now.
+ // method/function ptr?), generate a Violation now.
if (!IsInferencePossible ||
!(Flags & FunctionEffect::FE_InferrableOnCallees)) {
if (Callee.FuncType == SpecialFuncType::None)
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
- CallLoc, Callee.CDecl});
+ PFA.checkAddViolation(Inferring,
+ {Effect, ViolationID::CallsDeclWithoutEffect,
+ CallLoc, Callee.CDecl});
else
- PFA.checkAddDiagnostic(
- Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
+ PFA.checkAddViolation(
+ Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
} else {
// Inference is allowed and necessary; defer it.
PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
@@ -766,13 +775,13 @@ class Analyzer {
}
// Should only be called when determined to be complete.
- void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
+ void emitDiagnostics(SmallVector<Violation, 0> &Viols,
const CallableInfo &CInfo, Sema &S) {
- if (Diags.empty())
+ if (Viols.empty())
return;
const SourceManager &SM = S.getSourceManager();
- std::sort(Diags.begin(), Diags.end(),
- [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+ std::sort(Viols.begin(), Viols.end(),
+ [&SM](const Violation &LHS, const Violation &RHS) {
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
});
@@ -786,55 +795,56 @@ class Analyzer {
}
};
- // Top-level diagnostics are warnings.
- for (const Diagnostic &Diag : Diags) {
- StringRef effectName = Diag.Effect.name();
- switch (Diag.ID) {
- case DiagnosticID::None:
- case DiagnosticID::DeclDisallowsInference: // shouldn't happen
- // here
- llvm_unreachable("Unexpected diagnostic kind");
+ // Top-level violations are warnings.
+ for (const Violation &Viol1 : Viols) {
+ StringRef effectName = Viol1.Effect.name();
+ switch (Viol1.ID) {
+ case ViolationID::None:
+ case ViolationID::DeclDisallowsInference: // shouldn't happen
+ // here
+ llvm_unreachable("Unexpected violation kind");
break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
+ case ViolationID::AllocatesMemory:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+ case ViolationID::Throws:
+ case ViolationID::Catches:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
+ case ViolationID::HasStaticLocal:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
+ << effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+ case ViolationID::AccessesThreadLocal:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
+ case ViolationID::CallsObjC:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
+ case ViolationID::CallsExprWithoutEffect:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
<< effectName;
checkAddTemplateNote(CInfo.CDecl);
break;
- case DiagnosticID::CallsDeclWithoutEffect: {
- CallableInfo CalleeInfo(S, *Diag.Callee);
+ case ViolationID::CallsDeclWithoutEffect: {
+ CallableInfo CalleeInfo(S, *Viol1.Callee);
std::string CalleeName = CalleeInfo.name(S);
- S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
+ S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
<< effectName << CalleeName;
checkAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
// the callee safe?
- for (const Decl *Callee = Diag.Callee; Callee != nullptr;) {
+ for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) {
std::optional<CallableInfo> MaybeNextCallee;
CompleteFunctionAnalysis *Completed =
DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
@@ -850,61 +860,62 @@ class Analyzer {
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
- else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
+ else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_disallows_inference)
- << effectName;
+ << effectName
+ << FunctionEffect(Viol1.Effect.oppositeKind()).name();
else
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
<< effectName;
break;
}
- const Diagnostic *PtrDiag2 =
- Completed->firstDiagnosticForEffect(Diag.Effect);
- if (PtrDiag2 == nullptr)
+ const Violation *PtrViol2 =
+ Completed->firstViolationForEffect(Viol1.Effect);
+ if (PtrViol2 == nullptr)
break;
- const Diagnostic &Diag2 = *PtrDiag2;
- switch (Diag2.ID) {
- case DiagnosticID::None:
- llvm_unreachable("Unexpected diagnostic kind");
+ const Violation &Viol2 = *PtrViol2;
+ switch (Viol2.ID) {
+ case ViolationID::None:
+ llvm_unreachable("Unexpected violation kind");
break;
- case DiagnosticID::DeclDisallowsInference:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
- << effectName;
+ case ViolationID::DeclDisallowsInference:
+ S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
+ << effectName << Viol2.CalleeEffectPreventingInference.name();
break;
- case DiagnosticID::CallsExprWithoutEffect:
- S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+ case ViolationID::CallsExprWithoutEffect:
+ S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr)
<< effectName;
break;
- case DiagnosticID::AllocatesMemory:
- S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
+ case ViolationID::AllocatesMemory:
+ S.Diag(Viol2.Loc, diag::note_func_effect_allocates) << effectName;
break;
- case DiagnosticID::Throws:
- case DiagnosticID::Catches:
- S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+ case ViolationID::Throws:
+ case ViolationID::Catches:
+ S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches)
<< effectName;
break;
- case DiagnosticID::HasStaticLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+ case ViolationID::HasStaticLocal:
+ S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local)
<< effectName;
break;
- case DiagnosticID::AccessesThreadLocal:
- S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+ case ViolationID::AccessesThreadLocal:
+ S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local)
<< effectName;
break;
- case DiagnosticID::CallsObjC:
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
+ case ViolationID::CallsObjC:
+ S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
break;
- case DiagnosticID::CallsDeclWithoutEffect:
- MaybeNextCallee.emplace(S, *Diag2.Callee);
- S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
+ case ViolationID::CallsDeclWithoutEffect:
+ MaybeNextCallee.emplace(S, *Viol2.Callee);
+ S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
<< effectName << MaybeNextCallee->name(S);
break;
}
checkAddTemplateNote(Callee);
- Callee = Diag2.Callee;
+ Callee = Viol2.Callee;
if (MaybeNextCallee) {
CalleeInfo = *MaybeNextCallee;
CalleeName = CalleeInfo.name(S);
@@ -922,8 +933,7 @@ class Analyzer {
// [2] The function has not explicitly declared an effect in question, and is
// being checked for implicit conformance.
//
- // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
- // all diagnostic output.
+ // Violations are always routed to a PendingFunctionAnalysis.
struct FunctionBodyASTVisitor
: public RecursiveASTVisitor<FunctionBodyASTVisitor> {
@@ -951,32 +961,32 @@ class Analyzer {
// -- Methods implementing common logic --
// Handle a language construct forbidden by some effects. Only effects whose
- // flags include the specified flag receive a diagnostic. \p Flag describes
+ // flags include the specified flag receive a violation. \p Flag describes
// the construct.
- void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+ void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, ViolationID D,
SourceLocation Loc,
const Decl *Callee = nullptr) {
// If there are any declared verifiable effects which forbid the construct
- // represented by the flag, store just one diagnostic.
+ // represented by the flag, store just one violation..
for (const FunctionEffect &Effect :
CurrentFunction.DeclaredVerifiableEffects) {
if (Effect.flags() & Flag) {
- addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
+ addViolation(/*inferring=*/false, Effect, D, Loc, Callee);
break;
}
}
// For each inferred effect which forbids the construct, store a
- // diagnostic, if we don't already have a diagnostic for that effect.
+ // violation, if we don't already have a violation for that effect.
for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
if (Effect.flags() & Flag)
- addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
+ addViolation(/*inferring=*/true, Effect, D, Loc, Callee);
}
- void addDiagnostic(bool Inferring, const FunctionEffect &Effect,
- DiagnosticID D, SourceLocation Loc,
- const Decl *Callee = nullptr) {
- CurrentFunction.checkAddDiagnostic(Inferring,
- Diagnostic(Effect, D, Loc, Callee));
+ void addViolation(bool Inferring, const FunctionEffect &Effect,
+ ViolationID D, SourceLocation Loc,
+ const Decl *Callee = nullptr) {
+ CurrentFunction.checkAddViolation(Inferring,
+ Violation(Effect, D, Loc, Callee));
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -1003,8 +1013,8 @@ class Analyzer {
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
/*direct=*/false, CalleeFX))
- addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
- Call->getBeginLoc());
+ addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
+ Call->getBeginLoc());
};
for (const FunctionEffect &Effect :
@@ -1057,31 +1067,31 @@ class Analyzer {
bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- DiagnosticID::Throws, Throw->getThrowLoc());
+ ViolationID::Throws, Throw->getThrowLoc());
return true;
}
bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- DiagnosticID::Catches, Catch->getCatchLoc());
+ ViolationID::Catches, Catch->getCatchLoc());
return true;
}
bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- DiagnosticID::Throws, Throw->getThrowLoc());
+ ViolationID::Throws, Throw->getThrowLoc());
return true;
}
bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- DiagnosticID::Catches, Catch->getAtCatchLoc());
+ ViolationID::Catches, Catch->getAtCatchLoc());
return true;
}
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
- DiagnosticID::CallsObjC, Msg->getBeginLoc());
+ ViolationID::CallsObjC, Msg->getBeginLoc());
return true;
}
@@ -1116,7 +1126,7 @@ class Analyzer {
if (Var->isStaticLocal())
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
- DiagnosticID::HasStaticLocal,
+ ViolationID::HasStaticLocal,
Var->getLocation());
const QualType::DestructionKind DK =
@@ -1211,7 +1221,7 @@ class Analyzer {
// At least on macOS, thread-local variables are initialized on
// first access, including a heap allocation.
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
- DiagnosticID::AccessesThreadLocal,
+ ViolationID::AccessesThreadLocal,
E->getLocation());
}
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 7c7e322df80964..00fcc1714e22cf 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -138,7 +138,7 @@ void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{fun
template <bool V>
struct ComputedNB {
- void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+ void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}}
};
void nb11() [[clang::nonblocking]]
>From 06ca4c53d6de8575abd3b8c9981feb7d2e1cea0e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 07:26:41 -0700
Subject: [PATCH 10/44] Add tests around lambda traversal and contexts like
decltype, sizeof, etc.
---
.../Sema/attr-nonblocking-constraints.cpp | 43 ++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 00fcc1714e22cf..bad5309dbb543c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify %s
// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
// the FXAnalysis pass from running at all.
@@ -81,6 +81,29 @@ void nb8()
};
}
+void nb8a() [[clang::nonblocking]]
+{
+ // A blocking lambda shouldn't make the outer function unsafe.
+ auto unsafeLambda = []() {
+ throw 42;
+ };
+}
+
+void nb8b() [[clang::nonblocking]]
+{
+ // An unsafe lambda capture makes the outer function unsafe.
+ auto unsafeCapture = [foo = new int]() { // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+ delete foo;
+ };
+}
+
+void nb8c()
+{
+ // An unsafe lambda capture does not make the lambda unsafe.
+ auto unsafeCapture = [foo = new int]() [[clang::nonblocking]] {
+ };
+}
+
// Make sure template expansions are found and verified.
template <typename T>
struct Adder {
@@ -200,6 +223,24 @@ struct DerivedFromUnsafe : public Unsafe {
~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
};
+// Contexts where there is no function call, no diagnostic.
+bool bad();
+
+template <bool>
+requires requires { bad(); }
+void g() [[clang::nonblocking]] {}
+
+void g() [[clang::nonblocking]] {
+ decltype(bad()) a; // doesn't generate a call so, OK
+ [[maybe_unused]] auto b = noexcept(bad());
+ [[maybe_unused]] auto c = sizeof(bad());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wassume"
+ [[assume(bad())]]; // never evaluated, but maybe still semantically questionable?
+#pragma clang diagnostic pop
+}
+
+
// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
>From 9e45e6f59d6f6fc501181bc535741c415c1b8ead Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 08:06:30 -0700
Subject: [PATCH 11/44] Inline checks preceding maybeAddDeclWithEffects
---
clang/include/clang/Sema/Sema.h | 9 +++++++++
clang/lib/Sema/SemaDecl.cpp | 4 +---
clang/lib/Sema/SemaExpr.cpp | 4 +---
clang/lib/Sema/SemaLambda.cpp | 4 +---
4 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b166825bef4a5f..e27517f2ac541a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4337,6 +4337,15 @@ class Sema final : public SemaBase {
SourceLocation NewLoc,
SourceLocation OldLoc);
+ /// Inline checks from the start of maybeAddDeclWithEffects, to
+ /// minimize performance impact on code not using effects.
+ template <class FuncOrBlockDecl>
+ void maybeAddDeclWithEffects(FuncOrBlockDecl *D) {
+ if (Context.hasAnyFunctionEffects())
+ if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty())
+ maybeAddDeclWithEffects(D, FX);
+ }
+
/// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b21617a5db39ec..4d81594673678d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15679,9 +15679,7 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
- if (Context.hasAnyFunctionEffects())
- if (const auto FX = FD->getFunctionEffects(); !FX.empty())
- maybeAddDeclWithEffects(FD, FX);
+ maybeAddDeclWithEffects(FD);
return D;
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 126e924666356d..2412e84588b763 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16091,9 +16091,7 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
BlockDecl *BD = BSI->TheDecl;
- if (Context.hasAnyFunctionEffects())
- if (const auto FX = BD->getFunctionEffects(); !FX.empty())
- maybeAddDeclWithEffects(BD, FX);
+ maybeAddDeclWithEffects(BD);
if (BSI->HasImplicitReturnType)
deduceClosureReturnType(*BSI);
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index e3bf582db30279..bb797df28d09ad 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1948,9 +1948,7 @@ ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
ActOnFinishFunctionBody(LSI.CallOperator, Body);
- if (Context.hasAnyFunctionEffects())
- if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty())
- maybeAddDeclWithEffects(LSI.CallOperator, FX);
+ maybeAddDeclWithEffects(LSI.CallOperator);
return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
}
>From b99f7841ada916df8ed177006856ecfa38402067 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 08:41:49 -0700
Subject: [PATCH 12/44] Remove dead code in EffectAnalysis.cpp. Add comment
about AST traversal and lambdas.
add tests involving ms-extensions
---
clang/lib/Sema/EffectAnalysis.cpp | 125 ++----------------
.../Sema/attr-nonblocking-constraints-ms.cpp | 26 ++++
2 files changed, 37 insertions(+), 114 deletions(-)
create mode 100644 clang/test/Sema/attr-nonblocking-constraints-ms.cpp
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index a8bbb73faf9fd8..d9ec4197b9d428 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -12,6 +12,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/SemaInternal.h"
@@ -89,119 +90,6 @@ static bool isNoexcept(const FunctionDecl *FD) {
return false;
}
-#if 0
-/// A mutable set of FunctionEffect, for use in places where any conditions
-/// have been resolved or can be ignored.
-class EffectSet {
- // This implementation optimizes footprint, since we hold one of these for
- // every function visited, which, due to inference, can be many more functions
- // than have declared effects.
-
- template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
- SizeT Count = 0;
- T Items[Capacity] = {};
-
- using value_type = T;
-
- using iterator = T *;
- using const_iterator = const T *;
- iterator begin() { return &Items[0]; }
- iterator end() { return &Items[Count]; }
- const_iterator begin() const { return &Items[0]; }
- const_iterator end() const { return &Items[Count]; }
- const_iterator cbegin() const { return &Items[0]; }
- const_iterator cend() const { return &Items[Count]; }
-
- void insert(iterator I, const T &Value) {
- assert(Count < Capacity);
- iterator E = end();
- if (I != E)
- std::copy_backward(I, E, E + 1);
- *I = Value;
- ++Count;
- }
-
- void push_back(const T &Value) {
- assert(Count < Capacity);
- Items[Count++] = Value;
- }
- };
-
- // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
- // effects, this fixed-size vector with a capacity of 7 is more than
- // sufficient and is only 8 bytes.
- FixedVector<FunctionEffect, uint8_t, 7> Impl;
-
-public:
- EffectSet() = default;
- explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
-
- operator ArrayRef<FunctionEffect>() const {
- return ArrayRef(Impl.cbegin(), Impl.cend());
- }
-
- using iterator = const FunctionEffect *;
- iterator begin() const { return Impl.cbegin(); }
- iterator end() const { return Impl.cend(); }
-
- void insert(const FunctionEffect &Effect) {
- FunctionEffect *Iter = Impl.begin();
- FunctionEffect *End = Impl.end();
- // linear search; lower_bound is overkill for a tiny vector like this
- for (; Iter != End; ++Iter) {
- if (*Iter == Effect)
- return;
- if (Effect < *Iter)
- break;
- }
- Impl.insert(Iter, Effect);
- }
- void insert(const EffectSet &Set) {
- for (const FunctionEffect &Item : Set) {
- // push_back because set is already sorted
- Impl.push_back(Item);
- }
- }
- void insert(FunctionEffectsRef FX) {
- for (const FunctionEffectWithCondition &EC : FX) {
- assert(EC.Cond.getCondition() ==
- nullptr); // should be resolved by now, right?
- // push_back because set is already sorted
- Impl.push_back(EC.Effect);
- }
- }
- bool contains(const FunctionEffect::Kind EK) const {
- for (const FunctionEffect &E : Impl)
- if (E.kind() == EK)
- return true;
- return false;
- }
-
- void dump(llvm::raw_ostream &OS) const;
-
- static EffectSet difference(ArrayRef<FunctionEffect> LHS,
- ArrayRef<FunctionEffect> RHS) {
- EffectSet Result;
- std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
- std::back_inserter(Result.Impl));
- return Result;
- }
-};
-
-LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
- OS << "Effects{";
- bool First = true;
- for (const FunctionEffect &Effect : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << Effect.name();
- }
- OS << "}";
-}
-#endif
-
// Transitory, more extended information about a callable, which can be a
// function, block, function pointer, etc.
struct CallableInfo {
@@ -1095,6 +983,12 @@ class Analyzer {
return true;
}
+ bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
+ diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+ ViolationID::Catches, Exc->getExceptLoc());
+ return true;
+ }
+
bool VisitCallExpr(CallExpr *Call) {
LLVM_DEBUG(llvm::dbgs()
<< "VisitCallExpr : "
@@ -1199,7 +1093,10 @@ class Analyzer {
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.
+ // body. We have to explicitly traverse the captures. Why not return
+ // false from shouldVisitLambdaBody()? Because we need to visit a lambda's
+ // body when we are verifying the lambda itself; we only want to skip it
+ // in the context of the outer function.
for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
Lambda->capture_init_begin()[I]);
diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
new file mode 100644
index 00000000000000..dbdc39a304ba1b
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify %s
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// These need '-fms-extensions' (and maybe '-fdeclspec')
+void f1() [[clang::nonblocking]] {
+ __try {} __except (1) {} // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+}
+
+struct S {
+ int get_x(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ __declspec(property(get = get_x)) int x;
+
+ int get_nb() { return 42; }
+ __declspec(property(get = get_nb)) int nb;
+
+ int get_nb2() [[clang::nonblocking]];
+ __declspec(property(get = get_nb2)) int nb2;
+};
+
+void f2() [[clang::nonblocking]] {
+ S a;
+ a.x; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}}
+ a.nb;
+ a.nb2;
+}
>From 1b9874f8731ab5227b9698200015a38d9df77f78 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 09:10:40 -0700
Subject: [PATCH 13/44] patch what the bot's clang-format wishes clang-format
on my system would do
---
clang/lib/Sema/EffectAnalysis.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index d9ec4197b9d428..80dbb0f4adeb4f 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -628,7 +628,9 @@ class Analyzer {
llvm::dbgs()
<< " callee " << Callee.CDecl << " canonical "
<< CanonicalFunctionDecl(Callee.CDecl) << " redecls";
- for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
+ for (Decl *D
+ : Callee.CDecl->redecls()) llvm::dbgs()
+ << " " << D;
llvm::dbgs() << "\n";);
>From 8390e691ed71f4a35e4f9bd2e9eeb83ea0f64c31 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 8 Aug 2024 06:23:29 -0700
Subject: [PATCH 14/44] "function cannot be inferred" -> "declaration cannot be
inferred"
---
clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/test/Sema/attr-nonblocking-constraints-ms.cpp | 2 +-
clang/test/Sema/attr-nonblocking-constraints.cpp | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f1ffe4689daeff..ccec94c353d953 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10975,7 +10975,7 @@ def warn_func_effect_calls_expr_without_effect : Warning<
def note_func_effect_calls_func_without_effect : Note<
"function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
def note_func_effect_call_extern : Note<
- "function cannot be inferred '%0' because it has no definition in this translation unit">;
+ "declaration cannot be inferred '%0' because it has no definition in this translation unit">;
def note_func_effect_call_disallows_inference : Note<
"function does not permit inference of '%0' because it is declared '%1'">;
def note_func_effect_call_virtual : Note<
diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
index dbdc39a304ba1b..d2c25da462c404 100644
--- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -8,7 +8,7 @@ void f1() [[clang::nonblocking]] {
}
struct S {
- int get_x(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ int get_x(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
__declspec(property(get = get_x)) int x;
int get_nb() { return 42; }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index bad5309dbb543c..869e56374fe2c3 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -28,7 +28,7 @@ void nb3() [[clang::nonblocking]]
}
void nb4_inline() {}
-void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
void nb4() [[clang::nonblocking]]
{
@@ -47,7 +47,7 @@ void nb5() [[clang::nonblocking]]
hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
}
-void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
void nb6_transitively_unsafe()
{
nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
@@ -211,8 +211,8 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
// Verify traversal of implicit code paths - constructors and destructors.
struct Unsafe {
- static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
- static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
>From 7acda8c8891bdb19edd429863fcb8a1fc80ea2fc Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 8 Aug 2024 09:42:03 -0700
Subject: [PATCH 15/44] No need to override VisitCXXDefaultInitExpr. Minor
terseness improvements.
CallableInfo ctor doesn't need a Sema.
FunctionEffect::effectProhibitingInference can receive a FunctionEffectKindSet and eliminate the need for CallableInfo to cache a FunctionEffectsRef.
CallType -> CallableType; isDirectCall() -> isCalledDirectly().
---
clang/include/clang/AST/Type.h | 2 +-
clang/lib/AST/Type.cpp | 13 ++-
clang/lib/Sema/EffectAnalysis.cpp | 141 +++++++++++++++---------------
3 files changed, 76 insertions(+), 80 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 5d730ce98c1b13..cff10ffdc3a469 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4766,7 +4766,7 @@ class FunctionEffect {
/// function.
std::optional<FunctionEffect>
effectProhibitingInference(const Decl &Callee,
- const FunctionEffectsRef &CalleeFX) const;
+ const FunctionEffectKindSet &CalleeFX) const;
// Return false for success. When true is returned for a direct call, then the
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index cfc961647d04d5..4fd4032725f1a9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5138,18 +5138,17 @@ StringRef FunctionEffect::name() const {
}
std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
- const Decl &Callee, const FunctionEffectsRef &CalleeFX) const {
+ const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
- for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
+ for (const FunctionEffect &Effect : CalleeFX) {
// nonblocking/nonallocating cannot call allocating.
- if (CalleeEC.Effect.kind() == Kind::Allocating)
- return CalleeEC.Effect;
+ if (Effect.kind() == Kind::Allocating)
+ return Effect;
// nonblocking cannot call blocking.
- if (kind() == Kind::NonBlocking &&
- CalleeEC.Effect.kind() == Kind::Blocking)
- return CalleeEC.Effect;
+ if (kind() == Kind::NonBlocking && Effect.kind() == Kind::Blocking)
+ return Effect;
}
return std::nullopt;
}
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 80dbb0f4adeb4f..799500aa8caff7 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -64,7 +64,7 @@ struct Violation {
};
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallType {
+enum class CallableType {
// unknown: probably function pointer
Unknown,
Function,
@@ -91,32 +91,39 @@ static bool isNoexcept(const FunctionDecl *FD) {
}
// Transitory, more extended information about a callable, which can be a
-// function, block, function pointer, etc.
+// function, block, or function pointer.
struct CallableInfo {
// CDecl holds the function's definition, if any.
- // FunctionDecl if CallType::Function or Virtual
- // BlockDecl if CallType::Block
+ // FunctionDecl if CallableType::Function or Virtual
+ // BlockDecl if CallableType::Block
const Decl *CDecl;
+
+ // Remember whether the callable is a function, block, virtual method,
+ // or (presumed) function pointer.
+ CallableType CType = CallableType::Unknown;
+
+ // Remember whether the callable is an operator new or delete function,
+ // so that calls to them are reported more meaningfully, as memory
+ // allocations.
SpecialFuncType FuncType = SpecialFuncType::None;
- FunctionEffectsRef DeclEffects;
+
+ // We inevitably want to know the callable's declared effects, so cache them.
FunctionEffectKindSet Effects;
- CallType CType = CallType::Unknown;
- CallableInfo(Sema &SemaRef, const Decl &CD,
- SpecialFuncType FT = SpecialFuncType::None)
+ CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
: CDecl(&CD), FuncType(FT) {
-
+ FunctionEffectsRef DeclEffects;
if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
// Use the function's definition, if any.
if (const FunctionDecl *Def = FD->getDefinition())
CDecl = FD = Def;
- CType = CallType::Function;
+ CType = CallableType::Function;
if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->isVirtual())
- CType = CallType::Virtual;
+ CType = CallableType::Virtual;
DeclEffects = FD->getFunctionEffects();
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
- CType = CallType::Block;
+ CType = CallableType::Block;
DeclEffects = BD->getFunctionEffects();
} else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
// ValueDecl is function, enum, or variable, so just look at its type.
@@ -125,21 +132,21 @@ struct CallableInfo {
Effects = FunctionEffectKindSet(DeclEffects);
}
- bool isDirectCall() const {
- return CType == CallType::Function || CType == CallType::Block;
+ bool isCalledDirectly() const {
+ return CType == CallableType::Function || CType == CallableType::Block;
}
bool isVerifiable() const {
switch (CType) {
- case CallType::Unknown:
- case CallType::Virtual:
+ case CallableType::Unknown:
+ case CallableType::Virtual:
return false;
- case CallType::Block:
+ case CallableType::Block:
return true;
- case CallType::Function:
+ case CallableType::Function:
return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
}
- llvm_unreachable("undefined CallType");
+ llvm_unreachable("undefined CallableType");
}
/// Generate a name for logging and diagnostics.
@@ -250,7 +257,7 @@ class PendingFunctionAnalysis {
for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
std::optional<FunctionEffect> ProblemCalleeEffect =
- effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects);
+ effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
if (!ProblemCalleeEffect)
InferrableFX.insert(effect);
else {
@@ -306,7 +313,7 @@ class PendingFunctionAnalysis {
if (!UnverifiedDirectCalls.empty()) {
OS << "; Calls: ";
for (const DirectCall &Call : UnverifiedDirectCalls) {
- CallableInfo CI(SemaRef, *Call.Callee);
+ CallableInfo CI(*Call.Callee);
OS << " " << CI.name(SemaRef);
}
}
@@ -405,7 +412,7 @@ class Analyzer {
void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
OS << "\nAnalysisMap:\n";
for (const auto &item : *this) {
- CallableInfo CI(SemaRef, *item.first);
+ CallableInfo CI(*item.first);
const auto AP = item.second;
OS << item.first << " " << CI.name(SemaRef) << " : ";
if (AP.isNull())
@@ -499,7 +506,7 @@ class Analyzer {
// Verify a single Decl. Return the pending structure if that was the result,
// else null. This method must not recurse.
PendingFunctionAnalysis *verifyDecl(const Decl *D) {
- CallableInfo CInfo(Sem, *D);
+ CallableInfo CInfo(*D);
bool isExternC = false;
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
@@ -576,7 +583,7 @@ class Analyzer {
// not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
// the possibility of inference. Deletes Pending.
void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
- CallableInfo Caller(Sem, *D);
+ CallableInfo Caller(*D);
LLVM_DEBUG(llvm::dbgs()
<< "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
@@ -585,7 +592,7 @@ class Analyzer {
if (Call.Recursed)
continue;
- CallableInfo Callee(Sem, *Call.Callee);
+ CallableInfo Callee(*Call.Callee);
followCall(Caller, *Pending, Callee, Call.CallLoc,
/*AssertNoFurtherInference=*/true);
}
@@ -598,21 +605,20 @@ class Analyzer {
void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
const CallableInfo &Callee, SourceLocation CallLoc,
bool AssertNoFurtherInference) {
- const bool DirectCall = Callee.isDirectCall();
+ const bool DirectCall = Callee.isCalledDirectly();
// Initially, the declared effects; inferred effects will be added.
FunctionEffectKindSet CalleeEffects = Callee.Effects;
bool IsInferencePossible = DirectCall;
- if (DirectCall) {
+ if (DirectCall)
if (CompleteFunctionAnalysis *CFA =
DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
// Combine declared effects with those which may have been inferred.
CalleeEffects.insert(CFA->VerifiedEffects);
IsInferencePossible = false; // we've already traversed it
}
- }
if (AssertNoFurtherInference) {
assert(!IsInferencePossible);
@@ -635,25 +641,23 @@ class Analyzer {
llvm::dbgs() << "\n";);
auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
- FunctionEffect::Flags Flags = Effect.flags();
- bool Diagnose =
- Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
- if (Diagnose) {
- // If inference is not allowed, or the target is indirect (virtual
- // method/function ptr?), generate a Violation now.
- if (!IsInferencePossible ||
- !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
- if (Callee.FuncType == SpecialFuncType::None)
- PFA.checkAddViolation(Inferring,
- {Effect, ViolationID::CallsDeclWithoutEffect,
- CallLoc, Callee.CDecl});
- else
- PFA.checkAddViolation(
- Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
- } else {
- // Inference is allowed and necessary; defer it.
- PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
- }
+ if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
+ return;
+
+ // If inference is not allowed, or the target is indirect (virtual
+ // method/function ptr?), generate a Violation now.
+ if (!IsInferencePossible ||
+ !(Effect.flags() & FunctionEffect::FE_InferrableOnCallees)) {
+ if (Callee.FuncType == SpecialFuncType::None)
+ PFA.checkAddViolation(Inferring,
+ {Effect, ViolationID::CallsDeclWithoutEffect,
+ CallLoc, Callee.CDecl});
+ else
+ PFA.checkAddViolation(
+ Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
+ } else {
+ // Inference is allowed and necessary; defer it.
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
}
};
@@ -664,7 +668,8 @@ class Analyzer {
check1Effect(Effect, true);
}
- // Should only be called when determined to be complete.
+ // Should only be called when function's analysis is determined to be
+ // complete.
void emitDiagnostics(SmallVector<Violation, 0> &Viols,
const CallableInfo &CInfo, Sema &S) {
if (Viols.empty())
@@ -725,7 +730,7 @@ class Analyzer {
break;
case ViolationID::CallsDeclWithoutEffect: {
- CallableInfo CalleeInfo(S, *Viol1.Callee);
+ CallableInfo CalleeInfo(*Viol1.Callee);
std::string CalleeName = CalleeInfo.name(S);
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
@@ -743,10 +748,10 @@ class Analyzer {
// - non-inline
// - indirect (virtual or through function pointer)
// - effect has been explicitly disclaimed (e.g. "blocking")
- if (CalleeInfo.CType == CallType::Virtual)
+ if (CalleeInfo.CType == CallableType::Virtual)
S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
<< effectName;
- else if (CalleeInfo.CType == CallType::Unknown)
+ else if (CalleeInfo.CType == CallableType::Unknown)
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
@@ -799,7 +804,7 @@ class Analyzer {
S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
break;
case ViolationID::CallsDeclWithoutEffect:
- MaybeNextCallee.emplace(S, *Viol2.Callee);
+ MaybeNextCallee.emplace(*Viol2.Callee);
S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
<< effectName << MaybeNextCallee->name(S);
break;
@@ -853,15 +858,15 @@ class Analyzer {
// Handle a language construct forbidden by some effects. Only effects whose
// flags include the specified flag receive a violation. \p Flag describes
// the construct.
- void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, ViolationID D,
- SourceLocation Loc,
+ void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag,
+ ViolationID VID, SourceLocation Loc,
const Decl *Callee = nullptr) {
// If there are any declared verifiable effects which forbid the construct
// represented by the flag, store just one violation..
for (const FunctionEffect &Effect :
CurrentFunction.DeclaredVerifiableEffects) {
if (Effect.flags() & Flag) {
- addViolation(/*inferring=*/false, Effect, D, Loc, Callee);
+ addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
break;
}
}
@@ -869,7 +874,7 @@ class Analyzer {
// violation, if we don't already have a violation for that effect.
for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
if (Effect.flags() & Flag)
- addViolation(/*inferring=*/true, Effect, D, Loc, Callee);
+ addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
}
void addViolation(bool Inferring, const FunctionEffect &Effect,
@@ -892,8 +897,7 @@ class Analyzer {
/*AssertNoFurtherInference=*/false);
}
- void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
- const QualType CalleeType = CalleeExpr->getType();
+ void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
FunctionEffectKindSet CalleeFX;
@@ -942,7 +946,7 @@ class Analyzer {
if (Ty->isRecordType()) {
if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
+ CallableInfo CI(*Dtor);
followCall(CI, OuterDtor->getLocation());
}
}
@@ -999,7 +1003,7 @@ class Analyzer {
Expr *CalleeExpr = Call->getCallee();
if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
- CallableInfo CI(Outer.Sem, *Callee);
+ CallableInfo CI(*Callee);
followCall(CI, Call->getBeginLoc());
return true;
}
@@ -1009,7 +1013,7 @@ class Analyzer {
return true;
// No Decl, just an Expr. Just check based on its type.
- checkIndirectCall(Call, CalleeExpr);
+ checkIndirectCall(Call, CalleeExpr->getType());
return true;
}
@@ -1033,7 +1037,7 @@ class Analyzer {
if (const auto *CxxRec =
dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
- CallableInfo CI(Outer.Sem, *Dtor);
+ CallableInfo CI(*Dtor);
followCall(CI, Var->getLocation());
}
}
@@ -1046,7 +1050,7 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator new.
if (FunctionDecl *FD = New->getOperatorNew()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
+ CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
followCall(CI, New->getBeginLoc());
}
@@ -1062,7 +1066,7 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to operator delete.
if (FunctionDecl *FD = Delete->getOperatorDelete()) {
- CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
+ CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
followCall(CI, Delete->getBeginLoc());
}
@@ -1080,19 +1084,12 @@ class Analyzer {
// BUG? It seems incorrect that RecursiveASTVisitor does not
// visit the call to the constructor.
const CXXConstructorDecl *Ctor = Construct->getConstructor();
- CallableInfo CI(Outer.Sem, *Ctor);
+ CallableInfo CI(*Ctor);
followCall(CI, Construct->getLocation());
return true;
}
- bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
- if (Expr *E = DEI->getExpr())
- TraverseStmt(E);
-
- return true;
- }
-
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. Why not return
>From bffacc582a8c139feddfed26cc0b43dac488bc5c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 13 Aug 2024 13:32:45 -0700
Subject: [PATCH 16/44] Begin a list of unsafe builtin functions, starting with
malloc and friends.
---
clang/lib/Sema/EffectAnalysis.cpp | 48 +++++++++++++++----
.../Sema/attr-nonblocking-constraints.cpp | 7 +++
2 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 799500aa8caff7..b59d0786c57dc1 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -132,6 +132,8 @@ struct CallableInfo {
Effects = FunctionEffectKindSet(DeclEffects);
}
+ CallableType type() const { return CType; }
+
bool isCalledDirectly() const {
return CType == CallableType::Function || CType == CallableType::Block;
}
@@ -745,13 +747,15 @@ class Analyzer {
DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
if (Completed == nullptr) {
// No result - could be
- // - non-inline
+ // - non-inline and extern
// - indirect (virtual or through function pointer)
// - effect has been explicitly disclaimed (e.g. "blocking")
- if (CalleeInfo.CType == CallableType::Virtual)
+
+ CallableType CType = CalleeInfo.type();
+ if (CType == CallableType::Virtual)
S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
<< effectName;
- else if (CalleeInfo.CType == CallableType::Unknown)
+ else if (CType == CallableType::Unknown)
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_func_ptr)
<< effectName;
@@ -760,10 +764,13 @@ class Analyzer {
diag::note_func_effect_call_disallows_inference)
<< effectName
<< FunctionEffect(Viol1.Effect.oppositeKind()).name();
- else
+ else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
+ FD == nullptr || FD->getBuiltinID() == 0) {
+ // A builtin callee generally doesn't have a useful source
+ // location at which to insert a note.
S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
<< effectName;
-
+ }
break;
}
const Violation *PtrViol2 =
@@ -887,16 +894,41 @@ class Analyzer {
// Here we have a call to a Decl, either explicitly via a CallExpr or some
// other AST construct. CallableInfo pertains to the callee.
void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
- // Currently, built-in functions are always considered safe.
- // FIXME: Some are not.
if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
- FD && FD->getBuiltinID() != 0)
+ FD && isSafeBuiltinFunction(FD))
return;
Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
/*AssertNoFurtherInference=*/false);
}
+ // FIXME: This is currently specific to the `nonblocking` and
+ // `nonallocating` effects. More ideally, the builtin functions themselves
+ // would have the `allocating` attribute.
+ static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
+ unsigned BuiltinID = FD->getBuiltinID();
+ switch (BuiltinID) {
+ case 0: // not builtin
+ return false;
+ default: // not disallowed via cases below
+ return true;
+
+ // Disallow list
+ case Builtin::ID::BIaligned_alloc:
+ case Builtin::ID::BI__builtin_calloc:
+ case Builtin::ID::BI__builtin_malloc:
+ case Builtin::ID::BI__builtin_realloc:
+ case Builtin::ID::BI__builtin_free:
+ case Builtin::ID::BIcalloc:
+ case Builtin::ID::BImalloc:
+ case Builtin::ID::BImemalign:
+ case Builtin::ID::BIrealloc:
+ case Builtin::ID::BIfree:
+ return false;
+ }
+ llvm_unreachable("above switch is exhaustive");
+ }
+
void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 869e56374fe2c3..540267d2efae4b 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -209,6 +209,13 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
ref();
}
+// Builtin functions
+void nb18a() [[clang::nonblocking]] {
+ __builtin_assume(1);
+ void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
+ __builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+}
+
// Verify traversal of implicit code paths - constructors and destructors.
struct Unsafe {
static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
>From c718b5ab2de778ab4372615514f994ef43c85d34 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Wed, 14 Aug 2024 08:03:00 -0700
Subject: [PATCH 17/44] Apply suggestions from code review
Co-authored-by: Sirraide <aeternalmail at gmail.com>
---
clang/lib/Sema/EffectAnalysis.cpp | 61 ++++++++++++-------------------
1 file changed, 24 insertions(+), 37 deletions(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index b59d0786c57dc1..ef5a8de628abcc 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -64,7 +64,7 @@ struct Violation {
};
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallableType {
+enum class CallableType : uint8_t {
// unknown: probably function pointer
Unknown,
Function,
@@ -296,12 +296,12 @@ class PendingFunctionAnalysis {
return InferrableEffectToFirstViolation.lookup(effect);
}
- SmallVector<DirectCall, 0> &unverifiedCalls() {
+ ArrayRef<DirectCall> unverifiedCalls() {
assert(!isComplete());
return UnverifiedDirectCalls;
}
- SmallVector<Violation, 0> &getViolationsForExplicitFX() {
+ ArrayRef<Violation> getViolationsForExplicitFX() {
return ViolationsForExplicitFX;
}
@@ -386,7 +386,7 @@ class Analyzer {
// Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
// than complete state, so use different objects to represent them.
// The state pointers are owned by the container.
- class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+ class AnalysisMap : llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
public:
@@ -460,19 +460,11 @@ class Analyzer {
while (!VerificationQueue.empty()) {
const Decl *D = VerificationQueue.back();
if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
- if (isa<CompleteFunctionAnalysis *>(AP)) {
- // already done
- VerificationQueue.pop_back();
- continue;
- }
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // All children have been traversed; finish analysis.
- auto *Pending = AP.get<PendingFunctionAnalysis *>();
+ // All children have been traversed; finish analysis.
+ if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>())
finishPendingAnalysis(D, Pending);
- VerificationQueue.pop_back();
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
+ VerificationQueue.pop_back();
+ continue;
}
// Not previously visited; begin a new analysis for this Decl.
@@ -492,14 +484,12 @@ class Analyzer {
VerificationQueue.push_back(Call.Callee);
continue;
}
- if (isa<PendingFunctionAnalysis *>(AP)) {
- // This indicates recursion (not necessarily direct). For the
- // purposes of effect analysis, we can just ignore it since
- // no effects forbid recursion.
- Call.Recursed = true;
- continue;
- }
- llvm_unreachable("unexpected DeclAnalysis item");
+
+ // This indicates recursion (not necessarily direct). For the
+ // purposes of effect analysis, we can just ignore it since
+ // no effects forbid recursion.
+ assert(isa<PendingFunctionAnalysis *>(AP));
+ Call.Recursed = true;
}
}
}
@@ -569,7 +559,7 @@ class Analyzer {
// inserted in the container.
void completeAnalysis(const CallableInfo &CInfo,
PendingFunctionAnalysis &Pending) {
- if (SmallVector<Violation, 0> &Viols = Pending.getViolationsForExplicitFX();
+ if (ArrayRef<Violation> Viols = Pending.getViolationsForExplicitFX();
!Viols.empty())
emitDiagnostics(Viols, CInfo, Sem);
@@ -642,7 +632,7 @@ class Analyzer {
llvm::dbgs() << "\n";);
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
return;
@@ -672,7 +662,7 @@ class Analyzer {
// Should only be called when function's analysis is determined to be
// complete.
- void emitDiagnostics(SmallVector<Violation, 0> &Viols,
+ void emitDiagnostics(ArrayRef<Violation> Viols,
const CallableInfo &CInfo, Sema &S) {
if (Viols.empty())
return;
@@ -682,7 +672,7 @@ class Analyzer {
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
});
- 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(),
@@ -837,13 +827,13 @@ class Analyzer {
//
// Violations are always routed to a PendingFunctionAnalysis.
struct FunctionBodyASTVisitor
- : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ : RecursiveASTVisitor<FunctionBodyASTVisitor> {
Analyzer &Outer;
PendingFunctionAnalysis &CurrentFunction;
CallableInfo &CurrentCaller;
- FunctionBodyASTVisitor(Analyzer &outer,
+ FunctionBodyASTVisitor(Analyzer &Outer,
PendingFunctionAnalysis &CurrentFunction,
CallableInfo &CurrentCaller)
: Outer(outer), CurrentFunction(CurrentFunction),
@@ -869,7 +859,7 @@ class Analyzer {
ViolationID VID, SourceLocation Loc,
const Decl *Callee = nullptr) {
// If there are any declared verifiable effects which forbid the construct
- // represented by the flag, store just one violation..
+ // represented by the flag, store just one violation.
for (const FunctionEffect &Effect :
CurrentFunction.DeclaredVerifiableEffects) {
if (Effect.flags() & Flag) {
@@ -926,7 +916,6 @@ class Analyzer {
case Builtin::ID::BIfree:
return false;
}
- llvm_unreachable("above switch is exhaustive");
}
void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
@@ -936,7 +925,7 @@ class Analyzer {
if (FPT)
CalleeFX.insert(FPT->getFunctionEffects());
- auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
/*direct=*/false, CalleeFX))
addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
@@ -1142,10 +1131,8 @@ class Analyzer {
bool VisitDeclRefExpr(const DeclRefExpr *E) {
const ValueDecl *Val = E->getDecl();
- if (isa<VarDecl>(Val)) {
- const VarDecl *Var = cast<VarDecl>(Val);
- VarDecl::TLSKind TLSK = Var->getTLSKind();
- if (TLSK != VarDecl::TLS_None) {
+ if (const auto *Var = dyn_cast<VarDecl>(Val)) {
+ if (Var->getTLSKind() != VarDecl::TLS_None) {
// At least on macOS, thread-local variables are initialized on
// first access, including a heap allocation.
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
>From 8b225f43a7a7ac3fbdcdce7b0f078d4725310928 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 08:05:04 -0700
Subject: [PATCH 18/44] - FunctionEffect and FunctionEffectKindSet are tiny,
pass by value wherever possible and obtain values from iterators. -
Diagnostics: "local" -> "local variable" - No change needed to
AnalysisBasedWarnings - Fixes following review suggestions.
---
clang/include/clang/AST/Type.h | 25 ++---
.../clang/Basic/DiagnosticSemaKinds.td | 4 +-
clang/lib/AST/Type.cpp | 8 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 -
clang/lib/Sema/EffectAnalysis.cpp | 104 +++++++++---------
.../Sema/attr-nonblocking-constraints.cpp | 4 +-
6 files changed, 69 insertions(+), 78 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index cff10ffdc3a469..e7a0d79447dc4e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4766,22 +4766,22 @@ class FunctionEffect {
/// function.
std::optional<FunctionEffect>
effectProhibitingInference(const Decl &Callee,
- const FunctionEffectKindSet &CalleeFX) const;
+ FunctionEffectKindSet CalleeFX) const;
// Return false for success. When true is returned for a direct call, then the
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
// diagnostic. Caller should be assumed to have the effect (it may not have it
// explicitly when inferring).
bool shouldDiagnoseFunctionCall(bool Direct,
- const FunctionEffectKindSet &CalleeFX) const;
+ FunctionEffectKindSet CalleeFX) const;
- friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ friend bool operator==(FunctionEffect LHS, FunctionEffect RHS) {
return LHS.FKind == RHS.FKind;
}
- friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ friend bool operator!=(FunctionEffect LHS, FunctionEffect RHS) {
return !(LHS == RHS);
}
- friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+ friend bool operator<(FunctionEffect LHS, FunctionEffect RHS) {
return LHS.FKind < RHS.FKind;
}
};
@@ -4810,8 +4810,7 @@ struct FunctionEffectWithCondition {
EffectConditionExpr Cond;
FunctionEffectWithCondition() = default;
- FunctionEffectWithCondition(const FunctionEffect &E,
- const EffectConditionExpr &C)
+ FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C)
: Effect(E), Cond(C) {}
/// Return a textual description of the effect, and its condition, if any.
@@ -4972,22 +4971,20 @@ class FunctionEffectKindSet {
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, EndBitPos); }
- void insert(const FunctionEffect &Effect) {
- KindBits |= kindToBit(Effect.kind());
- }
+ void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); }
void insert(FunctionEffectsRef FX) {
- for (const FunctionEffect &Item : FX.effects())
+ for (FunctionEffect Item : FX.effects())
insert(Item);
}
- void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; }
+ void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
bool contains(const FunctionEffect::Kind EK) const {
return (KindBits & kindToBit(EK)) != 0;
}
void dump(llvm::raw_ostream &OS) const;
- static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS,
- const FunctionEffectKindSet &RHS) {
+ static FunctionEffectKindSet difference(FunctionEffectKindSet LHS,
+ FunctionEffectKindSet RHS) {
return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits);
}
};
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ccec94c353d953..7e25afb98d95ff 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10952,10 +10952,10 @@ def warn_func_effect_throws_or_catches : Warning<
def note_func_effect_throws_or_catches : Note<
"function cannot be inferred '%0' because it throws or catches exceptions">;
def warn_func_effect_has_static_local : Warning<
- "'%0' function must not have static locals">,
+ "'%0' function must not have static local variables">,
InGroup<FunctionEffects>;
def note_func_effect_has_static_local : Note<
- "function cannot be inferred '%0' because it has a static local">;
+ "function cannot be inferred '%0' because it has a static local variable">;
def warn_func_effect_uses_thread_local : Warning<
"'%0' function must not use thread-local variables">,
InGroup<FunctionEffects>;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 4fd4032725f1a9..8d36aa15c59572 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5138,11 +5138,11 @@ StringRef FunctionEffect::name() const {
}
std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
- const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const {
+ const Decl &Callee, FunctionEffectKindSet CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
- for (const FunctionEffect &Effect : CalleeFX) {
+ for (FunctionEffect Effect : CalleeFX) {
// nonblocking/nonallocating cannot call allocating.
if (Effect.kind() == Kind::Allocating)
return Effect;
@@ -5166,12 +5166,12 @@ std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
}
bool FunctionEffect::shouldDiagnoseFunctionCall(
- bool Direct, const FunctionEffectKindSet &CalleeFX) const {
+ bool Direct, FunctionEffectKindSet CalleeFX) const {
switch (kind()) {
case Kind::NonAllocating:
case Kind::NonBlocking: {
const Kind CallerKind = kind();
- for (const FunctionEffect &Effect : CalleeFX) {
+ for (FunctionEffect Effect : CalleeFX) {
const Kind EK = Effect.kind();
// Does callee have same or stronger constraint?
if (EK == CallerKind ||
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 8268117a0addab..0f604c61fa3af9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2397,8 +2397,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
};
} // namespace
-// =============================================================================
-
//===----------------------------------------------------------------------===//
// AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
// warnings on a function, method, or block.
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index ef5a8de628abcc..a074aece910c33 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -54,7 +54,7 @@ struct Violation {
Violation() = default;
- Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc,
+ Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
const Decl *Callee = nullptr,
const FunctionEffect *CalleeEffect = nullptr)
: Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
@@ -204,7 +204,7 @@ class EffectToViolationMap {
size_t size() const { return Impl ? Impl->size() : 0; }
private:
- ImplVec::iterator _find(const FunctionEffect &key) {
+ ImplVec::iterator _find(FunctionEffect key) {
// A linear search suffices for a tiny number of possible effects.
auto *End = Impl->end();
for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
@@ -250,14 +250,13 @@ class PendingFunctionAnalysis {
SmallVector<DirectCall, 0> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(
- Sema &Sem, const CallableInfo &CInfo,
- const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
+ FunctionEffectKindSet AllInferrableEffectsToVerify)
: DeclaredVerifiableEffects(CInfo.Effects) {
// Check for effects we are not allowed to infer
FunctionEffectKindSet InferrableFX;
- for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+ for (FunctionEffect effect : AllInferrableEffectsToVerify) {
std::optional<FunctionEffect> ProblemCalleeEffect =
effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
if (!ProblemCalleeEffect)
@@ -296,12 +295,18 @@ class PendingFunctionAnalysis {
return InferrableEffectToFirstViolation.lookup(effect);
}
- ArrayRef<DirectCall> unverifiedCalls() {
+ // Mutable because caller may need to set a DirectCall's Recursing flag.
+ MutableArrayRef<DirectCall> unverifiedCalls() {
assert(!isComplete());
return UnverifiedDirectCalls;
}
- ArrayRef<Violation> getViolationsForExplicitFX() {
+ ArrayRef<Violation> getSortedViolationsForExplicitFX(SourceManager &SM) {
+ if (!ViolationsForExplicitFX.empty())
+ std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(),
+ [&SM](const Violation &LHS, const Violation &RHS) {
+ return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+ });
return ViolationsForExplicitFX;
}
@@ -339,12 +344,11 @@ class CompleteFunctionAnalysis {
public:
// The incoming Pending analysis is consumed (member(s) are moved-from).
- CompleteFunctionAnalysis(
- ASTContext &Ctx, PendingFunctionAnalysis &Pending,
- const FunctionEffectKindSet &DeclaredEffects,
- const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+ CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+ FunctionEffectKindSet DeclaredEffects,
+ FunctionEffectKindSet AllInferrableEffectsToVerify)
: VerifiedEffects(DeclaredEffects) {
- for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+ for (FunctionEffect effect : AllInferrableEffectsToVerify)
if (Pending.violationForInferrableEffect(effect) == nullptr)
VerifiedEffects.insert(effect);
@@ -352,7 +356,7 @@ class CompleteFunctionAnalysis {
std::move(Pending.InferrableEffectToFirstViolation);
}
- const Violation *firstViolationForEffect(const FunctionEffect &Effect) {
+ const Violation *firstViolationForEffect(FunctionEffect Effect) {
return InferrableEffectToFirstViolation.lookup(Effect);
}
@@ -441,7 +445,7 @@ class Analyzer {
void run(const TranslationUnitDecl &TU) {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
- for (const FunctionEffect &Effect : Sem.AllEffectsToVerify) {
+ for (FunctionEffect Effect : Sem.AllEffectsToVerify) {
const FunctionEffect::Flags Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees)
AllInferrableEffectsToVerify.insert(Effect);
@@ -487,7 +491,7 @@ class Analyzer {
// This indicates recursion (not necessarily direct). For the
// purposes of effect analysis, we can just ignore it since
- // no effects forbid recursion.
+ // no effects forbid recursion.
assert(isa<PendingFunctionAnalysis *>(AP));
Call.Recursed = true;
}
@@ -510,7 +514,7 @@ class Analyzer {
// effects forbid throwing (e.g. nonblocking) then the function should also
// be declared noexcept.
if (Sem.getLangOpts().CPlusPlus && !isExternC) {
- for (const FunctionEffect &Effect : CInfo.Effects) {
+ for (FunctionEffect Effect : CInfo.Effects) {
if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
continue;
@@ -559,7 +563,8 @@ class Analyzer {
// inserted in the container.
void completeAnalysis(const CallableInfo &CInfo,
PendingFunctionAnalysis &Pending) {
- if (ArrayRef<Violation> Viols = Pending.getViolationsForExplicitFX();
+ if (ArrayRef<Violation> Viols =
+ Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager());
!Viols.empty())
emitDiagnostics(Viols, CInfo, Sem);
@@ -632,7 +637,7 @@ class Analyzer {
llvm::dbgs() << "\n";);
- auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
return;
@@ -653,26 +658,21 @@ class Analyzer {
}
};
- for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
- check1Effect(Effect, false);
+ for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
+ Check1Effect(Effect, false);
- for (const FunctionEffect &Effect : PFA.FXToInfer)
- check1Effect(Effect, true);
+ for (FunctionEffect Effect : PFA.FXToInfer)
+ Check1Effect(Effect, true);
}
// Should only be called when function's analysis is determined to be
// complete.
- void emitDiagnostics(ArrayRef<Violation> Viols,
- const CallableInfo &CInfo, Sema &S) {
+ void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo,
+ Sema &S) {
if (Viols.empty())
return;
- const SourceManager &SM = S.getSourceManager();
- std::sort(Viols.begin(), Viols.end(),
- [&SM](const Violation &LHS, const Violation &RHS) {
- return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
- });
- auto CheckAddTemplateNote = [&](const Decl *D) {
+ auto MaybeAddTemplateNote = [&](const Decl *D) {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
while (FD != nullptr && FD->isTemplateInstantiation()) {
S.Diag(FD->getPointOfInstantiation(),
@@ -693,32 +693,32 @@ class Analyzer {
break;
case ViolationID::AllocatesMemory:
S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::Throws:
case ViolationID::Catches:
S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
<< effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::HasStaticLocal:
S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
<< effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::AccessesThreadLocal:
S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
<< effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsObjC:
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsExprWithoutEffect:
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
<< effectName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsDeclWithoutEffect: {
@@ -727,7 +727,7 @@ class Analyzer {
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
<< effectName << CalleeName;
- checkAddTemplateNote(CInfo.CDecl);
+ MaybeAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
// the callee safe?
@@ -806,7 +806,7 @@ class Analyzer {
<< effectName << MaybeNextCallee->name(S);
break;
}
- checkAddTemplateNote(Callee);
+ MaybeAddTemplateNote(Callee);
Callee = Viol2.Callee;
if (MaybeNextCallee) {
CalleeInfo = *MaybeNextCallee;
@@ -826,8 +826,7 @@ class Analyzer {
// being checked for implicit conformance.
//
// Violations are always routed to a PendingFunctionAnalysis.
- struct FunctionBodyASTVisitor
- : RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ struct FunctionBodyASTVisitor : RecursiveASTVisitor<FunctionBodyASTVisitor> {
Analyzer &Outer;
PendingFunctionAnalysis &CurrentFunction;
@@ -836,7 +835,7 @@ class Analyzer {
FunctionBodyASTVisitor(Analyzer &Outer,
PendingFunctionAnalysis &CurrentFunction,
CallableInfo &CurrentCaller)
- : Outer(outer), CurrentFunction(CurrentFunction),
+ : Outer(Outer), CurrentFunction(CurrentFunction),
CurrentCaller(CurrentCaller) {}
// -- Entry point --
@@ -860,8 +859,7 @@ class Analyzer {
const Decl *Callee = nullptr) {
// If there are any declared verifiable effects which forbid the construct
// represented by the flag, store just one violation.
- for (const FunctionEffect &Effect :
- CurrentFunction.DeclaredVerifiableEffects) {
+ for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) {
if (Effect.flags() & Flag) {
addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
break;
@@ -869,14 +867,13 @@ class Analyzer {
}
// For each inferred effect which forbids the construct, store a
// violation, if we don't already have a violation for that effect.
- for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+ for (FunctionEffect Effect : CurrentFunction.FXToInfer)
if (Effect.flags() & Flag)
addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
}
- void addViolation(bool Inferring, const FunctionEffect &Effect,
- ViolationID D, SourceLocation Loc,
- const Decl *Callee = nullptr) {
+ void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D,
+ SourceLocation Loc, const Decl *Callee = nullptr) {
CurrentFunction.checkAddViolation(Inferring,
Violation(Effect, D, Loc, Callee));
}
@@ -925,19 +922,18 @@ class Analyzer {
if (FPT)
CalleeFX.insert(FPT->getFunctionEffects());
- auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+ auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
/*direct=*/false, CalleeFX))
addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
Call->getBeginLoc());
};
- for (const FunctionEffect &Effect :
- CurrentFunction.DeclaredVerifiableEffects)
- check1Effect(Effect, false);
+ for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
+ Check1Effect(Effect, false);
- for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
- check1Effect(Effect, true);
+ for (FunctionEffect Effect : CurrentFunction.FXToInfer)
+ Check1Effect(Effect, true);
}
// This destructor's body should be followed by the caller, but here we
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 540267d2efae4b..02247cb71975cf 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -15,7 +15,7 @@ void nb1() [[clang::nonblocking]]
void nb2() [[clang::nonblocking]]
{
- static int global; // expected-warning {{'nonblocking' function must not have static locals}}
+ static int global; // expected-warning {{'nonblocking' function must not have static local variables}}
}
void nb3() [[clang::nonblocking]]
@@ -178,7 +178,7 @@ void nb11() [[clang::nonblocking]]
// Verify that when attached to a redeclaration, the attribute successfully attaches.
void nb12() {
- static int x; // expected-warning {{'nonblocking' function must not have static locals}}
+ static int x; // expected-warning {{'nonblocking' function must not have static local variables}}
}
void nb12() [[clang::nonblocking]];
void nb13() [[clang::nonblocking]] { nb12(); }
>From 2cb4539d5cf5abf7091dd9d6f735b039f86d3a9c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 08:59:53 -0700
Subject: [PATCH 19/44] - Comments begin with capital letters and end with full
stops. - Simplify EffectToViolationMap. - FX -> Effects. - getCanonicalDecl()
is a method of Decl, no need to wrap it for functions. - Sem -> S. - Clean up
comments around RecursiveASTVisitor and implicit calls.
---
clang/lib/Sema/EffectAnalysis.cpp | 196 +++++++++++++-----------------
1 file changed, 85 insertions(+), 111 deletions(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index a074aece910c33..dbc429f22c2367 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -17,14 +17,14 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/SemaInternal.h"
-#define DEBUG_TYPE "fxanalysis"
+#define DEBUG_TYPE "effectanalysis"
using namespace clang;
namespace {
enum class ViolationID : uint8_t {
- None = 0, // sentinel for an empty Violation
+ None = 0, // Sentinel for an empty Violation.
Throws,
Catches,
CallsObjC,
@@ -32,7 +32,7 @@ enum class ViolationID : uint8_t {
HasStaticLocal,
AccessesThreadLocal,
- // These only apply to callees, where the analysis stops at the Decl
+ // These only apply to callees, where the analysis stops at the Decl.
DeclDisallowsInference,
CallsDeclWithoutEffect,
@@ -47,10 +47,10 @@ enum class ViolationID : uint8_t {
// be inferred as holding that effect.
struct Violation {
FunctionEffect Effect;
- FunctionEffect CalleeEffectPreventingInference; // only for certain IDs
+ FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs.
ViolationID ID = ViolationID::None;
SourceLocation Loc;
- const Decl *Callee = nullptr; // only valid for Calls*
+ const Decl *Callee = nullptr; // Only valid for Calls*.
Violation() = default;
@@ -65,7 +65,7 @@ struct Violation {
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
enum class CallableType : uint8_t {
- // unknown: probably function pointer
+ // Unknown: probably function pointer
Unknown,
Function,
Virtual,
@@ -152,12 +152,12 @@ struct CallableInfo {
}
/// Generate a name for logging and diagnostics.
- std::string name(Sema &Sem) const {
+ std::string name(Sema &S) const {
std::string Name;
llvm::raw_string_ostream OS(Name);
if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
- FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ FD->getNameForDiagnostic(OS, S.getPrintingPolicy(),
/*Qualified=*/true);
else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
OS << "(block " << BD->getBlockManglingNumber() << ")";
@@ -172,7 +172,7 @@ struct CallableInfo {
// violations pertaining to an effect, per function.
class EffectToViolationMap {
// Since we currently only have a tiny number of effects (typically no more
- // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+ // than 1), use a SmallVector with an inline capacity of 1. Since it
// is often empty, use a unique_ptr to the SmallVector.
// Note that Violation itself contains a FunctionEffect which is the key.
using ImplVec = llvm::SmallVector<Violation, 1>;
@@ -183,35 +183,23 @@ class EffectToViolationMap {
void maybeInsert(const Violation &Viol) {
if (Impl == nullptr)
Impl = std::make_unique<ImplVec>();
- auto *Iter = _find(Viol.Effect);
- if (Iter != Impl->end() && Iter->Effect == Viol.Effect)
+ else if (lookup(Viol.Effect) != nullptr)
return;
- Impl->insert(Iter, Viol);
+ Impl->push_back(Viol);
}
const Violation *lookup(FunctionEffect Key) {
if (Impl == nullptr)
return nullptr;
- auto *Iter = _find(Key);
- if (Iter != Impl->end() && Iter->Effect == Key)
- return &*Iter;
-
- return nullptr;
+ auto *Iter =
+ std::find_if(Impl->begin(), Impl->end(),
+ [&](const auto &Item) { return Item.Effect == Key; });
+ return Iter != Impl->end() ? &*Iter : nullptr;
}
size_t size() const { return Impl ? Impl->size() : 0; }
-
-private:
- ImplVec::iterator _find(FunctionEffect key) {
- // A linear search suffices for a tiny number of possible effects.
- auto *End = Impl->end();
- for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
- if (!(Iter->Effect < key))
- return Iter;
- return End;
- }
};
// ----------
@@ -236,11 +224,11 @@ class PendingFunctionAnalysis {
// 1. Effects declared explicitly by this function.
// 2. All other inferrable effects needing verification.
FunctionEffectKindSet DeclaredVerifiableEffects;
- FunctionEffectKindSet FXToInfer;
+ FunctionEffectKindSet EffectsToInfer;
private:
// Violations pertaining to the function's explicit effects.
- SmallVector<Violation, 0> ViolationsForExplicitFX;
+ SmallVector<Violation, 0> ViolationsForExplicitEffects;
// Violations pertaining to other, non-explicit, inferrable effects.
EffectToViolationMap InferrableEffectToFirstViolation;
@@ -250,17 +238,17 @@ class PendingFunctionAnalysis {
SmallVector<DirectCall, 0> UnverifiedDirectCalls;
public:
- PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
+ PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
FunctionEffectKindSet AllInferrableEffectsToVerify)
: DeclaredVerifiableEffects(CInfo.Effects) {
// Check for effects we are not allowed to infer
- FunctionEffectKindSet InferrableFX;
+ FunctionEffectKindSet InferrableEffects;
for (FunctionEffect effect : AllInferrableEffectsToVerify) {
std::optional<FunctionEffect> ProblemCalleeEffect =
effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
if (!ProblemCalleeEffect)
- InferrableFX.insert(effect);
+ InferrableEffects.insert(effect);
else {
// Add a Violation for this effect if a caller were to
// try to infer it.
@@ -269,17 +257,17 @@ class PendingFunctionAnalysis {
CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
}
}
- // InferrableFX is now the set of inferrable effects which are not
+ // InferrableEffects is now the set of inferrable effects which are not
// prohibited
- FXToInfer = FunctionEffectKindSet::difference(InferrableFX,
- DeclaredVerifiableEffects);
+ EffectsToInfer = FunctionEffectKindSet::difference(
+ InferrableEffects, DeclaredVerifiableEffects);
}
// Hide the way that Violations for explicitly required effects vs. inferred
// ones are handled differently.
void checkAddViolation(bool Inferring, const Violation &NewViol) {
if (!Inferring)
- ViolationsForExplicitFX.push_back(NewViol);
+ ViolationsForExplicitEffects.push_back(NewViol);
else
InferrableEffectToFirstViolation.maybeInsert(NewViol);
}
@@ -301,21 +289,22 @@ class PendingFunctionAnalysis {
return UnverifiedDirectCalls;
}
- ArrayRef<Violation> getSortedViolationsForExplicitFX(SourceManager &SM) {
- if (!ViolationsForExplicitFX.empty())
- std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(),
+ ArrayRef<Violation> getSortedViolationsForExplicitEffects(SourceManager &SM) {
+ if (!ViolationsForExplicitEffects.empty())
+ std::sort(ViolationsForExplicitEffects.begin(),
+ ViolationsForExplicitEffects.end(),
[&SM](const Violation &LHS, const Violation &RHS) {
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
});
- return ViolationsForExplicitFX;
+ return ViolationsForExplicitEffects;
}
void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
OS << "Pending: Declared ";
DeclaredVerifiableEffects.dump(OS);
- OS << ", " << ViolationsForExplicitFX.size() << " violations; ";
+ OS << ", " << ViolationsForExplicitEffects.size() << " violations; ";
OS << " Infer ";
- FXToInfer.dump(OS);
+ EffectsToInfer.dump(OS);
OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
if (!UnverifiedDirectCalls.empty()) {
OS << "; Calls: ";
@@ -368,18 +357,9 @@ class CompleteFunctionAnalysis {
}
};
-const Decl *CanonicalFunctionDecl(const Decl *D) {
- if (auto *FD = dyn_cast<FunctionDecl>(D)) {
- FD = FD->getCanonicalDecl();
- assert(FD != nullptr);
- return FD;
- }
- return D;
-}
-
// ==========
class Analyzer {
- Sema &Sem;
+ Sema &S;
// Subset of Sema.AllEffectsToVerify
FunctionEffectKindSet AllInferrableEffectsToVerify;
@@ -400,11 +380,11 @@ class Analyzer {
// that lookups and insertions are via the canonical Decls.
FuncAnalysisPtr lookup(const Decl *Key) const {
- return Base::lookup(CanonicalFunctionDecl(Key));
+ return Base::lookup(Key->getCanonicalDecl());
}
FuncAnalysisPtr &operator[](const Decl *Key) {
- return Base::operator[](CanonicalFunctionDecl(Key));
+ return Base::operator[](Key->getCanonicalDecl());
}
/// Shortcut for the case where we only care about completed analysis.
@@ -440,12 +420,12 @@ class Analyzer {
AnalysisMap DeclAnalysis;
public:
- Analyzer(Sema &S) : Sem(S) {}
+ Analyzer(Sema &S) : S(S) {}
void run(const TranslationUnitDecl &TU) {
// Gather all of the effects to be verified to see what operations need to
// be checked, and to see which ones are inferrable.
- for (FunctionEffect Effect : Sem.AllEffectsToVerify) {
+ for (FunctionEffect Effect : S.AllEffectsToVerify) {
const FunctionEffect::Flags Flags = Effect.flags();
if (Flags & FunctionEffect::FE_InferrableOnCallees)
AllInferrableEffectsToVerify.insert(Effect);
@@ -458,15 +438,16 @@ class Analyzer {
// depth-first traversal; there's no need for a second container. But first,
// reverse it, so when working from the end, Decls are verified in the order
// they are declared.
- SmallVector<const Decl *> &VerificationQueue = Sem.DeclsWithEffectsToVerify;
+ SmallVector<const Decl *> &VerificationQueue = S.DeclsWithEffectsToVerify;
std::reverse(VerificationQueue.begin(), VerificationQueue.end());
while (!VerificationQueue.empty()) {
const Decl *D = VerificationQueue.back();
if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
- // All children have been traversed; finish analysis.
- if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>())
+ if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>()) {
+ // All children have been traversed; finish analysis.
finishPendingAnalysis(D, Pending);
+ }
VerificationQueue.pop_back();
continue;
}
@@ -474,7 +455,7 @@ class Analyzer {
// Not previously visited; begin a new analysis for this Decl.
PendingFunctionAnalysis *Pending = verifyDecl(D);
if (Pending == nullptr) {
- // completed now
+ // Completed now.
VerificationQueue.pop_back();
continue;
}
@@ -505,15 +486,13 @@ class Analyzer {
CallableInfo CInfo(*D);
bool isExternC = false;
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
- assert(FD->getBuiltinID() == 0);
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D))
isExternC = FD->getCanonicalDecl()->isExternCContext();
- }
// For C++, with non-extern "C" linkage only - if any of the Decl's declared
// effects forbid throwing (e.g. nonblocking) then the function should also
// be declared noexcept.
- if (Sem.getLangOpts().CPlusPlus && !isExternC) {
+ if (S.getLangOpts().CPlusPlus && !isExternC) {
for (FunctionEffect Effect : CInfo.Effects) {
if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
continue;
@@ -528,8 +507,7 @@ class Analyzer {
}
}
if (!IsNoexcept)
- Sem.Diag(D->getBeginLoc(),
- diag::warn_perf_constraint_implies_noexcept)
+ S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
<< Effect.name();
break;
}
@@ -538,10 +516,10 @@ class Analyzer {
// Build a PendingFunctionAnalysis on the stack. If it turns out to be
// complete, we'll have avoided a heap allocation; if it's incomplete, it's
// a fairly trivial move to a heap-allocated object.
- PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
+ PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify);
- LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
- FAnalysis.dump(Sem, llvm::dbgs()););
+ LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(S) << " ";
+ FAnalysis.dump(S, llvm::dbgs()););
FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
@@ -555,7 +533,7 @@ class Analyzer {
new PendingFunctionAnalysis(std::move(FAnalysis));
DeclAnalysis[D] = PendingPtr;
LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
- DeclAnalysis.dump(Sem, llvm::dbgs()););
+ DeclAnalysis.dump(S, llvm::dbgs()););
return PendingPtr;
}
@@ -564,16 +542,16 @@ class Analyzer {
void completeAnalysis(const CallableInfo &CInfo,
PendingFunctionAnalysis &Pending) {
if (ArrayRef<Violation> Viols =
- Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager());
+ Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
!Viols.empty())
- emitDiagnostics(Viols, CInfo, Sem);
+ emitDiagnostics(Viols, CInfo, S);
- CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
- Sem.getASTContext(), Pending, CInfo.Effects,
- AllInferrableEffectsToVerify);
+ CompleteFunctionAnalysis *CompletePtr =
+ new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects,
+ AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
- DeclAnalysis.dump(Sem, llvm::dbgs()););
+ DeclAnalysis.dump(S, llvm::dbgs()););
}
// Called after all direct calls requiring inference have been found -- or
@@ -582,8 +560,8 @@ class Analyzer {
void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
CallableInfo Caller(*D);
LLVM_DEBUG(llvm::dbgs()
- << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
- Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
+ << "finishPendingAnalysis for " << Caller.name(S) << " : ";
+ Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";);
for (const PendingFunctionAnalysis::DirectCall &Call :
Pending->unverifiedCalls()) {
if (Call.Recursed)
@@ -614,7 +592,7 @@ class Analyzer {
DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
// Combine declared effects with those which may have been inferred.
CalleeEffects.insert(CFA->VerifiedEffects);
- IsInferencePossible = false; // we've already traversed it
+ IsInferencePossible = false; // We've already traversed it.
}
if (AssertNoFurtherInference) {
@@ -624,16 +602,13 @@ class Analyzer {
if (!Callee.isVerifiable())
IsInferencePossible = false;
- LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
- << Callee.name(Sem) << "; verifiable: "
+ LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(S) << " to "
+ << Callee.name(S) << "; verifiable: "
<< Callee.isVerifiable() << "; callee ";
CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
- llvm::dbgs()
- << " callee " << Callee.CDecl << " canonical "
- << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
- for (Decl *D
- : Callee.CDecl->redecls()) llvm::dbgs()
- << " " << D;
+ llvm::dbgs() << " callee " << Callee.CDecl << " canonical "
+ << Callee.CDecl->getCanonicalDecl() << " redecls";
+ for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
llvm::dbgs() << "\n";);
@@ -661,7 +636,7 @@ class Analyzer {
for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
Check1Effect(Effect, false);
- for (FunctionEffect Effect : PFA.FXToInfer)
+ for (FunctionEffect Effect : PFA.EffectsToInfer)
Check1Effect(Effect, true);
}
@@ -687,8 +662,8 @@ class Analyzer {
StringRef effectName = Viol1.Effect.name();
switch (Viol1.ID) {
case ViolationID::None:
- case ViolationID::DeclDisallowsInference: // shouldn't happen
- // here
+ case ViolationID::DeclDisallowsInference: // Shouldn't happen
+ // here.
llvm_unreachable("Unexpected violation kind");
break;
case ViolationID::AllocatesMemory:
@@ -867,7 +842,7 @@ class Analyzer {
}
// For each inferred effect which forbids the construct, store a
// violation, if we don't already have a violation for that effect.
- for (FunctionEffect Effect : CurrentFunction.FXToInfer)
+ for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
if (Effect.flags() & Flag)
addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
}
@@ -895,9 +870,9 @@ class Analyzer {
static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
unsigned BuiltinID = FD->getBuiltinID();
switch (BuiltinID) {
- case 0: // not builtin
+ case 0: // Not builtin.
return false;
- default: // not disallowed via cases below
+ default: // Not disallowed via cases below.
return true;
// Disallow list
@@ -917,14 +892,14 @@ class Analyzer {
void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
auto *FPT =
- CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
- FunctionEffectKindSet CalleeFX;
+ CalleeType->getAs<FunctionProtoType>(); // Null if FunctionType.
+ FunctionEffectKindSet CalleeEffects;
if (FPT)
- CalleeFX.insert(FPT->getFunctionEffects());
+ CalleeEffects.insert(FPT->getFunctionEffects());
auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
- /*direct=*/false, CalleeFX))
+ /*direct=*/false, CalleeEffects))
addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
Call->getBeginLoc());
};
@@ -932,7 +907,7 @@ class Analyzer {
for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
Check1Effect(Effect, false);
- for (FunctionEffect Effect : CurrentFunction.FXToInfer)
+ for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
Check1Effect(Effect, true);
}
@@ -1015,7 +990,7 @@ class Analyzer {
bool VisitCallExpr(CallExpr *Call) {
LLVM_DEBUG(llvm::dbgs()
<< "VisitCallExpr : "
- << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << Call->getBeginLoc().printToString(Outer.S.SourceMgr)
<< "\n";);
Expr *CalleeExpr = Call->getCallee();
@@ -1025,9 +1000,10 @@ class Analyzer {
return true;
}
- if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
- // just destroying a scalar, fine.
+ if (isa<CXXPseudoDestructorExpr>(CalleeExpr)) {
+ // Just destroying a scalar, fine.
return true;
+ }
// No Decl, just an Expr. Just check based on its type.
checkIndirectCall(Call, CalleeExpr->getType());
@@ -1038,7 +1014,7 @@ class Analyzer {
bool VisitVarDecl(VarDecl *Var) {
LLVM_DEBUG(llvm::dbgs()
<< "VisitVarDecl : "
- << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+ << Var->getBeginLoc().printToString(Outer.S.SourceMgr)
<< "\n";);
if (Var->isStaticLocal())
@@ -1047,7 +1023,7 @@ class Analyzer {
Var->getLocation());
const QualType::DestructionKind DK =
- Var->needsDestruction(Outer.Sem.getASTContext());
+ Var->needsDestruction(Outer.S.getASTContext());
if (DK == QualType::DK_cxx_destructor) {
QualType QT = Var->getType();
if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
@@ -1064,8 +1040,7 @@ class Analyzer {
}
bool VisitCXXNewExpr(CXXNewExpr *New) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator new.
+ // RecursiveASTVisitor does not visit the implicit call to operator new.
if (FunctionDecl *FD = New->getOperatorNew()) {
CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
followCall(CI, New->getBeginLoc());
@@ -1080,8 +1055,8 @@ class Analyzer {
}
bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to operator delete.
+ // RecursiveASTVisitor does not visit the implicit call to operator
+ // delete.
if (FunctionDecl *FD = Delete->getOperatorDelete()) {
CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
followCall(CI, Delete->getBeginLoc());
@@ -1095,11 +1070,11 @@ class Analyzer {
bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
<< Construct->getBeginLoc().printToString(
- Outer.Sem.SourceMgr)
+ Outer.S.SourceMgr)
<< "\n";);
- // BUG? It seems incorrect that RecursiveASTVisitor does not
- // visit the call to the constructor.
+ // RecursiveASTVisitor does not visit the implicit call to the
+ // constructor.
const CXXConstructorDecl *Ctor = Construct->getConstructor();
CallableInfo CI(*Ctor);
followCall(CI, Construct->getLocation());
@@ -1172,7 +1147,6 @@ namespace clang {
void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
- // exit if having uncompilable errors or ignoring all warnings:
return;
if (TU == nullptr)
return;
>From 0e07315aa4b880762f469bed476f55a507a1b793 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 09:55:10 -0700
Subject: [PATCH 20/44] Implement FunctionEffectKindSet with std::bitset.
---
clang/include/clang/AST/Type.h | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e7a0d79447dc4e..5579db711b59e7 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -48,6 +48,7 @@
#include "llvm/Support/PointerLikeTypeTraits.h"
#include "llvm/Support/TrailingObjects.h"
#include "llvm/Support/type_traits.h"
+#include <bitset>
#include <cassert>
#include <cstddef>
#include <cstdint>
@@ -4922,43 +4923,44 @@ class FunctionEffectsRef {
/// A mutable set of FunctionEffect::Kind.
class FunctionEffectKindSet {
// For now this only needs to be a bitmap.
- using KindBitsT = uint8_t;
constexpr static size_t EndBitPos = 8;
+ using KindBitsT = std::bitset<EndBitPos>;
- KindBitsT KindBits = 0;
-
- static KindBitsT kindToBit(FunctionEffect::Kind K) {
- return 1u << KindBitsT(K);
- }
+ KindBitsT KindBits{};
explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
+ constexpr static size_t kindToPos(FunctionEffect::Kind K) {
+ return size_t(K);
+ }
+
public:
FunctionEffectKindSet() = default;
explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
+ // Iterates through the bits which are set.
class iterator {
const FunctionEffectKindSet *Outer = nullptr;
size_t Idx = 0;
// If Idx does not reference a set bit, advance it until it does,
// or until it reaches EndBitPos.
- void advanceIdx() {
- while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx)))
+ void advanceToNextSetBit() {
+ while (Idx < EndBitPos && !Outer->KindBits.test(Idx))
++Idx;
}
public:
iterator();
iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
- advanceIdx();
+ advanceToNextSetBit();
}
bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
iterator operator++() {
++Idx;
- advanceIdx();
+ advanceToNextSetBit();
return *this;
}
@@ -4971,7 +4973,7 @@ class FunctionEffectKindSet {
iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, EndBitPos); }
- void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); }
+ void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); }
void insert(FunctionEffectsRef FX) {
for (FunctionEffect Item : FX.effects())
insert(Item);
@@ -4979,7 +4981,7 @@ class FunctionEffectKindSet {
void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
bool contains(const FunctionEffect::Kind EK) const {
- return (KindBits & kindToBit(EK)) != 0;
+ return KindBits.test(kindToPos(EK));
}
void dump(llvm::raw_ostream &OS) const;
>From fddd9d2775e7ee5fa88c9a12888872cafc411ead Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 14:26:02 -0700
Subject: [PATCH 21/44] - Diagnose __builtin_operator_new and delete - More
test cases involving function try-block and member initializers. - remove
construct which is blowing clang-format's mind
---
clang/lib/Sema/EffectAnalysis.cpp | 7 ++-
.../Sema/attr-nonblocking-constraints.cpp | 46 ++++++++++++++++++-
2 files changed, 47 insertions(+), 6 deletions(-)
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index dbc429f22c2367..c0f679d9ece206 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -607,10 +607,7 @@ class Analyzer {
<< Callee.isVerifiable() << "; callee ";
CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
llvm::dbgs() << " callee " << Callee.CDecl << " canonical "
- << Callee.CDecl->getCanonicalDecl() << " redecls";
- for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
-
- llvm::dbgs() << "\n";);
+ << Callee.CDecl->getCanonicalDecl() << "\n";);
auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
@@ -881,6 +878,8 @@ class Analyzer {
case Builtin::ID::BI__builtin_malloc:
case Builtin::ID::BI__builtin_realloc:
case Builtin::ID::BI__builtin_free:
+ case Builtin::ID::BI__builtin_operator_delete:
+ case Builtin::ID::BI__builtin_operator_new:
case Builtin::ID::BIcalloc:
case Builtin::ID::BImalloc:
case Builtin::ID::BImemalign:
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 02247cb71975cf..3515f7f4b7d6cc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -210,12 +210,54 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
}
// Builtin functions
-void nb18a() [[clang::nonblocking]] {
+void nb19() [[clang::nonblocking]] {
__builtin_assume(1);
void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
__builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+
+ void *p2 = __builtin_operator_new(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}}
+ __builtin_operator_delete(p2); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_delete'}}
+}
+
+// Function try-block
+void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+
+void nb20() [[clang::nonblocking]] {
+ catches(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'catches'}}
}
+struct S {
+ int x;
+ S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+ S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+};
+
+int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+struct A {
+ int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+};
+
+struct B {
+ int y = badi(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
+};
+
+void f() [[clang::nonblocking]] {
+ S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
+ S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
+ A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}}
+ B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}}
+}
+
+#if 0
+// FIXME: can we do better with default member initializers?
+struct T {
+ int x = badi();
+ T() [[clang::nonblocking]] {} // Warning: this calls bad().
+ T(int x) [[clang::nonblocking]] : x(x) {} // This does not.
+};
+#endif
+
// Verify traversal of implicit code paths - constructors and destructors.
struct Unsafe {
static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
@@ -251,6 +293,6 @@ void g() [[clang::nonblocking]] {
// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
-void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
{
}
>From eb536aba9985c4087d3cd68bd75f4013a8530ff6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 15 Aug 2024 11:01:13 -0700
Subject: [PATCH 22/44] EffectAnalysis.cpp => SemaFunctionEffects.cpp (minimal
first step; more reorg to follow)
---
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Sema/CMakeLists.txt | 2 +-
clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
rename clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} (99%)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c17cc9ce8bc443..f23d1aa9c95dfe 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,7 +530,7 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
const FunctionEffectsRef &New);
};
-// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and
+// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and
// move more of the effects implementation into that file?
void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index ea827323395d74..719c3a9312ec15 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -19,7 +19,6 @@ add_clang_library(clangSema
CodeCompleteConsumer.cpp
DeclSpec.cpp
DelayedDiagnostic.cpp
- EffectAnalysis.cpp
HLSLExternalSemaSource.cpp
IdentifierResolver.cpp
JumpDiagnostics.cpp
@@ -56,6 +55,7 @@ add_clang_library(clangSema
SemaExprMember.cpp
SemaExprObjC.cpp
SemaFixItUtils.cpp
+ SemaFunctionEffects.cpp
SemaHLSL.cpp
SemaHexagon.cpp
SemaInit.cpp
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
similarity index 99%
rename from clang/lib/Sema/EffectAnalysis.cpp
rename to clang/lib/Sema/SemaFunctionEffects.cpp
index c0f679d9ece206..2144cbf3a72bc6 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1,4 +1,4 @@
-//=== EffectAnalysis.cpp - Sema warnings for function effects -------------===//
+//=== SemaFunctionEffects.cpp - Sema warnings for function effects --------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
>From 15b399fd8b6a8bb9f59ac3367467bb84e20efd0c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 15 Aug 2024 11:28:59 -0700
Subject: [PATCH 23/44] Move most Sema methods involving effects into
SemaFunctionEffects.cpp. Two remain elsewhere: -
diagnoseFunctionEffectConversion (still in Sema.cpp next to
diagnoseNullableToNonnullConversion) - ActOnEffectExpression (still in
SemaType.cpp near handleNonBlockingNonAllocatingTypeAttr)
---
clang/include/clang/Sema/Sema.h | 90 +++++++++++---------
clang/lib/Sema/Sema.cpp | 2 +-
clang/lib/Sema/SemaDecl.cpp | 99 ----------------------
clang/lib/Sema/SemaFunctionEffects.cpp | 109 +++++++++++++++++++++++--
4 files changed, 156 insertions(+), 144 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f23d1aa9c95dfe..c46b2f6e7e5fa5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,10 +530,6 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
const FunctionEffectsRef &New);
};
-// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and
-// move more of the effects implementation into that file?
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
-
/// Sema - This implements semantic analysis and AST building for C.
/// \nosubgrouping
class Sema final : public SemaBase {
@@ -877,13 +873,6 @@ class Sema final : public SemaBase {
/// Warn when implicitly casting 0 to nullptr.
void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
- /// All functions/lambdas/blocks which have bodies and which have a non-empty
- /// FunctionEffectsRef to be verified.
- SmallVector<const Decl *> DeclsWithEffectsToVerify;
- /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
- /// are all null.
- FunctionEffectKindSet AllEffectsToVerify;
-
/// Warn when implicitly changing function effects.
void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
SourceLocation Loc);
@@ -4324,34 +4313,6 @@ class Sema final : public SemaBase {
// Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
- /// Warn and return true if adding a function effect to a set would create a
- /// conflict.
- bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
- const FunctionEffectWithCondition &EC,
- SourceLocation NewAttrLoc);
-
- // Report a failure to merge function effects between declarations due to a
- // conflict.
- void
- diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
- SourceLocation NewLoc,
- SourceLocation OldLoc);
-
- /// Inline checks from the start of maybeAddDeclWithEffects, to
- /// minimize performance impact on code not using effects.
- template <class FuncOrBlockDecl>
- void maybeAddDeclWithEffects(FuncOrBlockDecl *D) {
- if (Context.hasAnyFunctionEffects())
- if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty())
- maybeAddDeclWithEffects(D, FX);
- }
-
- /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
- void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
- /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
- void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
-
private:
/// Function or variable declarations to be checked for whether the deferred
/// diagnostics should be emitted.
@@ -15119,6 +15080,57 @@ class Sema final : public SemaBase {
bool OrNull);
///@}
+
+ //
+ //
+ // -------------------------------------------------------------------------
+ //
+ //
+
+ /// \name Function Effects
+ /// Implementations are in SemaFunctionEffects.cpp
+ ///@{
+public:
+ /// All functions/lambdas/blocks which have bodies and which have a non-empty
+ /// FunctionEffectsRef to be verified.
+ SmallVector<const Decl *> DeclsWithEffectsToVerify;
+
+ /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
+ /// are all null.
+ FunctionEffectKindSet AllEffectsToVerify;
+
+public:
+ /// Warn and return true if adding a function effect to a set would create a
+ /// conflict.
+ bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
+ const FunctionEffectWithCondition &EC,
+ SourceLocation NewAttrLoc);
+
+ // Report a failure to merge function effects between declarations due to a
+ // conflict.
+ void
+ diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
+ SourceLocation NewLoc,
+ SourceLocation OldLoc);
+
+ /// Inline checks from the start of maybeAddDeclWithEffects, to
+ /// minimize performance impact on code not using effects.
+ template <class FuncOrBlockDecl>
+ void maybeAddDeclWithEffects(FuncOrBlockDecl *D) {
+ if (Context.hasAnyFunctionEffects())
+ if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty())
+ maybeAddDeclWithEffects(D, FX);
+ }
+
+ /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+ void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+ /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
+ void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+ void performFunctionEffectAnalysis(TranslationUnitDecl *TU);
+
+ ///@}
};
DeductionFailureInfo
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 7afc2b5b35d4ba..12f0a0e7bfd753 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1529,7 +1529,7 @@ void Sema::ActOnEndOfTranslationUnit() {
AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
if (Context.hasAnyFunctionEffects())
- performEffectAnalysis(*this, Context.getTranslationUnitDecl());
+ performFunctionEffectAnalysis(Context.getTranslationUnitDecl());
// Check we've noticed that we're no longer parsing the initializer for every
// variable. If we miss cases, then at best we have a performance issue and
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8b464ab29498fb..554b35527b2201 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10919,49 +10919,6 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
return nullptr;
}
-// Should only be called when getFunctionEffects() returns a non-empty set.
-// Decl should be a FunctionDecl or BlockDecl.
-void Sema::maybeAddDeclWithEffects(const Decl *D,
- const FunctionEffectsRef &FX) {
- if (!D->hasBody()) {
- if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody())
- return;
- }
-
- if (Diags.getIgnoreAllWarnings() ||
- (Diags.getSuppressSystemWarnings() &&
- SourceMgr.isInSystemHeader(D->getLocation())))
- return;
-
- if (hasUncompilableErrorOccurred())
- return;
-
- // For code in dependent contexts, we'll do this at instantiation time.
- // Without this check, we would analyze the function based on placeholder
- // template parameters, and potentially generate spurious diagnostics.
- if (cast<DeclContext>(D)->isDependentContext())
- return;
-
- addDeclWithEffects(D, FX);
-}
-
-void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
- // To avoid the possibility of conflict, don't add effects which are
- // not FE_InferrableOnCallees and therefore not verified; this removes
- // blocking/allocating but keeps nonblocking/nonallocating.
- // Also, ignore any conditions when building the list of effects.
- bool AnyVerifiable = false;
- for (const FunctionEffectWithCondition &EC : FX)
- if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
- AllEffectsToVerify.insert(EC.Effect);
- AnyVerifiable = true;
- }
-
- // Record the declaration for later analysis.
- if (AnyVerifiable)
- DeclsWithEffectsToVerify.push_back(D);
-}
-
bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD,
QualType NewT, QualType OldT) {
if (!NewD->getLexicalDeclContext()->isDependentContext())
@@ -20331,59 +20288,3 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) {
return LangOpts.CUDA && !LangOpts.CUDAIsDevice &&
CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global;
}
-
-void Sema::diagnoseFunctionEffectMergeConflicts(
- const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc,
- SourceLocation OldLoc) {
- for (const FunctionEffectSet::Conflict &Conflict : Errs) {
- Diag(NewLoc, diag::warn_conflicting_func_effects)
- << Conflict.Kept.description() << Conflict.Rejected.description();
- Diag(OldLoc, diag::note_previous_declaration);
- }
-}
-
-bool Sema::diagnoseConflictingFunctionEffect(
- const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC,
- SourceLocation NewAttrLoc) {
- // If the new effect has a condition, we can't detect conflicts until the
- // condition is resolved.
- if (NewEC.Cond.getCondition() != nullptr)
- return false;
-
- // Diagnose the new attribute as incompatible with a previous one.
- auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
- Diag(NewAttrLoc, diag::err_attributes_are_not_compatible)
- << ("'" + NewEC.description() + "'")
- << ("'" + PrevEC.description() + "'") << false;
- // We don't necessarily have the location of the previous attribute,
- // so no note.
- return true;
- };
-
- // Compare against previous attributes.
- FunctionEffect::Kind NewKind = NewEC.Effect.kind();
-
- for (const FunctionEffectWithCondition &PrevEC : FX) {
- // Again, can't check yet when the effect is conditional.
- if (PrevEC.Cond.getCondition() != nullptr)
- continue;
-
- FunctionEffect::Kind PrevKind = PrevEC.Effect.kind();
- // Note that we allow PrevKind == NewKind; it's redundant and ignored.
-
- if (PrevEC.Effect.oppositeKind() == NewKind)
- return Incompatible(PrevEC);
-
- // A new allocating is incompatible with a previous nonblocking.
- if (PrevKind == FunctionEffect::Kind::NonBlocking &&
- NewKind == FunctionEffect::Kind::Allocating)
- return Incompatible(PrevEC);
-
- // A new nonblocking is incompatible with a previous allocating.
- if (PrevKind == FunctionEffect::Kind::Allocating &&
- NewKind == FunctionEffect::Kind::NonBlocking)
- return Incompatible(PrevEC);
- }
-
- return false;
-}
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 2144cbf3a72bc6..7624a072d2fca8 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1,4 +1,4 @@
-//=== SemaFunctionEffects.cpp - Sema warnings for function effects --------===//
+//=== SemaFunctionEffects.cpp - Sema handling of function effects ---------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
//
-// This file implements caller/callee analysis for function effects.
+// This file implements Sema handling of function effects.
//
//===----------------------------------------------------------------------===//
@@ -1144,12 +1144,111 @@ Analyzer::AnalysisMap::~AnalysisMap() {
namespace clang {
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
- if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+bool Sema::diagnoseConflictingFunctionEffect(
+ const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC,
+ SourceLocation NewAttrLoc) {
+ // If the new effect has a condition, we can't detect conflicts until the
+ // condition is resolved.
+ if (NewEC.Cond.getCondition() != nullptr)
+ return false;
+
+ // Diagnose the new attribute as incompatible with a previous one.
+ auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
+ Diag(NewAttrLoc, diag::err_attributes_are_not_compatible)
+ << ("'" + NewEC.description() + "'")
+ << ("'" + PrevEC.description() + "'") << false;
+ // We don't necessarily have the location of the previous attribute,
+ // so no note.
+ return true;
+ };
+
+ // Compare against previous attributes.
+ FunctionEffect::Kind NewKind = NewEC.Effect.kind();
+
+ for (const FunctionEffectWithCondition &PrevEC : FX) {
+ // Again, can't check yet when the effect is conditional.
+ if (PrevEC.Cond.getCondition() != nullptr)
+ continue;
+
+ FunctionEffect::Kind PrevKind = PrevEC.Effect.kind();
+ // Note that we allow PrevKind == NewKind; it's redundant and ignored.
+
+ if (PrevEC.Effect.oppositeKind() == NewKind)
+ return Incompatible(PrevEC);
+
+ // A new allocating is incompatible with a previous nonblocking.
+ if (PrevKind == FunctionEffect::Kind::NonBlocking &&
+ NewKind == FunctionEffect::Kind::Allocating)
+ return Incompatible(PrevEC);
+
+ // A new nonblocking is incompatible with a previous allocating.
+ if (PrevKind == FunctionEffect::Kind::Allocating &&
+ NewKind == FunctionEffect::Kind::NonBlocking)
+ return Incompatible(PrevEC);
+ }
+
+ return false;
+}
+
+void Sema::diagnoseFunctionEffectMergeConflicts(
+ const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc,
+ SourceLocation OldLoc) {
+ for (const FunctionEffectSet::Conflict &Conflict : Errs) {
+ Diag(NewLoc, diag::warn_conflicting_func_effects)
+ << Conflict.Kept.description() << Conflict.Rejected.description();
+ Diag(OldLoc, diag::note_previous_declaration);
+ }
+}
+
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+ const FunctionEffectsRef &FX) {
+ if (!D->hasBody()) {
+ if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody())
+ return;
+ }
+
+ if (Diags.getIgnoreAllWarnings() ||
+ (Diags.getSuppressSystemWarnings() &&
+ SourceMgr.isInSystemHeader(D->getLocation())))
+ return;
+
+ if (hasUncompilableErrorOccurred())
+ return;
+
+ // For code in dependent contexts, we'll do this at instantiation time.
+ // Without this check, we would analyze the function based on placeholder
+ // template parameters, and potentially generate spurious diagnostics.
+ if (cast<DeclContext>(D)->isDependentContext())
+ return;
+
+ addDeclWithEffects(D, FX);
+}
+
+void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
+ // To avoid the possibility of conflict, don't add effects which are
+ // not FE_InferrableOnCallees and therefore not verified; this removes
+ // blocking/allocating but keeps nonblocking/nonallocating.
+ // Also, ignore any conditions when building the list of effects.
+ bool AnyVerifiable = false;
+ for (const FunctionEffectWithCondition &EC : FX)
+ if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
+ AllEffectsToVerify.insert(EC.Effect);
+ AnyVerifiable = true;
+ }
+
+ // Record the declaration for later analysis.
+ if (AnyVerifiable)
+ DeclsWithEffectsToVerify.push_back(D);
+}
+
+void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) {
+ if (hasUncompilableErrorOccurred() || Diags.getIgnoreAllWarnings())
return;
if (TU == nullptr)
return;
- Analyzer{S}.run(*TU);
+ Analyzer{*this}.run(*TU);
}
} // namespace clang
>From dfebc1aa962a524e8767d8ca77b73c7b3840a069 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 07:34:07 -0700
Subject: [PATCH 24/44] Move FunctionEffectDiff/Differences into
SemaFunctionEffects.cpp.
---
clang/include/clang/Sema/Sema.h | 99 ++++++++--------
clang/lib/Sema/Sema.cpp | 150 -------------------------
clang/lib/Sema/SemaFunctionEffects.cpp | 150 +++++++++++++++++++++++++
3 files changed, 200 insertions(+), 199 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c46b2f6e7e5fa5..43f4d72a8fbefc 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -481,55 +481,6 @@ enum class FunctionEffectMode : uint8_t {
Dependent // effect(expr) where expr is dependent.
};
-struct FunctionEffectDiff {
- enum class Kind { Added, Removed, ConditionMismatch };
-
- FunctionEffect::Kind EffectKind;
- Kind DiffKind;
- FunctionEffectWithCondition Old; // invalid when Added.
- FunctionEffectWithCondition New; // invalid when Removed.
-
- StringRef effectName() const {
- if (Old.Effect.kind() != FunctionEffect::Kind::None)
- return Old.Effect.name();
- return New.Effect.name();
- }
-
- /// Describes the result of effects differing between a base class's virtual
- /// method and an overriding method in a subclass.
- enum class OverrideResult {
- NoAction,
- Warn,
- Merge // Merge missing effect from base to derived.
- };
-
- /// Return true if adding or removing the effect as part of a type conversion
- /// should generate a diagnostic.
- bool shouldDiagnoseConversion(QualType SrcType,
- const FunctionEffectsRef &SrcFX,
- QualType DstType,
- const FunctionEffectsRef &DstFX) const;
-
- /// Return true if adding or removing the effect in a redeclaration should
- /// generate a diagnostic.
- bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction,
- const FunctionEffectsRef &OldFX,
- const FunctionDecl &NewFunction,
- const FunctionEffectsRef &NewFX) const;
-
- /// Return true if adding or removing the effect in a C++ virtual method
- /// override should generate a diagnostic.
- OverrideResult shouldDiagnoseMethodOverride(
- const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
- const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const;
-};
-
-struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
- /// Caller should short-circuit by checking for equality first.
- FunctionEffectDifferences(const FunctionEffectsRef &Old,
- const FunctionEffectsRef &New);
-};
-
/// Sema - This implements semantic analysis and AST building for C.
/// \nosubgrouping
class Sema final : public SemaBase {
@@ -568,6 +519,7 @@ class Sema final : public SemaBase {
// 30. Constraints and Concepts (SemaConcept.cpp)
// 31. Types (SemaType.cpp)
// 32. FixIt Helpers (SemaFixItUtils.cpp)
+ // 33. Function Effects (SemaFunctionEffects.cpp)
/// \name Semantic Analysis
/// Implementations are in Sema.cpp
@@ -15091,6 +15043,55 @@ class Sema final : public SemaBase {
/// Implementations are in SemaFunctionEffects.cpp
///@{
public:
+ struct FunctionEffectDiff {
+ enum class Kind { Added, Removed, ConditionMismatch };
+
+ FunctionEffect::Kind EffectKind;
+ Kind DiffKind;
+ FunctionEffectWithCondition Old; // invalid when Added.
+ FunctionEffectWithCondition New; // invalid when Removed.
+
+ StringRef effectName() const {
+ if (Old.Effect.kind() != FunctionEffect::Kind::None)
+ return Old.Effect.name();
+ return New.Effect.name();
+ }
+
+ /// Describes the result of effects differing between a base class's virtual
+ /// method and an overriding method in a subclass.
+ enum class OverrideResult {
+ NoAction,
+ Warn,
+ Merge // Merge missing effect from base to derived.
+ };
+
+ /// Return true if adding or removing the effect as part of a type
+ /// conversion should generate a diagnostic.
+ bool shouldDiagnoseConversion(QualType SrcType,
+ const FunctionEffectsRef &SrcFX,
+ QualType DstType,
+ const FunctionEffectsRef &DstFX) const;
+
+ /// Return true if adding or removing the effect in a redeclaration should
+ /// generate a diagnostic.
+ bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction,
+ const FunctionEffectsRef &OldFX,
+ const FunctionDecl &NewFunction,
+ const FunctionEffectsRef &NewFX) const;
+
+ /// Return true if adding or removing the effect in a C++ virtual method
+ /// override should generate a diagnostic.
+ OverrideResult shouldDiagnoseMethodOverride(
+ const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+ const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const;
+ };
+
+ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
+ /// Caller should short-circuit by checking for equality first.
+ FunctionEffectDifferences(const FunctionEffectsRef &Old,
+ const FunctionEffectsRef &New);
+ };
+
/// All functions/lambdas/blocks which have bodies and which have a non-empty
/// FunctionEffectsRef to be verified.
SmallVector<const Decl *> DeclsWithEffectsToVerify;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 12f0a0e7bfd753..5c7460d480db07 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2783,153 +2783,3 @@ bool Sema::isDeclaratorFunctionLike(Declarator &D) {
});
return Result;
}
-
-FunctionEffectDifferences::FunctionEffectDifferences(
- const FunctionEffectsRef &Old, const FunctionEffectsRef &New) {
-
- FunctionEffectsRef::iterator POld = Old.begin();
- FunctionEffectsRef::iterator OldEnd = Old.end();
- FunctionEffectsRef::iterator PNew = New.begin();
- FunctionEffectsRef::iterator NewEnd = New.end();
-
- while (true) {
- int cmp = 0;
- if (POld == OldEnd) {
- if (PNew == NewEnd)
- break;
- cmp = 1;
- } else if (PNew == NewEnd)
- cmp = -1;
- else {
- FunctionEffectWithCondition Old = *POld;
- FunctionEffectWithCondition New = *PNew;
- if (Old.Effect.kind() < New.Effect.kind())
- cmp = -1;
- else if (New.Effect.kind() < Old.Effect.kind())
- cmp = 1;
- else {
- cmp = 0;
- if (Old.Cond.getCondition() != New.Cond.getCondition()) {
- // FIXME: Cases where the expressions are equivalent but
- // don't have the same identity.
- push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
- Old, New});
- }
- }
- }
-
- if (cmp < 0) {
- // removal
- FunctionEffectWithCondition Old = *POld;
- push_back(FunctionEffectDiff{
- Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
- ++POld;
- } else if (cmp > 0) {
- // addition
- FunctionEffectWithCondition New = *PNew;
- push_back(FunctionEffectDiff{
- New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
- ++PNew;
- } else {
- ++POld;
- ++PNew;
- }
- }
-}
-
-bool FunctionEffectDiff::shouldDiagnoseConversion(
- QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
- const FunctionEffectsRef &DstFX) const {
-
- switch (EffectKind) {
- case FunctionEffect::Kind::NonAllocating:
- // nonallocating can't be added (spoofed) during a conversion, unless we
- // have nonblocking.
- if (DiffKind == Kind::Added) {
- for (const auto &CFE : SrcFX) {
- if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
- return false;
- }
- }
- [[fallthrough]];
- case FunctionEffect::Kind::NonBlocking:
- // nonblocking can't be added (spoofed) during a conversion.
- switch (DiffKind) {
- case Kind::Added:
- return true;
- case Kind::Removed:
- return false;
- case Kind::ConditionMismatch:
- // FIXME: Condition mismatches are too coarse right now -- expressions
- // which are equivalent but don't have the same identity are detected as
- // mismatches. We're going to diagnose those anyhow until expression
- // matching is better.
- return true;
- }
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return false;
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
-
-bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
- const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
- const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
- switch (EffectKind) {
- case FunctionEffect::Kind::NonAllocating:
- case FunctionEffect::Kind::NonBlocking:
- // nonblocking/nonallocating can't be removed in a redeclaration.
- switch (DiffKind) {
- case Kind::Added:
- return false; // No diagnostic.
- case Kind::Removed:
- return true; // Issue diagnostic.
- case Kind::ConditionMismatch:
- // All these forms of mismatches are diagnosed.
- return true;
- }
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return false;
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
-
-FunctionEffectDiff::OverrideResult
-FunctionEffectDiff::shouldDiagnoseMethodOverride(
- const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
- const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
- switch (EffectKind) {
- case FunctionEffect::Kind::NonAllocating:
- case FunctionEffect::Kind::NonBlocking:
- switch (DiffKind) {
-
- // If added on an override, that's fine and not diagnosed.
- case Kind::Added:
- return OverrideResult::NoAction;
-
- // If missing from an override (removed), propagate from base to derived.
- case Kind::Removed:
- return OverrideResult::Merge;
-
- // If there's a mismatch involving the effect's polarity or condition,
- // issue a warning.
- case Kind::ConditionMismatch:
- return OverrideResult::Warn;
- }
-
- case FunctionEffect::Kind::Blocking:
- case FunctionEffect::Kind::Allocating:
- return OverrideResult::NoAction;
-
- case FunctionEffect::Kind::None:
- break;
- }
- llvm_unreachable("unknown effect kind");
-}
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 7624a072d2fca8..526c0bb8d114e6 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1251,4 +1251,154 @@ void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) {
Analyzer{*this}.run(*TU);
}
+Sema::FunctionEffectDifferences::FunctionEffectDifferences(
+ const FunctionEffectsRef &Old, const FunctionEffectsRef &New) {
+
+ FunctionEffectsRef::iterator POld = Old.begin();
+ FunctionEffectsRef::iterator OldEnd = Old.end();
+ FunctionEffectsRef::iterator PNew = New.begin();
+ FunctionEffectsRef::iterator NewEnd = New.end();
+
+ while (true) {
+ int cmp = 0;
+ if (POld == OldEnd) {
+ if (PNew == NewEnd)
+ break;
+ cmp = 1;
+ } else if (PNew == NewEnd)
+ cmp = -1;
+ else {
+ FunctionEffectWithCondition Old = *POld;
+ FunctionEffectWithCondition New = *PNew;
+ if (Old.Effect.kind() < New.Effect.kind())
+ cmp = -1;
+ else if (New.Effect.kind() < Old.Effect.kind())
+ cmp = 1;
+ else {
+ cmp = 0;
+ if (Old.Cond.getCondition() != New.Cond.getCondition()) {
+ // FIXME: Cases where the expressions are equivalent but
+ // don't have the same identity.
+ push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
+ Old, New});
+ }
+ }
+ }
+
+ if (cmp < 0) {
+ // removal
+ FunctionEffectWithCondition Old = *POld;
+ push_back(FunctionEffectDiff{
+ Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
+ ++POld;
+ } else if (cmp > 0) {
+ // addition
+ FunctionEffectWithCondition New = *PNew;
+ push_back(FunctionEffectDiff{
+ New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
+ ++PNew;
+ } else {
+ ++POld;
+ ++PNew;
+ }
+ }
+}
+
+bool Sema::FunctionEffectDiff::shouldDiagnoseConversion(
+ QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
+ const FunctionEffectsRef &DstFX) const {
+
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
+ // nonallocating can't be added (spoofed) during a conversion, unless we
+ // have nonblocking.
+ if (DiffKind == Kind::Added) {
+ for (const auto &CFE : SrcFX) {
+ if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
+ return false;
+ }
+ }
+ [[fallthrough]];
+ case FunctionEffect::Kind::NonBlocking:
+ // nonblocking can't be added (spoofed) during a conversion.
+ switch (DiffKind) {
+ case Kind::Added:
+ return true;
+ case Kind::Removed:
+ return false;
+ case Kind::ConditionMismatch:
+ // FIXME: Condition mismatches are too coarse right now -- expressions
+ // which are equivalent but don't have the same identity are detected as
+ // mismatches. We're going to diagnose those anyhow until expression
+ // matching is better.
+ return true;
+ }
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
+bool Sema::FunctionEffectDiff::shouldDiagnoseRedeclaration(
+ const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
+ const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
+ case FunctionEffect::Kind::NonBlocking:
+ // nonblocking/nonallocating can't be removed in a redeclaration.
+ switch (DiffKind) {
+ case Kind::Added:
+ return false; // No diagnostic.
+ case Kind::Removed:
+ return true; // Issue diagnostic.
+ case Kind::ConditionMismatch:
+ // All these forms of mismatches are diagnosed.
+ return true;
+ }
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return false;
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
+Sema::FunctionEffectDiff::OverrideResult
+Sema::FunctionEffectDiff::shouldDiagnoseMethodOverride(
+ const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+ const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
+ switch (EffectKind) {
+ case FunctionEffect::Kind::NonAllocating:
+ case FunctionEffect::Kind::NonBlocking:
+ switch (DiffKind) {
+
+ // If added on an override, that's fine and not diagnosed.
+ case Kind::Added:
+ return OverrideResult::NoAction;
+
+ // If missing from an override (removed), propagate from base to derived.
+ case Kind::Removed:
+ return OverrideResult::Merge;
+
+ // If there's a mismatch involving the effect's polarity or condition,
+ // issue a warning.
+ case Kind::ConditionMismatch:
+ return OverrideResult::Warn;
+ }
+
+ case FunctionEffect::Kind::Blocking:
+ case FunctionEffect::Kind::Allocating:
+ return OverrideResult::NoAction;
+
+ case FunctionEffect::Kind::None:
+ break;
+ }
+ llvm_unreachable("unknown effect kind");
+}
+
} // namespace clang
>From 82cb07d0dbd6a0a6bc21bd6d6406c05e05507d09 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 08:09:45 -0700
Subject: [PATCH 25/44] Combine the diagnostics for 5 violations into one using
%select{}
---
.../clang/Basic/DiagnosticSemaKinds.td | 35 ++------
clang/lib/Sema/SemaFunctionEffects.cpp | 89 ++++++++-----------
.../Sema/attr-nonblocking-constraints.cpp | 2 +-
3 files changed, 47 insertions(+), 79 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index be8aa4edb02c1b..b231207690f6c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10945,39 +10945,22 @@ def warn_imp_cast_drops_unaligned : Warning<
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
// Function effects
-def warn_func_effect_allocates : Warning<
- "'%0' function must not allocate or deallocate memory">,
+def warn_func_effect_violation : Warning<
+ "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|"
+ "have static local variables|use thread-local variables|access ObjC methods or properties}1">,
InGroup<FunctionEffects>;
-def note_func_effect_allocates : Note<
- "function cannot be inferred '%0' because it allocates/deallocates memory">;
-def warn_func_effect_throws_or_catches : Warning<
- "'%0' function must not throw or catch exceptions">,
- InGroup<FunctionEffects>;
-def note_func_effect_throws_or_catches : Note<
- "function cannot be inferred '%0' because it throws or catches exceptions">;
-def warn_func_effect_has_static_local : Warning<
- "'%0' function must not have static local variables">,
- InGroup<FunctionEffects>;
-def note_func_effect_has_static_local : Note<
- "function cannot be inferred '%0' because it has a static local variable">;
-def warn_func_effect_uses_thread_local : Warning<
- "'%0' function must not use thread-local variables">,
- InGroup<FunctionEffects>;
-def note_func_effect_uses_thread_local : Note<
- "function cannot be inferred '%0' because it uses a thread-local variable">;
-def warn_func_effect_calls_objc : Warning<
- "'%0' function must not access an ObjC method or property">,
- InGroup<FunctionEffects>;
-def note_func_effect_calls_objc : Note<
- "function cannot be inferred '%0' because it accesses an ObjC method or property">;
+def note_func_effect_violation : Note<
+ "function cannot be inferred '%0' because it %select{allocates or deallocates memory|"
+ "throws or catches exceptions|has a static local variable|uses a thread-local variable|"
+ "accesses an ObjC method or property}1">;
def warn_func_effect_calls_func_without_effect : Warning<
"'%0' function must not call non-'%0' function '%1'">,
InGroup<FunctionEffects>;
+def note_func_effect_calls_func_without_effect : Note<
+ "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
def warn_func_effect_calls_expr_without_effect : Warning<
"'%0' function must not call non-'%0' expression">,
InGroup<FunctionEffects>;
-def note_func_effect_calls_func_without_effect : Note<
- "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
def note_func_effect_call_extern : Note<
"declaration cannot be inferred '%0' because it has no definition in this translation unit">;
def note_func_effect_call_disallows_inference : Note<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 526c0bb8d114e6..de1abf773082f1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -25,12 +25,13 @@ namespace {
enum class ViolationID : uint8_t {
None = 0, // Sentinel for an empty Violation.
- Throws,
- Catches,
- CallsObjC,
- AllocatesMemory,
- HasStaticLocal,
- AccessesThreadLocal,
+ // These first few map to a %select{} in a diagnostic.
+ BaseDiagnosticIndex,
+ AllocatesMemory = BaseDiagnosticIndex,
+ ThrowsOrCatchesExceptions,
+ HasStaticLocalVariable,
+ AccessesThreadLocalVariable,
+ AccessesObjCMethodOrProperty,
// These only apply to callees, where the analysis stops at the Decl.
DeclDisallowsInference,
@@ -61,6 +62,10 @@ struct Violation {
if (CalleeEffect != nullptr)
CalleeEffectPreventingInference = *CalleeEffect;
}
+
+ unsigned diagnosticSelectIndex() const {
+ return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
+ }
};
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
@@ -664,27 +669,12 @@ class Analyzer {
llvm_unreachable("Unexpected violation kind");
break;
case ViolationID::AllocatesMemory:
- S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
- MaybeAddTemplateNote(CInfo.CDecl);
- break;
- case ViolationID::Throws:
- case ViolationID::Catches:
- S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
- << effectName;
- MaybeAddTemplateNote(CInfo.CDecl);
- break;
- case ViolationID::HasStaticLocal:
- S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
- << effectName;
- MaybeAddTemplateNote(CInfo.CDecl);
- break;
- case ViolationID::AccessesThreadLocal:
- S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
- << effectName;
- MaybeAddTemplateNote(CInfo.CDecl);
- break;
- case ViolationID::CallsObjC:
- S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
+ case ViolationID::ThrowsOrCatchesExceptions:
+ case ViolationID::HasStaticLocalVariable:
+ case ViolationID::AccessesThreadLocalVariable:
+ case ViolationID::AccessesObjCMethodOrProperty:
+ S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
+ << effectName << Viol1.diagnosticSelectIndex();
MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsExprWithoutEffect:
@@ -754,23 +744,12 @@ class Analyzer {
<< effectName;
break;
case ViolationID::AllocatesMemory:
- S.Diag(Viol2.Loc, diag::note_func_effect_allocates) << effectName;
- break;
- case ViolationID::Throws:
- case ViolationID::Catches:
- S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches)
- << effectName;
- break;
- case ViolationID::HasStaticLocal:
- S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local)
- << effectName;
- break;
- case ViolationID::AccessesThreadLocal:
- S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local)
- << effectName;
- break;
- case ViolationID::CallsObjC:
- S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
+ case ViolationID::ThrowsOrCatchesExceptions:
+ case ViolationID::HasStaticLocalVariable:
+ case ViolationID::AccessesThreadLocalVariable:
+ case ViolationID::AccessesObjCMethodOrProperty:
+ S.Diag(Viol2.Loc, diag::note_func_effect_violation)
+ << effectName << Viol2.diagnosticSelectIndex();
break;
case ViolationID::CallsDeclWithoutEffect:
MaybeNextCallee.emplace(*Viol2.Callee);
@@ -952,37 +931,43 @@ class Analyzer {
bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- ViolationID::Throws, Throw->getThrowLoc());
+ ViolationID::ThrowsOrCatchesExceptions,
+ Throw->getThrowLoc());
return true;
}
bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- ViolationID::Catches, Catch->getCatchLoc());
+ ViolationID::ThrowsOrCatchesExceptions,
+ Catch->getCatchLoc());
return true;
}
bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
- ViolationID::Throws, Throw->getThrowLoc());
+ ViolationID::ThrowsOrCatchesExceptions,
+ Throw->getThrowLoc());
return true;
}
bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- ViolationID::Catches, Catch->getAtCatchLoc());
+ ViolationID::ThrowsOrCatchesExceptions,
+ Catch->getAtCatchLoc());
return true;
}
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
- ViolationID::CallsObjC, Msg->getBeginLoc());
+ ViolationID::AccessesObjCMethodOrProperty,
+ Msg->getBeginLoc());
return true;
}
bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
- ViolationID::Catches, Exc->getExceptLoc());
+ ViolationID::ThrowsOrCatchesExceptions,
+ Exc->getExceptLoc());
return true;
}
@@ -1018,7 +1003,7 @@ class Analyzer {
if (Var->isStaticLocal())
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
- ViolationID::HasStaticLocal,
+ ViolationID::HasStaticLocalVariable,
Var->getLocation());
const QualType::DestructionKind DK =
@@ -1106,7 +1091,7 @@ class Analyzer {
// At least on macOS, thread-local variables are initialized on
// first access, including a heap allocation.
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
- ViolationID::AccessesThreadLocal,
+ ViolationID::AccessesThreadLocalVariable,
E->getLocation());
}
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 3515f7f4b7d6cc..0ff43608ac5a23 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -121,7 +121,7 @@ void nb8c()
friend Stringy operator+(const Stringy& x, const Stringy& y)
{
// Do something inferably unsafe
- auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates/deallocates memory}}
+ auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
return {};
}
};
>From f93ee015a672ba2e3a221bc3d47f464be7f885bb Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 10:35:19 -0700
Subject: [PATCH 26/44] Violation constructor: use optional instead of pointer.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index de1abf773082f1..58615a45e7a204 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -57,9 +57,9 @@ struct Violation {
Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
const Decl *Callee = nullptr,
- const FunctionEffect *CalleeEffect = nullptr)
+ std::optional<FunctionEffect> CalleeEffect = std::nullopt)
: Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
- if (CalleeEffect != nullptr)
+ if (CalleeEffect)
CalleeEffectPreventingInference = *CalleeEffect;
}
@@ -259,7 +259,7 @@ class PendingFunctionAnalysis {
// try to infer it.
InferrableEffectToFirstViolation.maybeInsert(Violation(
effect, ViolationID::DeclDisallowsInference,
- CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
+ CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
}
}
// InferrableEffects is now the set of inferrable effects which are not
@@ -338,7 +338,7 @@ class CompleteFunctionAnalysis {
public:
// The incoming Pending analysis is consumed (member(s) are moved-from).
- CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+ CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending,
FunctionEffectKindSet DeclaredEffects,
FunctionEffectKindSet AllInferrableEffectsToVerify)
: VerifiedEffects(DeclaredEffects) {
@@ -530,7 +530,7 @@ class Analyzer {
Visitor.run();
if (FAnalysis.isComplete()) {
- completeAnalysis(CInfo, FAnalysis);
+ completeAnalysis(CInfo, std::move(FAnalysis));
return nullptr;
}
// Move the pending analysis to the heap and save it in the map.
@@ -545,14 +545,14 @@ class Analyzer {
// Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
// inserted in the container.
void completeAnalysis(const CallableInfo &CInfo,
- PendingFunctionAnalysis &Pending) {
+ PendingFunctionAnalysis &&Pending) {
if (ArrayRef<Violation> Viols =
Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
!Viols.empty())
emitDiagnostics(Viols, CInfo, S);
CompleteFunctionAnalysis *CompletePtr =
- new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects,
+ new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects,
AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
@@ -576,7 +576,7 @@ class Analyzer {
followCall(Caller, *Pending, Callee, Call.CallLoc,
/*AssertNoFurtherInference=*/true);
}
- completeAnalysis(Caller, *Pending);
+ completeAnalysis(Caller, std::move(*Pending));
delete Pending;
}
>From 6cc0a6275ad3ce1065b28eaaee2aa68d4a634e74 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 10:36:27 -0700
Subject: [PATCH 27/44] clang-format
---
clang/lib/Sema/SemaFunctionEffects.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 58615a45e7a204..f357593668bee1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -551,9 +551,9 @@ class Analyzer {
!Viols.empty())
emitDiagnostics(Viols, CInfo, S);
- CompleteFunctionAnalysis *CompletePtr =
- new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects,
- AllInferrableEffectsToVerify);
+ CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+ S.getASTContext(), std::move(Pending), CInfo.Effects,
+ AllInferrableEffectsToVerify);
DeclAnalysis[CInfo.CDecl] = CompletePtr;
LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
DeclAnalysis.dump(S, llvm::dbgs()););
>From 69e1ae67a43e36c1cc0b8fb2dd209ba2e364aded Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 11:19:16 -0700
Subject: [PATCH 28/44] emitDiagnostics doesn't need to receive another S
---
clang/lib/Sema/SemaFunctionEffects.cpp | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f357593668bee1..2b4d321153d1b9 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -549,7 +549,7 @@ class Analyzer {
if (ArrayRef<Violation> Viols =
Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
!Viols.empty())
- emitDiagnostics(Viols, CInfo, S);
+ emitDiagnostics(Viols, CInfo);
CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
S.getASTContext(), std::move(Pending), CInfo.Effects,
@@ -644,8 +644,7 @@ class Analyzer {
// Should only be called when function's analysis is determined to be
// complete.
- void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo,
- Sema &S) {
+ void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo) {
if (Viols.empty())
return;
>From 076302ec7091cd46445df014ced97862e0f47e0c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 11:31:10 -0700
Subject: [PATCH 29/44] VisitVarDecl can reuse followTypeDtor().
---
clang/lib/Sema/SemaFunctionEffects.cpp | 25 ++++++++-----------------
1 file changed, 8 insertions(+), 17 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 2b4d321153d1b9..f36bd2c0ec1d9b 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -892,19 +892,20 @@ class Analyzer {
// follow the field and base destructors.
void followDestructor(const CXXRecordDecl *Rec,
const CXXDestructorDecl *Dtor) {
+ SourceLocation DtorLoc = Dtor->getLocation();
for (const FieldDecl *Field : Rec->fields())
- followTypeDtor(Field->getType(), Dtor);
+ followTypeDtor(Field->getType(), DtorLoc);
if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
for (const CXXBaseSpecifier &Base : Class->bases())
- followTypeDtor(Base.getType(), Dtor);
+ followTypeDtor(Base.getType(), DtorLoc);
for (const CXXBaseSpecifier &Base : Class->vbases())
- followTypeDtor(Base.getType(), Dtor);
+ followTypeDtor(Base.getType(), DtorLoc);
}
}
- void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) {
+ void followTypeDtor(QualType QT, SourceLocation CallSite) {
const Type *Ty = QT.getTypePtr();
while (Ty->isArrayType()) {
const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
@@ -916,7 +917,7 @@ class Analyzer {
if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
CallableInfo CI(*Dtor);
- followCall(CI, OuterDtor->getLocation());
+ followCall(CI, CallSite);
}
}
}
@@ -1007,18 +1008,8 @@ class Analyzer {
const QualType::DestructionKind DK =
Var->needsDestruction(Outer.S.getASTContext());
- if (DK == QualType::DK_cxx_destructor) {
- QualType QT = Var->getType();
- if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
- if (const auto *CxxRec =
- dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
- if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
- CallableInfo CI(*Dtor);
- followCall(CI, Var->getLocation());
- }
- }
- }
- }
+ if (DK == QualType::DK_cxx_destructor)
+ followTypeDtor(Var->getType(), Var->getLocation());
return true;
}
>From 7b891c630e0e7eb55e27c3eca374f7af83e4ecca Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 15:35:12 -0700
Subject: [PATCH 30/44] Add a test for a delegating initializer.
---
clang/test/Sema/attr-nonblocking-constraints.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 0ff43608ac5a23..f51ca1a855880e 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -265,10 +265,16 @@ struct Unsafe {
Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+
+ Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+ // Delegating initializer.
+ Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
};
struct DerivedFromUnsafe : public Unsafe {
DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+ DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
};
>From d1fcceb9b2d54674673f1162b0621d12017ab7e6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 15:35:40 -0700
Subject: [PATCH 31/44] Fix typo in comment for TraverseLambdaExpr.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f36bd2c0ec1d9b..f93193a2f93bdb 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1057,7 +1057,7 @@ class Analyzer {
}
bool TraverseLambdaExpr(LambdaExpr *Lambda) {
- // We override this so as the be able to skip traversal of the lambda's
+ // We override this so as to be able to skip traversal of the lambda's
// body. We have to explicitly traverse the captures. Why not return
// false from shouldVisitLambdaBody()? Because we need to visit a lambda's
// body when we are verifying the lambda itself; we only want to skip it
>From abcf022fc5a1b27a46d9d4a6ab50d897cbdbf489 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 21:37:53 -0700
Subject: [PATCH 32/44] Fix test broken by rewording of diagnostics.
---
clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index ff5873c11c4fe7..262d647142d548 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -8,7 +8,7 @@ - (void)method;
@end
void nb1(OCClass *oc) [[clang::nonblocking]] {
- [oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
+ [oc method]; // expected-warning {{'nonblocking' function must not access ObjC methods or properties}}
}
void nb2(OCClass *oc) {
[oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
>From 47ebf2767f59dd9a62b63c32b47aef0343a83c7c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 21:38:32 -0700
Subject: [PATCH 33/44] Diagnostics are now more specific about where a
construct was found, specially handling member initializers, and more
specifically describing Decls which are constructors, destructors, lambdas
and blocks.
---
.../clang/Basic/DiagnosticSemaKinds.td | 37 +++--
clang/lib/Sema/SemaFunctionEffects.cpp | 145 ++++++++++++++----
.../Sema/attr-nonblocking-constraints.cpp | 51 +++---
3 files changed, 161 insertions(+), 72 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3ebc7089a93d7b..e60088f0703ca3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10951,29 +10951,38 @@ def warn_imp_cast_drops_unaligned : Warning<
// Function effects
def warn_func_effect_violation : Warning<
- "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|"
- "have static local variables|use thread-local variables|access ObjC methods or properties}1">,
+ "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+ "must not %select{allocate or deallocate memory|throw or catch exceptions|"
+ "have static local variables|use thread-local variables|access ObjC methods or properties}2">,
InGroup<FunctionEffects>;
def note_func_effect_violation : Note<
- "function cannot be inferred '%0' because it %select{allocates or deallocates memory|"
- "throws or catches exceptions|has a static local variable|uses a thread-local variable|"
- "accesses an ObjC method or property}1">;
+ "%select{function|constructor|destructor|lambda|block|member initializer}0 "
+ "cannot be inferred '%1' because it "
+ "%select{allocates or deallocates memory|throws or catches exceptions|"
+ "has a static local variable|uses a thread-local variable|"
+ "accesses an ObjC method or property}2">;
def warn_func_effect_calls_func_without_effect : Warning<
- "'%0' function must not call non-'%0' function '%1'">,
+ "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+ "must not call non-'%0' "
+ "%select{function|constructor|destructor|lambda|block}2 "
+ "'%3'">,
InGroup<FunctionEffects>;
def note_func_effect_calls_func_without_effect : Note<
- "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+ "%select{function|constructor|destructor|lambda|block|member initializer}0 "
+ "cannot be inferred '%1' because it calls non-'%1' "
+ "%select{function|constructor|destructor|lambda|block}2 "
+ "'%3'">;
def warn_func_effect_calls_expr_without_effect : Warning<
- "'%0' function must not call non-'%0' expression">,
+ "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+ "must not call non-'%0' expression">,
InGroup<FunctionEffects>;
def note_func_effect_call_extern : Note<
"declaration cannot be inferred '%0' because it has no definition in this translation unit">;
def note_func_effect_call_disallows_inference : Note<
- "function does not permit inference of '%0' because it is declared '%1'">;
-def note_func_effect_call_virtual : Note<
- "virtual method cannot be inferred '%0'">;
-def note_func_effect_call_func_ptr : Note<
- "function pointer cannot be inferred '%0'">;
+ "%select{function|constructor|destructor|lambda|block}0 "
+ "does not permit inference of '%1' because it is declared '%2'">;
+def note_func_effect_call_indirect : Note<
+ "%select{virtual method|function pointer}0 cannot be inferred '%1'">;
def warn_perf_constraint_implies_noexcept : Warning<
"'%0' function should be declared noexcept">,
InGroup<PerfConstraintImpliesNoexcept>;
@@ -10981,6 +10990,8 @@ def warn_perf_constraint_implies_noexcept : Warning<
// FIXME: It would be nice if we could provide fuller template expansion notes.
def note_func_effect_from_template : Note<
"in template expansion here">;
+def note_func_effect_in_constructor : Note<
+ "in%select{| implicit}0 constructor here">;
// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f93193a2f93bdb..acf459ec9555c1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
@@ -40,6 +41,13 @@ enum class ViolationID : uint8_t {
CallsExprWithoutEffect,
};
+// Bits, describing the AST context in which a violation was found.
+// If none present, the context was the function body.
+using ViolationSite = unsigned;
+enum {
+ VSite_MemberInitializer = 1,
+};
+
// Represents a violation of the rules, potentially for the entire duration of
// the analysis phase, in order to refer to it when explaining why a caller has
// been made unsafe by a callee. Can be transformed into either a Diagnostic
@@ -48,17 +56,19 @@ enum class ViolationID : uint8_t {
// be inferred as holding that effect.
struct Violation {
FunctionEffect Effect;
- FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs.
+ FunctionEffect
+ CalleeEffectPreventingInference; // Only for certain IDs; can be None.
ViolationID ID = ViolationID::None;
+ ViolationSite Site;
SourceLocation Loc;
const Decl *Callee = nullptr; // Only valid for Calls*.
Violation() = default;
- Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
- const Decl *Callee = nullptr,
+ Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
+ SourceLocation Loc, const Decl *Callee = nullptr,
std::optional<FunctionEffect> CalleeEffect = std::nullopt)
- : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
+ : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) {
if (CalleeEffect)
CalleeEffectPreventingInference = *CalleeEffect;
}
@@ -220,9 +230,10 @@ class PendingFunctionAnalysis {
// Not all recursive calls are detected, just enough
// to break cycles.
bool Recursed = false;
+ ViolationSite VSite;
- DirectCall(const Decl *D, SourceLocation CallLoc)
- : Callee(D), CallLoc(CallLoc) {}
+ DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite)
+ : Callee(D), CallLoc(CallLoc), VSite(VSite) {}
};
// We always have two disjoint sets of effects to verify:
@@ -246,7 +257,7 @@ class PendingFunctionAnalysis {
PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
FunctionEffectKindSet AllInferrableEffectsToVerify)
: DeclaredVerifiableEffects(CInfo.Effects) {
- // Check for effects we are not allowed to infer
+ // Check for effects we are not allowed to infer.
FunctionEffectKindSet InferrableEffects;
for (FunctionEffect effect : AllInferrableEffectsToVerify) {
@@ -258,12 +269,12 @@ class PendingFunctionAnalysis {
// Add a Violation for this effect if a caller were to
// try to infer it.
InferrableEffectToFirstViolation.maybeInsert(Violation(
- effect, ViolationID::DeclDisallowsInference,
+ effect, ViolationID::DeclDisallowsInference, 0,
CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
}
}
// InferrableEffects is now the set of inferrable effects which are not
- // prohibited
+ // prohibited.
EffectsToInfer = FunctionEffectKindSet::difference(
InferrableEffects, DeclaredVerifiableEffects);
}
@@ -277,8 +288,9 @@ class PendingFunctionAnalysis {
InferrableEffectToFirstViolation.maybeInsert(NewViol);
}
- void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
- UnverifiedDirectCalls.emplace_back(D, CallLoc);
+ void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc,
+ ViolationSite VSite) {
+ UnverifiedDirectCalls.emplace_back(D, CallLoc, VSite);
}
// Analysis is complete when there are no unverified direct calls.
@@ -574,7 +586,7 @@ class Analyzer {
CallableInfo Callee(*Call.Callee);
followCall(Caller, *Pending, Callee, Call.CallLoc,
- /*AssertNoFurtherInference=*/true);
+ /*AssertNoFurtherInference=*/true, Call.VSite);
}
completeAnalysis(Caller, std::move(*Pending));
delete Pending;
@@ -584,7 +596,7 @@ class Analyzer {
// other AST construct. PFA pertains to the caller.
void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
const CallableInfo &Callee, SourceLocation CallLoc,
- bool AssertNoFurtherInference) {
+ bool AssertNoFurtherInference, ViolationSite VSite) {
const bool DirectCall = Callee.isCalledDirectly();
// Initially, the declared effects; inferred effects will be added.
@@ -625,13 +637,14 @@ class Analyzer {
if (Callee.FuncType == SpecialFuncType::None)
PFA.checkAddViolation(Inferring,
{Effect, ViolationID::CallsDeclWithoutEffect,
- CallLoc, Callee.CDecl});
+ VSite, CallLoc, Callee.CDecl});
else
PFA.checkAddViolation(
- Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
+ Inferring,
+ {Effect, ViolationID::AllocatesMemory, VSite, CallLoc});
} else {
// Inference is allowed and necessary; defer it.
- PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+ PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite);
}
};
@@ -658,6 +671,50 @@ class Analyzer {
}
};
+ // For note_func_effect_call_indirect.
+ enum { Indirect_VirtualMethod, Indirect_FunctionPtr };
+
+ // Describe a call site or target using an enum mapping to a %select{}
+ // in a diagnostic.
+ auto SiteDescIndex = [](const Decl *D, const Violation *V) {
+ enum {
+ VS_Function,
+ VS_Constructor,
+ VS_Destructor,
+ VS_Lambda,
+ VS_Block,
+ VS_MemberInitializer,
+ };
+
+ if (V != nullptr && V->Site == VSite_MemberInitializer)
+ return VS_MemberInitializer;
+ if (isa<BlockDecl>(D))
+ return VS_Block;
+ if (auto *Method = dyn_cast<CXXMethodDecl>(D)) {
+ if (isa<CXXConstructorDecl>(D))
+ return VS_Constructor;
+ if (isa<CXXDestructorDecl>(D))
+ return VS_Destructor;
+ const CXXRecordDecl *Rec = Method->getParent();
+ if (Rec->isLambda())
+ return VS_Lambda;
+ }
+ return VS_Function;
+ };
+
+ // If a Violation's site is a member initializer, adds a note referring to
+ // the constructor which invoked it.
+ auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) {
+ if (V.Site == VSite_MemberInitializer) {
+ unsigned ImplicitCtor = 0;
+ if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
+ Ctor && Ctor->isImplicit())
+ ImplicitCtor = 1;
+ S.Diag(D->getLocation(), diag::note_func_effect_in_constructor)
+ << ImplicitCtor;
+ }
+ };
+
// Top-level violations are warnings.
for (const Violation &Viol1 : Viols) {
StringRef effectName = Viol1.Effect.name();
@@ -673,12 +730,15 @@ class Analyzer {
case ViolationID::AccessesThreadLocalVariable:
case ViolationID::AccessesObjCMethodOrProperty:
S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
- << effectName << Viol1.diagnosticSelectIndex();
+ << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+ << Viol1.diagnosticSelectIndex();
+ MaybeAddCtorContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsExprWithoutEffect:
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
- << effectName;
+ << effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
+ MaybeAddCtorContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
@@ -687,7 +747,9 @@ class Analyzer {
std::string CalleeName = CalleeInfo.name(S);
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
- << effectName << CalleeName;
+ << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+ << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
+ MaybeAddCtorContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
@@ -704,16 +766,17 @@ class Analyzer {
CallableType CType = CalleeInfo.type();
if (CType == CallableType::Virtual)
- S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
- << effectName;
+ S.Diag(Callee->getLocation(),
+ diag::note_func_effect_call_indirect)
+ << Indirect_VirtualMethod << effectName;
else if (CType == CallableType::Unknown)
S.Diag(Callee->getLocation(),
- diag::note_func_effect_call_func_ptr)
- << effectName;
+ diag::note_func_effect_call_indirect)
+ << Indirect_FunctionPtr << effectName;
else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
S.Diag(Callee->getLocation(),
diag::note_func_effect_call_disallows_inference)
- << effectName
+ << SiteDescIndex(CInfo.CDecl, nullptr) << effectName
<< FunctionEffect(Viol1.Effect.oppositeKind()).name();
else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
FD == nullptr || FD->getBuiltinID() == 0) {
@@ -736,11 +799,12 @@ class Analyzer {
break;
case ViolationID::DeclDisallowsInference:
S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
- << effectName << Viol2.CalleeEffectPreventingInference.name();
+ << SiteDescIndex(CalleeInfo.CDecl, nullptr) << effectName
+ << Viol2.CalleeEffectPreventingInference.name();
break;
case ViolationID::CallsExprWithoutEffect:
- S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr)
- << effectName;
+ S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect)
+ << Indirect_FunctionPtr << effectName;
break;
case ViolationID::AllocatesMemory:
case ViolationID::ThrowsOrCatchesExceptions:
@@ -748,12 +812,16 @@ class Analyzer {
case ViolationID::AccessesThreadLocalVariable:
case ViolationID::AccessesObjCMethodOrProperty:
S.Diag(Viol2.Loc, diag::note_func_effect_violation)
- << effectName << Viol2.diagnosticSelectIndex();
+ << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
+ << Viol2.diagnosticSelectIndex();
+ MaybeAddCtorContext(CalleeInfo.CDecl, Viol2);
break;
case ViolationID::CallsDeclWithoutEffect:
MaybeNextCallee.emplace(*Viol2.Callee);
S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
- << effectName << MaybeNextCallee->name(S);
+ << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
+ << SiteDescIndex(Viol2.Callee, nullptr)
+ << MaybeNextCallee->name(S);
break;
}
MaybeAddTemplateNote(Callee);
@@ -777,10 +845,12 @@ class Analyzer {
//
// Violations are always routed to a PendingFunctionAnalysis.
struct FunctionBodyASTVisitor : RecursiveASTVisitor<FunctionBodyASTVisitor> {
+ using Base = RecursiveASTVisitor<FunctionBodyASTVisitor>;
Analyzer &Outer;
PendingFunctionAnalysis &CurrentFunction;
CallableInfo &CurrentCaller;
+ ViolationSite VSite = 0;
FunctionBodyASTVisitor(Analyzer &Outer,
PendingFunctionAnalysis &CurrentFunction,
@@ -822,10 +892,10 @@ class Analyzer {
addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
}
- void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D,
+ void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID,
SourceLocation Loc, const Decl *Callee = nullptr) {
- CurrentFunction.checkAddViolation(Inferring,
- Violation(Effect, D, Loc, Callee));
+ CurrentFunction.checkAddViolation(
+ Inferring, Violation(Effect, VID, VSite, Loc, Callee));
}
// Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -836,7 +906,7 @@ class Analyzer {
return;
Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
- /*AssertNoFurtherInference=*/false);
+ /*AssertNoFurtherInference=*/false, VSite);
}
// FIXME: This is currently specific to the `nonblocking` and
@@ -1056,6 +1126,15 @@ class Analyzer {
return true;
}
+ bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
+ ViolationSite PrevVS = VSite;
+ if (Init->isAnyMemberInitializer())
+ VSite = VSite_MemberInitializer;
+ bool Result = Base::TraverseConstructorInitializer(Init);
+ VSite = PrevVS;
+ return Result;
+ }
+
bool TraverseLambdaExpr(LambdaExpr *Lambda) {
// We override this so as to be able to skip traversal of the lambda's
// body. We have to explicitly traverse the captures. Why not return
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index f51ca1a855880e..6caa3d1ad6b226 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -69,7 +69,7 @@ void nb7()
{
// Make sure we verify blocks
auto blk = ^() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nonblocking' block must not throw or catch exceptions}}
};
}
@@ -77,7 +77,7 @@ void nb8()
{
// Make sure we verify lambdas
auto lambda = []() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}}
};
}
@@ -228,54 +228,53 @@ void nb20() [[clang::nonblocking]] {
struct S {
int x;
- S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
- S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+ S(int x) try : x(x) {} catch (...) {} // expected-note {{constructor cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+ S(double) : x((throw 3, 3)) {} // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}} \
+ expected-note {{in constructor here}}
};
-int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+ // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-struct A {
- int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+struct A { // expected-note {{in implicit constructor here}}
+ int x = (throw 3, 3); // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}}
};
struct B {
- int y = badi(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
+ int y = badi(); // expected-note {{member initializer cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
};
void f() [[clang::nonblocking]] {
- S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
- S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
- A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}}
- B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}}
+ S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
+ S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
+ A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}}
+ B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}}
}
-#if 0
-// FIXME: can we do better with default member initializers?
struct T {
- int x = badi();
- T() [[clang::nonblocking]] {} // Warning: this calls bad().
- T(int x) [[clang::nonblocking]] : x(x) {} // This does not.
+ int x = badi(); // expected-warning {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}}
+ T() [[clang::nonblocking]] {} // expected-note {{in constructor here}}
+ T(int x) [[clang::nonblocking]] : x(x) {} // OK
};
-#endif
// Verify traversal of implicit code paths - constructors and destructors.
struct Unsafe {
- static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
- static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+ static void problem2(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
- Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
- ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+ Unsafe() { problem1(); } // expected-note {{constructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
+ ~Unsafe() { problem2(); } // expected-note {{destructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
// Delegating initializer.
- Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+ Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
};
struct DerivedFromUnsafe : public Unsafe {
- DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
- DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
- ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
+ DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+ DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+ ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
};
// Contexts where there is no function call, no diagnostic.
>From 6650c1fae8d1023dffb8debf42ea7f4b2b6753d9 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 20 Aug 2024 08:39:01 -0700
Subject: [PATCH 34/44] Two fixes for default arguments: - they are never
attributed to the function, only its callers. - while the warning points to
the expression in the declaration, add a note pointing to the caller.
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/lib/Sema/SemaFunctionEffects.cpp | 70 ++++++++++++++-----
.../Sema/attr-nonblocking-constraints.cpp | 10 +++
3 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e60088f0703ca3..ee6072fbb38ca4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10992,6 +10992,8 @@ def note_func_effect_from_template : Note<
"in template expansion here">;
def note_func_effect_in_constructor : Note<
"in%select{| implicit}0 constructor here">;
+def note_in_evaluating_default_argument : Note<
+ "in evaluating default argument here">;
// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index acf459ec9555c1..9166aaa3d1ab7b 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -41,11 +41,17 @@ enum class ViolationID : uint8_t {
CallsExprWithoutEffect,
};
-// Bits, describing the AST context in which a violation was found.
-// If none present, the context was the function body.
-using ViolationSite = unsigned;
-enum {
- VSite_MemberInitializer = 1,
+// Information about the AST context in which a violation was found, so
+// that diagnostics can point to the correct source.
+struct ViolationSite {
+ enum class Kind : uint8_t {
+ Default = 0, // Function body.
+ MemberInitializer = 1,
+ DefaultArgExpr = 2
+ };
+
+ Kind VKind = Kind::Default;
+ CXXDefaultArgExpr *DefaultArgExpr = nullptr;
};
// Represents a violation of the rules, potentially for the entire duration of
@@ -269,7 +275,7 @@ class PendingFunctionAnalysis {
// Add a Violation for this effect if a caller were to
// try to infer it.
InferrableEffectToFirstViolation.maybeInsert(Violation(
- effect, ViolationID::DeclDisallowsInference, 0,
+ effect, ViolationID::DeclDisallowsInference, ViolationSite{},
CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
}
}
@@ -686,7 +692,8 @@ class Analyzer {
VS_MemberInitializer,
};
- if (V != nullptr && V->Site == VSite_MemberInitializer)
+ if (V != nullptr &&
+ V->Site.VKind == ViolationSite::Kind::MemberInitializer)
return VS_MemberInitializer;
if (isa<BlockDecl>(D))
return VS_Block;
@@ -702,10 +709,10 @@ class Analyzer {
return VS_Function;
};
- // If a Violation's site is a member initializer, adds a note referring to
- // the constructor which invoked it.
- auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) {
- if (V.Site == VSite_MemberInitializer) {
+ auto MaybeAddSiteContext = [&](const Decl *D, const Violation &V) {
+ // If a violation site is a member initializer, add a note pointing to
+ // the constructor which invoked it.
+ if (V.Site.VKind == ViolationSite::Kind::MemberInitializer) {
unsigned ImplicitCtor = 0;
if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
Ctor && Ctor->isImplicit())
@@ -713,6 +720,12 @@ class Analyzer {
S.Diag(D->getLocation(), diag::note_func_effect_in_constructor)
<< ImplicitCtor;
}
+
+ // If a violation site is a default argument expression, add a note
+ // pointing to the call site using the default argument.
+ else if (V.Site.VKind == ViolationSite::Kind::DefaultArgExpr)
+ S.Diag(V.Site.DefaultArgExpr->getUsedLocation(),
+ diag::note_in_evaluating_default_argument);
};
// Top-level violations are warnings.
@@ -732,13 +745,13 @@ class Analyzer {
S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
<< effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
<< Viol1.diagnosticSelectIndex();
- MaybeAddCtorContext(CInfo.CDecl, Viol1);
+ MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsExprWithoutEffect:
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
<< effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
- MaybeAddCtorContext(CInfo.CDecl, Viol1);
+ MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
@@ -749,7 +762,7 @@ class Analyzer {
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
<< effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
<< SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
- MaybeAddCtorContext(CInfo.CDecl, Viol1);
+ MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
// Emit notes explaining the transitive chain of inferences: Why isn't
@@ -814,7 +827,7 @@ class Analyzer {
S.Diag(Viol2.Loc, diag::note_func_effect_violation)
<< SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
<< Viol2.diagnosticSelectIndex();
- MaybeAddCtorContext(CalleeInfo.CDecl, Viol2);
+ MaybeAddSiteContext(CalleeInfo.CDecl, Viol2);
break;
case ViolationID::CallsDeclWithoutEffect:
MaybeNextCallee.emplace(*Viol2.Callee);
@@ -850,7 +863,7 @@ class Analyzer {
Analyzer &Outer;
PendingFunctionAnalysis &CurrentFunction;
CallableInfo &CurrentCaller;
- ViolationSite VSite = 0;
+ ViolationSite VSite;
FunctionBodyASTVisitor(Analyzer &Outer,
PendingFunctionAnalysis &CurrentFunction,
@@ -1129,12 +1142,35 @@ class Analyzer {
bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
ViolationSite PrevVS = VSite;
if (Init->isAnyMemberInitializer())
- VSite = VSite_MemberInitializer;
+ VSite.VKind = ViolationSite::Kind::MemberInitializer;
bool Result = Base::TraverseConstructorInitializer(Init);
VSite = PrevVS;
return Result;
}
+ bool TraverseCXXDefaultArgExpr(CXXDefaultArgExpr *E) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "TraverseCXXDefaultArgExpr : "
+ << E->getUsedLocation().printToString(Outer.S.SourceMgr)
+ << "\n";);
+
+ ViolationSite PrevVS = VSite;
+ if (VSite.VKind == ViolationSite::Kind::Default)
+ VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr,
+ .DefaultArgExpr = E};
+
+ bool Result = Base::TraverseCXXDefaultArgExpr(E);
+ VSite = PrevVS;
+ return Result;
+ }
+
+ bool TraverseParmVarDecl(ParmVarDecl *PV) {
+ // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid
+ // incorrectly attributing default argument expressions to this function;
+ // they are properly attributed to callers, via a CXXDefaultArgExpr.
+ return Base::TraverseVarDecl(PV);
+ }
+
bool TraverseLambdaExpr(LambdaExpr *Lambda) {
// We override this so as to be able to skip traversal of the lambda's
// body. We have to explicitly traverse the captures. Why not return
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 6caa3d1ad6b226..1e90696f00e0be 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -257,6 +257,16 @@ struct T {
T(int x) [[clang::nonblocking]] : x(x) {} // OK
};
+// Default arguments
+int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+}
+
+void nb21() [[clang::nonblocking]] {
+ hasDefaultArg(); // expected-note {{in evaluating default argument here}}
+}
+
// Verify traversal of implicit code paths - constructors and destructors.
struct Unsafe {
static void problem1(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
>From 250b80becb3769f09d2af32434e0322bfa08b83f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 20 Aug 2024 08:39:28 -0700
Subject: [PATCH 35/44] Type.cpp: remove asserts preceding llvm_unreachable().
---
clang/lib/AST/Type.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8d36aa15c59572..9c50b6b6087fa4 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5159,10 +5159,9 @@ std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
break;
case Kind::None:
- assert(0 && "effectProhibitingInference with None");
break;
}
- llvm_unreachable("unknown effect kind");
+ llvm_unreachable("unknown effect kind or None");
}
bool FunctionEffect::shouldDiagnoseFunctionCall(
@@ -5185,10 +5184,9 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
case Kind::Blocking:
return false;
case Kind::None:
- assert(0 && "shouldDiagnoseFunctionCall with None");
break;
}
- llvm_unreachable("unknown effect kind");
+ llvm_unreachable("unknown effect kind or None");
}
// =====
>From e8bcd9f6962a2b5c2286fccfcd1716b5b180bf47 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 22 Aug 2024 09:28:27 -0700
Subject: [PATCH 36/44] Fix pre-C++20 compile error. Test an unsafe default
argument.
---
clang/include/clang/AST/Type.h | 1 +
clang/lib/Sema/SemaFunctionEffects.cpp | 18 +++++++++---------
.../test/Sema/attr-nonblocking-constraints.cpp | 13 ++++++++++---
3 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 59221dcb85eeba..49077750744fff 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4998,6 +4998,7 @@ class FunctionEffectKindSet {
}
void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
+ bool empty() const { return KindBits.none(); }
bool contains(const FunctionEffect::Kind EK) const {
return KindBits.test(kindToPos(EK));
}
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 9166aaa3d1ab7b..9310171a401b0d 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -37,6 +37,10 @@ enum class ViolationID : uint8_t {
// These only apply to callees, where the analysis stops at the Decl.
DeclDisallowsInference,
+ // These both apply to indirect calls. The difference is that sometimes
+ // we have an actual Decl (generally a variable) which is the function
+ // pointer being called, and sometimes, typically due to a cast, we only
+ // have an expression.
CallsDeclWithoutEffect,
CallsExprWithoutEffect,
};
@@ -52,6 +56,10 @@ struct ViolationSite {
Kind VKind = Kind::Default;
CXXDefaultArgExpr *DefaultArgExpr = nullptr;
+
+ ViolationSite() = default;
+ explicit ViolationSite(CXXDefaultArgExpr *E)
+ : VKind(Kind::DefaultArgExpr), DefaultArgExpr(E) {}
};
// Represents a violation of the rules, potentially for the entire duration of
@@ -1156,21 +1164,13 @@ class Analyzer {
ViolationSite PrevVS = VSite;
if (VSite.VKind == ViolationSite::Kind::Default)
- VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr,
- .DefaultArgExpr = E};
+ VSite = ViolationSite{E};
bool Result = Base::TraverseCXXDefaultArgExpr(E);
VSite = PrevVS;
return Result;
}
- bool TraverseParmVarDecl(ParmVarDecl *PV) {
- // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid
- // incorrectly attributing default argument expressions to this function;
- // they are properly attributed to callers, via a CXXDefaultArgExpr.
- return Base::TraverseVarDecl(PV);
- }
-
bool TraverseLambdaExpr(LambdaExpr *Lambda) {
// We override this so as to be able to skip traversal of the lambda's
// body. We have to explicitly traverse the captures. Why not return
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 1e90696f00e0be..ca9fe199f5f668 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -258,13 +258,20 @@ struct T {
};
// Default arguments
-int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+ expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+ expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} \
+ expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}}
}
void nb21() [[clang::nonblocking]] {
- hasDefaultArg(); // expected-note {{in evaluating default argument here}}
+ hasDefaultArg(); // expected-note {{in evaluating default argument here}} \
+ expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}}
+}
+
+void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
}
// Verify traversal of implicit code paths - constructors and destructors.
>From ea7f3fc8c0b5269e430916c3f8955c694dbc9af5 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 09:43:54 -0500
Subject: [PATCH 37/44] Review feedback: ViolationSite can use a
PointerIntPair. Expand list of known builtins and make the implementation
slightly less specific to nonblocking/nonallocating.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 140 +++++++++++++++++--------
1 file changed, 98 insertions(+), 42 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 9310171a401b0d..186be26f767d91 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -47,19 +47,27 @@ enum class ViolationID : uint8_t {
// Information about the AST context in which a violation was found, so
// that diagnostics can point to the correct source.
-struct ViolationSite {
+class ViolationSite {
+public:
enum class Kind : uint8_t {
Default = 0, // Function body.
MemberInitializer = 1,
DefaultArgExpr = 2
};
- Kind VKind = Kind::Default;
- CXXDefaultArgExpr *DefaultArgExpr = nullptr;
+private:
+ llvm::PointerIntPair<CXXDefaultArgExpr *, 2, Kind> Impl;
+public:
ViolationSite() = default;
+
explicit ViolationSite(CXXDefaultArgExpr *E)
- : VKind(Kind::DefaultArgExpr), DefaultArgExpr(E) {}
+ : Impl(E, Kind::DefaultArgExpr) {}
+
+ Kind kind() const { return static_cast<Kind>(Impl.getInt()); }
+ CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); }
+
+ void setKind(Kind K) { Impl.setPointerAndInt(nullptr, K); }
};
// Represents a violation of the rules, potentially for the entire duration of
@@ -119,6 +127,74 @@ static bool isNoexcept(const FunctionDecl *FD) {
return false;
}
+static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
+ FunctionEffectKindSet Result;
+
+ switch (BuiltinID) {
+ case 0: // Not builtin.
+ default: // By default, builtins have no known effects.
+ break;
+
+ // These allocate/deallocate heap memory.
+ case Builtin::ID::BI__builtin_calloc:
+ case Builtin::ID::BI__builtin_malloc:
+ case Builtin::ID::BI__builtin_realloc:
+ case Builtin::ID::BI__builtin_free:
+ case Builtin::ID::BI__builtin_operator_delete:
+ case Builtin::ID::BI__builtin_operator_new:
+ case Builtin::ID::BIaligned_alloc:
+ case Builtin::ID::BIcalloc:
+ case Builtin::ID::BImalloc:
+ case Builtin::ID::BImemalign:
+ case Builtin::ID::BIrealloc:
+ case Builtin::ID::BIfree:
+
+ case Builtin::ID::BI__builtin_unwind_init: // ????
+ // __builtin_eh_return?
+ // __builtin_allow_runtime_check
+ // va_copy
+ // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf
+ // scanf family
+ // coroutine intrinsics?
+
+ case Builtin::ID::BIfopen:
+ case Builtin::ID::BIpthread_create:
+ case Builtin::ID::BI_Block_object_dispose:
+ Result.insert(FunctionEffect(FunctionEffect::Kind::Allocating));
+ break;
+
+ // These block in some other way than allocating memory.
+ case Builtin::ID::BIlongjmp:
+ case Builtin::ID::BI_longjmp:
+ case Builtin::ID::BIsiglongjmp:
+ case Builtin::ID::BI__builtin_longjmp:
+ case Builtin::ID::BIobjc_exception_throw:
+
+ case Builtin::ID::BIobjc_msgSend:
+ case Builtin::ID::BIobjc_msgSend_fpret:
+ case Builtin::ID::BIobjc_msgSend_fp2ret:
+ case Builtin::ID::BIobjc_msgSend_stret:
+ case Builtin::ID::BIobjc_msgSendSuper:
+ case Builtin::ID::BIobjc_getClass:
+ case Builtin::ID::BIobjc_getMetaClass:
+ case Builtin::ID::BIobjc_enumerationMutation:
+ case Builtin::ID::BIobjc_assign_ivar:
+ case Builtin::ID::BIobjc_assign_global:
+ case Builtin::ID::BIobjc_sync_enter:
+ case Builtin::ID::BIobjc_sync_exit:
+ case Builtin::ID::BINSLog:
+ case Builtin::ID::BINSLogv:
+
+ case Builtin::ID::BIfread:
+ case Builtin::ID::BIfwrite:
+ Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking));
+ break;
+ }
+
+ return Result;
+}
+
+
// Transitory, more extended information about a callable, which can be a
// function, block, or function pointer.
struct CallableInfo {
@@ -701,7 +777,7 @@ class Analyzer {
};
if (V != nullptr &&
- V->Site.VKind == ViolationSite::Kind::MemberInitializer)
+ V->Site.kind() == ViolationSite::Kind::MemberInitializer)
return VS_MemberInitializer;
if (isa<BlockDecl>(D))
return VS_Block;
@@ -720,7 +796,7 @@ class Analyzer {
auto MaybeAddSiteContext = [&](const Decl *D, const Violation &V) {
// If a violation site is a member initializer, add a note pointing to
// the constructor which invoked it.
- if (V.Site.VKind == ViolationSite::Kind::MemberInitializer) {
+ if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) {
unsigned ImplicitCtor = 0;
if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
Ctor && Ctor->isImplicit())
@@ -731,8 +807,8 @@ class Analyzer {
// If a violation site is a default argument expression, add a note
// pointing to the call site using the default argument.
- else if (V.Site.VKind == ViolationSite::Kind::DefaultArgExpr)
- S.Diag(V.Site.DefaultArgExpr->getUsedLocation(),
+ else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr)
+ S.Diag(V.Site.defaultArgExpr()->getUsedLocation(),
diag::note_in_evaluating_default_argument);
};
@@ -921,43 +997,23 @@ class Analyzer {
// Here we have a call to a Decl, either explicitly via a CallExpr or some
// other AST construct. CallableInfo pertains to the callee.
- void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
- if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
- FD && isSafeBuiltinFunction(FD))
- return;
+ void followCall(CallableInfo &CI, SourceLocation CallLoc) {
+ // Check for a call to a builtin function, whose effects are
+ // handled specially.
+ if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl)) {
+ if (unsigned BuiltinID = FD->getBuiltinID()) {
+ CI.Effects = getBuiltinFunctionEffects(BuiltinID);
+ if (CI.Effects.empty()) {
+ // A builtin with no known effects is assumed safe.
+ return;
+ }
+ }
+ }
Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
/*AssertNoFurtherInference=*/false, VSite);
}
- // FIXME: This is currently specific to the `nonblocking` and
- // `nonallocating` effects. More ideally, the builtin functions themselves
- // would have the `allocating` attribute.
- static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
- unsigned BuiltinID = FD->getBuiltinID();
- switch (BuiltinID) {
- case 0: // Not builtin.
- return false;
- default: // Not disallowed via cases below.
- return true;
-
- // Disallow list
- case Builtin::ID::BIaligned_alloc:
- case Builtin::ID::BI__builtin_calloc:
- case Builtin::ID::BI__builtin_malloc:
- case Builtin::ID::BI__builtin_realloc:
- case Builtin::ID::BI__builtin_free:
- case Builtin::ID::BI__builtin_operator_delete:
- case Builtin::ID::BI__builtin_operator_new:
- case Builtin::ID::BIcalloc:
- case Builtin::ID::BImalloc:
- case Builtin::ID::BImemalign:
- case Builtin::ID::BIrealloc:
- case Builtin::ID::BIfree:
- return false;
- }
- }
-
void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
auto *FPT =
CalleeType->getAs<FunctionProtoType>(); // Null if FunctionType.
@@ -1150,7 +1206,7 @@ class Analyzer {
bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
ViolationSite PrevVS = VSite;
if (Init->isAnyMemberInitializer())
- VSite.VKind = ViolationSite::Kind::MemberInitializer;
+ VSite.setKind(ViolationSite::Kind::MemberInitializer);
bool Result = Base::TraverseConstructorInitializer(Init);
VSite = PrevVS;
return Result;
@@ -1163,7 +1219,7 @@ class Analyzer {
<< "\n";);
ViolationSite PrevVS = VSite;
- if (VSite.VKind == ViolationSite::Kind::Default)
+ if (VSite.kind() == ViolationSite::Kind::Default)
VSite = ViolationSite{E};
bool Result = Base::TraverseCXXDefaultArgExpr(E);
>From d1a39e2792c200fa7647451bd7d4a30fc9fdb815 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 10:47:17 -0700
Subject: [PATCH 38/44] Fix: was traversing virtual base class destructors
twice, exposed by new test.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 6 +-----
clang/test/Sema/attr-nonblocking-constraints.cpp | 14 ++++++++++++++
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 186be26f767d91..d2e3f1b44ba0cb 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1043,13 +1043,9 @@ class Analyzer {
for (const FieldDecl *Field : Rec->fields())
followTypeDtor(Field->getType(), DtorLoc);
- if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+ if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec))
for (const CXXBaseSpecifier &Base : Class->bases())
followTypeDtor(Base.getType(), DtorLoc);
-
- for (const CXXBaseSpecifier &Base : Class->vbases())
- followTypeDtor(Base.getType(), DtorLoc);
- }
}
void followTypeDtor(QualType QT, SourceLocation CallSite) {
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index ca9fe199f5f668..2890199c609106 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -294,6 +294,20 @@ struct DerivedFromUnsafe : public Unsafe {
~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
};
+// Virtual inheritance
+struct VBase {
+ int *Ptr;
+
+ VBase() { Ptr = new int; } // expected-note {{constructor cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
+ virtual ~VBase() { delete Ptr; } // expected-note {{virtual method cannot be inferred 'nonblocking'}}
+};
+
+struct VDerived : virtual VBase {
+ VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}}
+
+ ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'VBase::~VBase'}}
+};
+
// Contexts where there is no function call, no diagnostic.
bool bad();
>From 75365efc34f8d4255f965940b7a5e3f3aa3fd122 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 11:03:09 -0700
Subject: [PATCH 39/44] More builtin functions.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 40 +++++++++++++++++++-------
1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index d2e3f1b44ba0cb..423ea54ec56313 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -63,7 +63,7 @@ class ViolationSite {
explicit ViolationSite(CXXDefaultArgExpr *E)
: Impl(E, Kind::DefaultArgExpr) {}
-
+
Kind kind() const { return static_cast<Kind>(Impl.getInt()); }
CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); }
@@ -127,6 +127,13 @@ static bool isNoexcept(const FunctionDecl *FD) {
return false;
}
+// This list is probably incomplete.
+// FIXME: Investigate:
+// __builtin_eh_return?
+// __builtin_allow_runtime_check?
+// __builtin_unwind_init and other similar things that sound exception-related.
+// va_copy?
+// coroutines?
static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
FunctionEffectKindSet Result;
@@ -149,14 +156,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
case Builtin::ID::BIrealloc:
case Builtin::ID::BIfree:
- case Builtin::ID::BI__builtin_unwind_init: // ????
- // __builtin_eh_return?
- // __builtin_allow_runtime_check
- // va_copy
- // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf
- // scanf family
- // coroutine intrinsics?
-
case Builtin::ID::BIfopen:
case Builtin::ID::BIpthread_create:
case Builtin::ID::BI_Block_object_dispose:
@@ -170,6 +169,7 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
case Builtin::ID::BI__builtin_longjmp:
case Builtin::ID::BIobjc_exception_throw:
+ // Objective-C runtime.
case Builtin::ID::BIobjc_msgSend:
case Builtin::ID::BIobjc_msgSend_fpret:
case Builtin::ID::BIobjc_msgSend_fp2ret:
@@ -185,8 +185,29 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
case Builtin::ID::BINSLog:
case Builtin::ID::BINSLogv:
+ // stdio.h
case Builtin::ID::BIfread:
case Builtin::ID::BIfwrite:
+
+ // stdio.h: printf family.
+ case Builtin::ID::BIprintf:
+ case Builtin::ID::BI__builtin_printf:
+ case Builtin::ID::BIfprintf:
+ case Builtin::ID::BIsnprintf:
+ case Builtin::ID::BIsprintf:
+ case Builtin::ID::BIvprintf:
+ case Builtin::ID::BIvfprintf:
+ case Builtin::ID::BIvsnprintf:
+ case Builtin::ID::BIvsprintf:
+
+ // stdio.h: scanf family.
+ case Builtin::ID::BIscanf:
+ case Builtin::ID::BIfscanf:
+ case Builtin::ID::BIsscanf:
+ case Builtin::ID::BIvscanf:
+ case Builtin::ID::BIvfscanf:
+ case Builtin::ID::BIvsscanf:
+
Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking));
break;
}
@@ -194,7 +215,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
return Result;
}
-
// Transitory, more extended information about a callable, which can be a
// function, block, or function pointer.
struct CallableInfo {
>From 6aadec09c3e1d2fd6b22b1e3f475433eaf81a31e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Wed, 4 Sep 2024 11:10:21 -0700
Subject: [PATCH 40/44] Apply suggestions from code review
Co-authored-by: Erich Keane <ekeane at nvidia.com>
---
clang/include/clang/AST/Type.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 49077750744fff..ad3fdf425b4316 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4949,7 +4949,7 @@ class FunctionEffectKindSet {
explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
constexpr static size_t kindToPos(FunctionEffect::Kind K) {
- return size_t(K);
+ return static_assert<size_t>(K);
}
public:
@@ -4983,7 +4983,7 @@ class FunctionEffectKindSet {
}
FunctionEffect operator*() const {
- assert(Idx < EndBitPos);
+ assert(Idx < EndBitPos && "Dereference of end iterator");
return FunctionEffect(FunctionEffect::Kind(Idx));
}
};
>From 9b123a64f1da2ad903defcbe2395ad67779ba628 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 13:26:36 -0700
Subject: [PATCH 41/44] Review feedback: - Add operator<< for FunctionEffect
and FunctionEffectWithCondition. - Simplified dump methods for
FunctionEffectsRef and FunctionEffectKindSet. - Diagnostics now say "function
with 'nonblocking' attribute" (where appropriate). - Other more minor tweaks.
---
clang/include/clang/AST/Type.h | 11 ++-
.../clang/Basic/DiagnosticSemaKinds.td | 13 ++--
clang/include/clang/Sema/Sema.h | 4 +-
clang/include/clang/Serialization/ASTReader.h | 2 +-
clang/lib/AST/Type.cpp | 38 ++++-----
clang/lib/Sema/SemaFunctionEffects.cpp | 16 ++--
clang/lib/Serialization/ASTReader.cpp | 21 ++---
.../Sema/attr-nonblocking-constraints-ms.cpp | 4 +-
.../Sema/attr-nonblocking-constraints.cpp | 78 +++++++++----------
9 files changed, 94 insertions(+), 93 deletions(-)
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index ad3fdf425b4316..e86a628cbc9e7e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4777,6 +4777,12 @@ class FunctionEffect {
/// The description printed in diagnostics, e.g. 'nonblocking'.
StringRef name() const;
+ friend raw_ostream &operator<<(raw_ostream &OS,
+ const FunctionEffect &Effect) {
+ OS << Effect.name();
+ return OS;
+ }
+
/// Determine whether the effect is allowed to be inferred on the callee,
/// which is either a FunctionDecl or BlockDecl. If the returned optional
/// is empty, inference is permitted; otherwise it holds the effect which
@@ -4834,6 +4840,9 @@ struct FunctionEffectWithCondition {
/// Return a textual description of the effect, and its condition, if any.
std::string description() const;
+
+ friend raw_ostream &operator<<(raw_ostream &OS,
+ const FunctionEffectWithCondition &CFE);
};
/// Support iteration in parallel through a pair of FunctionEffect and
@@ -4949,7 +4958,7 @@ class FunctionEffectKindSet {
explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
constexpr static size_t kindToPos(FunctionEffect::Kind K) {
- return static_assert<size_t>(K);
+ return static_cast<size_t>(K);
}
public:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ee6072fbb38ca4..efdfd53616cc9d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10951,7 +10951,8 @@ def warn_imp_cast_drops_unaligned : Warning<
// Function effects
def warn_func_effect_violation : Warning<
- "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+ "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+ "with '%1' attribute "
"must not %select{allocate or deallocate memory|throw or catch exceptions|"
"have static local variables|use thread-local variables|access ObjC methods or properties}2">,
InGroup<FunctionEffects>;
@@ -10962,8 +10963,9 @@ def note_func_effect_violation : Note<
"has a static local variable|uses a thread-local variable|"
"accesses an ObjC method or property}2">;
def warn_func_effect_calls_func_without_effect : Warning<
- "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
- "must not call non-'%0' "
+ "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+ "with '%1' attribute "
+ "must not call non-'%1' "
"%select{function|constructor|destructor|lambda|block}2 "
"'%3'">,
InGroup<FunctionEffects>;
@@ -10973,7 +10975,8 @@ def note_func_effect_calls_func_without_effect : Note<
"%select{function|constructor|destructor|lambda|block}2 "
"'%3'">;
def warn_func_effect_calls_expr_without_effect : Warning<
- "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+ "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+ "with '%1' attribute "
"must not call non-'%0' expression">,
InGroup<FunctionEffects>;
def note_func_effect_call_extern : Note<
@@ -10984,7 +10987,7 @@ def note_func_effect_call_disallows_inference : Note<
def note_func_effect_call_indirect : Note<
"%select{virtual method|function pointer}0 cannot be inferred '%1'">;
def warn_perf_constraint_implies_noexcept : Warning<
- "'%0' function should be declared noexcept">,
+ "function with '%0' attribute should be declared noexcept">,
InGroup<PerfConstraintImpliesNoexcept>;
// FIXME: It would be nice if we could provide fuller template expansion notes.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e5f9c3c7dbb09b..370985f7ce4bba 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15053,8 +15053,8 @@ class Sema final : public SemaBase {
FunctionEffect::Kind EffectKind;
Kind DiffKind;
- FunctionEffectWithCondition Old; // invalid when Added.
- FunctionEffectWithCondition New; // invalid when Removed.
+ FunctionEffectWithCondition Old; // invalid when Kind is Added.
+ FunctionEffectWithCondition New; // invalid when Kind is Removed.
StringRef effectName() const {
if (Old.Effect.kind() != FunctionEffect::Kind::None)
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 67032b684f4df0..46369b39cc4559 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -967,7 +967,7 @@ class ASTReader
llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
/// The IDs of all decls with function effects to be checked.
- SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify;
+ SmallVector<GlobalDeclID> DeclsWithEffectsToVerify;
private:
struct ImportedSubmodule {
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 9c50b6b6087fa4..05b5e2c9b494c9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5290,21 +5290,24 @@ FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
return Combined;
}
+namespace clang {
+
+raw_ostream &operator<<(raw_ostream &OS,
+ const FunctionEffectWithCondition &CFE) {
+ OS << CFE.Effect.name();
+ if (Expr *E = CFE.Cond.getCondition()) {
+ OS << '(';
+ E->dump();
+ OS << ')';
+ }
+ return OS;
+}
+
+} // namespace clang
+
LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
- bool First = true;
- for (const auto &CFE : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << CFE.Effect.name();
- if (Expr *E = CFE.Cond.getCondition()) {
- OS << '(';
- E->dump();
- OS << ')';
- }
- }
+ llvm::interleaveComma(*this, OS);
OS << "}";
}
@@ -5314,14 +5317,7 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const {
OS << "Effects{";
- bool First = true;
- for (const auto &Effect : *this) {
- if (!First)
- OS << ", ";
- else
- First = false;
- OS << Effect.name();
- }
+ llvm::interleaveComma(*this, OS);
OS << "}";
}
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 423ea54ec56313..c1df87bcacea01 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -90,10 +90,9 @@ struct Violation {
Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
SourceLocation Loc, const Decl *Callee = nullptr,
std::optional<FunctionEffect> CalleeEffect = std::nullopt)
- : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) {
- if (CalleeEffect)
- CalleeEffectPreventingInference = *CalleeEffect;
- }
+ : Effect(Effect), CalleeEffectPreventingInference(
+ CalleeEffect.value_or(FunctionEffect())),
+ ID(ID), Site(VS), Loc(Loc), Callee(Callee) {}
unsigned diagnosticSelectIndex() const {
return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
@@ -102,7 +101,7 @@ struct Violation {
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
enum class CallableType : uint8_t {
- // Unknown: probably function pointer
+ // Unknown: probably function pointer.
Unknown,
Function,
Virtual,
@@ -847,14 +846,14 @@ class Analyzer {
case ViolationID::AccessesThreadLocalVariable:
case ViolationID::AccessesObjCMethodOrProperty:
S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
- << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+ << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName
<< Viol1.diagnosticSelectIndex();
MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
case ViolationID::CallsExprWithoutEffect:
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
- << effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
+ << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName;
MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
break;
@@ -864,7 +863,7 @@ class Analyzer {
std::string CalleeName = CalleeInfo.name(S);
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
- << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+ << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName
<< SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
MaybeAddSiteContext(CInfo.CDecl, Viol1);
MaybeAddTemplateNote(CInfo.CDecl);
@@ -1362,7 +1361,6 @@ void Sema::diagnoseFunctionEffectMergeConflicts(
}
}
-// Should only be called when getFunctionEffects() returns a non-empty set.
// Decl should be a FunctionDecl or BlockDecl.
void Sema::maybeAddDeclWithEffects(const Decl *D,
const FunctionEffectsRef &FX) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 306bbc097a7df4..2a87631a603ab2 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8397,19 +8397,14 @@ void ASTReader::InitializeSema(Sema &S) {
NewOverrides.applyOverrides(SemaObj->getLangOpts());
}
- if (!DeclsWithEffectsToVerify.empty()) {
- for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
- Decl *D = GetDecl(ID);
- FunctionEffectsRef FX;
- if (auto *FD = dyn_cast<FunctionDecl>(D))
- FX = FD->getFunctionEffects();
- else if (auto *BD = dyn_cast<BlockDecl>(D))
- FX = BD->getFunctionEffects();
- if (!FX.empty())
- SemaObj->addDeclWithEffects(D, FX);
- }
- DeclsWithEffectsToVerify.clear();
- }
+ for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
+ Decl *D = GetDecl(ID);
+ if (auto *FD = dyn_cast<FunctionDecl>(D))
+ SemaObj->addDeclWithEffects(FD, FD->getFunctionEffects());
+ else if (auto *BD = dyn_cast<BlockDecl>(D))
+ SemaObj->addDeclWithEffects(BD, BD->getFunctionEffects());
+ }
+ DeclsWithEffectsToVerify.clear();
SemaObj->OpenCLFeatures = OpenCLExtensions;
diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
index d2c25da462c404..b81d3d475e47fc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -4,7 +4,7 @@
// These need '-fms-extensions' (and maybe '-fdeclspec')
void f1() [[clang::nonblocking]] {
- __try {} __except (1) {} // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ __try {} __except (1) {} // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
}
struct S {
@@ -20,7 +20,7 @@ struct S {
void f2() [[clang::nonblocking]] {
S a;
- a.x; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}}
+ a.x; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'S::get_x'}}
a.nb;
a.nb2;
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 2890199c609106..9ae30a39ff772c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -9,21 +9,21 @@
void nb1() [[clang::nonblocking]]
{
- int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
- delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+ int *pInt = new int; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
+ delete pInt; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
}
void nb2() [[clang::nonblocking]]
{
- static int global; // expected-warning {{'nonblocking' function must not have static local variables}}
+ static int global; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
}
void nb3() [[clang::nonblocking]]
{
try {
- throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
}
- catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
}
}
@@ -33,7 +33,7 @@ void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblo
void nb4() [[clang::nonblocking]]
{
nb4_inline(); // OK
- nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
}
@@ -44,7 +44,7 @@ struct HasVirtual {
void nb5() [[clang::nonblocking]]
{
HasVirtual hv;
- hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ hv.unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
}
void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
@@ -55,21 +55,21 @@ void nb6_transitively_unsafe()
void nb6() [[clang::nonblocking]]
{
- nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb6_transitively_unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
}
thread_local int tl_var{ 42 };
bool tl_test() [[clang::nonblocking]]
{
- return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
+ return tl_var > 0; // expected-warning {{function with 'nonblocking' attribute must not use thread-local variables}}
}
void nb7()
{
// Make sure we verify blocks
auto blk = ^() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' block must not throw or catch exceptions}}
+ throw 42; // expected-warning {{block with 'nonblocking' attribute must not throw or catch exceptions}}
};
}
@@ -77,7 +77,7 @@ void nb8()
{
// Make sure we verify lambdas
auto lambda = []() [[clang::nonblocking]] {
- throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}}
+ throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}}
};
}
@@ -92,7 +92,7 @@ void nb8a() [[clang::nonblocking]]
void nb8b() [[clang::nonblocking]]
{
// An unsafe lambda capture makes the outer function unsafe.
- auto unsafeCapture = [foo = new int]() { // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+ auto unsafeCapture = [foo = new int]() { // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
delete foo;
};
}
@@ -109,7 +109,7 @@ void nb8c()
struct Adder {
static T add_explicit(T x, T y) [[clang::nonblocking]]
{
- return x + y; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ return x + y; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
}
static T add_implicit(T x, T y)
{
@@ -140,7 +140,7 @@ void nb9() [[clang::nonblocking]]
Adder<int>::add_implicit(1, 2);
Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
- Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}} \
+ Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \
expected-note {{in template expansion here}}
}
@@ -149,7 +149,7 @@ void nb10(
void (*fp2)() [[clang::nonblocking]]
) [[clang::nonblocking]]
{
- fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
fp2();
}
@@ -166,19 +166,19 @@ struct ComputedNB {
void nb11() [[clang::nonblocking]]
{
- nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
- nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ nb11_no_inference_1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+ nb11_no_inference_2(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
ComputedNB<true> CNB_true;
CNB_true.method();
ComputedNB<false> CNB_false;
- CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ CNB_false.method(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
}
// Verify that when attached to a redeclaration, the attribute successfully attaches.
void nb12() {
- static int x; // expected-warning {{'nonblocking' function must not have static local variables}}
+ static int x; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
}
void nb12() [[clang::nonblocking]];
void nb13() [[clang::nonblocking]] { nb12(); }
@@ -212,18 +212,18 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
// Builtin functions
void nb19() [[clang::nonblocking]] {
__builtin_assume(1);
- void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
- __builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+ void *ptr = __builtin_malloc(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_malloc'}}
+ __builtin_free(ptr); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_free'}}
- void *p2 = __builtin_operator_new(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}}
- __builtin_operator_delete(p2); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_delete'}}
+ void *p2 = __builtin_operator_new(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_new'}}
+ __builtin_operator_delete(p2); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_delete'}}
}
// Function try-block
void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
void nb20() [[clang::nonblocking]] {
- catches(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'catches'}}
+ catches(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}}
}
struct S {
@@ -245,14 +245,14 @@ struct B {
};
void f() [[clang::nonblocking]] {
- S s1(3); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
- S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
- A a; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}}
- B b; // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}}
+ S s1(3); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
+ S s2(3.0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
+ A a; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'A::A'}}
+ B b; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'B::B'}}
}
struct T {
- int x = badi(); // expected-warning {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}}
+ int x = badi(); // expected-warning {{member initializer of constructor with 'nonblocking' attribute must not call non-'nonblocking' function 'badi'}}
T() [[clang::nonblocking]] {} // expected-note {{in constructor here}}
T(int x) [[clang::nonblocking]] : x(x) {} // OK
};
@@ -262,16 +262,16 @@ int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonbl
expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} \
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}} \
expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}}
}
void nb21() [[clang::nonblocking]] {
hasDefaultArg(); // expected-note {{in evaluating default argument here}} \
- expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}}
+ expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}}
}
-void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}}
}
// Verify traversal of implicit code paths - constructors and destructors.
@@ -285,13 +285,13 @@ struct Unsafe {
Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
// Delegating initializer.
- Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+ Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
};
struct DerivedFromUnsafe : public Unsafe {
- DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
- DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
- ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
+ DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+ DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+ ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
};
// Virtual inheritance
@@ -303,9 +303,9 @@ struct VBase {
};
struct VDerived : virtual VBase {
- VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}}
+ VDerived() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'VBase::VBase'}}
- ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'VBase::~VBase'}}
+ ~VDerived() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'VBase::~VBase'}}
};
// Contexts where there is no function call, no diagnostic.
@@ -329,6 +329,6 @@ void g() [[clang::nonblocking]] {
// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
-void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}}
{
}
>From ba57bfbde08716c353110516bc0c9b78a7c2c95c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 16:43:35 -0700
Subject: [PATCH 42/44] Fix: Don't try to follow a deleted destructor. (Happens
with a std::optional<T>, which contains a union with a deleted destructor.
Exposed by a recent fix which wasn't following the complete chain of
destructors.)
---
clang/lib/Sema/SemaFunctionEffects.cpp | 3 ++-
.../Sema/attr-nonblocking-constraints.cpp | 23 +++++++++++++++++++
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index c1df87bcacea01..36bc20d52aeeab 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1077,7 +1077,8 @@ class Analyzer {
if (Ty->isRecordType()) {
if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
- if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
+ if (CXXDestructorDecl *Dtor = Class->getDestructor();
+ Dtor && !Dtor->isDeleted()) {
CallableInfo CI(*Dtor);
followCall(CI, CallSite);
}
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 9ae30a39ff772c..9838d842abcafc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -294,6 +294,29 @@ struct DerivedFromUnsafe : public Unsafe {
~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
};
+// Don't try to follow a deleted destructor, as with std::optional<T>.
+struct HasDtor {
+ ~HasDtor() {}
+};
+
+template <typename T>
+struct Optional {
+ union {
+ char __null_state_;
+ T __val_;
+ };
+ bool engaged = false;
+
+ ~Optional() {
+ if (engaged)
+ __val_.~T();
+ }
+};
+
+void nb_opt() [[clang::nonblocking]] {
+ Optional<HasDtor> x;
+}
+
// Virtual inheritance
struct VBase {
int *Ptr;
>From 54feb05e7e697de5a98089693d1a6b3cabb0c37f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 17:33:42 -0700
Subject: [PATCH 43/44] Fix test broken by rewording of warnings.
---
clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index 262d647142d548..0ff72d3d6d19de 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -8,19 +8,19 @@ - (void)method;
@end
void nb1(OCClass *oc) [[clang::nonblocking]] {
- [oc method]; // expected-warning {{'nonblocking' function must not access ObjC methods or properties}}
+ [oc method]; // expected-warning {{function with 'nonblocking' attribute must not access ObjC methods or properties}}
}
void nb2(OCClass *oc) {
[oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
}
void nb3(OCClass *oc) [[clang::nonblocking]] {
- nb2(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}}
+ nb2(oc); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'nb2'}}
}
void nb4() [[clang::nonblocking]] {
@try {
- @throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ @throw @"foo"; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
}
- @catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+ @catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
}
}
>From 93cb74ea642cc231d551ed715e758fe63bd42707 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 5 Sep 2024 06:55:26 -0700
Subject: [PATCH 44/44] Fix: need to ignore concept requirements.
---
clang/lib/Sema/SemaFunctionEffects.cpp | 5 +++++
.../test/Sema/attr-nonblocking-constraints.cpp | 18 ++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 36bc20d52aeeab..b65281ca9df2f7 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1289,6 +1289,11 @@ class Analyzer {
bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return true; }
bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; }
+
+ // Skip concept requirements since they don't generate code.
+ bool TraverseConceptRequirement(concepts::Requirement *R) {
+ return true;
+ }
};
};
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 9838d842abcafc..2a491a16dc723c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -348,6 +348,24 @@ void g() [[clang::nonblocking]] {
#pragma clang diagnostic pop
}
+// Make sure we are skipping concept requirements -- they can trigger an unexpected
+// warning involving use of a function pointer (e.g. std::reverse_iterator::operator==
+struct HasFoo { int foo() const { return 0; } };
+
+template <class A, class B>
+inline bool compare(const A& a, const B& b)
+ requires requires {
+ a.foo();
+ }
+{
+ return a.foo() == b.foo();
+}
+
+void nb25() [[clang::nonblocking]] {
+ HasFoo a, b;
+ compare(a, b);
+}
+
// --- nonblocking implies noexcept ---
#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
More information about the cfe-commits
mailing list