[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