Skip to content
← Back

src/semantic/symbols/receiver_interior_imports.ghul

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