1 //: Ingredients of a recipe are meant to be immutable unless they're also
  2 //: products. This layer will start enforcing this check.
  3 //:
  4 //: One hole for now: variables in surrounding spaces are implicitly mutable.
  5 //: [tag: todo]
  6 
  7 :(scenario can_modify_ingredients_that_are_also_products)
  8 # mutable container
  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 # mutable address to container
 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 # mutable address
 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  # ignore this 'p'
 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 # immutable address to primitive
 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 # immutable container
 91 def foo x:point-number [
 92   local-scope
 93   load-ingredients
 94   # copy an element: ok
 95   y:point <- get x, xy:offset
 96   # modify the element: boom
 97   # This could be ok if y contains no addresses, but we're not going to try to be that smart.
 98   # It also makes the rules easier to reason about. If it's just an ingredient, just don't try to change it.
 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   # modify the address, not the payload
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 ]
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
  python_default_defs 
  cpp_h_extension "h" cpp_src_extension "cpp" java_extension "java" php_extension "php" python_extension "py" idl_extension "idl"
  cpp_inline_dont_force_incl_in_h

  type_forms 15 // uml cpp java idl cpp_in cpp_out cpp_inout cpp_return
    "void" "void" "void" "void" "${type}" "${type} &" "${type}" "${type}"
    "any" "void *" "Object" "any" "const ${type}" "${type}" "${type} &" "${type}"
    "bool" "bool" "boolean" "boolean" "${type}" "${type} &" "${type} &" "${type}"
    "char" "char" "char" "char" "${type}" "${type} &" "${type} &" "${type}"
    "uchar" "unsigned char" "char" "octet" "${type}" "${type} &" "${type} &" "${type}"
    "byte" "unsigned char" "byte" "octet" "${type}" "${type} &" "${type} &" "${type}"
    "short" "short" "short" "short" "${type}" "${type} &" "${type} &" "${type}"
    "ushort" "unsigned short" "short" "unsigned short" "${type}" "${type} &" "${type} &" "${type}"
    "int" "int" "int" "long" "${type}" "${type} &" "${type} &" "${type}"
    "uint" "unsigned int" "int" "unsigned long" "${type}" "${type} &" "${type} &" "${type}"
    "long" "long" "long" "long" "${type}" "${type} &" "${type} &" "${type}"
    "ulong" "unsigned long" "long" "unsigned long" "${type}" "${type} &" "${type} &" "${type}"
    "float" "float" "float" "float" "${type}" "${type} &" "${type} &" "${type}"
    "double" "double" "double" "double" "${type}" "${type} &" "${type} &" "${type}"
    "string" "string" "String" "string" "${type}" "${type} &" "${type} &" "${type}"
  
  relations_stereotypes 5 // uml cpp java pythonidl
    "sequence" "vector" "Vector" "list" "sequence"
    "vector" "vector" "Vector" "list" "sequence"
    "list" "list" "List" "list" "sequence"
    "set" "set" "Set" "set" "sequence"
    "map" "map" "Map" "dict" "sequence"
  
  classes_stereotypes 14 // uml cpp java php python idl
    "class" "class" "class" "class" "class" "valuetype"
    "interface" "class" "interface" "interface" "class" "interface"
    "exception" "class" "class" "class" "class" "exception"
    "enum" "enum" "enum" "enum" "enum" "enum"
    "enum_pattern" "enum" "enum_pattern" "enum" "enum" "enum"
    "struct" "struct" "class" "class" "class" "struct"
    "union" "union" "class" "class" "class" "union"
    "typedef" "typedef" "ignored" "ignored" "ignored" "typedef"
    "boundary" "class" "class" "class" "class" "interface"
    "control" "class" "class" "class" "class" "valuetype"
    "entity" "class" "class" "class" "class" "valuetype"
    "actor" "ignored" "ignored" "ignored" "ignored" "ignored"
    "@interface" "ignored" "@interface" "ignored" "ignored" "ignored"
    "stereotype" "ignored" "ignored" "ignored" "ignored" "ignored"
  
  cpp_enum_default_type_forms "${type}" "${type} &" "${type} &" "${type}" // in out inout return
  other_cpp_types_default_type_forms "const ${type} &" "${type} &" "${type} &" "${type}" // in out inout return

  cpp_default_h_content "#ifndef ${NAMESPACE}_${NAME}_H
