Skip to content

Commit 45bb2dd

Browse files
dougsonosDoug WyattSirraideerichkeane
authored andcommitted
Cherry-pick: [Clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (llvm#99656)
- In Sema, when encountering Decls with function effects needing verification, add them to a vector, DeclsWithEffectsToVerify. - Update AST serialization to include DeclsWithEffectsToVerify. - In AnalysisBasedWarnings, use DeclsWithEffectsToVerify as a work queue, verifying functions with declared effects, and inferring (when permitted and necessary) whether their callees have effects. --------- Co-authored-by: Doug Wyatt <[email protected]> Co-authored-by: Sirraide <[email protected]> Co-authored-by: Erich Keane <[email protected]>
1 parent 8646155 commit 45bb2dd

27 files changed

+2427
-357
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,11 @@ New features
13951395

13961396
- Support C++23 static operator calls. (#GH84972)
13971397

1398+
- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint"
1399+
attributes, are now verified. For example, for functions declared with the ``nonblocking``
1400+
attribute, the compiler can generate warnings about the use of any language features, or calls to
1401+
other functions, which may block.
1402+
13981403
Crash and bug fixes
13991404
^^^^^^^^^^^^^^^^^^^
14001405

clang/include/clang/AST/Type.h

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "llvm/Support/PointerLikeTypeTraits.h"
4949
#include "llvm/Support/TrailingObjects.h"
5050
#include "llvm/Support/type_traits.h"
51+
#include <bitset>
5152
#include <cassert>
5253
#include <cstddef>
5354
#include <cstdint>
@@ -119,6 +120,8 @@ class EnumDecl;
119120
class Expr;
120121
class ExtQualsTypeCommonBase;
121122
class FunctionDecl;
123+
class FunctionEffectsRef;
124+
class FunctionEffectKindSet;
122125
class FunctionEffectSet;
123126
class IdentifierInfo;
124127
class NamedDecl;
@@ -4695,12 +4698,13 @@ class FunctionEffect {
46954698
public:
46964699
/// Identifies the particular effect.
46974700
enum class Kind : uint8_t {
4698-
None = 0,
4699-
NonBlocking = 1,
4700-
NonAllocating = 2,
4701-
Blocking = 3,
4702-
Allocating = 4
4701+
NonBlocking,
4702+
NonAllocating,
4703+
Blocking,
4704+
Allocating,
4705+
Last = Allocating
47034706
};
4707+
constexpr static size_t KindCount = static_cast<size_t>(Kind::Last) + 1;
47044708

47054709
/// Flags describing some behaviors of the effect.
47064710
using Flags = unsigned;
@@ -4726,8 +4730,6 @@ class FunctionEffect {
47264730
// be considered for uniqueness.
47274731

47284732
public:
4729-
FunctionEffect() : FKind(Kind::None) {}
4730-
47314733
explicit FunctionEffect(Kind K) : FKind(K) {}
47324734

47334735
/// The kind of the effect.
@@ -4756,35 +4758,43 @@ class FunctionEffect {
47564758
case Kind::Blocking:
47574759
case Kind::Allocating:
47584760
return 0;
4759-
case Kind::None:
4760-
break;
47614761
}
47624762
llvm_unreachable("unknown effect kind");
47634763
}
47644764

47654765
/// The description printed in diagnostics, e.g. 'nonblocking'.
47664766
StringRef name() const;
47674767

4768-
/// Return true if the effect is allowed to be inferred on the callee,
4769-
/// which is either a FunctionDecl or BlockDecl.
4768+
friend raw_ostream &operator<<(raw_ostream &OS,
4769+
const FunctionEffect &Effect) {
4770+
OS << Effect.name();
4771+
return OS;
4772+
}
4773+
4774+
/// Determine whether the effect is allowed to be inferred on the callee,
4775+
/// which is either a FunctionDecl or BlockDecl. If the returned optional
4776+
/// is empty, inference is permitted; otherwise it holds the effect which
4777+
/// blocked inference.
47704778
/// Example: This allows nonblocking(false) to prevent inference for the
47714779
/// function.
4772-
bool canInferOnFunction(const Decl &Callee) const;
4780+
std::optional<FunctionEffect>
4781+
effectProhibitingInference(const Decl &Callee,
4782+
FunctionEffectKindSet CalleeFX) const;
47734783

47744784
// Return false for success. When true is returned for a direct call, then the
47754785
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
47764786
// diagnostic. Caller should be assumed to have the effect (it may not have it
47774787
// explicitly when inferring).
47784788
bool shouldDiagnoseFunctionCall(bool Direct,
4779-
ArrayRef<FunctionEffect> CalleeFX) const;
4789+
FunctionEffectKindSet CalleeFX) const;
47804790

4781-
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4791+
friend bool operator==(FunctionEffect LHS, FunctionEffect RHS) {
47824792
return LHS.FKind == RHS.FKind;
47834793
}
4784-
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4794+
friend bool operator!=(FunctionEffect LHS, FunctionEffect RHS) {
47854795
return !(LHS == RHS);
47864796
}
4787-
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4797+
friend bool operator<(FunctionEffect LHS, FunctionEffect RHS) {
47884798
return LHS.FKind < RHS.FKind;
47894799
}
47904800
};
@@ -4812,13 +4822,14 @@ struct FunctionEffectWithCondition {
48124822
FunctionEffect Effect;
48134823
EffectConditionExpr Cond;
48144824

4815-
FunctionEffectWithCondition() = default;
4816-
FunctionEffectWithCondition(const FunctionEffect &E,
4817-
const EffectConditionExpr &C)
4825+
FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C)
48184826
: Effect(E), Cond(C) {}
48194827

48204828
/// Return a textual description of the effect, and its condition, if any.
48214829
std::string description() const;
4830+
4831+
friend raw_ostream &operator<<(raw_ostream &OS,
4832+
const FunctionEffectWithCondition &CFE);
48224833
};
48234834

48244835
/// Support iteration in parallel through a pair of FunctionEffect and
@@ -4923,6 +4934,85 @@ class FunctionEffectsRef {
49234934
void dump(llvm::raw_ostream &OS) const;
49244935
};
49254936

4937+
/// A mutable set of FunctionEffect::Kind.
4938+
class FunctionEffectKindSet {
4939+
// For now this only needs to be a bitmap.
4940+
constexpr static size_t EndBitPos = FunctionEffect::KindCount;
4941+
using KindBitsT = std::bitset<EndBitPos>;
4942+
4943+
KindBitsT KindBits{};
4944+
4945+
explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
4946+
4947+
// Functions to translate between an effect kind, starting at 1, and a
4948+
// position in the bitset.
4949+
4950+
constexpr static size_t kindToPos(FunctionEffect::Kind K) {
4951+
return static_cast<size_t>(K);
4952+
}
4953+
4954+
constexpr static FunctionEffect::Kind posToKind(size_t Pos) {
4955+
return static_cast<FunctionEffect::Kind>(Pos);
4956+
}
4957+
4958+
// Iterates through the bits which are set.
4959+
class iterator {
4960+
const FunctionEffectKindSet *Outer = nullptr;
4961+
size_t Idx = 0;
4962+
4963+
// If Idx does not reference a set bit, advance it until it does,
4964+
// or until it reaches EndBitPos.
4965+
void advanceToNextSetBit() {
4966+
while (Idx < EndBitPos && !Outer->KindBits.test(Idx))
4967+
++Idx;
4968+
}
4969+
4970+
public:
4971+
iterator();
4972+
iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
4973+
advanceToNextSetBit();
4974+
}
4975+
bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
4976+
bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
4977+
4978+
iterator operator++() {
4979+
++Idx;
4980+
advanceToNextSetBit();
4981+
return *this;
4982+
}
4983+
4984+
FunctionEffect operator*() const {
4985+
assert(Idx < EndBitPos && "Dereference of end iterator");
4986+
return FunctionEffect(posToKind(Idx));
4987+
}
4988+
};
4989+
4990+
public:
4991+
FunctionEffectKindSet() = default;
4992+
explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
4993+
4994+
iterator begin() const { return iterator(*this, 0); }
4995+
iterator end() const { return iterator(*this, EndBitPos); }
4996+
4997+
void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); }
4998+
void insert(FunctionEffectsRef FX) {
4999+
for (FunctionEffect Item : FX.effects())
5000+
insert(Item);
5001+
}
5002+
void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
5003+
5004+
bool empty() const { return KindBits.none(); }
5005+
bool contains(const FunctionEffect::Kind EK) const {
5006+
return KindBits.test(kindToPos(EK));
5007+
}
5008+
void dump(llvm::raw_ostream &OS) const;
5009+
5010+
static FunctionEffectKindSet difference(FunctionEffectKindSet LHS,
5011+
FunctionEffectKindSet RHS) {
5012+
return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits);
5013+
}
5014+
};
5015+
49265016
/// A mutable set of FunctionEffects and possibly conditions attached to them.
49275017
/// Used to compare and merge effects on declarations.
49285018
///

clang/include/clang/Basic/AttrDocs.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8476,9 +8476,9 @@ compiler warnings:
84768476
- A redeclaration of a ``nonblocking`` or ``nonallocating`` function must also be declared with
84778477
the same attribute (or a stronger one). A redeclaration may add an attribute.
84788478

8479-
The warnings are controlled by ``-Wfunction-effects``, which is enabled by default.
8479+
The warnings are controlled by ``-Wfunction-effects``, which is disabled by default.
84808480

8481-
In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating``
8481+
The compiler also diagnoses function calls from ``nonblocking`` and ``nonallocating``
84828482
functions to other functions which lack the appropriate attribute.
84838483
}];
84848484
}

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,11 @@ def ThreadSafety : DiagGroup<"thread-safety",
11251125
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
11261126
def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;
11271127

1128+
// Warnings and notes related to the function effects system which underlies
1129+
// the nonblocking and nonallocating attributes.
1130+
def FunctionEffects : DiagGroup<"function-effects">;
1131+
def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
1132+
11281133
// Uniqueness Analysis warnings
11291134
def Consumed : DiagGroup<"consumed">;
11301135

@@ -1133,7 +1138,7 @@ def Consumed : DiagGroup<"consumed">;
11331138
// DefaultIgnore in addition to putting it here.
11341139
def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool,
11351140
MisleadingIndentation, PackedNonPod,
1136-
VLACxxExtension]>;
1141+
VLACxxExtension, PerfConstraintImpliesNoexcept]>;
11371142

