Skip to content

Commit 399ca50

Browse files
MalavikaSamakziqingluo-90
authored andcommitted
[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 (cherry picked from commit 678ded0)
1 parent 4091669 commit 399ca50

File tree

9 files changed

+264
-0
lines changed

9 files changed

+264
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,11 @@ Attribute Changes in Clang
418418
used ``201904L`` (the date the proposal was seen by the committee) by mistake.
419419
There were no other changes to the attribute behavior.
420420

421+
- Introduced a new record declaration attribute ``__attribute__((enforce_read_only_placement))``
422+
to support analysis of instances of a given type focused on read-only program
423+
memory placement. It emits a warning if something in the code provably prevents
424+
an instance from a read-only memory placement.
425+
421426
Windows Support
422427
---------------
423428
- For the MinGW driver, added the options ``-mguard=none``, ``-mguard=cf`` and

clang/include/clang/Basic/Attr.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4145,3 +4145,8 @@ def FunctionReturnThunks : InheritableAttr,
41454145
let Subjects = SubjectList<[Function]>;
41464146
let Documentation = [FunctionReturnThunksDocs];
41474147
}
4148+
def ReadOnlyPlacement : InheritableAttr {
4149+
let Spellings = [Clang<"enforce_read_only_placement">];
4150+
let Subjects = SubjectList<[Record]>;
4151+
let Documentation = [ReadOnlyPlacementDocs];
4152+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6910,3 +6910,42 @@ The symbol used for ``thunk-extern`` is target specific:
69106910
As such, this function attribute is currently only supported on X86 targets.
69116911
}];
69126912
}
6913+
6914+
def ReadOnlyPlacementDocs : Documentation {
6915+
let Category = DocCatType;
6916+
let Content = [{This attribute is attached to a structure, class or union declaration.
6917+
When attached to a record declaration/definition, it checks if all instances
6918+
of this type can be placed in the read-only data segment of the program. If it
6919+
finds an instance that can not be placed in a read-only segment, the compiler
6920+
emits a warning at the source location where the type was used.
6921+
6922+
Examples:
6923+
* ``struct __attribute__((enforce_read_only_placement)) Foo;``
6924+
* ``struct __attribute__((enforce_read_only_placement)) Bar { ... };``
6925+
6926+
Both ``Foo`` and ``Bar`` types have the ``enforce_read_only_placement`` attribute.
6927+
6928+
The goal of introducing this attribute is to assist developers with writing secure
6929+
code. A ``const``-qualified global is generally placed in the read-only section
6930+
of the memory that has additional run time protection from malicious writes. By
6931+
attaching this attribute to a declaration, the developer can express the intent
6932+
to place all instances of the annotated type in the read-only program memory.
6933+
6934+
Note 1: The attribute doesn't guarantee that the object will be placed in the
6935+
read-only data segment as it does not instruct the compiler to ensure such
6936+
a placement. It emits a warning if something in the code can be proven to prevent
6937+
an instance from being placed in the read-only data segment.
6938+
6939+
Note 2: Currently, clang only checks if all global declarations of a given type 'T'
6940+
are ``const``-qualified. The following conditions would also prevent the data to be
6941+
put into read only segment, but the corresponding warnings are not yet implemented.
6942+
6943+
1. An instance of type ``T`` is allocated on the heap/stack.
6944+
2. Type ``T`` defines/inherits a mutable field.
6945+
3. Type ``T`` defines/inherits non-constexpr constructor(s) for initialization.
6946+
4. A field of type ``T`` is defined by type ``Q``, which does not bear the
6947+
``enforce_read_only_placement`` attribute.
6948+
5. A type ``Q`` inherits from type ``T`` and it does not have the
6949+
``enforce_read_only_placement`` attribute.
6950+
}];
6951+
}

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,3 +1396,6 @@ def BranchProtection : DiagGroup<"branch-protection">;
13961396
// HLSL diagnostic groups
13971397
// Warnings for HLSL Clang extensions
13981398
def HLSLExtension : DiagGroup<"hlsl-extensions">;
1399+
1400+
// Warnings and notes related to const_var_decl_type attribute checks
1401+
def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5744,6 +5744,12 @@ def err_new_abi_tag_on_redeclaration : Error<
57445744
def note_use_ifdef_guards : Note<
57455745
"unguarded header; consider using #ifdef guards or #pragma once">;
57465746

5747+
def warn_var_decl_not_read_only : Warning<
5748+
"object of type %0 cannot be placed in read-only memory">,
5749+
InGroup<ReadOnlyPlacementChecks>;
5750+
def note_enforce_read_only_placement : Note<"type was declared read-only here">;
5751+
5752+
57475753
def note_deleted_dtor_no_operator_delete : Note<
57485754
"virtual destructor requires an unambiguous, accessible 'operator delete'">;
57495755
def note_deleted_special_member_class_subobject : Note<

clang/lib/Sema/SemaDecl.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7380,6 +7380,36 @@ static void copyAttrFromTypedefToDecl(Sema &S, Decl *D, const TypedefType *TT) {
73807380
}
73817381
}
73827382

7383+
// This function emits warning and a corresponding note based on the
7384+
// ReadOnlyPlacementAttr attribute. The warning checks that all global variable
7385+
// declarations of an annotated type must be const qualified.
7386+
void emitReadOnlyPlacementAttrWarning(Sema &S, const VarDecl *VD) {
7387+
QualType VarType = VD->getType().getCanonicalType();
7388+
7389+
// Ignore local declarations (for now) and those with const qualification.
7390+
// TODO: Local variables should not be allowed if their type declaration has
7391+
// ReadOnlyPlacementAttr attribute. To be handled in follow-up patch.
7392+
if (!VD || VD->hasLocalStorage() || VD->getType().isConstQualified())
7393+
return;
7394+
7395+
if (VarType->isArrayType()) {
7396+
// Retrieve element type for array declarations.
7397+
VarType = S.getASTContext().getBaseElementType(VarType);
7398+
}
7399+
7400+
const RecordDecl *RD = VarType->getAsRecordDecl();
7401+
7402+
// Check if the record declaration is present and if it has any attributes.
7403+
if (RD == nullptr)
7404+
return;
7405+
7406+
if (const auto *ConstDecl = RD->getAttr<ReadOnlyPlacementAttr>()) {
7407+
S.Diag(VD->getLocation(), diag::warn_var_decl_not_read_only) << RD;
7408+
S.Diag(ConstDecl->getLocation(), diag::note_enforce_read_only_placement);
7409+
return;
7410+
}
7411+
}
7412+
73837413
NamedDecl *Sema::ActOnVariableDeclarator(
73847414
Scope *S, Declarator &D, DeclContext *DC, TypeSourceInfo *TInfo,
73857415
LookupResult &Previous, MultiTemplateParamsArg TemplateParamLists,
@@ -8044,6 +8074,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
80448074
if (IsMemberSpecialization && !NewVD->isInvalidDecl())
80458075
CompleteMemberSpecialization(NewVD, Previous);
80468076

8077+
emitReadOnlyPlacementAttrWarning(*this, NewVD);
8078+
80478079
return NewVD;
80488080
}
80498081

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8561,6 +8561,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
85618561
case ParsedAttr::AT_X86ForceAlignArgPointer:
85628562
handleX86ForceAlignArgPointerAttr(S, D, AL);
85638563
break;
8564+
case ParsedAttr::AT_ReadOnlyPlacement:
8565+
handleSimpleAttribute<ReadOnlyPlacementAttr>(S, D, AL);
8566+
break;
85648567
case ParsedAttr::AT_DLLExport:
85658568
case ParsedAttr::AT_DLLImport:
85668569
handleDLLAttr(S, D, AL);

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
// CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method)
156156
// CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
157157
// CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
158+
// CHECK-NEXT: ReadOnlyPlacement (SubjectMatchRule_record)
158159
// CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
159160
// CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function)
160161
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// RUN: %clang_cc1 -Wread-only-types %s -verify -fsyntax-only
2+
// RUN: %clang_cc1 -std=c++2a -Wread-only-types %s -verify -fsyntax-only
3+
// RUN: %clang_cc1 -std=c++17 -Wread-only-types %s -verify -fsyntax-only
4+
5+
struct __attribute__((enforce_read_only_placement)) A { // #A_DECL
6+
};
7+
8+
A a1; // expected-warning {{object of type 'A' cannot be placed in read-only memory}}
9+
// expected-note@#A_DECL {{type was declared read-only here}}
10+
const A a2[10]; // no-warning
11+
A a3[20]; // expected-warning {{object of type 'A' cannot be placed in read-only memory}}
12+
// expected-note@#A_DECL {{type was declared read-only here}}
13+
14+
15+
16+
struct B;
17+
struct __attribute__((enforce_read_only_placement)) B { //#B_DECL
18+
};
19+
20+
B b1; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
21+
// expected-note@#B_DECL {{type was declared read-only here}}
22+
const B b2; // no-warning
23+
const B b3[4]; // no-warning
24+
B b4[5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
25+
// expected-note@#B_DECL {{type was declared read-only here}}
26+
B b5[5][5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
27+
// expected-note@#B_DECL {{type was declared read-only here}}
28+
B b10[5][5][5]; // expected-warning {{object of type 'B' cannot be placed in read-only memory}}
29+
// expected-note@#B_DECL {{type was declared read-only here}}
30+
31+
void method1() {
32+
static const B b6;
33+
static B b7;// expected-warning {{object of type 'B' cannot be placed in read-only memory}}
34+
// expected-note@#B_DECL {{type was declared read-only here}}
35+
B b8; // no-warning
36+
const B b9; // no-warning
37+
}
38+
39+
struct C;
40+
struct __attribute__((enforce_read_only_placement)) C; // expected-note {{type was declared read-only here}}
41+
struct C { // no-note. The note should be attached to the definition/declaration bearing the attribute
42+
};
43+
44+
C c1; // expected-warning {{object of type 'C' cannot be placed in read-only memory}}
45+
46+
// Cases to be handled by the follow-up patches.
47+
48+
// Attaching and checking the attribute in reverse, where the attribute is attached after the
49+
// type definition
50+
struct D;
51+
struct D { //expected-note{{previous definition is here}}
52+
};
53+
struct __attribute__((enforce_read_only_placement)) D; // #3
54+
// expected-warning@#3{{attribute declaration must precede definition}}
55+
56+
D d1; // We do not emit a warning here, as there is another warning for declaring
57+
// a type after the definition
58+
59+
60+
// Cases where the attribute must be explicitly attached to another type
61+
// Case 1: Inheriting from a type that has the attribute
62+
struct E : C { // FIXME: warn the user declarations of type `E`, that extends `C`, won't be
63+
// checked for read only placement because `E` is not marked as `C` is.
64+
};
65+
66+
// Case 2: Declaring a field of the type that has the attribute
67+
struct F {
68+
C c1; // FIXME: warn the user type `F` that wraps type `C` won't be checked for
69+
// read only placement
70+
};
71+
72+
struct BaseWithoutAttribute {
73+
int a;
74+
};
75+
76+
struct __attribute__((enforce_read_only_placement)) J : BaseWithoutAttribute { // no-warning
77+
};
78+
79+
struct __attribute__((enforce_read_only_placement)) BaseWithAttribute {
80+
int i;
81+
};
82+
83+
struct __attribute__((enforce_read_only_placement)) Derived : BaseWithAttribute { // no-warning
84+
int j;
85+
};
86+
87+
struct __attribute__((enforce_read_only_placement)) WrapperToAttributeInstance { // no-warning
88+
BaseWithAttribute b;
89+
};
90+
91+
struct __attribute__((enforce_read_only_placement)) WrapperToNoAttributeInstance { // no-warning
92+
BaseWithoutAttribute b;
93+
};
94+
95+
// Cases where the const qualification doesn't ensure read-only memory placement
96+
// of an instance.
97+
98+
// Case 1: The type defines/inherits mutable data members
99+
struct __attribute__((enforce_read_only_placement)) G {
100+
mutable int x; // FIXME: warn the user type `G` won't be placed in the read only program memory
101+
};
102+
103+
struct __attribute__((enforce_read_only_placement)) H : public G { // FIXME: Warn the user type `H`
104+
// won't be placed in the read only program memory
105+
};
106+
107+
struct __attribute__((enforce_read_only_placement)) K { // FIXME : Warn the user type `K` w on't be
108+
// placed in the read only program memory
109+
G g;
110+
};
111+
112+
113+
// Case 2: The type has a constructor that makes its fields modifiable
114+
struct __attribute__((enforce_read_only_placement)) L {
115+
int b;
116+
L(int val) { // FIXME: warn the user type `L` won't be placed in the read only program memory
117+
b = val;
118+
}
119+
};
120+
121+
struct __attribute__((enforce_read_only_placement)) ConstInClassInitializers { // no-warning
122+
int b = 12;
123+
124+
ConstInClassInitializers() = default;
125+
};
126+
127+
int foo();
128+
struct __attribute__((enforce_read_only_placement)) NonConstInClassInitializers {
129+
int b = foo(); // FIXME: warn the user type `NonConstInClassInitializers` won't be placed
130+
// in the read only program memory
131+
132+
NonConstInClassInitializers() = default;
133+
};
134+
135+
#if (__cplusplus >= 202002L)
136+
struct __attribute__((enforce_read_only_placement)) ConstevalCtor {
137+
int b;
138+
139+
consteval ConstevalCtor(int B) : b(B) {} // no-warning
140+
};
141+
#endif
142+
143+
#if (__cplusplus >= 201103L)
144+
struct __attribute__((enforce_read_only_placement)) ConstExprCtor { // no-warning
145+
int b;
146+
147+
constexpr ConstExprCtor(int B) : b(B) {}
148+
};
149+
150+
constexpr ConstExprCtor cec1(10); // no-warning
151+
152+
#endif
153+
154+
// Cases where an object is allocated on the heap or on the stack
155+
C *c2 = new C; // FIXME: warn the user this instance of 'C' won't be placed in the read only program memory
156+
157+
void func1(C c); // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
158+
159+
void func2(const C c); // FIXME: warn the user the instance of 'C' won't be placed in the read
160+
// only program memory
161+
162+
C func3(); // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
163+
164+
void func4() {
165+
C c; // FIXME: warn the user the instance of 'C' won't be placed in the read only program memory
166+
}
167+
168+
#if (__cplusplus >= 202002L)
169+
consteval void func4(C c); // no-warning
170+
#endif

0 commit comments

Comments
 (0)