Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -4090,3 +4090,8 @@ let Subjects = SubjectList<[Function]>; let Documentation = [FunctionReturnThunksDocs]; } +def ReadOnlyPlacement : InheritableAttr { + let Spellings = [Clang<"enforce_read_only_placement">]; + let Subjects = SubjectList<[Record]>; + let Documentation =[ReadOnlyPlacementDocs]; +} Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -6754,3 +6754,42 @@ 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 record declaration or the + definition. 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 in read-only segment, + the compiler emits a warning at the corresponding program location. + + Examples: + * ``struct __attribute__(enforce_read_only_placement) Foo;`` + * ``struct __attribute__(enforce_read_only_placement) Bar { ... };`` + + Both ``Foo`` and ``Bar`` types have the ConstVarDecl attribute. + + The goal of introducing this attribute is to assist developers to write secure + code. A const qualified global is generally placed in the read-only section + of the memory that has additional safety protection from malicious writes. + Therefore, by attaching this attribute 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 simply emits a warning if something in the code prevents 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. + }]; +} Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -1388,3 +1388,6 @@ // 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">; Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5672,6 +5672,11 @@ def note_use_ifdef_guards : Note< "unguarded header; consider using #ifdef guards or #pragma once">; +def warn_var_decl_not_read_only : Warning<"Variable of type %0 will not be in the read-only program memory.">, + InGroup; +def note_enforce_read_only_placement : Note<"Type %0 was declared as a read-only type 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< Index: clang/lib/Sema/SemaDecl.cpp =================================================================== --- clang/lib/Sema/SemaDecl.cpp +++ clang/lib/Sema/SemaDecl.cpp @@ -7377,6 +7377,59 @@ } } +/* + 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) { + + // 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().getCVRQualifiers() & Qualifiers::Const) + return; + + QualType type = VD->getType(); + type = type.getCanonicalType(); + + // Helper function to retrieve element type for array declarations. + auto arrayElementType = [&](QualType type) -> QualType { + while(type->isArrayType()) { + const ArrayType *AT = S.getASTContext().getAsArrayType(type); + type = AT->getElementType(); + } + return type; + }; + + if (type->isArrayType()) { + type = arrayElementType(type); + } else if (const auto *pointertype = dyn_cast(type)) { + // TODO: We should emit warning for pointers if the type declaration of the + // instance they point-to has ReadOnlyPlacementAttr attribute. + return; + } else if (const auto *eltype = dyn_cast(type)) { + type = eltype->getNamedType(); + } + + const clang::RecordDecl *RD = type->getAsRecordDecl(); + + // Check if the record declaration is present and if it has any attributes. + if (RD == nullptr || !RD->hasAttrs()) + return; + + if (const auto *constdecl = RD->getAttr()) { + // Warning attached to the varaibale declaration + S.Diag(VD->getLocation(), diag::warn_var_decl_not_read_only) << RD; + // Note attached to the record declaration. + S.Diag(((Attr *)constdecl)->getLocation(), + diag::note_enforce_read_only_placement) + << RD; + return; + } +} + NamedDecl *Sema::ActOnVariableDeclarator( Scope *S, Declarator &D, DeclContext *DC, TypeSourceInfo *TInfo, LookupResult &Previous, MultiTemplateParamsArg TemplateParamLists, @@ -8041,6 +8094,8 @@ if (IsMemberSpecialization && !NewVD->isInvalidDecl()) CompleteMemberSpecialization(NewVD, Previous); + emitReadOnlyPlacementAttrWarning(*this, NewVD); + return NewVD; } Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -7727,6 +7727,15 @@ D->addAttr(::new (S.Context) AMDGPUNumVGPRAttr(S.Context, AL, NumVGPR)); } +static void handleReadOnlyPlacementAttrs(Sema &S, Decl *D, + const ParsedAttr &AL) { + // TODO: Add a warning if atrribute added to declarations can not be + // in read only program memory. + if (isa(D)) { + D->addAttr(::new (S.Context) ReadOnlyPlacementAttr(S.Context, AL)); + } +} + static void handleX86ForceAlignArgPointerAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // If we try to apply it to a function pointer, don't warn, but don't @@ -8586,6 +8595,9 @@ case ParsedAttr::AT_X86ForceAlignArgPointer: handleX86ForceAlignArgPointerAttr(S, D, AL); break; + case ParsedAttr::AT_ReadOnlyPlacement: + handleReadOnlyPlacementAttrs(S, D, AL); + break; case ParsedAttr::AT_DLLExport: case ParsedAttr::AT_DLLImport: handleDLLAttr(S, D, AL); Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ 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) Index: clang/test/Sema/attr-read-only-placement.cpp =================================================================== --- /dev/null +++ clang/test/Sema/attr-read-only-placement.cpp @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only -Wread-only-types + +struct __attribute__((enforce_read_only_placement)) A { // expected-note 2 {{Type 'A' was declared as a read-only type here}} +}; + +A a1; // expected-warning {{Variable of type 'A' will not be in the read-only program memory.}} +const A a2[10]; // no-warning +A a3[20]; // expected-warning {{Variable of type 'A' will not be in the read-only program memory.}} + + +struct B; +struct __attribute__((enforce_read_only_placement)) B { // expected-note 5 {{Type 'B' was declared as a read-only type here.}} +}; + +B b1; // expected-warning {{Variable of type 'B' will not be in the read-only program memory.}} +const B b2; // no-warning +const B b3[4]; // no-warning +B b4[5]; // expected-warning {{Variable of type 'B' will not be in the read-only program memory.}} +B b5[5][5]; // expected-warning {{Variable of type 'B' will not be in the read-only program memory.}} +B b10[5][5][5]; // expected-warning {{Variable of type 'B' will not be in the read-only program memory.}} + +void method1() { + static const B b6; + static B b7;// expected-warning {{Variable of type 'B' will not be in the read-only program memory.}} + B b8; // no-warning + const B b9; // no-warning +} + + +struct C; +struct __attribute__((enforce_read_only_placement)) C; // expected-note {{Type 'C' was declared as a read-only type here}} +struct C { // no-note. The note should be attached to the definition/declaration bearing the attribute. +}; + +C c1; // expected-warning {{Variable of type 'C' will not be in the read-only program memory.}} + +C *c2 = new C; + +// 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; // expected-warning{{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 { // Warn the user type `E` won't be checked for read only placement. + +}; + +// Case 2: Declaring a field of the type that has the attribute. +struct F { + C c1; // Warn the user type `F` won't be checked for read only placement. +}; + +// 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; // Warn the user type `G` won't be placed in the read only program memory. +}; + +struct __attribute__((enforce_read_only_placement)) H : public G { // Warn the user type `H` won't be placed in the read only program memory. + +}; + +// Case 2: The type has a constructor that makes its fields modifiable. +struct J { + int b; + J(int val) { // Warn the user type `J` won't be placed in the read only program memory. + b = val; + } +};