This revision was automatically updated to reflect the committed changes.
Closed by commit rGc163aae45ef6: [attributes]  Add a facility for enforcing a 
Trusted Computing Base. (authored by dergachev.a).
Herald added a project: clang.

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D91898/new/

https://reviews.llvm.org/D91898

Files:
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Sema/Sema.h
  clang/lib/Sema/SemaChecking.cpp
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/test/Sema/attr-enforce-tcb-errors.cpp
  clang/test/Sema/attr-enforce-tcb.c
  clang/test/Sema/attr-enforce-tcb.cpp

Index: clang/test/Sema/attr-enforce-tcb.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-enforce-tcb.cpp
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define PLACE_IN_TCB(NAME) [[clang::enforce_tcb(NAME)]]
+#define PLACE_IN_TCB_LEAF(NAME) [[clang::enforce_tcb_leaf(NAME)]]
+
+PLACE_IN_TCB("foo") void in_tcb_foo();
+void not_in_tcb();
+
+// Test behavior on classes and methods.
+class C {
+  void bar();
+
+  PLACE_IN_TCB("foo")
+  void foo() {
+    // TODO: Figure out if we want to support methods at all.
+    // Does it even make sense to isolate individual methods into a TCB?
+    // Maybe a per-class attribute would make more sense?
+    bar(); // expected-warning{{calling 'bar' is a violation of trusted computing base 'foo'}}
+  }
+};
+
+// Test behavior on templates.
+template <typename Ty>
+PLACE_IN_TCB("foo")
+void foo_never_instantiated() {
+  not_in_tcb(); // expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}}
+  in_tcb_foo(); // no-warning
+}
+
+template <typename Ty>
+PLACE_IN_TCB("foo")
+void foo_specialized();
+
+template<>
+void foo_specialized<int>() {
+  not_in_tcb(); // expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}}
+  in_tcb_foo(); // no-warning
+}
+
+PLACE_IN_TCB("foo")
+void call_template_good() {
+  foo_specialized<int>(); // no-warning
+}
+PLACE_IN_TCB("bar")
+void call_template_bad() {
+  foo_specialized<int>(); // expected-warning{{calling 'foo_specialized<int>' is a violation of trusted computing base 'bar'}}
+}
+
+template<typename Ty>
+void foo_specialization_in_tcb();
+
+template<>
+PLACE_IN_TCB("foo")
+void foo_specialization_in_tcb<int>() {
+  not_in_tcb(); //expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}}
+  in_tcb_foo(); // no-warning
+}
+
+template<>
+void foo_specialization_in_tcb<double>() {
+  not_in_tcb(); // no-warning
+  in_tcb_foo(); // no-warning
+}
+
+PLACE_IN_TCB("foo")
+void call_specialization_in_tcb() {
+  foo_specialization_in_tcb<int>(); // no-warning
+  foo_specialization_in_tcb<long>(); // expected-warning{{calling 'foo_specialization_in_tcb<long>' is a violation of trusted computing base 'foo'}}
+  foo_specialization_in_tcb<double>(); // expected-warning{{'foo_specialization_in_tcb<double>' is a violation of trusted computing base 'foo'}}
+}
Index: clang/test/Sema/attr-enforce-tcb.c
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-enforce-tcb.c
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define PLACE_IN_TCB(NAME) __attribute__ ((enforce_tcb(NAME)))
+#define PLACE_IN_TCB_LEAF(NAME) __attribute__ ((enforce_tcb_leaf(NAME)))
+
+void foo1 (void) PLACE_IN_TCB("bar");
+void foo2 (void) PLACE_IN_TCB("bar");
+void foo3 (void); // not in any TCB
+void foo4 (void) PLACE_IN_TCB("bar2");
+void foo5 (void) PLACE_IN_TCB_LEAF("bar");
+void foo6 (void) PLACE_IN_TCB("bar2") PLACE_IN_TCB("bar");
+void foo7 (void) PLACE_IN_TCB("bar3");
+void foo8 (void) PLACE_IN_TCB("bar") PLACE_IN_TCB("bar2");
+void foo9 (void);
+
+void foo1() {
+    foo2(); // OK - function in same TCB
+    foo3(); // expected-warning {{calling 'foo3' is a violation of trusted computing base 'bar'}}
+    foo4(); // expected-warning {{calling 'foo4' is a violation of trusted computing base 'bar'}}
+    foo5(); // OK - in leaf node
+    foo6(); // OK - in multiple TCBs, one of which is the same
+    foo7(); // expected-warning {{calling 'foo7' is a violation of trusted computing base 'bar'}}
+    (void) __builtin_clz(5); // OK - builtins are excluded
+}
+
+// Normal use without any attributes works
+void foo3() {
+    foo9(); // no-warning
+}
+
+void foo5() {
+    // all calls should be okay, function in TCB leaf
+    foo2(); // no-warning
+    foo3(); // no-warning
+    foo4(); // no-warning
+}
+
+void foo6() {
+    foo1(); // expected-warning {{calling 'foo1' is a violation of trusted computing base 'bar2'}}
+    foo4(); // expected-warning {{calling 'foo4' is a violation of trusted computing base 'bar'}}
+    foo8(); // no-warning
+    foo7(); // #1
+    // expected-warning@#1 {{calling 'foo7' is a violation of trusted computing base 'bar2'}}
+    // expected-warning@#1 {{calling 'foo7' is a violation of trusted computing base 'bar'}}
+}
+
+// Ensure that attribute merging works as expected across redeclarations.
+void foo10() PLACE_IN_TCB("bar");
+void foo10() PLACE_IN_TCB("bar2");
+void foo10() PLACE_IN_TCB("bar3");
+void foo10() {
+  foo1(); // #2
+    // expected-warning@#2 {{calling 'foo1' is a violation of trusted computing base 'bar2'}}
+    // expected-warning@#2 {{calling 'foo1' is a violation of trusted computing base 'bar3'}}
+  foo3(); // #3
+    // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar'}}
+    // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar2'}}
+    // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar3'}}
+  foo4(); // #4
+    // expected-warning@#4 {{calling 'foo4' is a violation of trusted computing base 'bar'}}
+    // expected-warning@#4 {{calling 'foo4' is a violation of trusted computing base 'bar3'}}
+  foo7(); // #5
+    // expected-warning@#5 {{calling 'foo7' is a violation of trusted computing base 'bar'}}
+    // expected-warning@#5 {{calling 'foo7' is a violation of trusted computing base 'bar2'}}
+}
Index: clang/test/Sema/attr-enforce-tcb-errors.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-enforce-tcb-errors.cpp
@@ -0,0 +1,80 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+[[clang::enforce_tcb("oops")]] int wrong_subject_type; // expected-warning{{'enforce_tcb' attribute only applies to functions}}
+
+void no_arguments() __attribute__((enforce_tcb)); // expected-error{{'enforce_tcb' attribute takes one argument}}
+
+void too_many_arguments() __attribute__((enforce_tcb("test", 12))); // expected-error{{'enforce_tcb' attribute takes one argument}}
+
+void wrong_argument_type() __attribute__((enforce_tcb(12))); // expected-error{{'enforce_tcb' attribute requires a string}}
+
+[[clang::enforce_tcb_leaf("oops")]] int wrong_subject_type_leaf; // expected-warning{{'enforce_tcb_leaf' attribute only applies to functions}}
+
+void no_arguments_leaf() __attribute__((enforce_tcb_leaf)); // expected-error{{'enforce_tcb_leaf' attribute takes one argument}}
+
+void too_many_arguments_leaf() __attribute__((enforce_tcb_leaf("test", 12))); // expected-error{{'enforce_tcb_leaf' attribute takes one argument}}
+void wrong_argument_type_leaf() __attribute__((enforce_tcb_leaf(12))); // expected-error{{'enforce_tcb_leaf' attribute requires a string}}
+
+void foo();
+
+__attribute__((enforce_tcb("x")))
+__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}}
+void both_tcb_and_tcb_leaf() {
+  foo(); // no-warning
+}
+
+__attribute__((enforce_tcb_leaf("x"))) // expected-note{{conflicting attribute is here}}
+void both_tcb_and_tcb_leaf_on_separate_redeclarations();
+__attribute__((enforce_tcb("x"))) // expected-error{{attributes 'enforce_tcb("x")' and 'enforce_tcb_leaf("x")' are mutually exclusive}}
+void both_tcb_and_tcb_leaf_on_separate_redeclarations() {
+  // Error recovery: no need to emit a warning when we didn't
+  // figure out our attributes to begin with.
+  foo(); // no-warning
+}
+
+__attribute__((enforce_tcb_leaf("x")))
+__attribute__((enforce_tcb("x"))) // expected-error{{attributes 'enforce_tcb("x")' and 'enforce_tcb_leaf("x")' are mutually exclusive}}
+void both_tcb_and_tcb_leaf_opposite_order() {
+  foo(); // no-warning
+}
+
+__attribute__((enforce_tcb("x"))) // expected-note{{conflicting attribute is here}}
+void both_tcb_and_tcb_leaf_on_separate_redeclarations_opposite_order();
+__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}}
+void both_tcb_and_tcb_leaf_on_separate_redeclarations_opposite_order() {
+  foo(); // no-warning
+}
+
+__attribute__((enforce_tcb("x")))
+__attribute__((enforce_tcb_leaf("y"))) // no-error
+void both_tcb_and_tcb_leaf_but_different_identifiers() {
+  foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'x'}}
+}
+__attribute__((enforce_tcb_leaf("x")))
+__attribute__((enforce_tcb("y"))) // no-error
+void both_tcb_and_tcb_leaf_but_different_identifiers_opposite_order() {
+  foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'y'}}
+}
+
+__attribute__((enforce_tcb("x")))
+void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations();
+__attribute__((enforce_tcb_leaf("y"))) // no-error
+void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations() {
+  foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'x'}}
+}
+
+__attribute__((enforce_tcb_leaf("x")))
+void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations_opposite_order();
+__attribute__((enforce_tcb("y")))
+void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations_opposite_order() {
+  foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'y'}}
+}
+
+__attribute__((enforce_tcb("y")))
+__attribute__((enforce_tcb("x")))
+__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}}
+void error_recovery_over_individual_tcbs() {
+  // FIXME: Ideally this should warn. The conflict between attributes
+  // for TCB "x" shouldn't affect the warning about TCB "y".
+  foo(); // no-warning
+}
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ clang/lib/Sema/SemaDeclAttr.cpp
@@ -7517,6 +7517,75 @@
   D->addAttr(::new (S.Context) CFGuardAttr(S.Context, AL, Arg));
 }
 
