Skip to content

Commit

Permalink
[Clang] nonblocking/nonallocating attributes: 2nd pass caller/callee …
Browse files Browse the repository at this point in the history
…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 <dwyatt@apple.com>
Co-authored-by: Sirraide <aeternalmail@gmail.com>
Co-authored-by: Erich Keane <ekeane@nvidia.com>
  • Loading branch information
4 people authored Oct 3, 2024
1 parent db33d82 commit 7fe43ad
Show file tree
Hide file tree
Showing 27 changed files with 2,423 additions and 356 deletions.
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,11 @@ New features
if class of allocation and deallocation function mismatches.
`Documentation <https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator-c-c>`__.

- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint"
attributes, are now verified. For example, for functions declared with the ``nonblocking``
attribute, the compiler can generate warnings about the use of any language features, or calls to
other functions, which may block.

Crash and bug fixes
^^^^^^^^^^^^^^^^^^^

Expand Down
128 changes: 109 additions & 19 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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>
Expand Down Expand Up @@ -119,6 +120,8 @@ class EnumDecl;
class Expr;
class ExtQualsTypeCommonBase;
class FunctionDecl;
class FunctionEffectsRef;
class FunctionEffectKindSet;
class FunctionEffectSet;
class IdentifierInfo;
class NamedDecl;
Expand Down Expand Up @@ -4712,12 +4715,13 @@ class FunctionEffect {
public:
/// Identifies the particular effect.
enum class Kind : uint8_t {
None = 0,
NonBlocking = 1,
NonAllocating = 2,
Blocking = 3,
Allocating = 4
NonBlocking,
NonAllocating,
Blocking,
Allocating,
Last = Allocating
};
constexpr static size_t KindCount = static_cast<size_t>(Kind::Last) + 1;

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

public:
FunctionEffect() : FKind(Kind::None) {}

explicit FunctionEffect(Kind K) : FKind(K) {}

/// The kind of the effect.
Expand Down Expand Up @@ -4773,35 +4775,43 @@ class FunctionEffect {
case Kind::Blocking:
case Kind::Allocating:
return 0;
case Kind::None:
break;
}
llvm_unreachable("unknown effect kind");
}

/// 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.
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
/// 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,
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,
ArrayRef<FunctionEffect> 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;
}
};
Expand Down Expand Up @@ -4829,13 +4839,14 @@ struct FunctionEffectWithCondition {
FunctionEffect Effect;
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.
std::string description() const;

friend raw_ostream &operator<<(raw_ostream &OS,
const FunctionEffectWithCondition &CFE);
};

/// Support iteration in parallel through a pair of FunctionEffect and
Expand Down Expand Up @@ -4940,6 +4951,85 @@ 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.
constexpr static size_t EndBitPos = FunctionEffect::KindCount;
using KindBitsT = std::bitset<EndBitPos>;

KindBitsT KindBits{};

explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}

// Functions to translate between an effect kind, starting at 1, and a
// position in the bitset.

constexpr static size_t kindToPos(FunctionEffect::Kind K) {
return static_cast<size_t>(K);
}

constexpr static FunctionEffect::Kind posToKind(size_t Pos) {
return static_cast<FunctionEffect::Kind>(Pos);
}

// 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 advanceToNextSetBit() {
while (Idx < EndBitPos && !Outer->KindBits.test(Idx))
++Idx;
}

public:
iterator();
iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
advanceToNextSetBit();
}
bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }

iterator operator++() {
++Idx;
advanceToNextSetBit();
return *this;
}

FunctionEffect operator*() const {
assert(Idx < EndBitPos && "Dereference of end iterator");
return FunctionEffect(posToKind(Idx));
}
};

public:
FunctionEffectKindSet() = default;
explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }

iterator begin() const { return iterator(*this, 0); }
iterator end() const { return iterator(*this, EndBitPos); }

void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); }
void insert(FunctionEffectsRef FX) {
for (FunctionEffect Item : FX.effects())
insert(Item);
}
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));
}
void dump(llvm::raw_ostream &OS) const;

static FunctionEffectKindSet difference(FunctionEffectKindSet LHS,
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.
///
Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -8482,9 +8482,9 @@ compiler warnings:
- A redeclaration of a ``nonblocking`` or ``nonallocating`` function must also be declared with
the same attribute (or a stronger one). A redeclaration may add an attribute.

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

In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating``
The compiler also diagnoses function calls from ``nonblocking`` and ``nonallocating``
functions to other functions which lack the appropriate attribute.
}];
}
Expand Down
11 changes: 6 additions & 5 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,11 @@ def ThreadSafety : DiagGroup<"thread-safety",
def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;

// Warnings and notes related to the function effects system which underlies
// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;
def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;

// Uniqueness Analysis warnings
def Consumed : DiagGroup<"consumed">;

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

// Warnings that should be in clang-cl /w4.
def : DiagGroup<"CL4", [All, Extra]>;
Expand Down Expand Up @@ -1566,10 +1571,6 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">
def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>;

// Warnings and notes related to the function effects system underlying
// the nonblocking and nonallocating attributes.
def FunctionEffects : DiagGroup<"function-effects">;

// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;

Expand Down
57 changes: 53 additions & 4 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10972,19 +10972,68 @@ def warn_imp_cast_drops_unaligned : Warning<
InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;

// Function effects
def warn_func_effect_violation : Warning<
"%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>, DefaultIgnore;
def note_func_effect_violation : Note<
"%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<
"%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>, DefaultIgnore;
def note_func_effect_calls_func_without_effect : Note<
"%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<
"%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
"with '%1' attribute "
"must not call non-'%1' expression">,
InGroup<FunctionEffects>, DefaultIgnore;
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<
"%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<
"%select{function|constructor|destructor|lambda|block}0 "
"with '%1' attribute should be declared noexcept">,
InGroup<PerfConstraintImpliesNoexcept>, DefaultIgnore;

// 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">;
def note_in_evaluating_default_argument : Note<
"in evaluating default argument here">;

// spoofing nonblocking/nonallocating
def warn_invalid_add_func_effects : Warning<
"attribute '%0' should not be added via type conversion">,
InGroup<FunctionEffects>;
InGroup<FunctionEffects>, DefaultIgnore;
def warn_mismatched_func_effect_override : Warning<
"attribute '%0' on overriding function does not match base declaration">,
InGroup<FunctionEffects>;
InGroup<FunctionEffects>, DefaultIgnore;
def warn_mismatched_func_effect_redeclaration : Warning<
"attribute '%0' on function does not match previous declaration">,
InGroup<FunctionEffects>;
InGroup<FunctionEffects>, DefaultIgnore;
def warn_conflicting_func_effects : Warning<
"effects conflict when merging declarations; kept '%0', discarded '%1'">,
InGroup<FunctionEffects>;
InGroup<FunctionEffects>, DefaultIgnore;
def err_func_with_effects_no_prototype : Error<
"'%0' function must have a prototype">;

Expand Down
Loading

0 comments on commit 7fe43ad

Please sign in to comment.