[clang] 678ded0 - [clang] Support for read-only types

via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 15 12:09:10 PST 2022


Author: MalavikaSamak
Date: 2022-12-15T12:09:01-08:00
New Revision: 678ded017f21b22da3ab20ffafe2bc2dc4457493

URL: https://github.com/llvm/llvm-project/commit/678ded017f21b22da3ab20ffafe2bc2dc4457493
DIFF: https://github.com/llvm/llvm-project/commit/678ded017f21b22da3ab20ffafe2bc2dc4457493.diff

LOG: [clang] Support for read-only types

The main goal of this work is to allow developers to express the need to place instances
of a class or structure in the read-only part of the program memory. Such a placement is
desirable to prevent any further modifications to the instances of a given structure, by
leveraging the read-only run time protection.

To achieve this, we are introducing a new attribute that can be attached to any record
definition or a declaration. The compiler enforces that every instance of this type can
be placed in the read-only segment of the program memory, provided the target triplet
supports such a placement. If an instance of a given type bearing this attribute doesn’t
satisfy such a placement, the compiler attaches an appropriate warning at suitable program
locations. In other words, adding this attribute to a type requires every instance of this
type to be a global const, which are placed in the read-only segments for most target
triplets. However, this is *not a language feature* and it *need not* be true for
*all target triplets*.

The current patch emits a warning at global variable declaration sites of types bearing
the attribute without const qualification and corresponding note attached to the type
definition/declaration.

Differential Revision: https://reviews.llvm.org/D135851

Added: 
    clang/test/Sema/attr-read-only-placement.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/test/Misc/pragma-attribute-supported-attributes-list.test

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ae6a52f57209..0da79fc1a2a9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -546,6 +546,11 @@ Attribute Changes in Clang
   used ``201904L`` (the date the proposal was seen by the committee) by mistake.
   There were no other changes to the attribute behavior.
 
+- Introduced a new record declaration attribute ``__attribute__((enforce_read_only_placement))``
+  to support analysis of instances of a given type focused on read-only program
+  memory placement. It emits a warning if something in the code provably prevents
+  an instance from a read-only memory placement.
+
 Windows Support
 ---------------
 - For the MinGW driver, added the options ``-mguard=none``, ``-mguard=cf`` and

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index eaf4a6db3600..aef3356ed405 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4096,3 +4096,8 @@ def FunctionReturnThunks : InheritableAttr,
   let Subjects = SubjectList<[Function]>;
   let Documentation = [FunctionReturnThunksDocs];
 }
+def ReadOnlyPlacement : InheritableAttr {
+  let Spellings = [Clang<"enforce_read_only_placement">];
+  let Subjects = SubjectList<[Record]>;
+  let Documentation = [ReadOnlyPlacementDocs];
+}

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index a5ea3915a94d..77f72d2b2bd4 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6782,3 +6782,42 @@ The symbol used for ``thunk-extern`` is target specific:
 As such, this function attribute is currently only supported on X86 targets.
   }];
 }
+
+def ReadOnlyPlacementDocs : Documentation {
+  let Category = DocCatType;
+  let Content = [{This attribute is attached to a structure, class or union declaration.
+  When attached to a record declaration/definition, it checks if all instances
+  of this type can be placed in the read-only data segment of the program. If it
+  finds an instance that can not be placed in a read-only segment, the compiler
+  emits a warning at the source location where the type was used.
+
+  Examples:
+  * ``struct __attribute__((enforce_read_only_placement)) Foo;``
+  * ``struct __attribute__((enforce_read_only_placement)) Bar { ... };``
+
+  Both ``Foo`` and ``Bar`` types have the ``enforce_read_only_placement`` attribute.
+
+  The goal of introducing this attribute is to assist developers with writing secure
+  code. A ``const``-qualified global is generally placed in the read-only section
+  of the memory that has additional run time protection from malicious writes. By
+  attaching this attribute to a declaration, the developer can express the intent
+  to place all instances of the annotated type in the read-only program memory.
+
+  Note 1: The attribute doesn't guarantee that the object will be placed in the
+  read-only data segment as it does not instruct the compiler to ensure such
+  a placement. It emits a warning if something in the code can be proven to prevent
+  an instance from being placed in the read-only data segment.
+
+  Note 2: Currently, clang only checks if all global declarations of a given type 'T'
+  are ``const``-qualified. The following conditions would also prevent the data to be
+  put into read only segment, but the corresponding warnings are not yet implemented.
+
+  1. An instance of type ``T`` is allocated on the heap/stack.
+  2. Type ``T`` defines/inherits a mutable field.
+  3. Type ``T`` defines/inherits non-constexpr constructor(s) for initialization.
+  4. A field of type ``T`` is defined by type ``Q``, which does not bear the
+     ``enforce_read_only_placement`` attribute.
+  5. A type ``Q`` inherits from type ``T`` and it does not have the
+     ``enforce_read_only_placement`` attribute.
+  }];
+}

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 006e5afcd43b..abc1437cecff 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1390,3 +1390,6 @@ def BranchProtection : DiagGroup<"branch-protection">;
 // HLSL diagnostic groups
 // Warnings for HLSL Clang extensions
 def HLSLExtension : DiagGroup<"hlsl-extensions">;
