https://github.com/akkartik/mu/blob/master/101run_sandboxed.cc

      1 //: Helper for various programming environments: run arbitrary Mu code and
      2 //: return some result in text form.
      3
      4 :(scenario run_interactive_code)
      5 def main [
      6   1:num <- copy 0  # reserve space for the sandbox
      7   10:text <- new [1:num/raw <- copy 34]
      8 #?   $print 10:num [|] 11:num [: ] 1000:num [|] *10:text [ (] 10:text [)] 10/newline
      9   run-sandboxed 10:text
     10   20:num <- copy 1:num
     11 ]
     12 +mem: storing 34 in location 20
     13
     14 :(scenario run_interactive_empty)
     15 def main [
     16   10:text <- copy null
     17   20:text <- run-sandboxed 10:text
     18 ]
     19 # result is null
     20 +mem: storing 0 in location 20
     21 +mem: storing 0 in location 21
     22
     23 //: As the name suggests, 'run-sandboxed' will prevent certain operations that
     24 //: regular Mu code can perform.
     25 :(before "End Globals")
     26 bool Sandbox_mode = false;
     27 //: for starters, users can't override 'main' when the environment is running
     28 :(before "End Load Recipe Name")
     29 if (Sandbox_mode && result.name == "main") {
     30   slurp_balanced_bracket(in);
     31   return -1;
     32 }
     33
     34 //: run code in 'interactive mode', i.e. with errors off and return:
     35 //:   stringified output in case we want to print it to screen
     36 //:   any errors encountered
     37 //:   simulated screen any prints went to
     38 //:   any 'app' layer traces generated
     39 :(before "End Primitive Recipe Declarations")
     40 RUN_SANDBOXED,
     41 :(before "End Primitive Recipe Numbers")
     42 put(Recipe_ordinal, "run-sandboxed", RUN_SANDBOXED);
     43 :(before "End Primitive Recipe Checks")
     44 case RUN_SANDBOXED: {
     45   if (SIZE(inst.ingredients) != 1) {
     46     raise << maybe(get(Recipe, r).name) << "'run-sandboxed' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
     47     break;
     48   }
     49   if (!is_mu_text(inst.ingredients.at(0))) {
     50     raise << maybe(get(Recipe, r).name) << "first ingredient of 'run-sandboxed' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     51     break;
     52   }
     53   break;
     54 }
     55 :(before "End Primitive Recipe Implementations")
     56 case RUN_SANDBOXED: {
     57   bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1));
     58   if (!new_code_pushed_to_stack) {
     59     products.resize(5);
     60     products.at(0).push_back(/*alloc id*/0);
     61     products.at(0).push_back(0);
     62     products.at(1).push_back(/*alloc id*/0);
     63     products.at(1).push_back(trace_error_contents());
     64     products.at(2).push_back(/*alloc id*/0);
     65     products.at(2).push_back(0);
     66     products.at(3).push_back(/*alloc id*/0);
     67     products.at(3).push_back(trace_app_contents());
     68     products.at(4).push_back(1);  // completed
     69     run_code_end();
     70     break;  // done with this instruction
     71   }
     72   else {
     73     continue;  // not done with caller; don't increment current_step_index()
     74   }
     75 }
     76
     77 //: To show results in the sandbox Mu uses a hack: it saves the products
     78 //: returned by each instruction while Track_most_recent_products is true, and
     79 //: keeps the most recent such result around so that it can be returned as the
     80 //: result of a sandbox.
     81
     82 :(before "End Globals")
     83 bool Track_most_recent_products = false;
     84 int Call_depth_to_track_most_recent_products_at = 0;
     85 string Most_recent_products;
     86 :(before "End Reset")
     87 Track_most_recent_products = false;
     88 Call_depth_to_track_most_recent_products_at = 0;
     89 Most_recent_products = "";
     90
     91 :(before "End Globals")
     92 trace_stream* Save_trace_stream = NULL;
     93 string Save_trace_file;
     94 :(code)
     95 // reads a string, tries to call it as code (treating it as a test), saving
     96 // all errors.
     97 // returns true if successfully called (no errors found during load and transform)
     98 bool run_interactive(int address) {
     99 //?   cerr << "run_interactive: " << address << '\n';
    100   assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
    101   // try to sandbox the run as best you can
    102   // todo: test this
    103   if (!Current_scenario) {
    104     for (int i = 1; i < Reserved_for_tests; ++i)
    105       Memory.erase(i);
    106   }
    107   string command = trim(strip_comments(read_mu_text(address)));
    108 //?   cerr << "command: " << command << '\n';
    109   Name[get(Recipe_ordinal, "interactive")].clear();
    110   run_code_begin(/*should_stash_snapshots*/true);
    111   if (command.empty()) return false;
    112   // don't kill the current routine on parse errors
    113   routine* save_current_routine = Current_routine;
    114   Current_routine = NULL;
    115   // call run(string) but without the scheduling
    116   load(string("recipe! interactive [\n") +
    117           "local-scope\n" +
    118           "screen:&:screen <- next-ingredient\n" +
    119           "$start-tracking-products\n" +
    120           command + "\n" +
    121           "$stop-tracking-products\n" +
    122           "return screen\n" +
    123        "]\n");
    124   transform_all();
    125   Current_routine = save_current_routine;
    126   if (trace_count("error") > 0) return false;
    127   // now call 'sandbox' which will run 'interactive' in a separate routine,
    128   // and wait for it
    129   if (Save_trace_stream) {
    130     ++Save_trace_stream->callstack_depth;
    131     trace(9999, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
    132     assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
    133   }
    134   Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
    135   return true;
    136 }
    137
    138 //: Carefully update all state to exactly how it was -- including snapshots.
    139
    140 :(before "End Globals")
    141 bool Run_profiler_stash = false;
    142 map<string, recipe_ordinal> Recipe_ordinal_snapshot_stash;
    143 map<recipe_ordinal, recipe> Recipe_snapshot_stash;
    144 map<string, type_ordinal> Type_ordinal_snapshot_stash;
    145 map<type_ordinal, type_info> Type_snapshot_stash;
    146 map<recipe_ordinal, map<string, int> > Name_snapshot_stash;
    147 map<string, vector<recipe_ordinal> > Recipe_variants_snapshot_stash;
    148 map<string, type_tree*> Type_abbreviations_snapshot_stash;
    149 vector<scenario> Scenarios_snapshot_stash;
    150 set<string> Scenario_names_snapshot_stash;
    151
    152 :(code)
    153 void run_code_begin(bool should_stash_snapshots) {
    154   // stuff to undo later, in run_code_end()
    155   Hide_errors = true;
    156   Disable_redefine_checks = true;
    157   Run_profiler_stash = Run_profiler;
    158   Run_profiler = false;
    159   if (should_stash_snapshots)
    160     stash_snapshots();
    161   Save_trace_stream = Trace_stream;
    162   Trace_stream = new trace_stream;
    163   Trace_stream->collect_depth = App_depth;
    164 }
    165
    166 void run_code_end() {
    167   Hide_errors = false;
    168   Disable_redefine_checks = false;
    169   Run_profiler = Run_profiler_stash;
    170   Run_profiler_stash = false;
    171 //?   ofstream fout("sandbox.log");
    172 //?   fout << Trace_stream->readable_contents("");
    173 //?   fout.close();
    174   delete Trace_stream;
    175   Trace_stream = Save_trace_stream;
    176   Save_trace_stream = NULL;
    177   Save_trace_file.clear();
    178   Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
    179   if (!Recipe_snapshot_stash.empty())
    180     unstash_snapshots();
    181 }
    182
    183 // keep sync'd with save_snapshots and restore_snapshots
    184 void stash_snapshots() {
    185   assert(Recipe_ordinal_snapshot_stash.empty());
    186   Recipe_ordinal_snapshot_stash = Recipe_ordinal_snapshot;
    187   assert(Recipe_snapshot_stash.empty());
    188   Recipe_snapshot_stash = Recipe_snapshot;
    189   assert(Type_ordinal_snapshot_stash.empty());
    190   Type_ordinal_snapshot_stash = Type_ordinal_snapshot;
    191   assert(Type_snapshot_stash.empty());
    192   Type_snapshot_stash = Type_snapshot;
    193   assert(Name_snapshot_stash.empty());
    194   Name_snapshot_stash = Name_snapshot;
    195   assert(Recipe_variants_snapshot_stash.empty());
    196   Recipe_variants_snapshot_stash = Recipe_variants_snapshot;
    197   assert(Type_abbreviations_snapshot_stash.empty());
    198   Type_abbreviations_snapshot_stash = Type_abbreviations_snapshot;
    199   assert(Scenarios_snapshot_stash.empty());
    200   Scenarios_snapshot_stash = Scenarios_snapshot;
    201   assert(Scenario_names_snapshot_stash.empty());
    202   Scenario_names_snapshot_stash = Scenario_names_snapshot;
    203   save_snapshots();
    204 }
    205 void unstash_snapshots() {
    206   restore_snapshots();
    207   Recipe_ordinal_snapshot = Recipe_ordinal_snapshot_stash;  Recipe_ordinal_snapshot_stash.clear();
    208   Recipe_snapshot = Recipe_snapshot_stash;  Recipe_snapshot_stash.clear();
    209   Type_ordinal_snapshot = Type_ordinal_snapshot_stash;  Type_ordinal_snapshot_stash.clear();
    210   Type_snapshot = Type_snapshot_stash;  Type_snapshot_stash.clear();
    211   Name_snapshot = Name_snapshot_stash;  Name_snapshot_stash.clear();
    212   Recipe_variants_snapshot = Recipe_variants_snapshot_stash;  Recipe_variants_snapshot_stash.clear();
    213   Type_abbreviations_snapshot = Type_abbreviations_snapshot_stash;  Type_abbreviations_snapshot_stash.clear();
    214   Scenarios_snapshot = Scenarios_snapshot_stash;  Scenarios_snapshot_stash.clear();
    215   Scenario_names_snapshot = Scenario_names_snapshot_stash;  Scenario_names_snapshot_stash.clear();
    216 }
    217
    218 :(before "End Mu Prelude")
    219 load(string(
    220 "recipe interactive [\n") +  // just a dummy version to initialize the Recipe_ordinal and so on
    221 "]\n" +
    222 "recipe sandbox [\n" +
    223   "local-scope\n" +
    224 //?   "$print [aaa] 10/newline\n" +
    225   "screen:&:screen <- new-fake-screen 30, 5\n" +
    226   "routine-id:num <- start-running interactive, screen\n" +
    227   "limit-time routine-id, 100000/instructions\n" +
    228   "wait-for-routine routine-id\n" +
    229 //?   "$print [bbb] 10/newline\n" +
    230   "instructions-run:num <- number-of-instructions routine-id\n" +
    231   "stash instructions-run [instructions run]\n" +
    232   "sandbox-state:num <- routine-state routine-id\n" +
    233   "completed?:bool <- equal sandbox-state, 1/completed\n" +
    234 //?   "$print [completed: ] completed? 10/newline\n" +
    235   "output:text <- $most-recent-products\n" +
    236 //?   "$print [zzz] 10/newline\n" +
    237 //?   "$print output\n" +
    238   "errors:text <- save-errors\n" +
    239   "stashes:text <- save-app-trace\n" +
    240   "$cleanup-run-sandboxed\n" +
    241   "return output, errors, screen, stashes, completed?\n" +
    242 "]\n");
    243
    244 //: adjust errors in the sandbox
    245 :(before "End maybe(recipe_name) Special-cases")
    246 if (recipe_name == "interactive") return "";
    247
    248 :(scenario run_interactive_comments)
    249 def main [
    250   1:text <- new [# ab
    251 add 2, 2]
    252   2:text <- run-sandboxed 1:text
    253   3:@:char <- copy *2:text
    254 ]
    255 +mem: storing 52 in location 4
    256
    257 :(before "End Primitive Recipe Declarations")
    258 _START_TRACKING_PRODUCTS,
    259 :(before "End Primitive Recipe Numbers")
    260 put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
    261 :(before "End Primitive Recipe Checks")
    262 case _START_TRACKING_PRODUCTS: {
    263   break;
    264 }
    265 :(before "End Primitive Recipe Implementations")
    266 case _START_TRACKING_PRODUCTS: {
    267   Track_most_recent_products = true;
    268   Call_depth_to_track_most_recent_products_at = SIZE(Current_routine->calls);
    269   break;
    270 }
    271
    272 :(before "End Primitive Recipe Declarations")
    273 _STOP_TRACKING_PRODUCTS,
    274 :(before "End Primitive Recipe Numbers")
    275 put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
    276 :(before "End Primitive Recipe Checks")
    277 case _STOP_TRACKING_PRODUCTS: {
    278   break;
    279 }
    280 :(before "End Primitive Recipe Implementations")
    281 case _STOP_TRACKING_PRODUCTS: {
    282   Track_most_recent_products = false;
    283   break;
    284 }
    285
    286 :(before "End Primitive Recipe Declarations")
    287 _MOST_RECENT_PRODUCTS,
    288 :(before "End Primitive Recipe Numbers")
    289 put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
    290 :(before "End Primitive Recipe Checks")
    291 case _MOST_RECENT_PRODUCTS: {
    292   break;
    293 }
    294 :(before "End Primitive Recipe Implementations")
    295 case _MOST_RECENT_PRODUCTS: {
    296   products.resize(1);
    297   products.at(0).push_back(/*alloc id*/0);
    298   products.at(0).push_back(new_mu_text(Most_recent_products));
    299   break;
    300 }
    301
    302 :(before "End Primitive Recipe Declarations")
    303 SAVE_ERRORS,
    304 :(before "End Primitive Recipe Numbers")
    305 put(Recipe_ordinal, "save-errors", SAVE_ERRORS);
    306 :(before "End Primitive Recipe Checks")
    307 case SAVE_ERRORS: {
    308   break;
    309 }
    310 :(before "End Primitive Recipe Implementations")
    311 case SAVE_ERRORS: {
    312   products.resize(1);
    313   products.at(0).push_back(/*alloc id*/0);
    314   products.at(0).push_back(trace_error_contents());
    315   break;
    316 }
    317
    318 :(before "End Primitive Recipe Declarations")
    319 SAVE_APP_TRACE,
    320 :(before "End Primitive Recipe Numbers")
    321 put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
    322 :(before "End Primitive Recipe Checks")
    323 case SAVE_APP_TRACE: {
    324   break;
    325 }
    326 :(before "End Primitive Recipe Implementations")
    327 case SAVE_APP_TRACE: {
    328   products.resize(1);
    329   products.at(0).push_back(/*alloc id*/0);
    330   products.at(0).push_back(trace_app_contents());
    331   break;
    332 }
    333
    334 :(before "End Primitive Recipe Declarations")
    335 _CLEANUP_RUN_SANDBOXED,
    336 :(before "End Primitive Recipe Numbers")
    337 put(Recipe_ordinal, "$cleanup-run-sandboxed", _CLEANUP_RUN_SANDBOXED);
    338 :(before "End Primitive Recipe Checks")
    339 case _CLEANUP_RUN_SANDBOXED: {
    340   break;
    341 }
    342 :(before "End Primitive Recipe Implementations")
    343 case _CLEANUP_RUN_SANDBOXED: {
    344   run_code_end();
    345   break;
    346 }
    347
    348 :(scenario "run_interactive_converts_result_to_text")
    349 def main [
    350   # try to interactively add 2 and 2
    351   10:text <- new [add 2, 2]
    352   20:text <- run-sandboxed 10:text
    353   30:@:char <- copy *20:text
    354 ]
    355 # first letter in the output should be '4' in unicode
    356 +mem: storing 52 in location 31
    357
    358 :(scenario "run_interactive_ignores_products_in_nested_functions")
    359 def main [
    360   10:text <- new [foo]
    361   20:text <- run-sandboxed 10:text
    362   30:@:char <- copy *20:text
    363 ]
    364 def foo [
    365   40:num <- copy 1234
    366   {
    367     break
    368     reply 5678
    369   }
    370 ]
    371 # no product should have been tracked
    372 +mem: storing 0 in location 30
    373
    374 :(scenario "run_interactive_ignores_products_in_previous_instructions")
    375 def main [
    376   10:text <- new [
    377     add 1, 1  # generates a product
    378     foo]  # no products
    379   20:text <- run-sandboxed 10:text
    380   30:@:char <- copy *20:text
    381 ]
    382 def foo [
    383   40:num <- copy 1234
    384   {
    385     break
    386     reply 5678
    387   }
    388 ]
    389 # no product should have been tracked
    390 +mem: storing 0 in location 30
    391
    392 :(scenario "run_interactive_remembers_products_before_final_label")
    393 def main [
    394   10:text <- new [
    395     add 1, 1  # generates a product
    396     +foo]  # no products
    397   20:text <- run-sandboxed 10:text
    398   30:@:char <- copy *20:text
    399 ]
    400 def foo [
    401   40:num <- copy 1234
    402   {
    403     break
    404     reply 5678
    405   }
    406 ]
    407 # product tracked
    408 +mem: storing 50 in location 31
    409
    410 :(scenario "run_interactive_returns_text")
    411 def main [
    412   # try to interactively add 2 and 2
    413   1:text <- new [
    414     x:text <- new [a]
    415     y:text <- new [b]
    416     z:text <- append x:text, y:text
    417   ]
    418   10:text <- run-sandboxed 1:text
    419 #?   $print 10:text 10/newline
    420   20:@:char <- copy *10:text
    421 ]
    422 # output contains "ab"
    423 +mem: storing 97 in location 21
    424 +mem: storing 98 in location 22
    425
    426 :(scenario "run_interactive_returns_errors")
    427 def main [
    428   # run a command that generates an error
    429   10:text <- new [x:num <- copy 34
    430 get x:num, foo:offset]
    431   20:text, 30:text <- run-sandboxed 10:text
    432   40:@:char <- copy *30:text
    433 ]
    434 # error should be "unknown element foo in container number"
    435 +mem: storing 117 in location 41
    436 +mem: storing 110 in location 42
    437 +mem: storing 107 in location 43
    438 +mem: storing 110 in location 44
    439 # ...
    440
    441 :(scenario run_interactive_with_comment)
    442 def main [
    443   # 2 instructions, with a comment after the first
    444   10:text <- new [a:num <- copy 0  # abc
    445 b:num <- copy 0
    446 ]
    447   20:text, 30:text <- run-sandboxed 10:text
    448 ]
    449 # no errors
    450 # skip alloc id
    451 +mem: storing 0 in location 30
    452 +mem: storing 0 in location 31
    453
    454 :(after "Running One Instruction")
    455 if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at
    456     && !current_instruction().is_label
    457     && current_instruction().name != "$stop-tracking-products") {
    458   Most_recent_products = "";
    459 }
    460 :(before "End Running One Instruction")
    461 if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at) {
    462   Most_recent_products = track_most_recent_products(current_instruction(), products);
    463 //?   cerr << "most recent products: " << Most_recent_products << '\n';
    464 }
    465 :(code)
    466 string track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
    467   ostringstream out;
    468   for (int i = 0; i < SIZE(products); ++i) {
    469     // A sandbox can print a string result, but only if it is actually saved
    470     // to a variable in the sandbox, because otherwise the results are
    471     // reclaimed before the sandbox sees them. So you get these interactions
    472     // in the sandbox:
    473     //
    474     //    new [abc]
    475     //    => <address>
    476     //
    477     //    x:text <- new [abc]
    478     //    => abc
    479     if (i < SIZE(instruction.products)) {
    480       if (is_mu_text(instruction.products.at(i))) {
    481         if (SIZE(products.at(i)) != 2) continue;  // weak silent check for address
    482         out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n';
    483         continue;
    484       }
    485     }
    486     for (int j = 0; j < SIZE(products.at(i)); ++j)
    487       out << no_scientific(products.at(i).at(j)) << ' ';
    488     out << '\n';
    489   }
    490   return out.str();
    491 }
    492
    493 :(code)
    494 string strip_comments(string in) {
    495   ostringstream result;
    496   for (int i = 0; i < SIZE(in); ++i) {
    497     if (in.at(i) != '#') {
    498       result << in.at(i);
    499     }
    500     else {
    501       while (i+1 < SIZE(in) && in.at(i+1) != '\n')
    502         ++i;
    503     }
    504   }
    505   return result.str();
    506 }
    507
    508 int stringified_value_of_location(int address) {
    509   // convert to string
    510   ostringstream out;
    511   out << no_scientific(get_or_insert(Memory, address));
    512   return new_mu_text(out.str());
    513 }
    514
    515 int trace_error_contents() {
    516   if (!Trace_stream) return 0;
    517   ostringstream out;
    518   for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
    519     if (p->label != "error") continue;
    520     out << p->contents;
    521     if (*--p->contents.end() != '\n') out << '\n';
    522   }
    523   string result = out.str();
    524   truncate(result);
    525   if (result.empty()) return 0;
    526   return new_mu_text(result);
    527 }
    528
    529 int trace_app_contents() {
    530   if (!Trace_stream) return 0;
    531   ostringstream out;
    532   for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
    533     if (p->depth != App_depth) continue;
    534     out << p->contents;
    535     if (*--p->contents.end() != '\n') out << '\n';
    536   }
    537   string result = out.str();
    538   if (result.empty()) return 0;
    539   truncate(result);
    540   return new_mu_text(result);
    541 }
    542
    543 void truncate(string& x) {
    544   if (SIZE(x) > 1024) {
    545     x.erase(1024);
    546     *x.rbegin() = '\n';
    547     *++x.rbegin() = '.';
    548     *++++x.rbegin() = '.';
    549   }
    550 }
    551
    552 //: simpler version of run-sandboxed: doesn't do any running, just loads
    553 //: recipes and reports errors.
    554
    555 :(before "End Primitive Recipe Declarations")
    556 RELOAD,
    557 :(before "End Primitive Recipe Numbers")
    558 put(Recipe_ordinal, "reload", RELOAD);
    559 :(before "End Primitive Recipe Checks")
    560 case RELOAD: {
    561   if (SIZE(inst.ingredients) != 1) {
    562     raise << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
    563     break;
    564   }
    565   if (!is_mu_text(inst.ingredients.at(0))) {
    566     raise << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
    567     break;
    568   }
    569   break;
    570 }
    571 :(before "End Primitive Recipe Implementations")
    572 case RELOAD: {
    573   restore_non_recipe_snapshots();
    574   string code = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
    575   run_code_begin(/*should_stash_snapshots*/false);
    576   routine* save_current_routine = Current_routine;
    577   Current_routine = NULL;
    578   Sandbox_mode = true;
    579   vector<recipe_ordinal> recipes_reloaded = load(code);
    580   transform_all();
    581   Trace_stream->newline();  // flush trace
    582   Sandbox_mode = false;
    583   Current_routine = save_current_routine;
    584   products.resize(1);
    585   products.at(0).push_back(/*alloc id*/0);
    586   products.at(0).push_back(trace_error_contents());
    587   run_code_end();  // wait until we're done with the trace contents
    588   break;
    589 }
    590
    591 :(scenario reload_loads_function_definitions)
    592 def main [
    593   local-scope
    594   x:text <- new [recipe foo [
    595     1:num/raw <- copy 34
    596   ]]
    597   reload x
    598   run-sandboxed [foo]
    599   2:num/raw <- copy 1:num/raw
    600 ]
    601 +mem: storing 34 in location 2
    602
    603 :(scenario reload_continues_past_error)
    604 def main [
    605   local-scope
    606   x:text <- new [recipe foo [
    607     get 1234:num, foo:offset
    608   ]]
    609   reload x
    610   1:num/raw <- copy 34
    611 ]
    612 +mem: storing 34 in location 1
    613
    614 :(scenario reload_can_repeatedly_load_container_definitions)
    615 # define a container and try to create it (merge requires knowing container size)
    616 def main [
    617   local-scope
    618   x:text <- new [
    619     container foo [
    620       x:num
    621       y:num
    622     ]
    623     recipe bar [
    624       local-scope
    625       x:foo <- merge 34, 35
    626     ]
    627   ]
    628   # save warning addresses in locations of type 'number' to avoid spurious changes to them due to 'abandon'
    629   10:text/raw <- reload x
    630   20:text/raw <- reload x
    631 ]
    632 # no errors on either load
    633 +mem: storing 0 in location 10
    634 +mem: storing 0 in location 11
    635 +mem: storing 0 in location 20
    636 +mem: storing 0 in location 21