Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use Ghul.Pipes | |
| 5 | ||
| 6 | use Logging | |
| 7 | use Trees | |
| 8 | ||
| 9 | use Semantic.Types.Type | |
| 10 | ||
| 11 | use Function = Semantic.Symbols.Function | |
| 12 | use Symbol = Semantic.Symbols.Symbol | |
| 13 | ||
| 14 | // The function-context walk: function, property, indexer and lambda entry, assignments and | |
| 15 | // store-target classification. | |
| 16 | partial INFER_STORE_FREE is | |
| 17 | // ==== function context ==== | |
| 18 | ||
| 19 | pre(function: Definitions.FUNCTION) -> bool is | |
| 20 | enter_scope(function) | |
| 21 | ||
| 22 | if _current? then | |
| 23 | // function definitions do not nest; if the tree ever | |
| 24 | // produces one, poison the enclosing classification | |
| 25 | // rather than mis-attribute the nested body's facts | |
| 26 | _current.is_disqualified = true | |
| 27 | fi | |
| 28 | ||
| 29 | _typer.reset() | |
| 30 | ||
| 31 | let symbol = function_for(function) | |
| 32 | ||
| 33 | if symbol? then | |
| 34 | let for_property = function.for_property | |
| 35 | ||
| 36 | if for_property? /\ !function.is_assign_accessor then | |
| 37 | let shape = MONOTONE_MEMOISER_SHAPE(_logger, _symbol_table, _namespaces, _typer) | |
| 38 | ||
| 39 | MONOTONE_MEMOISER.note_getter( | |
| 40 | symbol, | |
| 41 | shape.classify_getter(function.body), | |
| 42 | function.location.file_name) | |
| 43 | fi | |
| 44 | ||
| 45 | let facts = STORE_FREE_FACTS() | |
| 46 | ||
| 47 | _facts[symbol] = facts | |
| 48 | ||
| 49 | _current = facts | |
| 50 | _current_function = symbol | |
| 51 | ||
| 52 | _note_study_function(function, symbol) | |
| 53 | else | |
| 54 | _current = null | |
| 55 | _current_function = null | |
| 56 | _current_record = null | |
| 57 | fi | |
| 58 | ||
| 59 | return false | |
| 60 | si | |
| 61 | ||
| 62 | visit(function: Definitions.FUNCTION) is | |
| 63 | leave_scope(function) | |
| 64 | ||
| 65 | _current = null | |
| 66 | _current_function = null | |
| 67 | si | |
| 68 | ||
| 69 | // Property and indexer accessor bodies arrive as synthesised | |
| 70 | // sibling FUNCTION definitions; the original nodes carry the | |
| 71 | // same body and would double-classify it. | |
| 72 | pre(property: Definitions.PROPERTY) -> bool => true | |
| 73 | visit(property: Definitions.PROPERTY) is si | |
| 74 | ||
| 75 | pre(indexer: Definitions.INDEXER) -> bool => true | |
| 76 | visit(indexer: Definitions.INDEXER) is si | |
| 77 | ||
| 78 | // A lambda body does not run when the enclosing function | |
| 79 | // merely creates the closure; any invocation happens through | |
| 80 | // a delegate-typed value, which is never a bounded callee. | |
| 81 | // Closure symbols are never marked store-free. | |
| 82 | // | |
| 83 | // In resolved mode the body is walked anyway, under the | |
| 84 | // closure's own function symbol, so the effect-polymorphic | |
| 85 | // absorption has a write set for every function value it can | |
| 86 | // trace to a literal. | |
| 87 | pre(function: Expressions.FUNCTION) -> bool is | |
| 88 | if _resolved_mode then | |
| 89 | _study_walk_lambda(function) | |
| 90 | fi | |
| 91 | ||
| 92 | return true | |
| 93 | si | |
| 94 | ||
| 95 | visit(function: Expressions.FUNCTION) is si | |
| 96 | ||
| 97 | _study_walk_lambda(function: Expressions.FUNCTION) is | |
| 98 | let symbol = function_value_of(function.value) | |
| 99 | ||
| 100 | if !symbol? then | |
| 101 | return | |
| 102 | fi | |
| 103 | ||
| 104 | let root = cast Function?(symbol.root_specialized_from) | |
| 105 | ||
| 106 | if !root? \/ _facts.contains_key(root) then | |
| 107 | return | |
| 108 | fi | |
| 109 | ||
| 110 | let saved_facts = _current | |
| 111 | let saved_function = _current_function | |
| 112 | let saved_record = _current_record | |
| 113 | ||
| 114 | let facts = STORE_FREE_FACTS() | |
| 115 | ||
| 116 | _facts[root] = facts | |
| 117 | _current = facts | |
| 118 | _current_function = root | |
| 119 | ||
| 120 | EFFECT_FACTS.note_function(root, root.name, "", "{function.location}", "lambda") | |
| 121 | _current_record = EFFECT_FACTS.record_for(root) | |
| 122 | ||
| 123 | enter_scope(function) | |
| 124 | function.body.walk(self) | |
| 125 | leave_scope(function) | |
| 126 | ||
| 127 | _current = saved_facts | |
| 128 | _current_function = saved_function | |
| 129 | _current_record = saved_record | |
| 130 | si | |
| 131 | ||
| 132 | // ==== stores ==== | |
| 133 | ||
| 134 | // The target of a store is written, not read: walking it | |
| 135 | // generically would classify the member as a read as well, | |
| 136 | // adding its getter's edges - and disqualifying outright when | |
| 137 | // the property is assign-only and has no getter. So the | |
| 138 | // children are driven by hand: the value and every receiver | |
| 139 | // and index expression inside the target are walked, the | |
| 140 | // target member itself is not. | |
| 141 | pre(assign: Statements.ASSIGNMENT) -> bool is | |
| 142 | assign.right.walk(self) | |
| 143 | ||
| 144 | _walk_assignment_target_operands(assign.left) | |
| 145 | ||
| 146 | return true | |
| 147 | si | |
| 148 | ||
| 149 | _walk_assignment_target_operands(left: Trees.Expressions.AssignmentLeftExpression?) is | |
| 150 | if !left? then | |
| 151 | return | |
| 152 | fi | |
| 153 | ||
| 154 | if isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then | |
| 155 | _walk_store_target_operands((cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression) | |
| 156 | elif isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then | |
| 157 | for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do | |
| 158 | _walk_assignment_target_operands(element) | |
| 159 | od | |
| 160 | fi | |
| 161 | si | |
| 162 | ||
| 163 | _walk_store_target_operands(target: Trees.Expressions.Expression?) is | |
| 164 | if !target? then | |
| 165 | return | |
| 166 | fi | |
| 167 | ||
| 168 | if let member: Trees.Expressions.MEMBER = target then | |
| 169 | if !isa Trees.Expressions.SELF(member.left) then | |
| 170 | member.left.walk(self) | |
| 171 | fi | |
| 172 | ||
| 173 | return | |
| 174 | fi | |
| 175 | ||
| 176 | if let index: Trees.Expressions.INDEX = target then | |
| 177 | index.left.walk(self) | |
| 178 | index.index.walk(self) | |
| 179 | return | |
| 180 | fi | |
| 181 | ||
| 182 | if let ambiguous: Trees.Expressions.AMBIGUOUS_EXPRESSION = target then | |
| 183 | if ambiguous.result == Trees.Expressions.AmbiguousExpressionResult.INDEX then | |
| 184 | _walk_store_target_operands(ambiguous.index) | |
| 185 | elif ambiguous.left? then | |
| 186 | ambiguous.left.walk(self) | |
| 187 | fi | |
| 188 | ||
| 189 | return | |
| 190 | fi | |
| 191 | ||
| 192 | // a bare identifier target: nothing inside it to read | |
| 193 | si | |
| 194 | ||
| 195 | visit(assign: Statements.ASSIGNMENT) is | |
| 196 | if !_current? then | |
| 197 | return | |
| 198 | fi | |
| 199 | ||
| 200 | // Study-only: whether this assignment's value could be | |
| 201 | // absent. An RHS of declared non-optional type cannot be | |
| 202 | // null under the language's own rules; anything optional or | |
| 203 | // untypeable counts as possibly-null. | |
| 204 | let rhs_type = _typer.try_type(assign.right) | |
| 205 | ||
| 206 | _study_rhs_nulling = !rhs_type? \/ rhs_type.is_optional | |
| 207 | ||
| 208 | _classify_assignment_target(assign.left) | |
| 209 | ||
| 210 | _study_rhs_nulling = false | |
| 211 | si | |
| 212 | ||
| 213 | ||
| 214 | _study_rhs_nulling: bool | |
| 215 | ||
| 216 | // Flag the current function as assigning a possibly-absent | |
| 217 | // value to member-or-wider storage. A constructor's writes to | |
| 218 | // its own fresh instance cannot make any pre-existing member | |
| 219 | // absent, so they are excluded. | |
| 220 | _study_note_nulling(own_instance: bool) is | |
| 221 | if !_current_record? \/ !_study_rhs_nulling then | |
| 222 | return | |
| 223 | fi | |
| 224 | ||
| 225 | if _in_constructor /\ own_instance then | |
| 226 | return | |
| 227 | fi | |
| 228 | ||
| 229 | _current_record.assigns_optional = true | |
| 230 | si | |
| 231 | ||
| 232 | // The member-keyed refinement of the above: record which member | |
| 233 | // the possibly-null assignment targets, or that it could not be | |
| 234 | // resolved. Unlike the coarse flag, a constructor's writes to | |
| 235 | // its own instance are NOT excluded: the fresh-instance | |
| 236 | // argument only holds at a construction site, and the solved | |
| 237 | // sets answer for plain calls too - a constructor reached by | |
| 238 | // delegation, or invoked directly on an existing receiver, | |
| 239 | // nulls a pre-existing member like any other method would. | |
| 240 | _study_note_nulling_member(symbol: Symbol?, own_instance: bool) is | |
| 241 | if !_current_record? \/ !_study_rhs_nulling then | |
| 242 | return | |
| 243 | fi | |
| 244 | ||
| 245 | if symbol? then | |
| 246 | _current_record.nulling_writes.add(symbol.root_specialized_from) | |
| 247 | else | |
| 248 | _current_record.nulling_unbounded = true | |
| 249 | fi | |
| 250 | si | |
| 251 | ||
| 252 | _classify_assignment_target(left: Trees.Expressions.AssignmentLeftExpression?) is | |
| 253 | if !left? then | |
| 254 | _disqualify_because("assign-target-null") | |
| 255 | return | |
| 256 | fi | |
| 257 | ||
| 258 | if isa Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left) then | |
| 259 | _classify_simple_store((cast Trees.Expressions.SIMPLE_LEFT_EXPRESSION(left)).expression) | |
| 260 | elif isa Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left) then | |
| 261 | for element in (cast Trees.Expressions.DESTRUCTURING_LEFT_EXPRESSION(left)).elements do | |
| 262 | _classify_assignment_target(element) | |
| 263 | od | |
| 264 | else | |
| 265 | _disqualify_because("assign-target-shape") | |
| 266 | fi | |
| 267 | si | |
| 268 | ||
| 269 | _classify_simple_store(target: Trees.Expressions.Expression?) is | |
| 270 | _study_note_store(target) | |
| 271 | ||
| 272 | if _writes_own_instance_field_member(target) then | |
| 273 | // `self.field = …` writes only the receiver's own | |
| 274 | // state: disqualifies the strict bit but not | |
| 275 | // construction, exactly like the bare-field form below | |
| 276 | _disqualify_strict_only() | |
| 277 | return | |
| 278 | fi | |
| 279 | ||
| 280 | let member_assign_function = _writes_own_instance_property_member(target) | |
| 281 | ||
| 282 | if member_assign_function? then | |
| 283 | // `self.property = …` reaches that same state through | |
| 284 | // the property's accessor | |
| 285 | _disqualify_strict_only() | |
| 286 | _add_construction_callee(member_assign_function) | |
| 287 | return | |
| 288 | fi | |
| 289 | ||
| 290 | if _is_local_target(target) then | |
| 291 | // Reassigning the body's own local variable or | |
| 292 | // parameter is callee-private. A local captured from | |
| 293 | // an enclosing scope is not: the closure and the | |
| 294 | // declaring scope share one heap cell, so the write | |
| 295 | // escapes the callee - a heap store, recorded exactly | |
| 296 | // (the write set carries the local's symbol) rather | |
| 297 | // than unbounding the set. | |
| 298 | if _is_captured_local_write(target) then | |
| 299 | _disqualify_captured_store() | |
| 300 | fi | |
| 301 | ||
| 302 | return | |
| 303 | fi | |
| 304 | ||
| 305 | if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then | |
| 306 | // a member, index or other compound target is a heap | |
| 307 | // store | |
| 308 | _disqualify_store() | |
| 309 | return | |
| 310 | fi | |
| 311 | ||
| 312 | let identifier = (cast Trees.Expressions.IDENTIFIER(target)).identifier | |
| 313 | ||
| 314 | if identifier.is_qualified then | |
| 315 | _disqualify_store() | |
| 316 | return | |
| 317 | fi | |
| 318 | ||
| 319 | let symbol = try_find(identifier) | |
| 320 | ||
| 321 | if symbol? /\ symbol.is_field /\ symbol.is_instance then | |
| 322 | // a bare instance-field name resolves to the receiver's | |
| 323 | // own field, so the write disqualifies the strict bit | |
| 324 | // but not construction | |
| 325 | _disqualify_strict_only() | |
| 326 | return | |
| 327 | fi | |
| 328 | ||
| 329 | let assign_function = _instance_property_assign(symbol) | |
| 330 | ||
| 331 | if assign_function? then | |
| 332 | // the bare-name form of the own-property write handled | |
| 333 | // above | |
| 334 | _disqualify_strict_only() | |
| 335 | _add_construction_callee(assign_function) | |
| 336 | return | |
| 337 | fi | |
| 338 | ||
| 339 | // a global variable, or a target the walk could not | |
| 340 | // resolve: either way the write is not to a local | |
| 341 | _disqualify_store() | |
| 342 | si | |
| 343 | ||
| 344 | // Whether `target` is a local the current body captured from | |
| 345 | // an enclosing scope - the store then writes a heap cell the | |
| 346 | // declaring scope shares, not private storage. | |
| 347 | _is_captured_local_write(target: Trees.Expressions.Expression?) -> bool is | |
| 348 | if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then | |
| 349 | return false | |
| 350 | fi | |
| 351 | ||
| 352 | let symbol = try_find((cast Trees.Expressions.IDENTIFIER(target)).identifier) | |
| 353 | ||
| 354 | if !symbol? then | |
| 355 | return false | |
| 356 | fi | |
| 357 | ||
| 358 | return _is_captured_local_store(symbol) | |
| 359 | si | |
| 360 | ||
| 361 | _is_captured_local_store(symbol: Semantic.Symbols.Symbol) -> bool is | |
| 362 | if !isa Semantic.Symbols.LOCAL_VARIABLE(symbol) then | |
| 363 | return false | |
| 364 | fi | |
| 365 | ||
| 366 | let closure = current_closure | |
| 367 | ||
| 368 | return closure? /\ symbol.owner != closure | |
| 369 | si | |
| 370 | ||
| 371 | // A bare name for a local variable or a parameter — the one | |
| 372 | // storage location a function can write without any caller | |
| 373 | // seeing it. | |
| 374 | _is_local_target(target: Trees.Expressions.Expression?) -> bool is | |
| 375 | if !target? \/ !isa Trees.Expressions.IDENTIFIER(target) then | |
| 376 | return false | |
| 377 | fi | |
| 378 | ||
| 379 | let identifier = (cast Trees.Expressions.IDENTIFIER(target)).identifier | |
| 380 | ||
| 381 | if identifier.is_qualified then | |
| 382 | return false | |
| 383 | fi | |
| 384 | ||
| 385 | let symbol = try_find(identifier) | |
| 386 | ||
| 387 | return | |
| 388 | symbol? /\ | |
| 389 | (isa Semantic.Symbols.LOCAL_VARIABLE(symbol) \/ isa Semantic.Symbols.LOCAL_ARGUMENT(symbol)) | |
| 390 | si | |
| 391 | ||
| 392 | si | |
| 393 | si |