+
+template <typename AttrTy>
+static const AttrTy *findEnforceTCBAttrByName(Decl *D, StringRef Name) {
+  auto Attrs = D->specific_attrs<AttrTy>();
+  auto I = llvm::find_if(Attrs,
+                         [Name](const AttrTy *A) {
+                           return A->getTCBName() == Name;
+                         });
+  return I == Attrs.end() ? nullptr : *I;
+}
+
+template <typename AttrTy, typename ConflictingAttrTy>
+static void handleEnforceTCBAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef Argument;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, Argument))
+    return;
+
+  // A function cannot be have both regular and leaf membership in the same TCB.
+  if (const ConflictingAttrTy *ConflictingAttr =
+      findEnforceTCBAttrByName<ConflictingAttrTy>(D, Argument)) {
+    // We could attach a note to the other attribute but in this case
+    // there's no need given how the two are very close to each other.
+    S.Diag(AL.getLoc(), diag::err_tcb_conflicting_attributes)
+      << AL.getAttrName()->getName() << ConflictingAttr->getAttrName()->getName()
+      << Argument;
+
+    // Error recovery: drop the non-leaf attribute so that to suppress
+    // all future warnings caused by erroneous attributes. The leaf attribute
+    // needs to be kept because it can only suppresses warnings, not cause them.
+    D->dropAttr<EnforceTCBAttr>();
+    return;
+  }
+
+  D->addAttr(AttrTy::Create(S.Context, Argument, AL));
+}
+
+template <typename AttrTy, typename ConflictingAttrTy>
+static AttrTy *mergeEnforceTCBAttrImpl(Sema &S, Decl *D, const AttrTy &AL) {
+  // Check if the new redeclaration has different leaf-ness in the same TCB.
+  StringRef TCBName = AL.getTCBName();
+  if (const ConflictingAttrTy *ConflictingAttr =
+      findEnforceTCBAttrByName<ConflictingAttrTy>(D, TCBName)) {
+    S.Diag(ConflictingAttr->getLoc(), diag::err_tcb_conflicting_attributes)
+      << ConflictingAttr->getAttrName()->getName()
+      << AL.getAttrName()->getName() << TCBName;
+
+    // Add a note so that the user could easily find the conflicting attribute.
+    S.Diag(AL.getLoc(), diag::note_conflicting_attribute);
+
+    // More error recovery.
+    D->dropAttr<EnforceTCBAttr>();
+    return nullptr;
+  }
+
+  ASTContext &Context = S.getASTContext();
+  return ::new(Context) AttrTy(Context, AL, AL.getTCBName());
+}
+
+EnforceTCBAttr *Sema::mergeEnforceTCBAttr(Decl *D, const EnforceTCBAttr &AL) {
+  return mergeEnforceTCBAttrImpl<EnforceTCBAttr, EnforceTCBLeafAttr>(
+      *this, D, AL);
+}
+
+EnforceTCBLeafAttr *Sema::mergeEnforceTCBLeafAttr(
+    Decl *D, const EnforceTCBLeafAttr &AL) {
+  return mergeEnforceTCBAttrImpl<EnforceTCBLeafAttr, EnforceTCBAttr>(
+      *this, D, AL);
+}
+
 //===----------------------------------------------------------------------===//
 // Top Level Sema Entry Points
 //===----------------------------------------------------------------------===//
