Appearance
| 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 |