mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-30 07:06:07 +00:00

Note that PointerUnion::dyn_cast has been soft deprecated in PointerUnion.h: // FIXME: Replace the uses of is(), get() and dyn_cast() with // isa<T>, cast<T> and the llvm::dyn_cast<T> Literal migration would result in dyn_cast_if_present (see the definition of PointerUnion::dyn_cast), but this patch uses dyn_cast because we expect AP to be nonnull.
1642 lines
60 KiB
C++
1642 lines
60 KiB
C++
//=== 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.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements Sema handling of function effects.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DynamicRecursiveASTVisitor.h"
|
|
#include "clang/AST/ExprObjC.h"
|
|
#include "clang/AST/Stmt.h"
|
|
#include "clang/AST/StmtObjC.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Sema/SemaInternal.h"
|
|
|
|
#define DEBUG_TYPE "effectanalysis"
|
|
|
|
using namespace clang;
|
|
|
|
namespace {
|
|
|
|
enum class ViolationID : uint8_t {
|
|
None = 0, // Sentinel for an empty Violation.
|
|
// These first 5 map to a %select{} in one of several FunctionEffects
|
|
// diagnostics, e.g. warn_func_effect_violation.
|
|
BaseDiagnosticIndex,
|
|
AllocatesMemory = BaseDiagnosticIndex,
|
|
ThrowsOrCatchesExceptions,
|
|
HasStaticLocalVariable,
|
|
AccessesThreadLocalVariable,
|
|
AccessesObjCMethodOrProperty,
|
|
|
|
// 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,
|
|
};
|
|
|
|
// Information about the AST context in which a violation was found, so
|
|
// that diagnostics can point to the correct source.
|
|
class ViolationSite {
|
|
public:
|
|
enum class Kind : uint8_t {
|
|
Default, // Function body.
|
|
MemberInitializer,
|
|
DefaultArgExpr
|
|
};
|
|
|
|
private:
|
|
llvm::PointerIntPair<CXXDefaultArgExpr *, 2, Kind> Impl;
|
|
|
|
public:
|
|
ViolationSite() = default;
|
|
|
|
explicit ViolationSite(CXXDefaultArgExpr *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
|
|
// 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;
|
|
std::optional<FunctionEffect>
|
|
CalleeEffectPreventingInference; // Only for certain IDs; can be nullopt.
|
|
ViolationID ID = ViolationID::None;
|
|
ViolationSite Site;
|
|
SourceLocation Loc;
|
|
const Decl *Callee =
|
|
nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect.
|
|
|
|
Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
|
|
SourceLocation Loc, const Decl *Callee = nullptr,
|
|
std::optional<FunctionEffect> CalleeEffect = std::nullopt)
|
|
: Effect(Effect), CalleeEffectPreventingInference(CalleeEffect), ID(ID),
|
|
Site(VS), Loc(Loc), Callee(Callee) {}
|
|
|
|
unsigned diagnosticSelectIndex() const {
|
|
return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
|
|
}
|
|
};
|
|
|
|
enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
|
|
enum class CallableType : uint8_t {
|
|
// 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()->getAs<FunctionProtoType>();
|
|
return FPT && (FPT->isNothrow() || FD->hasAttr<NoThrowAttr>());
|
|
}
|
|
|
|
// 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;
|
|
|
|
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::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.
|
|
// longjmp() and friends are presumed unsafe because they are the moral
|
|
// equivalent of throwing a C++ exception, which is unsafe.
|
|
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:
|
|
|
|
// Objective-C runtime.
|
|
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:
|
|
|
|
// 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;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
// Transitory, more extended information about a callable, which can be a
|
|
// function, block, or function pointer.
|
|
struct CallableInfo {
|
|
// CDecl holds the function's definition, if any.
|
|
// 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;
|
|
|
|
// We inevitably want to know the callable's declared effects, so cache them.
|
|
FunctionEffectKindSet Effects;
|
|
|
|
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 = CallableType::Function;
|
|
if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
|
|
Method && Method->isVirtual())
|
|
CType = CallableType::Virtual;
|
|
DeclEffects = FD->getFunctionEffects();
|
|
} else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
|
|
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.
|
|
DeclEffects = FunctionEffectsRef::get(VD->getType());
|
|
}
|
|
Effects = FunctionEffectKindSet(DeclEffects);
|
|
}
|
|
|
|
CallableType type() const { return CType; }
|
|
|
|
bool isCalledDirectly() const {
|
|
return CType == CallableType::Function || CType == CallableType::Block;
|
|
}
|
|
|
|
bool isVerifiable() const {
|
|
switch (CType) {
|
|
case CallableType::Unknown:
|
|
case CallableType::Virtual:
|
|
return false;
|
|
case CallableType::Block:
|
|
return true;
|
|
case CallableType::Function:
|
|
return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
|
|
}
|
|
llvm_unreachable("undefined CallableType");
|
|
}
|
|
|
|
/// Generate a name for logging and diagnostics.
|
|
std::string getNameForDiagnostic(Sema &S) const {
|
|
std::string Name;
|
|
llvm::raw_string_ostream OS(Name);
|
|
|
|
if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
|
|
FD->getNameForDiagnostic(OS, S.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;
|
|
}
|
|
};
|
|
|
|
// ----------
|
|
// 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 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.
|
|
// FIXME: Is there a way to simplify this using existing data structures?
|
|
using ImplVec = llvm::SmallVector<Violation, 1>;
|
|
std::unique_ptr<ImplVec> Impl;
|
|
|
|
public:
|
|
// 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>();
|
|
else if (lookup(Viol.Effect) != nullptr)
|
|
return;
|
|
|
|
Impl->push_back(Viol);
|
|
}
|
|
|
|
const Violation *lookup(FunctionEffect Key) {
|
|
if (Impl == nullptr)
|
|
return nullptr;
|
|
|
|
auto *Iter = llvm::find_if(
|
|
*Impl, [&](const auto &Item) { return Item.Effect == Key; });
|
|
return Iter != Impl->end() ? &*Iter : nullptr;
|
|
}
|
|
|
|
size_t size() const { return Impl ? Impl->size() : 0; }
|
|
};
|
|
|
|
// ----------
|
|
// 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;
|
|
ViolationSite VSite;
|
|
|
|
DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite)
|
|
: Callee(D), CallLoc(CallLoc), VSite(VSite) {}
|
|
};
|
|
|
|
// We always have two disjoint sets of effects to verify:
|
|
// 1. Effects declared explicitly by this function.
|
|
// 2. All other inferrable effects needing verification.
|
|
FunctionEffectKindSet DeclaredVerifiableEffects;
|
|
FunctionEffectKindSet EffectsToInfer;
|
|
|
|
private:
|
|
// Violations pertaining to the function's explicit effects.
|
|
SmallVector<Violation, 0> ViolationsForExplicitEffects;
|
|
|
|
// 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.
|
|
SmallVector<DirectCall, 0> UnverifiedDirectCalls;
|
|
|
|
public:
|
|
PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
|
|
FunctionEffectKindSet AllInferrableEffectsToVerify)
|
|
: DeclaredVerifiableEffects(CInfo.Effects) {
|
|
// Check for effects we are not allowed to infer.
|
|
FunctionEffectKindSet InferrableEffects;
|
|
|
|
for (FunctionEffect effect : AllInferrableEffectsToVerify) {
|
|
std::optional<FunctionEffect> ProblemCalleeEffect =
|
|
effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
|
|
if (!ProblemCalleeEffect)
|
|
InferrableEffects.insert(effect);
|
|
else {
|
|
// Add a Violation for this effect if a caller were to
|
|
// try to infer it.
|
|
InferrableEffectToFirstViolation.maybeInsert(Violation(
|
|
effect, ViolationID::DeclDisallowsInference, ViolationSite{},
|
|
CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
|
|
}
|
|
}
|
|
// InferrableEffects is now the set of inferrable effects which are not
|
|
// prohibited.
|
|
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)
|
|
ViolationsForExplicitEffects.push_back(NewViol);
|
|
else
|
|
InferrableEffectToFirstViolation.maybeInsert(NewViol);
|
|
}
|
|
|
|
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.
|
|
bool isComplete() const { return UnverifiedDirectCalls.empty(); }
|
|
|
|
const Violation *violationForInferrableEffect(FunctionEffect effect) {
|
|
return InferrableEffectToFirstViolation.lookup(effect);
|
|
}
|
|
|
|
// Mutable because caller may need to set a DirectCall's Recursing flag.
|
|
MutableArrayRef<DirectCall> unverifiedCalls() {
|
|
assert(!isComplete());
|
|
return UnverifiedDirectCalls;
|
|
}
|
|
|
|
ArrayRef<Violation> getSortedViolationsForExplicitEffects(SourceManager &SM) {
|
|
if (!ViolationsForExplicitEffects.empty())
|
|
llvm::sort(ViolationsForExplicitEffects,
|
|
[&SM](const Violation &LHS, const Violation &RHS) {
|
|
return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
|
|
});
|
|
return ViolationsForExplicitEffects;
|
|
}
|
|
|
|
void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
|
|
OS << "Pending: Declared ";
|
|
DeclaredVerifiableEffects.dump(OS);
|
|
OS << ", " << ViolationsForExplicitEffects.size() << " violations; ";
|
|
OS << " Infer ";
|
|
EffectsToInfer.dump(OS);
|
|
OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
|
|
if (!UnverifiedDirectCalls.empty()) {
|
|
OS << "; Calls: ";
|
|
for (const DirectCall &Call : UnverifiedDirectCalls) {
|
|
CallableInfo CI(*Call.Callee);
|
|
OS << " " << CI.getNameForDiagnostic(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.
|
|
FunctionEffectKindSet VerifiedEffects;
|
|
|
|
private:
|
|
// This is used to generate notes about failed inference.
|
|
EffectToViolationMap InferrableEffectToFirstViolation;
|
|
|
|
public:
|
|
// The incoming Pending analysis is consumed (member(s) are moved-from).
|
|
CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending,
|
|
FunctionEffectKindSet DeclaredEffects,
|
|
FunctionEffectKindSet AllInferrableEffectsToVerify)
|
|
: VerifiedEffects(DeclaredEffects) {
|
|
for (FunctionEffect effect : AllInferrableEffectsToVerify)
|
|
if (Pending.violationForInferrableEffect(effect) == nullptr)
|
|
VerifiedEffects.insert(effect);
|
|
|
|
InferrableEffectToFirstViolation =
|
|
std::move(Pending.InferrableEffectToFirstViolation);
|
|
}
|
|
|
|
const Violation *firstViolationForEffect(FunctionEffect Effect) {
|
|
return InferrableEffectToFirstViolation.lookup(Effect);
|
|
}
|
|
|
|
void dump(llvm::raw_ostream &OS) const {
|
|
OS << "Complete: Verified ";
|
|
VerifiedEffects.dump(OS);
|
|
OS << "; Infer ";
|
|
OS << InferrableEffectToFirstViolation.size() << " violations\n";
|
|
}
|
|
};
|
|
|
|
// ==========
|
|
class Analyzer {
|
|
Sema &S;
|
|
|
|
// Subset of Sema.AllEffectsToVerify
|
|
FunctionEffectKindSet 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 : 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(Key->getCanonicalDecl());
|
|
}
|
|
|
|
FuncAnalysisPtr &operator[](const Decl *Key) {
|
|
return Base::operator[](Key->getCanonicalDecl());
|
|
}
|
|
|
|
/// 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 cast<CompleteFunctionAnalysis *>(AP);
|
|
return nullptr;
|
|
}
|
|
|
|
void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
|
|
OS << "\nAnalysisMap:\n";
|
|
for (const auto &item : *this) {
|
|
CallableInfo CI(*item.first);
|
|
const auto AP = item.second;
|
|
OS << item.first << " " << CI.getNameForDiagnostic(SemaRef) << " : ";
|
|
if (AP.isNull()) {
|
|
OS << "null\n";
|
|
} else if (auto *CFA = dyn_cast<CompleteFunctionAnalysis *>(AP)) {
|
|
OS << CFA << " ";
|
|
CFA->dump(OS);
|
|
} else if (auto *PFA = dyn_cast<PendingFunctionAnalysis *>(AP)) {
|
|
OS << PFA << " ";
|
|
PFA->dump(SemaRef, OS);
|
|
} else
|
|
llvm_unreachable("never");
|
|
}
|
|
OS << "---\n";
|
|
}
|
|
};
|
|
AnalysisMap DeclAnalysis;
|
|
|
|
public:
|
|
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 : S.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";);
|
|
|
|
// 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 = S.DeclsWithEffectsToVerify;
|
|
std::reverse(VerificationQueue.begin(), VerificationQueue.end());
|
|
|
|
while (!VerificationQueue.empty()) {
|
|
const Decl *D = VerificationQueue.back();
|
|
if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
|
|
if (auto *Pending = dyn_cast<PendingFunctionAnalysis *>(AP)) {
|
|
// All children have been traversed; finish analysis.
|
|
finishPendingAnalysis(D, Pending);
|
|
}
|
|
VerificationQueue.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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(*D);
|
|
bool isExternC = false;
|
|
|
|
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 (S.getLangOpts().CPlusPlus && !isExternC) {
|
|
for (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()->castAs<FunctionProtoType>();
|
|
IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
|
|
}
|
|
}
|
|
if (!IsNoexcept)
|
|
S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
|
|
<< GetCallableDeclKind(D, nullptr) << 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(S, CInfo, AllInferrableEffectsToVerify);
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "\nVerifying " << CInfo.getNameForDiagnostic(S) << " ";
|
|
FAnalysis.dump(S, llvm::dbgs()););
|
|
|
|
FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
|
|
|
|
Visitor.run();
|
|
if (FAnalysis.isComplete()) {
|
|
completeAnalysis(CInfo, std::move(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(S, llvm::dbgs()););
|
|
return PendingPtr;
|
|
}
|
|
|
|
// Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
|
|
// inserted in the container.
|
|
void completeAnalysis(const CallableInfo &CInfo,
|
|
PendingFunctionAnalysis &&Pending) {
|
|
if (ArrayRef<Violation> Viols =
|
|
Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
|
|
!Viols.empty())
|
|
emitDiagnostics(Viols, CInfo);
|
|
|
|
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()););
|
|
}
|
|
|
|
// 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(*D);
|
|
LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for "
|
|
<< Caller.getNameForDiagnostic(S) << " : ";
|
|
Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";);
|
|
for (const PendingFunctionAnalysis::DirectCall &Call :
|
|
Pending->unverifiedCalls()) {
|
|
if (Call.Recursed)
|
|
continue;
|
|
|
|
CallableInfo Callee(*Call.Callee);
|
|
followCall(Caller, *Pending, Callee, Call.CallLoc,
|
|
/*AssertNoFurtherInference=*/true, Call.VSite);
|
|
}
|
|
completeAnalysis(Caller, std::move(*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, ViolationSite VSite) {
|
|
const bool DirectCall = Callee.isCalledDirectly();
|
|
|
|
// Initially, the declared effects; inferred effects will be added.
|
|
FunctionEffectKindSet 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.getNameForDiagnostic(S)
|
|
<< " to " << Callee.getNameForDiagnostic(S)
|
|
<< "; verifiable: " << Callee.isVerifiable() << "; callee ";
|
|
CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
|
|
llvm::dbgs() << " callee " << Callee.CDecl << " canonical "
|
|
<< Callee.CDecl->getCanonicalDecl() << "\n";);
|
|
|
|
auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
|
|
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,
|
|
VSite, CallLoc, Callee.CDecl});
|
|
else
|
|
PFA.checkAddViolation(
|
|
Inferring,
|
|
{Effect, ViolationID::AllocatesMemory, VSite, CallLoc});
|
|
} else {
|
|
// Inference is allowed and necessary; defer it.
|
|
PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite);
|
|
}
|
|
};
|
|
|
|
for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
|
|
Check1Effect(Effect, false);
|
|
|
|
for (FunctionEffect Effect : PFA.EffectsToInfer)
|
|
Check1Effect(Effect, true);
|
|
}
|
|
|
|
// Describe a callable Decl for a diagnostic.
|
|
// (Not an enum class because the value is always converted to an integer for
|
|
// use in a diagnostic.)
|
|
enum CallableDeclKind {
|
|
CDK_Function,
|
|
CDK_Constructor,
|
|
CDK_Destructor,
|
|
CDK_Lambda,
|
|
CDK_Block,
|
|
CDK_MemberInitializer,
|
|
};
|
|
|
|
// Describe a call site or target using an enum mapping to a %select{}
|
|
// in a diagnostic, e.g. warn_func_effect_violation,
|
|
// warn_perf_constraint_implies_noexcept, and others.
|
|
static CallableDeclKind GetCallableDeclKind(const Decl *D,
|
|
const Violation *V) {
|
|
if (V != nullptr &&
|
|
V->Site.kind() == ViolationSite::Kind::MemberInitializer)
|
|
return CDK_MemberInitializer;
|
|
if (isa<BlockDecl>(D))
|
|
return CDK_Block;
|
|
if (auto *Method = dyn_cast<CXXMethodDecl>(D)) {
|
|
if (isa<CXXConstructorDecl>(D))
|
|
return CDK_Constructor;
|
|
if (isa<CXXDestructorDecl>(D))
|
|
return CDK_Destructor;
|
|
const CXXRecordDecl *Rec = Method->getParent();
|
|
if (Rec->isLambda())
|
|
return CDK_Lambda;
|
|
}
|
|
return CDK_Function;
|
|
};
|
|
|
|
// Should only be called when function's analysis is determined to be
|
|
// complete.
|
|
void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo) {
|
|
if (Viols.empty())
|
|
return;
|
|
|
|
auto MaybeAddTemplateNote = [&](const Decl *D) {
|
|
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
|
|
while (FD != nullptr && FD->isTemplateInstantiation() &&
|
|
FD->getPointOfInstantiation().isValid()) {
|
|
S.Diag(FD->getPointOfInstantiation(),
|
|
diag::note_func_effect_from_template);
|
|
FD = FD->getTemplateInstantiationPattern();
|
|
}
|
|
}
|
|
};
|
|
|
|
// For note_func_effect_call_indirect.
|
|
enum { Indirect_VirtualMethod, Indirect_FunctionPtr };
|
|
|
|
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.kind() == ViolationSite::Kind::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;
|
|
}
|
|
|
|
// 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.kind() == ViolationSite::Kind::DefaultArgExpr)
|
|
S.Diag(V.Site.defaultArgExpr()->getUsedLocation(),
|
|
diag::note_in_evaluating_default_argument);
|
|
};
|
|
|
|
// 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 ViolationID::AllocatesMemory:
|
|
case ViolationID::ThrowsOrCatchesExceptions:
|
|
case ViolationID::HasStaticLocalVariable:
|
|
case ViolationID::AccessesThreadLocalVariable:
|
|
case ViolationID::AccessesObjCMethodOrProperty:
|
|
S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
|
|
<< GetCallableDeclKind(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)
|
|
<< GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName;
|
|
MaybeAddSiteContext(CInfo.CDecl, Viol1);
|
|
MaybeAddTemplateNote(CInfo.CDecl);
|
|
break;
|
|
|
|
case ViolationID::CallsDeclWithoutEffect: {
|
|
CallableInfo CalleeInfo(*Viol1.Callee);
|
|
std::string CalleeName = CalleeInfo.getNameForDiagnostic(S);
|
|
|
|
S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
|
|
<< GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName
|
|
<< GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << CalleeName;
|
|
MaybeAddSiteContext(CInfo.CDecl, Viol1);
|
|
MaybeAddTemplateNote(CInfo.CDecl);
|
|
|
|
// Emit notes explaining the transitive chain of inferences: Why isn't
|
|
// the callee safe?
|
|
for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) {
|
|
std::optional<CallableInfo> MaybeNextCallee;
|
|
CompleteFunctionAnalysis *Completed =
|
|
DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
|
|
if (Completed == nullptr) {
|
|
// No result - could be
|
|
// - non-inline and extern
|
|
// - indirect (virtual or through function pointer)
|
|
// - effect has been explicitly disclaimed (e.g. "blocking")
|
|
|
|
CallableType CType = CalleeInfo.type();
|
|
if (CType == CallableType::Virtual)
|
|
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_indirect)
|
|
<< Indirect_FunctionPtr << effectName;
|
|
else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
|
|
S.Diag(Callee->getLocation(),
|
|
diag::note_func_effect_call_disallows_inference)
|
|
<< GetCallableDeclKind(CInfo.CDecl, nullptr) << effectName
|
|
<< FunctionEffect(Viol1.Effect.oppositeKind()).name();
|
|
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 =
|
|
Completed->firstViolationForEffect(Viol1.Effect);
|
|
if (PtrViol2 == nullptr)
|
|
break;
|
|
|
|
const Violation &Viol2 = *PtrViol2;
|
|
switch (Viol2.ID) {
|
|
case ViolationID::None:
|
|
llvm_unreachable("Unexpected violation kind");
|
|
break;
|
|
case ViolationID::DeclDisallowsInference:
|
|
S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
|
|
<< GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << effectName
|
|
<< Viol2.CalleeEffectPreventingInference->name();
|
|
break;
|
|
case ViolationID::CallsExprWithoutEffect:
|
|
S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect)
|
|
<< Indirect_FunctionPtr << effectName;
|
|
break;
|
|
case ViolationID::AllocatesMemory:
|
|
case ViolationID::ThrowsOrCatchesExceptions:
|
|
case ViolationID::HasStaticLocalVariable:
|
|
case ViolationID::AccessesThreadLocalVariable:
|
|
case ViolationID::AccessesObjCMethodOrProperty:
|
|
S.Diag(Viol2.Loc, diag::note_func_effect_violation)
|
|
<< GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName
|
|
<< Viol2.diagnosticSelectIndex();
|
|
MaybeAddSiteContext(CalleeInfo.CDecl, Viol2);
|
|
break;
|
|
case ViolationID::CallsDeclWithoutEffect:
|
|
MaybeNextCallee.emplace(*Viol2.Callee);
|
|
S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
|
|
<< GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName
|
|
<< GetCallableDeclKind(Viol2.Callee, nullptr)
|
|
<< MaybeNextCallee->getNameForDiagnostic(S);
|
|
break;
|
|
}
|
|
MaybeAddTemplateNote(Callee);
|
|
Callee = Viol2.Callee;
|
|
if (MaybeNextCallee) {
|
|
CalleeInfo = *MaybeNextCallee;
|
|
CalleeName = CalleeInfo.getNameForDiagnostic(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.
|
|
//
|
|
// Violations are always routed to a PendingFunctionAnalysis.
|
|
struct FunctionBodyASTVisitor : DynamicRecursiveASTVisitor {
|
|
Analyzer &Outer;
|
|
PendingFunctionAnalysis &CurrentFunction;
|
|
CallableInfo &CurrentCaller;
|
|
ViolationSite VSite;
|
|
const Expr *TrailingRequiresClause = nullptr;
|
|
const Expr *NoexceptExpr = nullptr;
|
|
|
|
FunctionBodyASTVisitor(Analyzer &Outer,
|
|
PendingFunctionAnalysis &CurrentFunction,
|
|
CallableInfo &CurrentCaller)
|
|
: Outer(Outer), CurrentFunction(CurrentFunction),
|
|
CurrentCaller(CurrentCaller) {
|
|
ShouldVisitImplicitCode = true;
|
|
ShouldWalkTypesOfTypeLocs = false;
|
|
}
|
|
|
|
// -- Entry point --
|
|
void run() {
|
|
// 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);
|
|
|
|
if (auto *FD = dyn_cast<FunctionDecl>(CurrentCaller.CDecl)) {
|
|
TrailingRequiresClause = FD->getTrailingRequiresClause();
|
|
|
|
// Note that FD->getType->getAs<FunctionProtoType>() can yield a
|
|
// noexcept Expr which has been boiled down to a constant expression.
|
|
// Going through the TypeSourceInfo obtains the actual expression which
|
|
// will be traversed as part of the function -- unless we capture it
|
|
// here and have TraverseStmt skip it.
|
|
if (TypeSourceInfo *TSI = FD->getTypeSourceInfo()) {
|
|
if (FunctionProtoTypeLoc TL =
|
|
TSI->getTypeLoc().getAs<FunctionProtoTypeLoc>())
|
|
if (const FunctionProtoType *FPT = TL.getTypePtr())
|
|
NoexceptExpr = FPT->getNoexceptExpr();
|
|
}
|
|
}
|
|
|
|
// 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 violation. \p Flag describes
|
|
// the construct.
|
|
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 (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) {
|
|
if (Effect.flags() & Flag) {
|
|
addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
|
|
break;
|
|
}
|
|
}
|
|
// 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.EffectsToInfer)
|
|
if (Effect.flags() & Flag)
|
|
addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
|
|
}
|
|
|
|
void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID,
|
|
SourceLocation Loc, const Decl *Callee = nullptr) {
|
|
CurrentFunction.checkAddViolation(
|
|
Inferring, Violation(Effect, VID, VSite, 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(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;
|
|
}
|
|
// A builtin WITH effects doesn't get any special treatment for
|
|
// being noreturn/noexcept, e.g. longjmp(), so we skip the check
|
|
// below.
|
|
} else {
|
|
// If the callee is both `noreturn` and `noexcept`, it presumably
|
|
// terminates. Ignore it for the purposes of effect analysis.
|
|
// If not C++, `noreturn` alone is sufficient.
|
|
if (FD->isNoReturn() &&
|
|
(!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD)))
|
|
return;
|
|
}
|
|
}
|
|
|
|
Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
|
|
/*AssertNoFurtherInference=*/false, VSite);
|
|
}
|
|
|
|
void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
|
|
FunctionEffectKindSet CalleeEffects;
|
|
if (FunctionEffectsRef Effects = FunctionEffectsRef::get(CalleeType);
|
|
!Effects.empty())
|
|
CalleeEffects.insert(Effects);
|
|
|
|
auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
|
|
if (Effect.shouldDiagnoseFunctionCall(
|
|
/*direct=*/false, CalleeEffects))
|
|
addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
|
|
Call->getBeginLoc());
|
|
};
|
|
|
|
for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
|
|
Check1Effect(Effect, false);
|
|
|
|
for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
|
|
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) {
|
|
SourceLocation DtorLoc = Dtor->getLocation();
|
|
for (const FieldDecl *Field : Rec->fields())
|
|
followTypeDtor(Field->getType(), DtorLoc);
|
|
|
|
if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec))
|
|
for (const CXXBaseSpecifier &Base : Class->bases())
|
|
followTypeDtor(Base.getType(), DtorLoc);
|
|
}
|
|
|
|
void followTypeDtor(QualType QT, SourceLocation CallSite) {
|
|
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();
|
|
Dtor && !Dtor->isDeleted()) {
|
|
CallableInfo CI(*Dtor);
|
|
followCall(CI, CallSite);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- Methods for use of RecursiveASTVisitor --
|
|
|
|
bool VisitCXXThrowExpr(CXXThrowExpr *Throw) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Throw->getThrowLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXCatchStmt(CXXCatchStmt *Catch) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Catch->getCatchLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Throw->getThrowLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Catch->getAtCatchLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCAtFinallyStmt(ObjCAtFinallyStmt *Finally) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Finally->getAtFinallyLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
|
|
ViolationID::AccessesObjCMethodOrProperty,
|
|
Msg->getBeginLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCAutoreleasePoolStmt(ObjCAutoreleasePoolStmt *ARP) override {
|
|
// Under the hood, @autorelease (potentially?) allocates memory and
|
|
// invokes ObjC methods. We don't currently have memory allocation as
|
|
// a "language construct" but we do have ObjC messaging, so diagnose that.
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
|
|
ViolationID::AccessesObjCMethodOrProperty,
|
|
ARP->getBeginLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitObjCAtSynchronizedStmt(ObjCAtSynchronizedStmt *Sync) override {
|
|
// Under the hood, this calls objc_sync_enter and objc_sync_exit, wrapped
|
|
// in a @try/@finally block. Diagnose this generically as "ObjC
|
|
// messaging".
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
|
|
ViolationID::AccessesObjCMethodOrProperty,
|
|
Sync->getBeginLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitSEHExceptStmt(SEHExceptStmt *Exc) override {
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
|
|
ViolationID::ThrowsOrCatchesExceptions,
|
|
Exc->getExceptLoc());
|
|
return true;
|
|
}
|
|
|
|
bool VisitCallExpr(CallExpr *Call) override {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "VisitCallExpr : "
|
|
<< Call->getBeginLoc().printToString(Outer.S.SourceMgr)
|
|
<< "\n";);
|
|
|
|
Expr *CalleeExpr = Call->getCallee();
|
|
if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
|
|
CallableInfo CI(*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->getType());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisitVarDecl(VarDecl *Var) override {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "VisitVarDecl : "
|
|
<< Var->getBeginLoc().printToString(Outer.S.SourceMgr)
|
|
<< "\n";);
|
|
|
|
if (Var->isStaticLocal())
|
|
diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
|
|
ViolationID::HasStaticLocalVariable,
|
|
Var->getLocation());
|
|
|
|
const QualType::DestructionKind DK =
|
|
Var->needsDestruction(Outer.S.getASTContext());
|
|
if (DK == QualType::DK_cxx_destructor)
|
|
followTypeDtor(Var->getType(), Var->getLocation());
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXNewExpr(CXXNewExpr *New) override {
|
|
// RecursiveASTVisitor does not visit the implicit call to operator new.
|
|
if (FunctionDecl *FD = New->getOperatorNew()) {
|
|
CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
|
|
followCall(CI, New->getBeginLoc());
|
|
}
|
|
|
|
// It's a bit excessive to check operator delete here, since it's
|
|
// just a fallback for operator new followed by a failed constructor.
|
|
// We could check it via New->getOperatorDelete().
|
|
|
|
// It DOES however visit the called constructor
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) override {
|
|
// RecursiveASTVisitor does not visit the implicit call to operator
|
|
// delete.
|
|
if (FunctionDecl *FD = Delete->getOperatorDelete()) {
|
|
CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
|
|
followCall(CI, Delete->getBeginLoc());
|
|
}
|
|
|
|
// It DOES however visit the called destructor
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXConstructExpr(CXXConstructExpr *Construct) override {
|
|
LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
|
|
<< Construct->getBeginLoc().printToString(
|
|
Outer.S.SourceMgr)
|
|
<< "\n";);
|
|
|
|
// RecursiveASTVisitor does not visit the implicit call to the
|
|
// constructor.
|
|
const CXXConstructorDecl *Ctor = Construct->getConstructor();
|
|
CallableInfo CI(*Ctor);
|
|
followCall(CI, Construct->getLocation());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TraverseStmt(Stmt *Statement) override {
|
|
// If this statement is a `requires` clause from the top-level function
|
|
// being traversed, ignore it, since it's not generating runtime code.
|
|
// We skip the traversal of lambdas (beyond their captures, see
|
|
// TraverseLambdaExpr below), so just caching this from our constructor
|
|
// should suffice.
|
|
if (Statement != TrailingRequiresClause && Statement != NoexceptExpr)
|
|
return DynamicRecursiveASTVisitor::TraverseStmt(Statement);
|
|
return true;
|
|
}
|
|
|
|
bool TraverseConstructorInitializer(CXXCtorInitializer *Init) override {
|
|
ViolationSite PrevVS = VSite;
|
|
if (Init->isAnyMemberInitializer())
|
|
VSite.setKind(ViolationSite::Kind::MemberInitializer);
|
|
bool Result =
|
|
DynamicRecursiveASTVisitor::TraverseConstructorInitializer(Init);
|
|
VSite = PrevVS;
|
|
return Result;
|
|
}
|
|
|
|
bool TraverseCXXDefaultArgExpr(CXXDefaultArgExpr *E) override {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "TraverseCXXDefaultArgExpr : "
|
|
<< E->getUsedLocation().printToString(Outer.S.SourceMgr)
|
|
<< "\n";);
|
|
|
|
ViolationSite PrevVS = VSite;
|
|
if (VSite.kind() == ViolationSite::Kind::Default)
|
|
VSite = ViolationSite{E};
|
|
|
|
bool Result = DynamicRecursiveASTVisitor::TraverseCXXDefaultArgExpr(E);
|
|
VSite = PrevVS;
|
|
return Result;
|
|
}
|
|
|
|
bool TraverseLambdaExpr(LambdaExpr *Lambda) override {
|
|
// 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
|
|
// 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]);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TraverseBlockExpr(BlockExpr * /*unused*/) override {
|
|
// As with lambdas, don't traverse the block's body.
|
|
// TODO: are the capture expressions (ctor call?) safe?
|
|
return true;
|
|
}
|
|
|
|
bool VisitDeclRefExpr(DeclRefExpr *E) override {
|
|
const ValueDecl *Val = E->getDecl();
|
|
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,
|
|
ViolationID::AccessesThreadLocalVariable,
|
|
E->getLocation());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) override {
|
|
return TraverseStmt(Node->getResultExpr());
|
|
}
|
|
bool
|
|
TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) override {
|
|
return true;
|
|
}
|
|
|
|
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) override {
|
|
return true;
|
|
}
|
|
|
|
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) override { return true; }
|
|
|
|
bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) override {
|
|
return true;
|
|
}
|
|
|
|
bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override { return true; }
|
|
|
|
// Skip concept requirements since they don't generate code.
|
|
bool TraverseConceptRequirement(concepts::Requirement *R) override {
|
|
return true;
|
|
}
|
|
};
|
|
};
|
|
|
|
Analyzer::AnalysisMap::~AnalysisMap() {
|
|
for (const auto &Item : *this) {
|
|
FuncAnalysisPtr AP = Item.second;
|
|
if (auto *PFA = dyn_cast<PendingFunctionAnalysis *>(AP))
|
|
delete PFA;
|
|
else
|
|
delete cast<CompleteFunctionAnalysis *>(AP);
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace clang {
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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{*this}.run(*TU);
|
|
}
|
|
|
|
Sema::FunctionEffectDiffVector::FunctionEffectDiffVector(
|
|
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,
|
|
std::nullopt});
|
|
++POld;
|
|
} else if (cmp > 0) {
|
|
// addition
|
|
FunctionEffectWithCondition New = *PNew;
|
|
push_back(FunctionEffectDiff{New.Effect.kind(),
|
|
FunctionEffectDiff::Kind::Added,
|
|
std::nullopt, 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;
|
|
}
|
|
break;
|
|
case FunctionEffect::Kind::Blocking:
|
|
case FunctionEffect::Kind::Allocating:
|
|
return false;
|
|
}
|
|
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;
|
|
}
|
|
break;
|
|
case FunctionEffect::Kind::Blocking:
|
|
case FunctionEffect::Kind::Allocating:
|
|
return false;
|
|
}
|
|
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;
|
|
}
|
|
break;
|
|
case FunctionEffect::Kind::Blocking:
|
|
case FunctionEffect::Kind::Allocating:
|
|
return OverrideResult::NoAction;
|
|
}
|
|
llvm_unreachable("unknown effect kind");
|
|
}
|
|
|
|
} // namespace clang
|