Appearance
| 1 | namespace Semantic.Symbols is | |
| 2 | // Imported functions that do write the heap, but write only their | |
| 3 | // own receiver's internal state, and run no user code while doing | |
| 4 | // it. A call to one is weaker than store-free and stronger than an | |
| 5 | // arbitrary call: it can change what a getter reading the receiver | |
| 6 | // returns, so property facts and getter-bearing paths die, but it | |
| 7 | // cannot change what any field holds, so field facts survive. | |
| 8 | // | |
| 9 | // The soundness argument, in two halves: | |
| 10 | // | |
| 11 | // - Field facts survive because the state these members mutate | |
| 12 | // is a BCL type's private storage. It is not addressable as a | |
| 13 | // ghūl member at all, so no field fact can be about it, and a | |
| 14 | // store into it cannot be a store into any ghūl field. | |
| 15 | // - Property facts die because a ghūl getter can read the | |
| 16 | // mutated collection — `count: int => _items.count` returns | |
| 17 | // something different after `_items.add(x)`. | |
| 18 | // | |
| 19 | // The third half is what keeps the first two true: a member listed | |
| 20 | // here must not be able to re-enter user code, because user code | |
| 21 | // can store anything. That rules out every overload taking a | |
| 22 | // delegate or a comparer, the lookup and removal members that | |
| 23 | // consult a key type's equals and hash members, the members taking | |
| 24 | // an arbitrary sequence, and `object`-taking formatting overloads | |
| 25 | // that call `to_string` on their argument. | |
| 26 | // | |
| 27 | // Curation is by name, as in STORE_FREE_IMPORTS, so an entry | |
| 28 | // covers every overload of that name. Where a name has both safe | |
| 29 | // and unsafe overloads it goes in `scalar_argument_entries` | |
| 30 | // instead, which additionally requires every parameter to be a | |
| 31 | // scalar, a string or an enum — none of which can dispatch | |
| 32 | // anywhere under caller control: an enum cannot be inherited any | |
| 33 | // more than a sealed string can be overridden. That test is | |
| 34 | // IMPORT_ARGUMENTS, and it is still selective where it declines: | |
| 35 | // a `TimeSpan` and a tuple are value types whose interiors hold | |
| 36 | // references, and both decline. | |
| 37 | class RECEIVER_INTERIOR_IMPORTS is | |
| 38 | _entries: Collections.SET[string]? static | |
| 39 | ||
| 40 | // Entries safe whatever their arguments are: the receiver | |
| 41 | // stores the argument and never calls anything on it. | |
| 42 | entries() -> Collections.SET[string] static is | |
| 43 | if !_entries? then | |
| 44 | let result = Collections.SET[string]() | |
| 45 | ||
| 46 | // LIST: the non-virtual mutators that write the | |
| 47 | // backing array and the size, and call nothing on the | |
| 48 | // element. `remove`, `contains` and `index_of` are | |
| 49 | // excluded — they run the element type's equality | |
| 50 | // through the default comparer. `add_range`, | |
| 51 | // `insert_range` and `sort` are excluded — they | |
| 52 | // enumerate a caller-supplied sequence or run a | |
| 53 | // caller-supplied comparer. | |
| 54 | result.add("Collections.LIST.add") | |
| 55 | result.add("Collections.LIST.insert") | |
| 56 | result.add("Collections.LIST.remove_at") | |
| 57 | result.add("Collections.LIST.clear") | |
| 58 | result.add("Collections.LIST.set_Item") | |
| 59 | ||
| 60 | // Ghul.BOX: the value setter writes the box's own | |
| 61 | // backing field and calls nothing on the argument. It | |
| 62 | // is the store half of the captured-local plumbing the | |
| 63 | // read half of which is curated in STORE_FREE_IMPORTS. | |
| 64 | result.add("Ghul.BOX.$$set_value") | |
| 65 | result.add("Ghul.BOX.set_value") | |
| 66 | ||
| 67 | // STACK: push writes the backing array and the size, | |
| 68 | // pop and clear write the size and null out the slot. | |
| 69 | // None of them touches the element. | |
| 70 | result.add("Collections.STACK.push") | |
| 71 | result.add("Collections.STACK.pop") | |
| 72 | result.add("Collections.STACK.clear") | |
| 73 | ||
| 74 | _entries = result | |
| 75 | fi | |
| 76 | ||
| 77 | return _entries | |
| 78 | si | |
| 79 | ||
| 80 | _scalar_argument_entries: Collections.SET[string]? static | |
| 81 | ||
| 82 | // Entries trusted only when every parameter is a scalar or a | |
| 83 | // string, as IMPORT_ARGUMENTS decides it — which admits less | |
| 84 | // than every value type; see the class comment above. The | |
| 85 | // names below have overloads that do reach user | |
| 86 | // code — `StringBuilder.append(object)` calls `to_string` on | |
| 87 | // its argument — and the parameter check is what separates | |
| 88 | // them from the safe overloads of the same name. | |
| 89 | scalar_argument_entries() -> Collections.SET[string] static is | |
| 90 | if !_scalar_argument_entries? then | |
| 91 | let result = Collections.SET[string]() | |
| 92 | ||
| 93 | // StringBuilder is sealed, so each of these dispatches | |
| 94 | // to exactly one body, and each writes only the | |
| 95 | // builder's own chunk storage. | |
| 96 | result.add("System.Text.StringBuilder.append") | |
| 97 | result.add("System.Text.StringBuilder.append_line") | |
| 98 | result.add("System.Text.StringBuilder.insert") | |
| 99 | result.add("System.Text.StringBuilder.remove") | |
| 100 | result.add("System.Text.StringBuilder.clear") | |
| 101 | ||
| 102 | _scalar_argument_entries = result | |
| 103 | fi | |
| 104 | ||
| 105 | return _scalar_argument_entries | |
| 106 | si | |
| 107 | ||
| 108 | writes_only_receiver_interior(function: Function?) -> bool static is | |
| 109 | if !function? \/ !function.is_reflected then | |
| 110 | return false | |
| 111 | fi | |
| 112 | ||
| 113 | let owner = function.owner | |
| 114 | ||
| 115 | if !owner? then | |
| 116 | return false | |
| 117 | fi | |
| 118 | ||
| 119 | let key = "{owner.qualified_name}.{function.name}" | |
| 120 | ||
| 121 | if entries().contains(key) then | |
| 122 | return true | |
| 123 | fi | |
| 124 | ||
| 125 | return scalar_argument_entries().contains(key) /\ IMPORT_ARGUMENTS.all_arguments_scalar(function) | |
| 126 | si | |
| 127 | si | |
| 128 | si |