11381143
// Warnings that should be in clang-cl /w4.
11391144
def : DiagGroup<"CL4", [All, Extra]>;
@@ -1562,10 +1567,6 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">
15621567
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
15631568
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>;
15641569

1565-
// Warnings and notes related to the function effects system underlying
1566-
// the nonblocking and nonallocating attributes.
1567-
def FunctionEffects : DiagGroup<"function-effects">;
1568-
15691570
// Warnings and notes InstallAPI verification.
15701571
def InstallAPIViolation : DiagGroup<"installapi-violation">;
15711572

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10990,19 +10990,68 @@ def warn_imp_cast_drops_unaligned : Warning<
1099010990
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
1099110991

1099210992
// Function effects
10993+
def warn_func_effect_violation : Warning<
10994+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
10995+
"with '%1' attribute "
10996+
"must not %select{allocate or deallocate memory|throw or catch exceptions|"
10997+
"have static local variables|use thread-local variables|access ObjC methods or properties}2">,
10998+
InGroup<FunctionEffects>, DefaultIgnore;
10999+
def note_func_effect_violation : Note<
11000+
"%select{function|constructor|destructor|lambda|block|member initializer}0 "
11001+
"cannot be inferred '%1' because it "
11002+
"%select{allocates or deallocates memory|throws or catches exceptions|"
11003+
"has a static local variable|uses a thread-local variable|"
11004+
"accesses an ObjC method or property}2">;
11005+
def warn_func_effect_calls_func_without_effect : Warning<
11006+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
11007+
"with '%1' attribute "
11008+
"must not call non-'%1' "
11009+
"%select{function|constructor|destructor|lambda|block}2 "
11010+
"'%3'">,
11011+
InGroup<FunctionEffects>, DefaultIgnore;
11012+
def note_func_effect_calls_func_without_effect : Note<
11013+
"%select{function|constructor|destructor|lambda|block|member initializer}0 "
11014+
"cannot be inferred '%1' because it calls non-'%1' "
11015+
"%select{function|constructor|destructor|lambda|block}2 "
11016+
"'%3'">;
11017+
def warn_func_effect_calls_expr_without_effect : Warning<
11018+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
11019+
"with '%1' attribute "
11020+
"must not call non-'%1' expression">,
11021+
InGroup<FunctionEffects>, DefaultIgnore;
11022+
def note_func_effect_call_extern : Note<
11023+
"declaration cannot be inferred '%0' because it has no definition in this translation unit">;
11024+
def note_func_effect_call_disallows_inference : Note<
11025+
"%select{function|constructor|destructor|lambda|block}0 "
11026+
"does not permit inference of '%1' because it is declared '%2'">;
11027+
def note_func_effect_call_indirect : Note<
11028+
"%select{virtual method|function pointer}0 cannot be inferred '%1'">;
11029+
def warn_perf_constraint_implies_noexcept : Warning<
11030+
"%select{function|constructor|destructor|lambda|block}0 "
11031+
"with '%1' attribute should be declared noexcept">,
11032+
InGroup<PerfConstraintImpliesNoexcept>, DefaultIgnore;
11033+
11034+
// FIXME: It would be nice if we could provide fuller template expansion notes.
11035+
def note_func_effect_from_template : Note<
11036+
"in template expansion here">;
11037+
def note_func_effect_in_constructor : Note<
11038+
"in%select{| implicit}0 constructor here">;
11039+
def note_in_evaluating_default_argument : Note<
11040+
"in evaluating default argument here">;
11041+
1099311042
// spoofing nonblocking/nonallocating
1099411043
def warn_invalid_add_func_effects : Warning<
1099511044
"attribute '%0' should not be added via type conversion">,
10996-
InGroup<FunctionEffects>;
11045+
InGroup<FunctionEffects>, DefaultIgnore;
1099711046
def warn_mismatched_func_effect_override : Warning<
1099811047
"attribute '%0' on overriding function does not match base declaration">,
10999-
InGroup<FunctionEffects>;
11048+
InGroup<FunctionEffects>, DefaultIgnore;
1100011049
def warn_mismatched_func_effect_redeclaration : Warning<
1100111050
"attribute '%0' on function does not match previous declaration">,
11002-
InGroup<FunctionEffects>;
11051+
InGroup<FunctionEffects>, DefaultIgnore;
1100311052
def warn_conflicting_func_effects : Warning<
1100411053
"effects conflict when merging declarations; kept '%0', discarded '%1'">,
11005-
InGroup<FunctionEffects>;
11054+
InGroup<FunctionEffects>, DefaultIgnore;
1100611055
def err_func_with_effects_no_prototype : Error<
1100711056
"'%0' function must have a prototype">;
1100811057

0 commit comments

Comments
 (0)