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
"Delimiter">()); 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