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.string.get_length");
31
result.add("Ghul.string.$get_length");
32
result.add("Ghul.string.length");
33
result.add("Ghul.string.index_of");
34
result.add("Ghul.string.last_index_of");
35
result.add("Ghul.string.substring");
36
result.add("Ghul.string.contains");
37
result.add("Ghul.string.starts_with");
38
result.add("Ghul.string.ends_with");
39
result.add("Ghul.string.to_string");
40
41
// string: further sealed reads and transforms. Each
42
// returns a fresh string, a fresh array or a value and
43
// takes only value types, strings, char arrays or the
44
// StringComparison / StringSplitOptions enums — no
45
// overload of any of these names takes a delegate, an
46
// object or an interface, so none can dispatch into
47
// user code.
48
result.add("Ghul.string.split");
49
result.add("Ghul.string.trim");
50
result.add("Ghul.string.trim_start");
51
result.add("Ghul.string.trim_end");
52
result.add("Ghul.string.pad_left");
53
result.add("Ghul.string.pad_right");
54
result.add("Ghul.string.remove");
55
result.add("Ghul.string.to_char_array");
56
result.add("Ghul.string.index_of_any");
57
result.add("Ghul.string.last_index_of_any");
58
result.add("Ghul.string.is_null_or_empty");
59
result.add("Ghul.string.is_null_or_white_space");
60
result.add("Ghul.string.to_lower_invariant");
61
result.add("Ghul.string.to_upper_invariant");
62
63
// string: case conversion and replacement. The
64
// no-argument and ordinal forms are sealed reads; the
65
// names also cover overloads taking a CultureInfo,
66
// which is not sealed, so a pathological custom culture
67
// could in principle re-enter user code during
68
// formatting. That is the same deliberately-accepted
69
// risk as the equals-and-hash contract above, and the
70
// compiler only ever calls the culture-free forms.
71
result.add("Ghul.string.to_lower");
72
result.add("Ghul.string.to_upper");
73
result.add("Ghul.string.replace");
74
75
// primitive to_string: exact (constrained) dispatch
76
result.add("Ghul.int.to_string");
77
result.add("Ghul.long.to_string");
78
result.add("Ghul.short.to_string");
79
result.add("Ghul.byte.to_string");
80
result.add("Ghul.ubyte.to_string");
81
result.add("Ghul.uint.to_string");
82
result.add("Ghul.ulong.to_string");
83
result.add("Ghul.ushort.to_string");
84
result.add("Ghul.bool.to_string");
85
result.add("Ghul.char.to_string");
86
result.add("Ghul.single.to_string");
87
result.add("Ghul.double.to_string");
88
89
// char: the Unicode classifiers and case converters are
90
// statics on a sealed struct. Every overload takes only
91
// value types - a char, a (string, int) pair, or the
92
// (char, char) and (char, char, char) forms of the
93
// range and surrogate checks - and returns a bool, an
94
// enum, a numeric value or a char, so none can dispatch
95
// into user code. The culture-taking converters
96
// (to_lower / to_upper with a CultureInfo) carry the
97
// same accepted pathological-culture caveat noted for
98
// string case conversion above; the invariant forms do
99
// not.
100
result.add("Ghul.char.is_white_space");
101
result.add("Ghul.char.is_digit");
102
result.add("Ghul.char.is_letter");
103
result.add("Ghul.char.is_letter_or_digit");
104
result.add("Ghul.char.is_upper");
105
result.add("Ghul.char.is_lower");
106
result.add("Ghul.char.is_number");
107
result.add("Ghul.char.is_symbol");
108
result.add("Ghul.char.is_control");
109
result.add("Ghul.char.is_punctuation");
110
result.add("Ghul.char.is_separator");
111
result.add("Ghul.char.is_surrogate");
112
result.add("Ghul.char.is_high_surrogate");
113
result.add("Ghul.char.is_low_surrogate");
114
result.add("Ghul.char.is_surrogate_pair");
115
result.add("Ghul.char.is_between");
116
result.add("Ghul.char.is_ascii");
117
result.add("Ghul.char.is_ascii_digit");
118
result.add("Ghul.char.is_ascii_letter");
119
result.add("Ghul.char.is_ascii_letter_or_digit");
120
result.add("Ghul.char.is_ascii_letter_lower");
121
result.add("Ghul.char.is_ascii_letter_upper");
122
result.add("Ghul.char.is_ascii_hex_digit");
123
result.add("Ghul.char.is_ascii_hex_digit_lower");
124
result.add("Ghul.char.is_ascii_hex_digit_upper");
125
result.add("Ghul.char.get_unicode_category");
126
result.add("Ghul.char.get_numeric_value");
127
result.add("Ghul.char.to_lower_invariant");
128
result.add("Ghul.char.to_upper_invariant");
129
result.add("Ghul.char.to_lower");
130
result.add("Ghul.char.to_upper");
131
132
// object.to_string: the imported body prints a type
133
// name; storing overrides are caught via overrider
134
// links
135
result.add("Ghul.object.to_string");
136
137
// concrete collections: non-virtual reads
138
result.add("Collections.LIST.get_Item");
139
result.add("Collections.LIST.get_count");
140
result.add("Collections.LIST.$get_count");
141
result.add("Collections.LIST.count");
142
result.add("Collections.STACK.peek");
143
result.add("Collections.STACK.get_count");
144
result.add("Collections.STACK.$get_count");
145
result.add("Collections.STACK.count");
146
147
// dictionary / set reads: trusts the equals-and-hash
148
// contract of the key type
149
result.add("Collections.MAP.get_Item");
150
result.add("Collections.MAP.contains_key");
151
result.add("Collections.MAP.try_get_value");
152
result.add("Collections.MAP.get_count");
153
result.add("Collections.MAP.$get_count");
154
result.add("Collections.MAP.count");
155
result.add("Collections.SET.contains");
156
result.add("Collections.SET.get_count");
157
result.add("Collections.SET.$get_count");
158
result.add("Collections.SET.count");
159
160
// read-only interface views: same reads through the
161
// IReadOnly* interfaces; trusts implementations to
162
// honour the read-only contract
163
result.add("Collections.List.get_Item");
164
result.add("Collections.List.get_count");
165
result.add("Collections.List.$get_count");
166
result.add("Collections.List.count");
167
result.add("Collections.Map.get_Item");
168
result.add("Collections.Map.contains_key");
169
result.add("Collections.Map.try_get_value");
170
result.add("Collections.Map.get_count");
171
result.add("Collections.Map.$get_count");
172
result.add("Collections.Map.count");
173
result.add("Collections.Bag.get_count");
174
result.add("Collections.Bag.$get_count");
175
result.add("Collections.Bag.count");
176
177
// value-type optional carriers: `x?` / `x!` on an
178
// `int?`-style slot lower to these accessors, which
179
// read a field of a sealed struct and can dispatch
180
// nowhere else. Without them every value-type
181
// presence test would count as a possibly-storing
182
// call and kill the very facts it establishes.
183
result.add("System.Nullable.get_has_value");
184
result.add("System.Nullable.$get_has_value");
185
result.add("System.Nullable.has_value");
186
result.add("System.Nullable.get_value");
187
result.add("System.Nullable.$get_value");
188
result.add("System.Nullable.value");
189
result.add("Ghul.MAYBE.get_has_value");
190
result.add("Ghul.MAYBE.$get_has_value");
191
result.add("Ghul.MAYBE.has_value");
192
result.add("Ghul.MAYBE.get_value");
193
result.add("Ghul.MAYBE.$get_value");
194
result.add("Ghul.MAYBE.value");
195
196
// console writes: console state is not user-visible
197
// heap, so a debug print cannot invalidate a
198
// narrowing. The object-taking overloads format via
199
// to_string, where a pathological storing override
200
// could slip through — accepted so that adding debug
201
// output does not silently break dependent
202
// narrowings.
203
result.add("IO.Std.write");
204
result.add("IO.Std.write_line");
205
result.add("IO.Std.get_error");
206
result.add("IO.Std.$get_error");
207
result.add("IO.Std.error");
208
result.add("IO.Std.get_out");
209
result.add("IO.Std.$get_out");
210
result.add("IO.Std.out");
211
result.add("IO.TextWriter.write");
212
result.add("IO.TextWriter.write_line");
213
214
// path manipulation: statics on a sealed class that
215
// combine and dissect path strings. Every overload
216
// takes only strings and returns a string or a bool;
217
// none takes a delegate, an object or an interface, so
218
// no call reaches user code. These read process state
219
// (the current directory, for get_full_path) but write
220
// no user-visible heap. The file-touching members
221
// (get_temp_file_name, and the filesystem probes) are
222
// deliberately left out.
223
result.add("IO.Path.combine");
224
result.add("IO.Path.join");
225
result.add("IO.Path.get_directory_name");
226
result.add("IO.Path.get_file_name");
227
result.add("IO.Path.get_file_name_without_extension");
228
result.add("IO.Path.get_extension");
229
result.add("IO.Path.get_full_path");
230
result.add("IO.Path.get_path_root");
231
result.add("IO.Path.get_relative_path");
232
result.add("IO.Path.change_extension");
233
result.add("IO.Path.has_extension");
234
result.add("IO.Path.is_path_rooted");
235
result.add("IO.Path.is_path_fully_qualified");
236
result.add("IO.Path.ends_in_directory_separator");
237
result.add("IO.Path.trim_ending_directory_separator");
238
239
// object.get_type: unlike to_string, GetType is not
240
// virtual, so no override can exist and no poisoning is
241
// needed — it reads the runtime type and returns it.
242
result.add("Ghul.object.get_type");
243
244
// StringBuilder reads: the sealed builder's to_string
245
// and length getter only read its contents. The
246
// mutating members (append, insert, clear, the length
247
// and Chars setters) are excluded.
248
result.add("System.Text.StringBuilder.to_string");
249
result.add("System.Text.StringBuilder.get_length");
250
result.add("System.Text.StringBuilder.$get_length");
251
result.add("System.Text.StringBuilder.length");
252
253
// reflected type name reads: name and full_name on a
254
// System.Type. Type is abstract, so a custom subclass
255
// could override these getters to store — the same
256
// accepted pathological-implementation risk as the
257
// equals-and-hash contract above; the compiler only
258
// ever reads them off runtime types.
259
result.add("System.Type.name");
260
result.add("System.Type.get_name");
261
result.add("System.Type.$get_name");
262
result.add("System.Type.full_name");
263
result.add("System.Type.get_full_name");
264
result.add("System.Type.$get_full_name");
265
266
// KeyValuePair construction: the read-only pair struct's
267
// constructor copies its key and value into the fresh
268
// struct and runs no behaviour on them, so it writes no
269
// pre-existing heap slot. Trusting it lets a dictionary
270
// populated from a pipe of pairs keep its narrowings — a
271
// storing argument still disqualifies where it is
272
// classified.
273
result.add("Collections.KeyValuePair.init");
274
275
_entries = result;
276
fi
277
278
return _entries;
279
si
280
281
is_store_free(function: Function?) -> bool static is
282
if !function? \/ !function.is_reflected then
283
return false;
284
fi
285
286
let owner = function.owner;
287
288
if !owner? then
289
return false;
290
fi
291
292
let key = "{owner.qualified_name}.{function.name}";
293
return entries().contains(key);
294
si
295
si
296
si