+
+// Warnings and notes related to const_var_decl_type attribute checks
+def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7cda2a0d0334..8b4de86ca5d0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5681,6 +5681,12 @@ def err_new_abi_tag_on_redeclaration : Error<
 def note_use_ifdef_guards : Note<
   "unguarded header; consider using #ifdef guards or #pragma once">;
 
+def warn_var_decl_not_read_only : Warning<
+  "object of type %0 cannot be placed in read-only memory">,
+  InGroup<ReadOnlyPlacementChecks>;
+def note_enforce_read_only_placement : Note<"type was declared read-only here">;
+
+
 def note_deleted_dtor_no_operator_delete : Note<
   "virtual destructor requires an unambiguous, accessible 'operator delete'">;
 def note_deleted_special_member_class_subobject : Note<

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a0f917c4852e..9c0df162a8b9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7378,6 +7378,36 @@ static void copyAttrFromTypedefToDecl(Sema &S, Decl *D, const TypedefType *TT) {
   }
 }
 
+// This function emits warning and a corresponding note based on the
+// ReadOnlyPlacementAttr attribute. The warning checks that all global variable
+// declarations of an annotated type must be const qualified.
+void emitReadOnlyPlacementAttrWarning(Sema &S, const VarDecl *VD) {
+  QualType VarType = VD->getType().getCanonicalType();
+
+  // Ignore local declarations (for now) and those with const qualification.
+  // TODO: Local variables should not be allowed if their type declaration has
+  // ReadOnlyPlacementAttr attribute. To be handled in follow-up patch.
+  if (!VD || VD->hasLocalStorage() || VD->getType().isConstQualified())
+    return;
+
+  if (VarType->isArrayType()) {
+    // Retrieve element type for array declarations.
+    VarType = S.getASTContext().getBaseElementType(VarType);
+  }
+
+  const RecordDecl *RD = VarType->getAsRecordDecl();
+
+  // Check if the record declaration is present and if it has any attributes.
+  if (RD == nullptr)
+    return;
+
+  if (const auto *ConstDecl = RD->getAttr<ReadOnlyPlacementAttr>()) {
+    S.Diag(VD->getLocation(), diag::warn_var_decl_not_read_only) << RD;
+    S.Diag(ConstDecl->getLocation(), diag::note_enforce_read_only_placement);
+    return;
+  }
+}
+
 NamedDecl *Sema::ActOnVariableDeclarator(
     Scope *S, Declarator &D, DeclContext *DC, TypeSourceInfo *TInfo,
     LookupResult &Previous, MultiTemplateParamsArg TemplateParamLists,
@@ -8042,6 +8072,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
   if (IsMemberSpecialization && !NewVD->isInvalidDecl())
     CompleteMemberSpecialization(NewVD, Previous);
 
+  emitReadOnlyPlacementAttrWarning(*this, NewVD);
+
   return NewVD;
 }
 

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index dcddf4c91073..93cc897e393f 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8598,6 +8598,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_X86ForceAlignArgPointer:
     handleX86ForceAlignArgPointerAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_ReadOnlyPlacement:
+    handleSimpleAttribute<ReadOnlyPlacementAttr>(S, D, AL);
+    break;
   case ParsedAttr::AT_DLLExport:
   case ParsedAttr::AT_DLLImport:
     handleDLLAttr(S, D, AL);

diff  --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index b6a52832dadd..1326cec0a431 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -154,6 +154,7 @@
 // CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method)
 // CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
 // CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
+// CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record)
 // CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
 // CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function)
 // CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)

