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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits