You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: unleashed/docs/strip-rtti.md
+57-18Lines changed: 57 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,22 +21,32 @@ Strip RTTI does not remove the RTTI structures themselves (they are still walked
21
21
22
22
When `striprtti` is active, the following shortstrings are nulled:
23
23
24
-
| Source | What it is |Stripped|
24
+
| Source | What it is |Whitelist via `expose`|
25
25
|---|---|---|
26
-
| RTTI header (`write_header`) | Type name in the `TTypeInfo` block | yes |
27
-
| Object/class RTTI (`write_objectdef_rtti`) | Class real name (`def.objrealname`) | yes |
28
-
| Class VMT (`ncgvmt`) | Class name in the VMT (`_class.RttiName`) | yes |
29
-
| Member RTTI (`write_member_rtti`) | Field/property/method name (`sym.realname`) | yes |
30
-
| Used-units list | Module name in the units-of-use list (`hp.realname`) | yes |
31
-
| Module name (multiple sites) |`current_module.realmodulename^` written into class/interface/object RTTI | yes |
32
-
| Parameter names | Method parameter names in published method RTTI | yes |
33
-
| Type alias name | The `prettyname` written by `write_rtti_data_singleref` for an alias | yes |
26
+
| RTTI header (`write_header`) | Type name in the `TTypeInfo` block | on the type itself |
27
+
| Object/class RTTI (`write_objectdef_rtti`) | Class real name (`def.objrealname`) | on the class |
28
+
| Class VMT (`ncgvmt`) | Class name in the VMT (`_class.RttiName`) | on the class |
29
+
| Type alias name | The `prettyname` written by `write_rtti_data_singleref`| on the alias |
30
+
| Property name (typeinfo) |`sym.realname` for published property | on the owning class |
31
+
| Enum value names |`hp.realname` for each enum value | on the enum type |
32
+
| Procvar parameter names |`parasym.realname` in procvar RTTI | on the procvar type |
33
+
| Method parameter names (extended RTTI) |`para.realname` in extended method table | on the owning class |
34
+
| Published method name (VMT method table) |`tsym.realname` used by `MethodAddress`| on the owning class |
35
+
| Published method name (extended RTTI) |`sym.realname` in extended method entry | on the owning class |
36
+
| Published field name (VMT field table) |`tfieldvarsym.realname` used by `FieldAddress`| on the owning class |
37
+
| Published field name (extended RTTI) |`fldsym.realname` in extended field entry | on the owning class |
38
+
| Module name (RTTI structs) |`current_module.realmodulename^` in class/interface/object RTTI | (not whitelistable per type) |
39
+
| Module name (unit init/finalize table) | Used-unit names in the init/fini dispatch table | (not whitelistable per type) |
40
+
| Used-units list | Module name in the units-of-use list (`hp.realname`) | (not whitelistable per type) |
41
+
42
+
**Whitelist propagation:** the `expose` keyword on a type sets `df_expose_rtti` on its `tdef`. Members of an exposed type (its properties, enum values, procvar parameters, published methods/fields, and method parameters) inherit the whitelist - their names stay in the binary too. Without `expose` on the parent, members are stripped even if you want a single property name visible; this is by design (whitelist is a per-type opt-in).
34
43
35
44
The following are **not** stripped (intentionally):
36
45
37
46
| Source | Why |
38
47
|---|---|
39
48
| Interface GUID string (`def.iidstr^` for `odt_interfacecorba`) | Functional - COM dispatch and `IUnknown.QueryInterface` look it up by string. Stripping breaks COM. |
| Format strings, `writeln` arguments, RTL string constants | These are not RTTI - they are program data. |
41
51
| Symbol names exposed to the linker | Linker-visible symbols are governed by smart-linking and `{$L+}`, not by RTTI stripping. |
42
52
@@ -68,6 +78,32 @@ type
68
78
expose TColor = (red, green, blue); // and on enums, sets, ranges, aliases...
69
79
```
70
80
81
+
#### Where `expose` can be used
82
+
83
+
The keyword is a generic prefix: parser sets a boolean before reading the type, and the resulting `tdef` (whatever kind) gets `df_expose_rtti`. So `expose` works in front of every kind of type Pascal allows in a `type` block:
84
+
85
+
| Type kind | Example |
86
+
|---|---|
87
+
| class |`expose TForm1 = class(TForm) ... end;`|
88
+
| object |`expose TOldObj = object ... end;`|
89
+
| interface |`expose IFoo = interface ... end;`|
90
+
| record |`expose TPoint = record x, y: integer; end;`|
91
+
| class helper |`expose TStrHelper = class helper for string ... end;`|
92
+
| record / type helper |`expose THelp = record helper for integer ... end;`|
| generic |`expose generic TList<T> = class ... end;`|
103
+
| file type |`expose TLogFile = file of TRecord;`|
104
+
105
+
What gets kept depends on the type kind, because of the propagation rules - exposing a class keeps its property/method/field/method-param names; exposing an enum keeps its value names; exposing a procvar keeps its parameter names; exposing a record keeps the type name. See the propagation rules above.
106
+
71
107
The keyword is gated on `m_unleashed`, not on `m_strip_rtti`. That means:
72
108
73
109
- In any other mode, `expose` is a regular identifier - existing code with a field, variable, or routine called `expose` keeps compiling.
@@ -134,17 +170,17 @@ Comparisons are case-insensitive (patterns are lowercased on insertion, names lo
134
170
135
171
## Side effects
136
172
137
-
Compiling LCL or anything that walks RTTI by string with `striprtti` on will likely brick the program at startup, because code such as:
138
-
139
-
```pas
140
-
Application.CreateForm(TForm1, Form1);
141
-
```
173
+
Anything that walks RTTI by string and isn't whitelisted will fail at runtime. Concrete cases:
142
174
143
-
resolves `TForm1` against the resource section by **string** comparison. With `striprtti` and no whitelist, the compiler emits `''` for the type name - so the lookup looks for `""` and fails.
175
+
-**`Application.CreateForm(TForm1, Form1)`** - resolves `TForm1` against the resource section by string comparison. With `striprtti` and no whitelist, the type name is `''` and the lookup fails.
176
+
-**`SomeObject.MethodAddress('OnClick')`** - the VMT method table has empty names for stripped methods, so the lookup returns `nil`. LCL component event hookup uses this path during form streaming.
177
+
-**`SomeObject.FieldAddress('myButton')`** - same story for the VMT field table.
-**`WriteStr(s, someEnumValue)` / `ReadStr(s, someEnumValue)`** - empty enum value names produce empty output / fail to parse input.
144
180
145
181
The fix is one of:
146
182
147
-
- whitelist the affected types (preferred): `--rttiexpose=TForm*,TFrame*,TDataModule*` or `expose TForm1 = class(...)` per declaration,
183
+
- whitelist the affected types: `--rttiexpose=TForm*,TFrame*,TDataModule*` or `expose TForm1 = class(...)` per declaration,
148
184
- enable `striprtti` only in units that do not need RTTI lookup (e.g. business-logic units, but not units containing forms),
149
185
- leave `striprtti` off for the whole project (default).
150
186
@@ -184,8 +220,11 @@ end.
184
220
185
221
## Implementation notes
186
222
187
-
- Decision is encoded as `df_expose_rtti` on `tdef.defoptions` (set during parsing). RTTI emit reads it via the helper `rtti_string(s, def)` in `ncgrtti`.
188
-
-`rtti_string` returns `s` if `striprtti` is off **or**`df_expose_rtti` is set on `def`; otherwise returns `''`. Sites that emit a name without an associated `tdef` (module name, parameter name) call `rtti_string(s)` without `def` - they cannot be whitelisted by name.
223
+
- Decision is encoded as `df_expose_rtti` on `tdef.defoptions` (set during parsing). RTTI emit reads it via the helper `rtti_string(s, def, parent_def)` in `ncgrtti`.
224
+
-`rtti_string` returns `s` if `striprtti` is off **or**`df_expose_rtti` is set on `def`**or** on `parent_def`; otherwise returns `''`.
225
+
-`def` is the type whose name is being emitted (used at type-name emit sites).
226
+
-`parent_def` is the owning type for member strings: the class for a property/method/field name, the enum for an enum value, the procvar for a parameter name. This is how an `expose` on the parent propagates to its members.
227
+
- Sites that emit a name without any associated `tdef` (module name in unit init/finalize table) pass neither - those are stripped unconditionally and not whitelistable per type.
189
228
- The flag is preserved across PPU - whitelisting decisions made in one compile run survive into binary form, so dependent units see the same `df_expose_rtti` state without re-running `--rttiexpose=` matching.
190
229
- Forward declarations (`type TFoo = class;`) - `expose` on the forward applies the flag to the same `tdef` that the final declaration completes, so both writes see it. `{$rttiexpose}` and `--rttiexpose=` match the name when the final declaration is parsed.
191
230
- Generic specialization - the flag follows the specialized def. If you `expose TList<T> = class ...`, every specialization (`TList<integer>`, `TList<string>`, etc.) inherits the flag.
0 commit comments