#define ${NAMESPACE}_${NAME}_H

${comment}
${includes}
${declarations}
${namespace_start}
${definition}
${namespace_end}
#endif
"
  cpp_default_src_content "${comment}
${includes}
${namespace_start}
${members}
${namespace_end}"
  cpp_default_class_decl "${comment}${template}class ${name}${inherit} {
${members}};
${inlines}
"
  cpp_default_external_class_decl "${name}
#include <${name}.h>
"
  cpp_default_struct_decl "${comment}${template}struct ${name}${inherit} {
${members}};
${inlines}
"
  cpp_default_union_decl "${comment}${template}union ${name} {
${members}};
${inlines}
"
  cpp_default_enum_decl "${comment}enum ${name} {
${items}
};
"
  cpp_default_typedef_decl "${comment}typedef ${type} ${name};
"
  cpp_default_attribute_declaration "    ${comment}${static}${mutable}${volatile}${const}${type} ${name}${value};
" // multiplicity 1
  "    ${comment}${static}${mutable}${volatile}${const}${stereotype}<${type}> ${name}${value};
" // multiplicity * a..b
  "    ${comment}${static}${mutable}${volatile}${const}${type} ${name}${multiplicity}${value};
" // multiplicity [..]
  cpp_default_enum_item_declaration "  ${name}${value},${comment}"
  cpp_association_aggregation_declaration
    "    ${comment}${static}${mutable}${volatile}${const}${type} * ${name}${value};
" // multiplicity 1
    "    ${comment}${static}${mutable}${volatile}${const}${stereotype}<${type} *> ${name}${value};
" // multiplicity * a..b
    "    ${comment}${static}${mutable}${volatile}${const}${type} * ${name}${multiplicity}${value};
" // multiplicity [..]
  cpp_aggregation_by_value_declaration
    "    ${comment}${static}${mutable}${volatile}${const}${type} ${name}${value};
" // multiplicity 1
    "    ${comment}${static}${mutable}${volatile}${const}${stereotype}<${type}> ${name}${value};
" // multiplicity * a..b
    "    ${comment}${static}${mutable}${volatile}${const}${type} ${name}${multiplicity}${value};
" // multiplicity [..]
  cpp_get "get_${name}" inline const value_const public
  cpp_set "set_${name}" public
  cpp_default_operation_declaration "    ${comment}${friend}${static}${inline}${virtual}${type} ${name}${(}${)}${const}${volatile}${throw}${abstract};
"
  cpp_default_operation_definition "${comment}${inline}${type} ${class}::${name}${(}${)}${const}${volatile}${throw}${staticnl}{
  ${body}}
"
  java_default_src_content "${comment}
${package}
${imports}
${definition}"
  java_default_class_decl "${comment}${@}${visibility}${final}${abstract}class ${name}${extends}${implements} {
${members}}
"
  java_default_external_class_decl "${name}"
  java_default_interface_decl "${comment}${@}${visibility}interface ${name}${extends} {
${members}}
"
  java5_default_enum_decl "${comment}${@}${visibility}${final}${abstract}enum ${name}${implements} {
${items};
${members}}
"
  java_default_enum_decl "${comment}${@}${visibility}final class ${name} {
${members}
  private final int value;

  public int value() {
    return value;
  }

  public static ${name} fromInt(int value) {
    switch (value) {
${cases}    default: throw new Error();
    }

  }
  private ${name}(int v) { value = v; };
}
"
  java_default_attribute_declaration "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${type} ${name}${value};
" // multiplicity 1
  "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${stereotype}<${type}> ${name}${value};
" // multiplicity * a..b
  "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${type}${multiplicity} ${name}${value};
" // multiplicity N
  java5_default_enum_item_declaration "  ${@}${name}${value},${comment}"
  java_default_enum_item_declaration "  ${comment}${@}public static final int _${name}${value};
public static final ${class} ${name} = new ${class}(_${name});
"
  java_default_enum_case "    case _${name}: return ${name};
"
  java_association_aggregation_declaration
    "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${type} ${name}${value};
