https://github.com/akkartik/mu1/blob/master/057immutable.cc
1
2
3
4
5
6
7 :(scenario can_modify_ingredients_that_are_also_products)
8
9 def main [
10 local-scope
11 p:point <- merge 34, 35
12 p <- foo p
13 ]
14 def foo p:point -> p:point [
15 local-scope
16 load-ingredients
17 p <- put p, x:offset, 34
18 ]
19 $error: 0
20
21 :(scenario can_modify_ingredients_that_are_also_products_2)
22 def main [
23 local-scope
24 p:&:point <- new point:type
25 p <- foo p
26 ]
27
28 def foo p:&:point -> p:&:point [
29 local-scope
30 load-ingredients
31 *p <- put *p, x:offset, 34
32 ]
33 $error: 0
34
35 :(scenario can_modify_ingredients_that_are_also_products_3)
36 def main [
37 local-scope
38 p:&:@:num <- new number:type, 3
39 p <- foo p
40 ]
41
42 def foo p:&:@:num -> p:&:@:num [
43 local-scope
44 load-ingredients
45 *p <- put-index *p, 0, 34
46 ]
47 $error: 0
48
49 :(scenario ignore_literal_ingredients_for_immutability_checks)
50 def main [
51 local-scope
52 p:&:d1 <- new d1:type
53 q:num <- foo p
54 ]
55 def foo p:&:d1 -> q:num [
56 local-scope
57 load-ingredients
58 x:&:d1 <- new d1:type
59 *x <- put *x, p:offset, 34
60 return 36
61 ]
62 container d1 [
63 p:num
64 q:num
65 ]
66 $error: 0
67
68 :(scenario cannot_modify_immutable_ingredients)
69 % Hide_errors = true;
70 def main [
71 local-scope
72 x:&:num <- new number:type
73 foo x
74 ]
75
76 def foo x:&:num [
77 local-scope
78 load-ingredients
79 *x <- copy 34
80 ]
81 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product
82
83 :(scenario cannot_modify_immutable_containers)
84 % Hide_errors = true;
85 def main [
86 local-scope
87 x:point-number <- merge 34, 35, 36
88 foo x
89 ]
90
91 def foo x:point-number [
92 local-scope
93 load-ingredients
94
95 y:point <- get x, xy:offset
96
97
98
99 y <- put y, x:offset, 37
100 ]
101 +error: foo: cannot modify 'y' in instruction 'y <- put y, x:offset, 37' because that would modify 'x' which is an ingredient of recipe foo but not also a product
102
103 :(scenario can_modify_immutable_pointers)
104 def main [
105 local-scope
106 x:&:num <- new number:type
107 foo x
108 ]
109 def foo x:&:num [
110 local-scope
111 load-ingredients
112
113 x <- copy null
114 ]
115 $error: 0
116
117 :(scenario can_modify_immutable_pointers_but_not_their_payloads)
118 % Hide_errors = true;
119 def main [
120 local-scope
121 x:&:num <- new number:type
122 foo x
123 ]
124 def foo x:&:num [
125 local-scope
126 load-ingredients
127
128 x <- new number:type
129
130
131 *x <- copy 34
132 ]
133 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product
134
135 :(scenario cannot_call_mutating_recipes_on_immutable_ingredients)
136 % Hide_errors = true;
137 def main [
138 local-scope
139 p:&:point <- new point:type
140 foo p
141 ]
142 def foo p:&:point [
143 local-scope
144 load-ingredients
145 bar p
146 ]
147 def bar p:&:point -> p:&:point [
148 local-scope
149 load-ingredients
150
151
152 ]
153 +error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product
154
155 :(scenario cannot_modify_copies_of_immutable_ingredients)
156 % Hide_errors = true;
157 def main [
158 local-scope
159 p:&:point <- new point:type
160 foo p
161 ]
162 def foo p:&:point [
163 local-scope
164 load-ingredients
165 q:&:point <- copy p
166 *q <- put *q, x:offset, 34
167 ]
168 +error: foo: cannot modify 'q' in instruction '*q <- put *q, x:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product
169
170 :(scenario can_modify_copies_of_mutable_ingredients)
171 def main [
172 local-scope
173 p:&:point <- new point:type
174 foo p
175 ]
176 def foo p:&:point -> p:&:point [
177 local-scope
178 load-ingredients
179 q:&:point <- copy p
180 *q <- put *q, x:offset, 34
181 ]
182 $error: 0
183
184 :(scenario cannot_modify_address_inside_immutable_ingredients)
185 % Hide_errors = true;
186 container foo [
187 x:&:@:num
188 ]
189 def main [
190
191 ]
192 def foo a:&:foo [
193 local-scope
194 load-ingredients
195 x:&:@:num <- get *a, x:offset
196 *x <- put-index *x, 0, 34
197 ]
198 +error: foo: cannot modify 'x' in instruction '*x <- put-index *x, 0, 34' because that would modify a which is an ingredient of recipe foo but not also a product
199
200 :(scenario cannot_modify_address_inside_immutable_ingredients_2)
201 container foo [
202 x:&:@:num
203 ]
204 def main [
205
206 ]
207 def foo a:&:foo [
208 local-scope
209 load-ingredients
210 b:foo <- merge null
211
212 x:&:@:num <- get b, x:offset
213 *x <- put-index *x, 0, 34
214 ]
215 $error: 0
216
217 :(scenario cannot_modify_address_inside_immutable_ingredients_3)
218 % Hide_errors = true;
219 def main [
220
221 ]
222 def foo a:&:@:&:num [
223 local-scope
224 load-ingredients
225 x:&:num <- index *a, 0
226 *x <- copy 34
227 ]
228 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because that would modify a which is an ingredient of recipe foo but not also a product
229
230 :(scenario cannot_modify_address_inside_immutable_ingredients_4)
231 def main [
232
233 ]
234 def foo a:&:@:&:num [
235 local-scope
236 load-ingredients
237 b:&:@:&:num <- new {(address number): type}, 3
238
239 x:&:num <- index *b, 0
240 *x <- copy 34
241 ]
242 $error: 0
243
244 :(scenario latter_ingredient_of_index_is_immutable)
245 def main [
246
247 ]
248 def foo a:&:@:&:@:num, b:num -> a:&:@:&:@:num [
249 local-scope
250 load-ingredients
251 x:&:@:num <- index *a, b
252 *x <- put-index *x, 0, 34
253 ]
254 $error: 0
255
256 :(scenario can_traverse_immutable_ingredients)
257 container test-list [
258 next:&:test-list
259 ]
260 def main [
261 local-scope
262 p:&:test-list <- new test-list:type
263 foo p
264 ]
265 def foo p:&:test-list [
266 local-scope
267 load-ingredients
268 p2:&:test-list <- bar p
269 ]
270 def bar x:&:test-list -> y:&:test-list [
271 local-scope
272 load-ingredients
273 y <- get *x, next:offset
274 ]
275 $error: 0
276
277 :(scenario treat_optional_ingredients_as_mutable)
278 def main [
279 k:&:num <- new number:type
280 test k
281 ]
282
283 def test k:&:num [
284 local-scope
285 load-ingredients
286 foo k
287 ]
288
289 def foo -> [
290 local-scope
291 load-ingredients
292 k:&:num, found?:bool <- next-ingredient
293
294 ]
295 $error: 0
296
297 :(scenario treat_optional_ingredients_as_mutable_2)
298 % Hide_errors = true;
299 def main [
300 local-scope
301 p:&:point <- new point:type
302 foo p
303 ]
304 def foo p:&:point [
305 local-scope
306 load-ingredients
307 bar p
308 ]
309 def bar [
310 local-scope
311 load-ingredients
312 p:&:point <- next-ingredient
313 ]
314 +error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product
315
316
317 :(scenario check_space_of_reagents_in_immutability_checks)
318 def main [
319 a:space/names:new-closure <- new-closure
320 b:&:num <- new number:type
321 run-closure b:&:num, a:space
322 ]
323 def new-closure [
324 local-scope
325 x:&:num <- new number:type
326 return default-space/names:new-closure
327 ]
328 def run-closure x:&:num, s:space/names:new-closure [
329 local-scope
330 load-ingredients
331 0:space/names:new-closure <- copy s
332
333 *x:&:num/space:1 <- copy 34
334 ]
335 $error: 0
336
337 :(before "End Transforms")
338 Transform.push_back(check_immutable_ingredients);
339
340 :(code)
341 void check_immutable_ingredients(const recipe_ordinal r) {
342
343
344
345
346 const recipe& caller = get(Recipe, r);
347 trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end();
348 if (!caller.has_header) return;
349 for (int i = 0; i < SIZE(caller.ingredients); ++i) {
350 const reagent& current_ingredient = caller.ingredients.at(i);
351 if (is_present_in_products(caller, current_ingredient.name)) continue;
352
353 set<reagent, name_and_space_lt> immutable_vars;
354 immutable_vars.insert(current_ingredient);
355 for (int i = 0; i < SIZE(caller.steps); ++i) {
356 const instruction& inst = caller.steps.at(i);
357 check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller);
358 if (inst.operation == INDEX && SIZE(inst.ingredients) > 1 && inst.ingredients.at(1).name == current_ingredient.name) continue;
359 update_aliases(inst, immutable_vars);
360 }
361 }
362 }
363
364 void update_aliases(const instruction& inst, set<reagent, name_and_space_lt>& current_ingredient_and_aliases) {
365 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
366 if (!contains_key(Recipe, inst.operation)) {
367
368 switch (inst.operation) {
369 case COPY:
370 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p)
371 current_ingredient_and_aliases.insert(inst.products.at(*p).name);
372 break;
373 case GET:
374 case INDEX:
375 case MAYBE_CONVERT:
376
377 if (!current_ingredient_indices.empty() && !inst.products.empty()) {
378 if (is_mu_address(inst.products.at(0)) || is_mu_container(inst.products.at(0)) || is_mu_exclusive_container(inst.products.at(0)))
379 current_ingredient_and_aliases.insert(inst.products.at(0));
380 }
381 break;
382 default: break;
383 }
384 }
385 else {
386
387 set<int> contained_in_product_indices = scan_contained_in_product_indices(inst, current_ingredient_indices);
388 for (set<int>::iterator p = contained_in_product_indices.begin(); p != contained_in_product_indices.end(); ++p) {
389 if (*p < SIZE(inst.products))
390 current_ingredient_and_aliases.insert(inst.products.at(*p));
391 }
392 }
393 }
394
395 set<int> scan_contained_in_product_indices(const instruction& inst, set<int>& ingredient_indices) {
396 set<reagent, name_and_space_lt> selected_ingredients;
397 const recipe& callee = get(Recipe, inst.operation);
398 for (set<int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) {
399 if (*p >= SIZE(callee.ingredients)) continue;
400 selected_ingredients.insert(callee.ingredients.at(*p));
401 }
402 set<int> result;
403 for (int i = 0; i < SIZE(callee.products); ++i) {
404 const reagent& current_product = callee.products.at(i);
405 const string_tree* contained_in_name = property(current_product, "contained-in");
406 if (contained_in_name && selected_ingredients.find(contained_in_name->value) != selected_ingredients.end())
407 result.insert(i);
408 }
409 return result;
410 }
411
412 bool is_mu_container(const reagent& r) {
413 return is_mu_container(r.type);
414 }
415 bool is_mu_container(const type_tree* type) {
416 if (!type) return false;
417 if (!type->atom)
418 return is_mu_container(get_base_type(type));
419 if (type->value == 0) return false;
420 if (!contains_key(Type, type->value)) return false;
421 type_info& info = get(Type, type->value);
422 return info.kind == CONTAINER;
423 }
424
425 bool is_mu_exclusive_container(const reagent& r) {
426 return is_mu_exclusive_container(r.type);
427 }
428 bool is_mu_exclusive_container(const type_tree* type) {
429 if (!type) return false;
430 if (!type->atom)
431 return is_mu_exclusive_container(get_base_type(type));
432 if (type->value == 0) return false;
433 if (!contains_key(Type, type->value)) return false;
434 type_info& info = get(Type, type->value);
435 return info.kind == EXCLUSIVE_CONTAINER;
436 }
437
438 :(before "End Types")
439
440 struct name_and_space_lt {
441 bool operator()(const reagent& a, const reagent& b) const;
442 };
443 :(code)
444 bool name_and_space_lt::operator()(const reagent& a, const reagent& b) const {
445 int aspace = 0, bspace = 0;
446 if (has_property(a, "space")) aspace = to_integer(property(a, "space")->value);
447 if (has_property(b, "space")) bspace = to_integer(property(b, "space")->value);
448 if (aspace != bspace) return aspace < bspace;
449 return a.name < b.name;
450 }
451
452 :(scenarios transform)
453 :(scenario immutability_infects_contained_in_variables)
454 % Hide_errors = true;
455 container test-list [
456 value:num
457 next:&:test-list
458 ]
459 def main [
460 local-scope
461 p:&:test-list <- new test-list:type
462 foo p
463 ]
464 def foo p:&:test-list [
465 local-scope
466 load-ingredients
467 p2:&:test-list <- test-next p
468 *p2 <- put *p2, value:offset, 34
469 ]
470 def test-next x:&:test-list -> y:&:test-list/contained-in:x [
471 local-scope
472 load-ingredients
473 y <- get *x, next:offset
474 ]
475 +error: foo: cannot modify 'p2' in instruction '*p2 <- put *p2, value:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product
476
477 :(code)
478 void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent, name_and_space_lt>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
479
480 for (int i = 0; i < SIZE(inst.products); ++i) {
481 if (has_property(inst.products.at(i), "lookup")
482 && current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) {
483 string current_product_name = inst.products.at(i).name;
484 if (current_product_name == original_ingredient_name)
485 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
486 else
487 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because that would modify " << original_ingredient_name << " which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
488 return;
489 }
490 }
491
492 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
493 if (current_ingredient_indices.empty()) return;
494 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
495 const int current_ingredient_index = *p;
496 reagent current_ingredient = inst.ingredients.at(current_ingredient_index);
497 canonize_type(current_ingredient);
498 const string& current_ingredient_name = current_ingredient.name;
499 if (!contains_key(Recipe, inst.operation)) {
500
501
502
503
504
505 if (inst.operation == PUT || inst.operation == PUT_INDEX) {
506 if (current_ingredient_index == 0) {
507 if (current_ingredient_name == original_ingredient_name)
508 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
509 else
510 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
511 }
512 }
513 }
514 else {
515
516 if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) {
517 if (current_ingredient_name == original_ingredient_name)
518 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
519 else
520 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
521 }
522 }
523 }
524 }
525
526 bool is_modified_in_recipe(const recipe_ordinal r, const int ingredient_index, const recipe& caller) {
527 const recipe& callee = get(Recipe, r);
528 if (!callee.has_header) {
529 raise << maybe(caller.name) << "can't check mutability of ingredients in recipe " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end();
530 return true;
531 }
532 if (ingredient_index >= SIZE(callee.ingredients)) return false;
533 return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name);
534 }
535
536 bool is_present_in_products(const recipe& callee, const string& ingredient_name) {
537 for (int i = 0; i < SIZE(callee.products); ++i) {
538 if (callee.products.at(i).name == ingredient_name)
539 return true;
540 }
541 return false;
542 }
543
544 set<int> ingredient_indices(const instruction& inst, const set<reagent, name_and_space_lt>& ingredient_names) {
545 set<int> result;
546 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
547 if (is_literal(inst.ingredients.at(i))) continue;
548 if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end())
549 result.insert(i);
550 }
551 return result;
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565 :(scenarios transform)
566 :(scenario can_modify_contained_in_addresses)
567 container test-list [
568 value:num
569 next:&:test-list
570 ]
571 def main [
572 local-scope
573 p:&:test-list <- new test-list:type
574 foo p
575 ]
576 def foo p:&:test-list -> p:&:test-list [
577 local-scope
578 load-ingredients
579 p2:&:test-list <- test-next p
580 p <- test-remove p2, p
581 ]
582 def test-next x:&:test-list -> y:&:test-list [
583 local-scope
584 load-ingredients
585 y <- get *x, next:offset
586 ]
587 def test-remove x:&:test-list/contained-in:from, from:&:test-list -> from:&:test-list [
588 local-scope
589 load-ingredients
590 *x <- put *x, value:offset, 34
591 ]
592 $error: 0
593
594 :(before "End Immutable Ingredients Special-cases")
595 if (has_property(current_ingredient, "contained-in")) {
596 const string_tree* tmp = property(current_ingredient, "contained-in");
597 if (!tmp->atom
598 || (!is_present_in_ingredients(caller, tmp->value)
599 && !is_present_in_products(caller, tmp->value))) {
600 raise << maybe(caller.name) << "/contained-in can only point to another ingredient or product, but got '" << to_string(property(current_ingredient, "contained-in")) << "'\n" << end();
601 }
602 continue;
603 }
604
605 :(scenario contained_in_product)
606 container test-list [
607 value:num
608 next:&:test-list
609 ]
610 def foo x:&:test-list/contained-in:result -> result:&:test-list [
611 local-scope
612 load-ingredients
613 result <- copy null
614 ]
615 $error: 0
616
617 :(scenario contained_in_is_mutable)
618 container test-list [
619 value:num
620 next:&:test-list
621 ]
622 def foo x:&:test-list/contained-in:result -> result:&:test-list [
623 local-scope
624 load-ingredients
625 result <- copy x
626 put *x, value:offset, 34
627 ]
628 $error: 0