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 0
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 0
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> 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>& 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> 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 :(scenarios transform)
439 :(scenario immutability_infects_contained_in_variables)
440 % Hide_errors = true;
441 container test-list [
442 value:num
443 next:&:test-list
444 ]
445 def main [
446 local-scope
447 p:&:test-list <- new test-list:type
448 foo p
449 ]
450 def foo p:&:test-list [
451 local-scope
452 load-ingredients
453 p2:&:test-list <- test-next p
454 *p2 <- put *p2, value:offset, 34
455 ]
456 def test-next x:&:test-list -> y:&:test-list/contained-in:x [
457 local-scope
458 load-ingredients
459 y <- get *x, next:offset
460 ]
461 +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
462
463 :(code)
464 void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
465
466 for (int i = 0; i < SIZE(inst.products); ++i) {
467 if (has_property(inst.products.at(i), "lookup")
468 && current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) {
469 string current_product_name = inst.products.at(i).name;
470 if (current_product_name == original_ingredient_name)
471 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();
472 else
473 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();
474 return;
475 }
476 }
477
478 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
479 if (current_ingredient_indices.empty()) return;
480 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
481 const int current_ingredient_index = *p;
482 reagent current_ingredient = inst.ingredients.at(current_ingredient_index);
483 canonize_type(current_ingredient);
484 const string& current_ingredient_name = current_ingredient.name;
485 if (!contains_key(Recipe, inst.operation)) {
486
487
488
489
490
491 if (inst.operation == PUT || inst.operation == PUT_INDEX) {
492 if (current_ingredient_index == 0) {
493 if (current_ingredient_name == original_ingredient_name)
494 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();
495 else
496 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();
497 }
498 }
499 }
500 else {
501
502 if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) {
503 if (current_ingredient_name == original_ingredient_name)
504 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();
505 else
506 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();
507 }
508 }
509 }
510 }
511
512 bool is_modified_in_recipe(const recipe_ordinal r, const int ingredient_index, const recipe& caller) {
513 const recipe& callee = get(Recipe, r);
514 if (!callee.has_header) {
515 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();
516 return true;
517 }
518 if (ingredient_index >= SIZE(callee.ingredients)) return false;
519 return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name);
520 }
521
522 bool is_present_in_products(const recipe& callee, const string& ingredient_name) {
523 for (int i = 0; i < SIZE(callee.products); ++i) {
524 if (callee.products.at(i).name == ingredient_name)
525 return true;
526 }
527 return false;
528 }
529
530 set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) {
531 set<int> result;
532 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
533 if (is_literal(inst.ingredients.at(i))) continue;
534 if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end())
535 result.insert(i);
536 }
537 return result;
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551 :(scenarios transform)
552 :(scenario can_modify_contained_in_addresses)
553 container test-list [
554 value:num
555 next:&:test-list
556 ]
557 def main [
558 local-scope
559 p:&:test-list <- new test-list:type
560 foo p
561 ]
562 def foo p:&:test-list -> p:&:test-list [
563 local-scope
564 load-ingredients
565 p2:&:test-list <- test-next p
566 p <- test-remove p2, p
567 ]
568 def test-next x:&:test-list -> y:&:test-list [
569 local-scope
570 load-ingredients
571 y <- get *x, next:offset
572 ]
573 def test-remove x:&:test-list/contained-in:from, from:&:test-list -> from:&:test-list [
574 local-scope
575 load-ingredients
576 *x <- put *x, value:offset, 34
577 ]
578 $error: 0
579
580 :(before "End Immutable Ingredients Special-cases")
581 if (has_property(current_ingredient, "contained-in")) {
582 const string_tree* tmp = property(current_ingredient, "contained-in");
583 if (!tmp->atom
584 || (!is_present_in_ingredients(caller, tmp->value)
585 && !is_present_in_products(caller, tmp->value))) {
586 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();
587 }
588 continue;
589 }
590
591 :(scenario contained_in_product)
592 container test-list [
593 value:num
594 next:&:test-list
595 ]
596 def foo x:&:test-list/contained-in:result -> result:&:test-list [
597 local-scope
598 load-ingredients
599 result <- copy 0
600 ]
601 $error: 0
602
603 :(scenario contained_in_is_mutable)
604 container test-list [
605 value:num
606 next:&:test-list
607 ]
608 def foo x:&:test-list/contained-in:result -> result:&:test-list [
609 local-scope
610 load-ingredients
611 result <- copy x
612 put *x, value:offset, 34
613 ]
614 $error: 0