Skip to content

Commit 7fe43ad

Browse files
dougsonosDoug WyattSirraideerichkeane
authored
[Clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (#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 db33d82 commit 7fe43ad

27 files changed

+2423
-356
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,11 @@ New features
632632
if class of allocation and deallocation function mismatches.
633633
`Documentation <https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator-c-c>`__.
634634

635+
- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint"
636+
attributes, are now verified. For example, for functions declared with the ``nonblocking``
637+
attribute, the compiler can generate warnings about the use of any language features, or calls to
638+
other functions, which may block.
639+
635640
Crash and bug fixes
636641
^^^^^^^^^^^^^^^^^^^
637642

clang/include/clang/AST/Type.h

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "llvm/Support/PointerLikeTypeTraits.h"
5050
#include "llvm/Support/TrailingObjects.h"
5151
#include "llvm/Support/type_traits.h"
52+
#include <bitset>
5253
#include <cassert>
5354
#include <cstddef>
5455
#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;
@@ -4712,12 +4715,13 @@ class FunctionEffect {
47124715
public:
47134716
/// Identifies the particular effect.
47144717
enum class Kind : uint8_t {
4715-
None = 0,
4716-
NonBlocking = 1,
4717-
NonAllocating = 2,
4718-
Blocking = 3,
4719-
Allocating = 4
4718+
NonBlocking,
4719+
NonAllocating,
4720+
Blocking,
4721+
Allocating,
4722+
Last = Allocating
47204723
};
4724+
constexpr static size_t KindCount = static_cast<size_t>(Kind::Last) + 1;
47214725

47224726
/// Flags describing some behaviors of the effect.
47234727
using Flags = unsigned;
@@ -4743,8 +4747,6 @@ class FunctionEffect {
47434747
// be considered for uniqueness.
47444748

47454749
public:
4746-
FunctionEffect() : FKind(Kind::None) {}
4747-
47484750
explicit FunctionEffect(Kind K) : FKind(K) {}
47494751

47504752
/// The kind of the effect.
@@ -4773,35 +4775,43 @@ class FunctionEffect {
47734775
case Kind::Blocking:
47744776
case Kind::Allocating:
47754777
return 0;
4776-
case Kind::None:
4777-
break;
47784778
}
47794779
llvm_unreachable("unknown effect kind");
47804780
}
47814781

47824782
/// The description printed in diagnostics, e.g. 'nonblocking'.
47834783
StringRef name() const;
47844784

4785-
/// Return true if the effect is allowed to be inferred on the callee,
4786-
/// which is either a FunctionDecl or BlockDecl.
4785+
friend raw_ostream &operator<<(raw_ostream &OS,
4786+
const FunctionEffect &Effect) {
4787+
OS << Effect.name();
4788+
return OS;
4789+
}
4790+
4791+
/// Determine whether the effect is allowed to be inferred on the callee,
4792+
/// which is either a FunctionDecl or BlockDecl. If the returned optional
4793+
/// is empty, inference is permitted; otherwise it holds the effect which
4794+
/// blocked inference.
47874795
/// Example: This allows nonblocking(false) to prevent inference for the
47884796
/// function.
4789-
bool canInferOnFunction(const Decl &Callee) const;
4797+
std::optional<FunctionEffect>
4798+
effectProhibitingInference(const Decl &Callee,
4799+
FunctionEffectKindSet CalleeFX) const;
47904800

47914801
// Return false for success. When true is returned for a direct call, then the
47924802
// FE_InferrableOnCallees flag may trigger inference rather than an immediate
47934803
// diagnostic. Caller should be assumed to have the effect (it may not have it
47944804
// explicitly when inferring).
47954805
bool shouldDiagnoseFunctionCall(bool Direct,
4796-
ArrayRef<FunctionEffect> CalleeFX) const;
4806+
FunctionEffectKindSet CalleeFX) const;
47974807

4798-
friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4808+
friend bool operator==(FunctionEffect LHS, FunctionEffect RHS) {
47994809
return LHS.FKind == RHS.FKind;
48004810
}
4801-
friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4811+
friend bool operator!=(FunctionEffect LHS, FunctionEffect RHS) {
48024812
return !(LHS == RHS);
48034813
}
4804-
friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
4814+
friend bool operator<(FunctionEffect LHS, FunctionEffect RHS) {
48054815
return LHS.FKind < RHS.FKind;
48064816
}
48074817
};
@@ -4829,13 +4839,14 @@ struct FunctionEffectWithCondition {
48294839
FunctionEffect Effect;
48304840
EffectConditionExpr Cond;
48314841

4832-
FunctionEffectWithCondition() = default;
4833-
FunctionEffectWithCondition(const FunctionEffect &E,
4834-
const EffectConditionExpr &C)
4842+
FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C)
48354843
: Effect(E), Cond(C) {}
48364844

48374845
/// Return a textual description of the effect, and its condition, if any.
48384846
std::string description() const;
4847+
4848+
friend raw_ostream &operator<<(raw_ostream &OS,
4849+
const FunctionEffectWithCondition &CFE);
48394850
};
48404851

48414852
/// Support iteration in parallel through a pair of FunctionEffect and
@@ -4940,6 +4951,85 @@ class FunctionEffectsRef {
49404951
void dump(llvm::raw_ostream &OS) const;
49414952
};
49424953

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

clang/include/clang/Basic/AttrDocs.td

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

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

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

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]>;
@@ -1566,10 +1571,6 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">
15661571
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
15671572
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>;
15681573

1569-
// Warnings and notes related to the function effects system underlying
1570-
// the nonblocking and nonallocating attributes.
1571-
def FunctionEffects : DiagGroup<"function-effects">;
1572-
15731574
// Warnings and notes InstallAPI verification.
15741575
def InstallAPIViolation : DiagGroup<"installapi-violation">;
15751576

clang/include/clang/Basic/DiagnosticSemaKinds.td

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

1097410974
// Function effects
10975+
def warn_func_effect_violation : Warning<
10976+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
10977+
"with '%1' attribute "
10978+
"must not %select{allocate or deallocate memory|throw or catch exceptions|"
10979+
"have static local variables|use thread-local variables|access ObjC methods or properties}2">,
10980+
InGroup<FunctionEffects>, DefaultIgnore;
10981+
def note_func_effect_violation : Note<
10982+
"%select{function|constructor|destructor|lambda|block|member initializer}0 "
10983+
"cannot be inferred '%1' because it "
10984+
"%select{allocates or deallocates memory|throws or catches exceptions|"
10985+
"has a static local variable|uses a thread-local variable|"
10986+
"accesses an ObjC method or property}2">;
10987+
def warn_func_effect_calls_func_without_effect : Warning<
10988+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
10989+
"with '%1' attribute "
10990+
"must not call non-'%1' "
10991+
"%select{function|constructor|destructor|lambda|block}2 "
10992+
"'%3'">,
10993+
InGroup<FunctionEffects>, DefaultIgnore;
10994+
def note_func_effect_calls_func_without_effect : Note<
10995+
"%select{function|constructor|destructor|lambda|block|member initializer}0 "
10996+
"cannot be inferred '%1' because it calls non-'%1' "
10997+
"%select{function|constructor|destructor|lambda|block}2 "
10998+
"'%3'">;
10999+
def warn_func_effect_calls_expr_without_effect : Warning<
11000+
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
11001+
"with '%1' attribute "
11002+
"must not call non-'%1' expression">,
11003+
InGroup<FunctionEffects>, DefaultIgnore;
11004+
def note_func_effect_call_extern : Note<
11005+
"declaration cannot be inferred '%0' because it has no definition in this translation unit">;
11006+
def note_func_effect_call_disallows_inference : Note<
11007+
"%select{function|constructor|destructor|lambda|block}0 "
11008+
"does not permit inference of '%1' because it is declared '%2'">;
11009+
def note_func_effect_call_indirect : Note<
11010+
"%select{virtual method|function pointer}0 cannot be inferred '%1'">;
11011+
def warn_perf_constraint_implies_noexcept : Warning<
11012+
"%select{function|constructor|destructor|lambda|block}0 "
11013+
"with '%1' attribute should be declared noexcept">,
11014+
InGroup<PerfConstraintImpliesNoexcept>, DefaultIgnore;
11015+
11016+
// FIXME: It would be nice if we could provide fuller template expansion notes.
11017+
def note_func_effect_from_template : Note<
11018+
"in template expansion here">;
11019+
def note_func_effect_in_constructor : Note<
11020+
"in%select{| implicit}0 constructor here">;
11021+
def note_in_evaluating_default_argument : Note<
11022+
"in evaluating default argument here">;
11023+
1097511024
// spoofing nonblocking/nonallocating
1097611025
def warn_invalid_add_func_effects : Warning<
1097711026
"attribute '%0' should not be added via type conversion">,
10978-
InGroup<FunctionEffects>;
11027+
InGroup<FunctionEffects>, DefaultIgnore;
1097911028
def warn_mismatched_func_effect_override : Warning<
1098011029
"attribute '%0' on overriding function does not match base declaration">,
10981-
InGroup<FunctionEffects>;
11030+
InGroup<FunctionEffects>, DefaultIgnore;
1098211031
def warn_mismatched_func_effect_redeclaration : Warning<
1098311032
"attribute '%0' on function does not match previous declaration">,
10984-
InGroup<FunctionEffects>;
11033+
InGroup<FunctionEffects>, DefaultIgnore;
1098511034
def warn_conflicting_func_effects : Warning<
1098611035
"effects conflict when merging declarations; kept '%0', discarded '%1'">,
10987-
InGroup<FunctionEffects>;
11036+
InGroup<FunctionEffects>, DefaultIgnore;
1098811037
def err_func_with_effects_no_prototype : Error<
1098911038
"'%0' function must have a prototype">;
1099011039

0 commit comments

Comments
 (0)