@@ -8220,6 +8289,14 @@
   case ParsedAttr::AT_UseHandle:
     handleHandleAttr<UseHandleAttr>(S, D, AL);
     break;
+
+  case ParsedAttr::AT_EnforceTCB:
+    handleEnforceTCBAttr<EnforceTCBAttr, EnforceTCBLeafAttr>(S, D, AL);
+    break;
+
+  case ParsedAttr::AT_EnforceTCBLeaf:
+    handleEnforceTCBAttr<EnforceTCBLeafAttr, EnforceTCBAttr>(S, D, AL);
+    break;
   }
 }
 
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ clang/lib/Sema/SemaDecl.cpp
@@ -2612,6 +2612,10 @@
     NewAttr = S.mergeImportModuleAttr(D, *IMA);
   else if (const auto *INA = dyn_cast<WebAssemblyImportNameAttr>(Attr))
     NewAttr = S.mergeImportNameAttr(D, *INA);
+  else if (const auto *TCBA = dyn_cast<EnforceTCBAttr>(Attr))
+    NewAttr = S.mergeEnforceTCBAttr(D, *TCBA);
+  else if (const auto *TCBLA = dyn_cast<EnforceTCBLeafAttr>(Attr))
+    NewAttr = S.mergeEnforceTCBLeafAttr(D, *TCBLA);
   else if (Attr->shouldInheritEvenIfAlreadyPresent() || !DeclHasAttr(D, Attr))
     NewAttr = cast<InheritableAttr>(Attr->clone(S.Context));
 
