[clang] nonblocking/nonallocating attributes (was: nolock/noalloc) (PR #84983)
Doug Wyatt via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 10 10:15:05 PDT 2024
================
@@ -5016,3 +5024,254 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
getTypeConstraintConcept(), getTypeConstraintArguments());
}
+
+FunctionEffect::FunctionEffect(Type T)
+ : Type_(static_cast<unsigned>(T)), Flags_(0), Padding(0) {
+ switch (T) {
+ case Type::NonBlocking:
+ Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
+ FE_ExcludeThreadLocalVars;
+ break;
+
+ case Type::NonAllocating:
+ // Same as NonBlocking, except without FE_ExcludeStaticLocalVars
+ Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
+ break;
+ default:
+ break;
+ }
+}
+
+StringRef FunctionEffect::name() const {
+ switch (type()) {
+ default:
+ return "";
+ case Type::NonBlocking:
+ return "nonblocking";
+ case Type::NonAllocating:
+ return "nonallocating";
+ }
+}
+
+bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX,
+ QualType NewType,
+ FunctionEffectSet NewFX) const {
+
+ switch (type()) {
+ case Type::NonAllocating:
+ // nonallocating can't be added (spoofed) during a conversion, unless we
+ // have nonblocking
+ if (Adding) {
+ for (const auto &Effect : OldFX) {
+ if (Effect.type() == Type::NonBlocking)
+ return false;
+ }
+ }
+ [[fallthrough]];
+ case Type::NonBlocking:
+ // nonblocking can't be added (spoofed) during a conversion
+ return Adding;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool FunctionEffect::diagnoseRedeclaration(bool Adding,
+ const FunctionDecl &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ FunctionEffectSet NewFX) const {
+ switch (type()) {
+ case Type::NonAllocating:
+ case Type::NonBlocking:
+ // nonblocking/nonallocating can't be removed in a redeclaration
+ // adding -> false, removing -> true (diagnose)
+ return !Adding;
+ default:
+ break;
+ }
+ return false;
+}
+
+FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
+ bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
+ const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
+ switch (type()) {
+ case Type::NonAllocating:
+ case Type::NonBlocking:
+ // if added on an override, that's fine and not diagnosed.
+ // if missing from an override (removed), propagate from base to derived.
+ return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
+ default:
+ break;
+ }
+ return OverrideResult::Ignore;
+}
+
+bool FunctionEffect::canInferOnFunction(QualType QT,
+ const TypeSourceInfo *FType) const {
+ switch (type()) {
+ case Type::NonAllocating:
+ case Type::NonBlocking: {
+ // Does the sugar have nonblocking(false) / nonallocating(false) ?
+ if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::Blocking
+ : attr::Kind::Allocating)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ default:
+ break;
+ }
+ return false;
+}
+
+bool FunctionEffect::diagnoseFunctionCall(bool Direct,
+ FunctionEffectSet CalleeFX) const {
+ switch (type()) {
+ case Type::NonAllocating:
+ case Type::NonBlocking: {
+ const Type CallerType = type();
+ for (const auto &Effect : CalleeFX) {
+ const Type ET = Effect.type();
+ // Does callee have same or stronger constraint?
+ if (ET == CallerType ||
+ (CallerType == Type::NonAllocating && ET == Type::NonBlocking)) {
+ return false; // no diagnostic
+ }
+ }
+ return true; // warning
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+// =====
+
+MutableFunctionEffectSet::MutableFunctionEffectSet(
+ const FunctionEffect &effect) {
+ push_back(effect);
+}
+
+void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
+ const auto &Iter = std::lower_bound(begin(), end(), Effect);
+ if (Iter == end() || *Iter != Effect) {
+ insert(Iter, Effect);
+ }
+}
+
+MutableFunctionEffectSet &
+MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
+ // TODO: For large RHS sets, use set_union or a custom insert-in-place
+ for (const auto &Effect : RHS) {
+ insert(Effect);
+ }
+ return *this;
+}
+
+FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
+ const FunctionEffectSet &LHS,
+ const FunctionEffectSet &RHS) {
+ // Optimize for one of the two sets being empty
+ if (LHS.empty())
+ return RHS;
+ if (RHS.empty())
+ return LHS;
+
+ // Optimize the case where the two sets are identical
+ if (LHS == RHS)
+ return LHS;
+
+ MutableFunctionEffectSet Vec;
+ Vec.reserve(LHS.size() + RHS.size());
+ std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Vec));
+ // The result of a set operation is an ordered/unique set.
+ return C.getUniquedFunctionEffectSet(Vec);
+}
+
+MutableFunctionEffectSet
+FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
+ const FunctionEffectSet &LHS = *this;
+ if (LHS.empty() || RHS.empty()) {
+ return {};
+ }
+
+ MutableFunctionEffectSet Vec;
+ std::set_intersection(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+ std::back_inserter(Vec));
+ // The result of a set operation is an ordered/unique set.
+ return Vec;
+}
+
+// TODO: inline?
+FunctionEffectSet FunctionEffectSet::get(QualType QT) {
+ if (QT->isReferenceType())
+ QT = QT.getNonReferenceType();
+ if (QT->isPointerType())
+ QT = QT->getPointeeType();
+
+ if (const auto *BT = QT->getAs<BlockPointerType>()) {
+ QT = BT->getPointeeType();
+ } else if (const auto *MP = QT->getAs<MemberPointerType>()) {
+ if (MP->isMemberFunctionPointer()) {
+ QT = MP->getPointeeType();
+ }
+ }
+
+ if (const auto *FPT = QT->getAs<FunctionProtoType>())
+ return FPT->getFunctionEffects();
----------------
dougsonos wrote:
The common theme here is having to peel off references, pointers, etc. in order to get to the `FunctionProtoType`. I too am unhappy that it seems brittle. But at least it's not repeated into its callers, which are:
`Sema::diagnoseFunctionEffectConversion` which only has a pair of `QualType`s. This is only called from `Sema::ImpCastExprToType`, which only has a `QualType` and an `Expr*`.
In the analysis phase, we can have a `ValueDecl` where adoption in a large codebase revealed the need for all of these situations. The difficulties arise with patterns where a function is being called indirectly, and the function/block/PTMF might itself be part of a cast expression, or a variable that is a reference or pointer.
I would hope this problem has been solved elsewhere and I just didn't know where to look.
https://github.com/llvm/llvm-project/pull/84983
More information about the cfe-commits
mailing list