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