Index: clang/lib/Sema/SemaChecking.cpp
===================================================================
--- clang/lib/Sema/SemaChecking.cpp
+++ clang/lib/Sema/SemaChecking.cpp
@@ -75,6 +75,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/ADT/Triple.h"
 #include "llvm/Support/AtomicOrdering.h"
@@ -4569,6 +4570,8 @@
   if (!FnInfo)
     return false;
 
+  CheckTCBEnforcement(TheCall, FDecl);
+
   CheckAbsoluteValueFunction(TheCall, FDecl);
   CheckMaxUnsignedZero(TheCall, FDecl);
 
@@ -16059,3 +16062,37 @@
 
   return CallResult;
 }
+
+/// \brief Enforce the bounds of a TCB
+/// CheckTCBEnforcement - Enforces that every function in a named TCB only
+/// directly calls other functions in the same TCB as marked by the enforce_tcb
+/// and enforce_tcb_leaf attributes.
+void Sema::CheckTCBEnforcement(const CallExpr *TheCall,
+                               const FunctionDecl *Callee) {
+  const FunctionDecl *Caller = getCurFunctionDecl();
+
+  // Calls to builtins are not enforced.
+  if (!Caller || !Caller->hasAttr<EnforceTCBAttr>() ||
+      Callee->getBuiltinID() != 0)
+    return;
+
+  // Search through the enforce_tcb and enforce_tcb_leaf attributes to find
+  // all TCBs the callee is a part of.
+  llvm::StringSet<> CalleeTCBs;
+  for_each(Callee->specific_attrs<EnforceTCBAttr>(),
+           [&](const auto *A) { CalleeTCBs.insert(A->getTCBName()); });
+  for_each(Callee->specific_attrs<EnforceTCBLeafAttr>(),
+           [&](const auto *A) { CalleeTCBs.insert(A->getTCBName()); });
+
+  // Go through the TCBs the caller is a part of and emit warnings if Caller
+  // is in a TCB that the Callee is not.
+  for_each(
+      Caller->specific_attrs<EnforceTCBAttr>(),
+      [&](const auto *A) {
+        StringRef CallerTCB = A->getTCBName();
+        if (CalleeTCBs.count(CallerTCB) == 0) {
+          Diag(TheCall->getExprLoc(), diag::warn_tcb_enforcement_violation)
+              << Callee << CallerTCB;
+        }
+      });
+}
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -3210,6 +3210,9 @@
       Decl *D, const WebAssemblyImportNameAttr &AL);
   WebAssemblyImportModuleAttr *mergeImportModuleAttr(
       Decl *D, const WebAssemblyImportModuleAttr &AL);
