Skip to content
← Back

src/semantic/symbols/store_free_imports.ghul

1
namespace Semantic.Symbols is
2
// Imported functions trusted not to store to any pre-existing,
3
// user-visible heap location. Curation rules, in order of trust:
4
//
5
// - Sealed-receiver members (string, the numeric primitives)
6
// cannot dispatch anywhere else and take no function-typed
7
// arguments, so nothing can re-enter user code.
8
// - Non-virtual members of the concrete collection classes
9
// (LIST, STACK) dispatch to exactly one BCL body. Reads that
10
// consult an equality comparer (MAP and SET lookups, and the
11
// read-only interface views) additionally trust the standard
12
// .NET contract that a key type's equals / hash members do
13
// not mutate; a pathological key type can break that, which
14
// is accepted deliberately.
15
// - Virtual members (object.to_string) are trusted only for
16
// the imported body; the fixpoint separately checks their
17
// in-assembly overriders, so a storing override still
18
// poisons every call that could dispatch to it.
19
//
20
// Members that run user callbacks structurally (delegate-taking
21
// LINQ shapes, sort with comparers) must never appear here.
22
class STORE_FREE_IMPORTS is
23
_entries: Collections.SET[string]? static
24
25
entries() -> Collections.SET[string] static is
26
if !_entries? then
27
let result = Collections.SET[string]()
28
29
// string: sealed, no callbacks
30
result.add("Ghul.Intrinsics.string.get_Item")
31
result.add("Ghul.Intrinsics.string.get_length")
32
result.add("Ghul.Intrinsics.string.$get_length")
33
result.add("Ghul.Intrinsics.string.length")
34
result.add("Ghul.Intrinsics.string.index_of")
35
result.add("Ghul.Intrinsics.string.last_index_of")
36
result.add("Ghul.Intrinsics.string.substring")
37
result.add("Ghul.Intrinsics.string.contains")
38
result.add("Ghul.Intrinsics.string.starts_with")
39
result.add("Ghul.Intrinsics.string.ends_with")
40
result.add("Ghul.Intrinsics.string.to_string")
41
42
// string: further sealed reads and transforms. Each
43
// returns a fresh string, a fresh array or a value and
44
// takes only value types, strings, char arrays or the
45
// StringComparison / StringSplitOptions enums — no
46
// overload of any of these names takes a delegate, an
47
// object or an interface, so none can dispatch into
48
// user code.
49
result.add("Ghul.Intrinsics.string.split")
50
result.add("Ghul.Intrinsics.string.trim")
51
result.add("Ghul.Intrinsics.string.trim_start")
52
result.add("Ghul.Intrinsics.string.trim_end")
53
result.add("Ghul.Intrinsics.string.pad_left")
54
result.add("Ghul.Intrinsics.string.pad_right")
55
result.add("Ghul.Intrinsics.string.remove")
56
result.add("Ghul.Intrinsics.string.to_char_array")
57
result.add("Ghul.Intrinsics.string.index_of_any")
58
result.add("Ghul.Intrinsics.string.last_index_of_any")
59
result.add("Ghul.Intrinsics.string.is_null_or_empty")
60
result.add("Ghul.Intrinsics.string.is_null_or_white_space")
61
result.add("Ghul.Intrinsics.string.to_lower_invariant")
62
result.add("Ghul.Intrinsics.string.to_upper_invariant")
63
64
// string: case conversion and replacement. The
65
// no-argument and ordinal forms are sealed reads; the
66
// names also cover overloads taking a CultureInfo,
67
// which is not sealed, so a pathological custom culture
68
// could in principle re-enter user code during
69
// formatting. That is the same deliberately-accepted
70
// risk as the equals-and-hash contract above, and the
71
// compiler only ever calls the culture-free forms.
72
result.add("Ghul.Intrinsics.string.to_lower")
73
result.add("Ghul.Intrinsics.string.to_upper")
74
result.add("Ghul.Intrinsics.string.replace")
75
76
// Ghul.BOX backs a captured, reassigned local: the
77
// compiler routes every read of such a local through
78
// the box's `value` getter, a synthesized backing-field
79
// read with no overriders. Left uncurated, the
80
// compiler's own plumbing read would cross the very
81
// fact being read.
82
result.add("Ghul.BOX.$$get_value")
83
result.add("Ghul.BOX.value")
84
85
// primitive to_string: exact (constrained) dispatch
86
result.add("Ghul.Intrinsics.int.to_string")
87
result.add("Ghul.Intrinsics.long.to_string")
88
result.add("Ghul.Intrinsics.short.to_string")
89
result.add("Ghul.Intrinsics.byte.to_string")
90
result.add("Ghul.Intrinsics.ubyte.to_string")
91
result.add("Ghul.Intrinsics.uint.to_string")
92
result.add("Ghul.Intrinsics.ulong.to_string")
93
result.add("Ghul.Intrinsics.ushort.to_string")
94
result.add("Ghul.Intrinsics.bool.to_string")
95
result.add("Ghul.Intrinsics.char.to_string")
96
result.add("Ghul.Intrinsics.single.to_string")
97
result.add("Ghul.Intrinsics.double.to_string")
98
99
// char: the Unicode classifiers and case converters are
100
// statics on a sealed struct. Every overload takes only
101
// value types - a char, a (string, int) pair, or the
102
// (char, char) and (char, char, char) forms of the
103
// range and surrogate checks - and returns a bool, an
104
// enum, a numeric value or a char, so none can dispatch
105
// into user code. The culture-taking converters
106
// (to_lower / to_upper with a CultureInfo) carry the
107
// same accepted pathological-culture caveat noted for
108
// string case conversion above; the invariant forms do
109
// not.
110
result.add("Ghul.Intrinsics.char.is_white_space")
111
result.add("Ghul.Intrinsics.char.is_digit")
112
result.add("Ghul.Intrinsics.char.is_letter")
113
result.add("Ghul.Intrinsics.char.is_letter_or_digit")
114
result.add("Ghul.Intrinsics.char.is_upper")
115
result.add("Ghul.Intrinsics.char.is_lower")
116
result.add("Ghul.Intrinsics.char.is_number")
117
result.add("Ghul.Intrinsics.char.is_symbol")
118
result.add("Ghul.Intrinsics.char.is_control")
119
result.add("Ghul.Intrinsics.char.is_punctuation")
120
result.add("Ghul.Intrinsics.char.is_separator")
121
result.add("Ghul.Intrinsics.char.is_surrogate")
122
result.add("Ghul.Intrinsics.char.is_high_surrogate")
123
result.add("Ghul.Intrinsics.char.is_low_surrogate")
124
result.add("Ghul.Intrinsics.char.is_surrogate_pair")
125
result.add("Ghul.Intrinsics.char.is_between")
126
result.add("Ghul.Intrinsics.char.is_ascii")
127
result.add("Ghul.Intrinsics.char.is_ascii_digit")
128
result.add("Ghul.Intrinsics.char.is_ascii_letter")
129
result.add("Ghul.Intrinsics.char.is_ascii_letter_or_digit")
130
result.add("Ghul.Intrinsics.char.is_ascii_letter_lower")
131
result.add("Ghul.Intrinsics.char.is_ascii_letter_upper")
132
result.add("Ghul.Intrinsics.char.is_ascii_hex_digit")
133
result.add("Ghul.Intrinsics.char.is_ascii_hex_digit_lower")
134
result.add("Ghul.Intrinsics.char.is_ascii_hex_digit_upper")
135
result.add("Ghul.Intrinsics.char.get_unicode_category")
136
result.add("Ghul.Intrinsics.char.get_numeric_value")
137
result.add("Ghul.Intrinsics.char.to_lower_invariant")
138
result.add("Ghul.Intrinsics.char.to_upper_invariant")
139
result.add("Ghul.Intrinsics.char.to_lower")
140
result.add("Ghul.Intrinsics.char.to_upper")
141
142
// object.to_string: the imported body prints a type
143
// name; storing overrides are caught via overrider
144
// links
145
result.add("Ghul.Intrinsics.object.to_string")
146
147
// concrete collections: non-virtual reads
148
result.add("Collections.LIST.get_Item")
149
result.add("Collections.LIST.get_count")
150
result.add("Collections.LIST.$get_count")
151
result.add("Collections.LIST.count")
152
result.add("Collections.STACK.peek")
153
result.add("Collections.STACK.get_count")
154
result.add("Collections.STACK.$get_count")
155
result.add("Collections.STACK.count")
156
157
// dictionary / set reads: trusts the equals-and-hash
158
// contract of the key type
159
result.add("Collections.MAP.get_Item")
160
result.add("Collections.MAP.contains_key")
161
result.add("Collections.MAP.try_get_value")
162
result.add("Collections.MAP.get_count")
163
result.add("Collections.MAP.$get_count")
164
result.add("Collections.MAP.count")
165
result.add("Collections.SET.contains")
166
result.add("Collections.SET.get_count")
167
result.add("Collections.SET.$get_count")
168
result.add("Collections.SET.count")
169
170
// read-only interface views: same reads through the
171
// IReadOnly* interfaces; trusts implementations to
172
// honour the read-only contract
173
result.add("Collections.List.get_Item")
174
result.add("Collections.List.get_count")
175
result.add("Collections.List.$get_count")
176
result.add("Collections.List.count")
177
result.add("Collections.Map.get_Item")
178
result.add("Collections.Map.contains_key")
179
result.add("Collections.Map.try_get_value")
180
result.add("Collections.Map.get_count")
181
result.add("Collections.Map.$get_count")
182
result.add("Collections.Map.count")
183
result.add("Collections.Bag.get_count")
184
result.add("Collections.Bag.$get_count")
185
result.add("Collections.Bag.count")
186
187
// value-type optional carriers: `x?` / `x!` on an
188
// `int?`-style slot lower to these accessors, which
189
// read a field of a sealed struct and can dispatch
190
// nowhere else. Without them every value-type
191
// presence test would count as a possibly-storing
192
// call and kill the very facts it establishes.
193
result.add("System.Nullable.get_has_value")
194
result.add("System.Nullable.$get_has_value")
195
result.add("System.Nullable.has_value")
196
result.add("System.Nullable.get_value")
197
result.add("System.Nullable.$get_value")
198
result.add("System.Nullable.value")
199
result.add("Ghul.MAYBE.get_has_value")
200
result.add("Ghul.MAYBE.$get_has_value")
201
result.add("Ghul.MAYBE.has_value")
202
result.add("Ghul.MAYBE.get_value")
203
result.add("Ghul.MAYBE.$get_value")
204
result.add("Ghul.MAYBE.value")
205
206
// console writes: console state is not user-visible
207
// heap, so a debug print cannot invalidate a
208
// narrowing. The object-taking overloads format via
209
// to_string, where a pathological storing override
210
// could slip through — accepted so that adding debug
211
// output does not silently break dependent
212
// narrowings.
213
result.add("IO.Std.write")
214
result.add("IO.Std.write_line")
215
result.add("IO.Std.get_error")
216
result.add("IO.Std.$get_error")
217
result.add("IO.Std.error")
218
result.add("IO.Std.get_out")
219
result.add("IO.Std.$get_out")
220
result.add("IO.Std.out")
221
result.add("IO.TextWriter.write")
222
result.add("IO.TextWriter.write_line")
223
224
// path manipulation: statics on a sealed class that
225
// combine and dissect path strings. Every overload
226
// takes only strings and returns a string or a bool;
227
// none takes a delegate, an object or an interface, so
228
// no call reaches user code. These read process state
229
// (the current directory, for get_full_path) but write
230
// no user-visible heap. The file-touching members
231
// (get_temp_file_name, and the filesystem probes) are
232
// deliberately left out.
233
result.add("IO.Path.combine")
234
result.add("IO.Path.join")
235
result.add("IO.Path.get_directory_name")
236
result.add("IO.Path.get_file_name")
237
result.add("IO.Path.get_file_name_without_extension")
238
result.add("IO.Path.get_extension")
239
result.add("IO.Path.get_full_path")
240
result.add("IO.Path.get_path_root")
241
result.add("IO.Path.get_relative_path")
242
result.add("IO.Path.change_extension")
243
result.add("IO.Path.has_extension")
244
result.add("IO.Path.is_path_rooted")
245
result.add("IO.Path.is_path_fully_qualified")
246
result.add("IO.Path.ends_in_directory_separator")
247
result.add("IO.Path.trim_ending_directory_separator")
248
249
// object.get_type: unlike to_string, GetType is not
250
// virtual, so no override can exist and no poisoning is
251
// needed — it reads the runtime type and returns it.
252
result.add("Ghul.Intrinsics.object.get_type")
253
254
// StringBuilder reads: the sealed builder's to_string
255
// and length getter only read its contents. The
256
// mutating members (append, insert, clear, the length
257
// and Chars setters) are excluded.
258
result.add("System.Text.StringBuilder.to_string")
259
result.add("System.Text.StringBuilder.get_length")
260
result.add("System.Text.StringBuilder.$get_length")
261
result.add("System.Text.StringBuilder.length")
262
263
// reflected type name reads: name and full_name on a
264
// System.Type. Type is abstract, so a custom subclass
265
// could override these getters to store — the same
266
// accepted pathological-implementation risk as the
267
// equals-and-hash contract above; the compiler only
268
// ever reads them off runtime types.
269
result.add("System.Type.name")
270
result.add("System.Type.get_name")
271
result.add("System.Type.$get_name")
272
result.add("System.Type.full_name")
273
result.add("System.Type.get_full_name")
274
result.add("System.Type.$get_full_name")
275
276
// System.Type.to_string: overridden by every runtime
277
// Type implementation to print a type name; takes no
278
// arguments, so nothing it could dispatch to is under
279
// caller control. Same accepted override risk as the
280
// name / full_name reads above.
281
result.add("System.Type.to_string")
282
283
// GetCustomAttributesData: reads the reflected
284
// custom-attribute metadata of a member or a parameter
285
// into a fresh list. Both MemberInfo and ParameterInfo
286
// are abstract and declare it virtual, so a foreign
287
// subclass could override it to store — the same
288
// accepted override risk as the Type reads above; the
289
// compiler only ever reads it off runtime reflection
290
// objects.
291
result.add("System.Reflection.MemberInfo.get_custom_attributes_data")
292
result.add("System.Reflection.ParameterInfo.get_custom_attributes_data")
293
294
// KeyValuePair.Key / .Value: read-only getters on the
295
// sealed pair struct, each returning one of the two
296
// values the pair was constructed with.
297
result.add("Collections.KeyValuePair.key")
298
result.add("Collections.KeyValuePair.get_key")
299
result.add("Collections.KeyValuePair.$get_key")
300
result.add("Collections.KeyValuePair.value")
301
result.add("Collections.KeyValuePair.get_value")
302
result.add("Collections.KeyValuePair.$get_value")
303
304
// KeyValuePair construction: the read-only pair struct's
305
// constructor copies its key and value into the fresh
306
// struct and runs no behaviour on them, so it writes no
307
// pre-existing heap slot. Trusting it lets a dictionary
308
// populated from a pipe of pairs keep its narrowings — a
309
// storing argument still disqualifies where it is
310
// classified.
311
result.add("Collections.KeyValuePair.init")
312
313
_entries = result
314
fi
315
316
return _entries
317
si
318
319
is_store_free(function: Function?) -> bool static is
320
if !function? \/ !function.is_reflected then
321
return false
322
fi
323
324
let owner = function.owner
325
326
if !owner? then
327
return false
328
fi
329
330
let key = "{owner.qualified_name}.{function.name}"
331
return entries().contains(key)
332
si
333
si
334
si