diff  --git a/clang/test/Sema/attr-read-only-placement.cpp b/clang/test/Sema/attr-read-only-placement.cpp
new file mode 100644
index 000000000000..25a1839f838f
--- /dev/null
+++ b/clang/test/Sema/attr-read-only-placement.cpp
@@ -0,0 +1,170 @@
+// RUN: %clang_cc1 -Wread-only-types %s -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++2a -Wread-only-types %s -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++17 -Wread-only-types %s -verify -fsyntax-only
+
+struct __attribute__((enforce_read_only_placement)) A { // #A_DECL
+};
+
+A a1; // expected-warning {{object of type 'A' cannot be placed in read-only memory}}
+      // expected-note@#A_DECL {{type was declared read-only here}}
+const A a2[10]; // no-warning
+A a3[20]; // expected-warning {{object of type 'A' cannot be placed in read-only memory}}
+          // expected-note@#A_DECL {{type was declared read-only here}}
+
+
+
+struct B;
+struct __attribute__((enforce_read_only_placement)) B { //#B_DECL
+};
+
+B b1; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
+      // expected-note@#B_DECL {{type was declared read-only here}}
+const B b2; // no-warning
+const B b3[4]; // no-warning
+B b4[5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
+         // expected-note@#B_DECL {{type was declared read-only here}}
+B b5[5][5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
+            // expected-note@#B_DECL {{type was declared read-only here}}
+B b10[5][5][5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
+                // expected-note@#B_DECL {{type was declared read-only here}}
+
+void method1() {
+    static const B b6;
+    static B b7;// expected-warning {{object of type 'B' cannot be placed in read-only memory}}
+                // expected-note@#B_DECL {{type was declared read-only here}}
+    B b8; // no-warning
+    const B b9; // no-warning
+}
+
+struct C;
+struct __attribute__((enforce_read_only_placement)) C; // expected-note {{type was declared read-only here}}
+struct C { // no-note. The note should be attached to the definition/declaration bearing the attribute
+};
+
+C c1; // expected-warning {{object of type 'C' cannot be placed in read-only memory}}
+
+// Cases to be handled by the follow-up patches.
+
+// Attaching and checking the attribute in reverse, where the attribute is attached after the
+// type definition
+struct D;
+struct D { //expected-note{{previous definition is here}}
+};
+struct __attribute__((enforce_read_only_placement)) D; // #3
+                // expected-warning@#3{{attribute declaration must precede definition}}
+
+D d1; // We do not  emit a warning here, as there is another warning for declaring
+      // a type after the definition
+
+
+// Cases where the attribute must be explicitly attached to another type
+// Case 1: Inheriting from a type that has the attribute
+struct E : C { // FIXME: warn the user declarations of type `E`, that extends `C`, won't be
+               // checked for read only placement because `E` is not marked as `C` is.
+};
+
+// Case 2: Declaring a field of the type that has the attribute
+struct F {
+    C c1; // FIXME: warn the user type `F` that wraps type `C` won't be checked for
+          // read only placement
+};
+
+struct BaseWithoutAttribute {
+    int a;
+};
+
+struct  __attribute__((enforce_read_only_placement)) J : BaseWithoutAttribute { // no-warning
+};
+
+struct __attribute__((enforce_read_only_placement)) BaseWithAttribute {
+    int i;
+};
+
+struct __attribute__((enforce_read_only_placement)) Derived : BaseWithAttribute { // no-warning
+    int j;
+};
+
+struct __attribute__((enforce_read_only_placement)) WrapperToAttributeInstance { // no-warning
+    BaseWithAttribute b;
+};
+
+struct __attribute__((enforce_read_only_placement)) WrapperToNoAttributeInstance { // no-warning
+    BaseWithoutAttribute b;
+};
+
+// Cases where the const qualification doesn't ensure read-only memory placement
+// of an instance.
+
+// Case 1: The type defines/inherits mutable data members
+struct __attribute__((enforce_read_only_placement)) G {
+    mutable int x; // FIXME: warn the user type `G` won't be placed in the read only program memory
+};
+
+struct __attribute__((enforce_read_only_placement)) H : public G { // FIXME: Warn the user type `H`
+                                                // won't be placed in the read only program memory
+};
+
+struct __attribute__((enforce_read_only_placement)) K { // FIXME : Warn the user type `K` w on't be
+                                                // placed in the read only program memory
+    G g;
+};
+
+
+// Case 2: The type has a constructor that makes its fields modifiable
+struct  __attribute__((enforce_read_only_placement)) L {
+    int b;
+    L(int val) { // FIXME: warn the user type `L` won't be placed in the read only program memory
+      b = val;
+    }
+};
+
+struct __attribute__((enforce_read_only_placement)) ConstInClassInitializers { // no-warning
+  int b = 12;
+
+  ConstInClassInitializers() = default;
+};
+
+int foo();
+struct __attribute__((enforce_read_only_placement)) NonConstInClassInitializers {
+  int b = foo(); // FIXME: warn the user type `NonConstInClassInitializers` won't be placed
+                 // in the read only program memory
+
+  NonConstInClassInitializers() = default;
+};
+
+#if (__cplusplus >= 202002L)
+struct __attribute__((enforce_read_only_placement)) ConstevalCtor {
+  int b;
+
+  consteval ConstevalCtor(int B) : b(B) {} // no-warning
+};
+#endif
+
+#if (__cplusplus >= 201103L)
+struct __attribute__((enforce_read_only_placement)) ConstExprCtor { // no-warning
+  int b;
+
+  constexpr ConstExprCtor(int B) : b(B) {}
+};
+
+constexpr ConstExprCtor cec1(10); // no-warning
+
+#endif
+
+// Cases where an object is allocated on the heap or on the stack
+C *c2 = new C; // FIXME: warn the user this instance of 'C' won't be placed in the read only program memory
+
+void func1(C c); // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
+
+void func2(const C c); // FIXME: warn the user the instance of 'C' won't be placed in the read
+                       // only program memory
+
+C func3(); // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
+
+void func4() {
+    C c; // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
+}
+
+#if (__cplusplus >= 202002L)
+consteval void func4(C c); // no-warning
+#endif


        


More information about the cfe-commits mailing list