+  EnforceTCBAttr *mergeEnforceTCBAttr(Decl *D, const EnforceTCBAttr &AL);
+  EnforceTCBLeafAttr *mergeEnforceTCBLeafAttr(Decl *D,
+                                              const EnforceTCBLeafAttr &AL);
 
   void mergeDeclAttributes(NamedDecl *New, Decl *Old,
                            AvailabilityMergeKind AMK = AMK_Redeclaration);
@@ -12427,6 +12430,8 @@
   /// attempts to add itself into the container
   void CheckObjCCircularContainer(ObjCMessageExpr *Message);
 
+  void CheckTCBEnforcement(const CallExpr *TheCall, const FunctionDecl *Callee);
+
   void AnalyzeDeleteExprMismatch(const CXXDeleteExpr *DE);
   void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc,
                                  bool DeleteWasArrayForm);
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11116,4 +11116,11 @@
 def err_probability_out_of_range : Error<
    "probability argument to __builtin_expect_with_probability is outside the "
    "range [0.0, 1.0]">;
+
+// TCB warnings
+def err_tcb_conflicting_attributes : Error<
+  "attributes '%0(\"%2\")' and '%1(\"%2\")' are mutually exclusive">;
+def warn_tcb_enforcement_violation : Warning<
+  "calling %0 is a violation of trusted computing base '%1'">,
+  InGroup<DiagGroup<"tcb-enforcement">>;
 } // end of sema component.
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -5725,3 +5725,28 @@
 }];
   let Heading = "always_inline, __force_inline";
 }
+
+def EnforceTCBDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+  The ``enforce_tcb`` attribute can be placed on functions to enforce that a
+  trusted compute base (TCB) does not call out of the TCB. This generates a
+  warning every time a function not marked with an ``enforce_tcb`` attribute is
+  called from a function with the ``enforce_tcb`` attribute. A function may be a
+  part of multiple TCBs. Invocations through function pointers are currently
+  not checked. Builtins are considered to a part of every TCB.
+
+  - ``enforce_tcb(Name)`` indicates that this function is a part of the TCB named ``Name``
+  }];
+}
+
+def EnforceTCBLeafDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+  The ``enforce_tcb_leaf`` attribute satisfies the requirement enforced by
+  ``enforce_tcb`` for the marked function to be in the named TCB but does not
+  continue to check the functions called from within the leaf function.
+
+  - ``enforce_tcb_leaf(Name)`` indicates that this function is a part of the TCB named ``Name``
+  }];
+}
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -3653,3 +3653,19 @@
   let SemaHandler = 0;
   let Documentation = [Undocumented];
 }
+
+def EnforceTCB : InheritableAttr {
+  let Spellings = [Clang<"enforce_tcb">];
+  let Subjects = SubjectList<[Function]>;
+  let Args = [StringArgument<"TCBName">];
+  let Documentation = [EnforceTCBDocs];
+  bit InheritEvenIfAlreadyPresent = 1;
+}
+
+def EnforceTCBLeaf : InheritableAttr {
+  let Spellings = [Clang<"enforce_tcb_leaf">];
+  let Subjects = SubjectList<[Function]>;
+  let Args = [StringArgument<"TCBName">];
+  let Documentation = [EnforceTCBLeafDocs];
+  bit InheritEvenIfAlreadyPresent = 1;
+}
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to