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 <- new-closure
320 b:&:num <- new number:type
321 run-closure b:&:num, a:space
322 ]
323 def new-closure [
324 new-default-space
325 x:&:num <- new number:type
326 return default-space
327 ]
328 def run-closure x:&:num, s:space [
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 :(scenarios transform)
413 :(scenario immutability_infects_contained_in_variables)
414 % Hide_errors = true;
415 container test-list [
416 value:num
417 next:&:test-list
418 ]
419 def main [
420 local-scope
421 p:&:test-list <- new test-list:type
422 foo p
423 ]
424 def foo p:&:test-list [
425 local-scope
426 load-ingredients
427 p2:&:test-list <- test-next p
428 *p2 <- put *p2, value:offset, 34
429 ]
430 def test-next x:&:test-list -> y:&:test-list/contained-in:x [
431 local-scope
432 load-ingredients
433 y <- get *x, next:offset
434 ]
435 +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
436
437 :(code)
438 void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
439
440 for (int i = 0; i < SIZE(inst.products); ++i) {
441 if (has_property(inst.products.at(i), "lookup")
442 && current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) {
443 string current_product_name = inst.products.at(i).name;
444 if (current_product_name == original_ingredient_name)
445 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << inst.original_string << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
446 else
447 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << inst.original_string << "' because that would modify " << original_ingredient_name << " which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
448 return;
449 }
450 }
451
452 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
453 if (current_ingredient_indices.empty()) return;
454 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
455 const int current_ingredient_index = *p;
456 reagent current_ingredient = inst.ingredients.at(current_ingredient_index);
457 canonize_type(current_ingredient);
458 const string& current_ingredient_name = current_ingredient.name;
459 if (!contains_key(Recipe, inst.operation)) {
460
461
462
463
464
465 if (inst.operation == PUT || inst.operation == PUT_INDEX) {
466 if (current_ingredient_index == 0) {
467 if (current_ingredient_name == original_ingredient_name)
468 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << inst.original_string << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
469 else
470 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << inst.original_string << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
471 }
472 }
473 }
474 else {
475
476 if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) {
477 if (current_ingredient_name == original_ingredient_name)
478 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << inst.original_string << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end();
479 else
480 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << inst.original_string << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end();
481 }
482 }
483 }
484 }
485
486 bool is_modified_in_recipe(const recipe_ordinal r, const int ingredient_index, const recipe& caller) {
487 const recipe& callee = get(Recipe, r);
488 if (!callee.has_header) {
489 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();
490 return true;
491 }
492 if (ingredient_index >= SIZE(callee.ingredients)) return false;
493 return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name);
494 }
495
496 bool is_present_in_products(const recipe& callee, const string& ingredient_name) {
497 for (int i = 0; i < SIZE(callee.products); ++i) {
498 if (callee.products.at(i).name == ingredient_name)
499 return true;
500 }
501 return false;
502 }
503
504 set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) {
505 set<int> result;
506 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
507 if (is_literal(inst.ingredients.at(i))) continue;
508 if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end())
509 result.insert(i);
510 }
511 return result;
512 }
513
514
515
516
517
518
519
520
521
522
523
524
525 :(scenarios transform)
526 :(scenario can_modify_contained_in_addresses)
527 container test-list [
528 value:num
529 next:&:test-list
530 ]
531 def main [
532 local-scope
533 p:&:test-list <- new test-list:type
534 foo p
535 ]
536 def foo p:&:test-list -> p:&:test-list [
537 local-scope
538 load-ingredients
539 p2:&:test-list <- test-next p
540 p <- test-remove p2, p
541 ]
542 def test-next x:&:test-list -> y:&:test-list [
543 local-scope
544 load-ingredients
545 y <- get *x, next:offset
546 ]
547 def test-remove x:&:test-list/contained-in:from, from:&:test-list -> from:&:test-list [
548 local-scope
549 load-ingredients
550 *x <- put *x, value:offset, 34
551 ]
552 $error: 0
553
554 :(before "End Immutable Ingredients Special-cases")
555 if (has_property(current_ingredient, "contained-in")) {
556 const string_tree* tmp = property(current_ingredient, "contained-in");
557 if (!tmp->atom
558 || (!is_present_in_ingredients(caller, tmp->value)
559 && !is_present_in_products(caller, tmp->value))) {
560 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();
561 }
562 continue;
563 }
564
565 :(scenario contained_in_check)
566 container test-list [
567 value:num
568 next:&:test-list
569 ]
570 def test-remove x:&:test-list/contained-in:result, from:&:test-list -> result:&:test-list [
571 local-scope
572 load-ingredients
573 result <- copy 0
574 ]
575 $error: 0