" // multiplicity 1
    "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${stereotype}<${type}> ${name}${value};
" // multiplicity * a..b
    "  ${comment}${@}${visibility}${static}${final}${transient}${volatile}${type}${multiplicity} ${name}${value};
" // multiplicity N
  java_get "get${Name}" final public
  java_set "set${Name}" public
  java_default_operation_definition "  ${comment}${@}${visibility}${final}${static}${abstract}${synchronized}${type} ${name}${(}${)}${throws}${staticnl}{
  ${body}}
"
  php_default_src_content "<?php
${comment}
${definition}
?>
"
  php_default_class_decl "${comment}${final}${visibility}${abstract}class ${name}${extends}${implements} {
${members}}
"
  php_default_enum_decl "${comment}${visibility}final class ${name} {
${items}}
"
  php_default_external_class_decl "${name}"
  php_default_interface_decl "${comment}${visibility}interface ${name} {
${members}}
"
  php_default_attribute_declaration "  ${comment}${visibility}${const}${static}${var}${name}${value};
"
  php_default_enum_item_decl "  const ${name}${value};${comment}
"
  php_default_relation_declaration"  ${comment}${visibility}${const}${static}${var}${name}${value};
"
  php_get "get${Name}" final
  php_set "set${Name}"
  php_default_operation_definition "  ${comment}${final}${visibility}${abstract}${static}function ${name}${(}${)}
{
  ${body}}
"
  python_2_2
  python_indent_step "    "
  python_default_src_content "${comment}
${import}
${definition}"
  python_default_class_decl "class ${name}${inherit}:
${docstring}${members}
"
  python_default_enum_decl "class ${name}:
${docstring}${members}
"
  python_default_external_class_decl "${name}"
  python_default_attribute_declaration "${comment}${self}${name} = ${value}
" // multiplicity 1
  "${comment}${self}${name} = ${stereotype}()
" // multiplicity != 1
  python_default_enum_item_decl "${comment}${self}${name} = ${value}
"
  python_default_relation_declaration"${comment}${self}${name} = ${value}
" // multiplicity 1
  "${comment}${self}${name} = ${stereotype}()
" // multiplicity != 1
  python_default_composition_declaration"${comment}${self}${name} = ${type}()
" // multiplicity 1
  "${comment}${self}${name} = ${stereotype}()
" // multiplicity != 1
  python_default_operation_definition "${@}${static}${abstract}def ${name}${(}${)}:
${docstring}${body}
"
  python_default_initoperation_definition "${@}${static}${abstract}def ${name}${(}${p0}${v0}${)}:
${docstring}super(${class}, ${p0}).__init__()
${body}
"
  python_get "get${Name}"
  python_set "set${Name}"
  idl_default_src_content "#ifndef ${MODULE}_${NAME}_H
#define ${MODULE}_${NAME}_H

${comment}
${includes}
${module_start}
${definition}
${module_end}
#endif
"
  idl_default_interface_decl "${comment}${abstract}${local}interface ${name}${inherit} {
${members}};
"
  idl_default_valuetype_decl "${comment}${abstract}${custom}valuetype ${name}${inherit} {
${members}};
"
  idl_default_struct_decl "${comment}struct ${name} {
${members}};
"
  idl_default_typedef_decl "${comment}typedef ${type} ${name};
"
  idl_default_exception_decl "${comment}exception ${name} {
${members}};
"
  idl_default_union_decl "${comment}union ${name} switch(${switch}) {
${members}};
"
  idl_default_enum_decl "${comment}enum ${name} {
${items}};
"
  idl_default_external_class_decl "${name}
#include \"${name}.idl\"
"
  idl_default_attribute_declaration "  ${comment}${readonly}${attribute}${type} ${name};
" // multiplicity 1
  "  ${comment}${readonly}${attribute}${stereotype}<${type}> ${name};
" // multiplicity * a..b
  "  ${comment}${readonly}${attribute}${stereotype}<${type},${multiplicity}> ${name};
" // multiplicity N
  idl_default_valuetype_attribute_declaration "  ${comment}${visibility}${type} ${name};
" // multiplicity 1
  "  ${comment}${visibility}${stereotype}<${type}> ${name};
" // multiplicity * a..b
  "  ${comment}${visibility}${stereotype}<${type},${multiplicity}> ${name};
" // multiplicity N
  idl_default_const_declaration "  ${comment}const ${type} ${name}${value};
" // multiplicity 1
  "  ${comment}const ${stereotype}<${type}> ${name}${value};
" // multiplicity * a..b
  "  ${comment}const ${stereotype}<${type},${multiplicity}> ${name}${value};
" // multiplicity N
  idl_default_enum_item_declaration "  ${name},${comment}"
  idl_default_union_item_declaration "  ${comment}case ${case} : ${readonly}${type} ${name};" // multiplicity 1
  "  ${comment}case ${case} : ${readonly}${stereotype}<${type}> ${name};" // multiplicity * a..b
  "  ${comment}case ${case} : ${readonly}${stereotype}<${type},${multiplicity}> ${name};" // multiplicity N
  idl_association_aggregation_declaration
    "  ${comment}${readonly}${attribute}${type} ${name};
" // multiplicity 1
    "  ${comment}${readonly}${attribute}${stereotype}<${type}> ${name};
" // multiplicity * a..b
    "  ${comment}${readonly}${attribute}${stereotype}<${type},${multiplicity}> ${name};
" // multiplicity N
  idl_valuetype_association_aggregation_declaration
    "  ${comment}${visibility}${type} ${name};
" // multiplicity 1
    "  ${comment}${visibility}${stereotype}<${type}> ${name};
" // multiplicity * a..b
    "  ${comment}${visibility}${stereotype}<${type},${multiplicity}> ${name};
" // multiplicity N
  idl_union_association_aggregation_declaration
    "  ${comment}case ${case} : ${readonly}${type} ${name};" // multiplicity 1
    "  ${comment}case ${case} : ${readonly}${stereotype}<${type}> ${name};" // multiplicity * a..b
    "  ${comment}case ${case} : ${readonly}${stereotype}<${type},${multiplicity}> ${name};" // multiplicity N
  idl_get "get_${name}"
  idl_set "set_${name}"  twoways
  idl_default_operation_declaration "  ${comment}${oneway}${type} ${name}${(}${)}${raisesnl}${raises};
"
  uml_get_name uml uml_set_name uml
end
ter">(check_immutable_ingredients); // idempotent 339 340 :(code) 341 void check_immutable_ingredients(const recipe_ordinal r) { 342 // to ensure an address reagent isn't modified, it suffices to show that 343 // a) we never write to its contents directly, 344 // b) we never call 'put' or 'put-index' on it, and 345 // c) any non-primitive recipe calls in the body aren't returning it as a product 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; // skip check for old-style recipes calling next-ingredient directly 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; // not expected to be immutable 352 ¦ // End Immutable Ingredients Special-cases 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 ¦ // primitive recipe 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 ¦ ¦ ¦ // current_ingredient_indices can only have 0 or one value 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 ¦ // defined recipe 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; // optional immutable ingredient 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 [ # p is immutable 425 local-scope 426 load-ingredients 427 p2:&:test-list <- test-next p # p2 is immutable 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 // first check if the instruction is directly modifying something it shouldn't 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 // check if there's any indirect modification going on 452 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); 453 if (current_ingredient_indices.empty()) return; // ingredient not found in call 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 ¦ ¦ // primitive recipe 461 ¦ ¦ // we got here only because we got an instruction with an implicit product, and the instruction didn't explicitly spell it out 462 ¦ ¦ // put x, y:offset, z 463 ¦ ¦ // instead of 464 ¦ ¦ // x <- put x, y:offset, z 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 ¦ ¦ // defined recipe 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; // optional immutable ingredient 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 //: Sometimes you want to pass in two addresses, one pointing inside the 515 //: other. For example, you want to delete a node from a linked list. You 516 //: can't pass both pointers back out, because if a caller tries to make both 517 //: identical then you can't tell which value will be written on the way out. 518 //: 519 //: Experimental solution: just tell Mu that one points inside the other. 520 //: This way we can return just one pointer as high up as necessary to capture 521 //: all modifications performed by a recipe. 522 //: 523 //: We'll see if we end up wanting to abuse /contained-in for other reasons. 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 # can modify x 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