diff options
author | Kartik Agaram <vc@akkartik.com> | 2019-07-27 16:01:55 -0700 |
---|---|---|
committer | Kartik Agaram <vc@akkartik.com> | 2019-07-27 17:47:59 -0700 |
commit | 6e1eeeebfb453fa7c871869c19375ce60fbd7413 (patch) | |
tree | 539c4a3fdf1756ae79770d5c4aaf6366f1d1525e /archive/2.vm/101run_sandboxed.cc | |
parent | 8846a7f85cc04b77b2fe8a67b6d317723437b00c (diff) | |
download | mu-6e1eeeebfb453fa7c871869c19375ce60fbd7413.tar.gz |
5485 - promote SubX to top-level
Diffstat (limited to 'archive/2.vm/101run_sandboxed.cc')
-rw-r--r-- | archive/2.vm/101run_sandboxed.cc | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/archive/2.vm/101run_sandboxed.cc b/archive/2.vm/101run_sandboxed.cc new file mode 100644 index 00000000..e464bbe3 --- /dev/null +++ b/archive/2.vm/101run_sandboxed.cc @@ -0,0 +1,711 @@ +//: Helper for various programming environments: run arbitrary Mu code and +//: return some result in text form. + +void test_run_interactive_code() { + run( + "def main [\n" + " 1:num <- copy 0\n" // reserve space for the sandbox + " 10:text <- new [1:num/raw <- copy 34]\n" +//? " $print 10:num [|] 11:num [: ] 1000:num [|] *10:text [ (] 10:text [)] 10/newline\n" + " run-sandboxed 10:text\n" + " 20:num <- copy 1:num\n" + "]\n" + ); + CHECK_TRACE_CONTENTS( + "mem: storing 34 in location 20\n" + ); +} + +void test_run_interactive_empty() { + run( + "def main [\n" + " 10:text <- copy null\n" + " 20:text <- run-sandboxed 10:text\n" + "]\n" + ); + // result is null + CHECK_TRACE_CONTENTS( + "mem: storing 0 in location 20\n" + "mem: storing 0 in location 21\n" + ); +} + +//: As the name suggests, 'run-sandboxed' will prevent certain operations that +//: regular Mu code can perform. +:(before "End Globals") +bool Sandbox_mode = false; +//: for starters, users can't override 'main' when the environment is running +:(before "End Load Recipe Name") +if (Sandbox_mode && result.name == "main") { + slurp_balanced_bracket(in); + return -1; +} + +//: run code in 'interactive mode', i.e. with errors off and return: +//: stringified output in case we want to print it to screen +//: any errors encountered +//: simulated screen any prints went to +//: any 'app' layer traces generated +:(before "End Primitive Recipe Declarations") +RUN_SANDBOXED, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "run-sandboxed", RUN_SANDBOXED); +:(before "End Primitive Recipe Checks") +case RUN_SANDBOXED: { + if (SIZE(inst.ingredients) != 1) { + raise << maybe(get(Recipe, r).name) << "'run-sandboxed' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_text(inst.ingredients.at(0))) { + 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(); + break; + } + break; +} +:(before "End Primitive Recipe Implementations") +case RUN_SANDBOXED: { + bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(/*skip alloc id*/1)); + if (!new_code_pushed_to_stack) { + products.resize(5); + products.at(0).push_back(/*alloc id*/0); + products.at(0).push_back(0); + products.at(1).push_back(/*alloc id*/0); + products.at(1).push_back(trace_error_contents()); + products.at(2).push_back(/*alloc id*/0); + products.at(2).push_back(0); + products.at(3).push_back(/*alloc id*/0); + products.at(3).push_back(trace_app_contents()); + products.at(4).push_back(1); // completed + run_code_end(); + break; // done with this instruction + } + else { + continue; // not done with caller; don't increment current_step_index() + } +} + +//: To show results in the sandbox Mu uses a hack: it saves the products +//: returned by each instruction while Track_most_recent_products is true, and +//: keeps the most recent such result around so that it can be returned as the +//: result of a sandbox. + +:(before "End Globals") +bool Track_most_recent_products = false; +int Call_depth_to_track_most_recent_products_at = 0; +string Most_recent_products; +:(before "End Reset") +Track_most_recent_products = false; +Call_depth_to_track_most_recent_products_at = 0; +Most_recent_products = ""; + +:(before "End Globals") +trace_stream* Save_trace_stream = NULL; +string Save_trace_file; +int Save_callstack_depth = 0; +:(code) +// reads a string, tries to call it as code (treating it as a test), saving +// all errors. +// returns true if successfully called (no errors found during load and transform) +bool run_interactive(int address) { +//? cerr << "run_interactive: " << address << '\n'; + assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0); + // try to sandbox the run as best you can + // todo: test this + if (!Current_scenario) { + for (int i = 1; i < Reserved_for_tests; ++i) + Memory.erase(i); + } + string command = trim(strip_comments(read_mu_text(address))); +//? cerr << "command: " << command << '\n'; + Name[get(Recipe_ordinal, "interactive")].clear(); + run_code_begin(/*should_stash_snapshots*/true); + if (command.empty()) return false; + // don't kill the current routine on parse errors + routine* save_current_routine = Current_routine; + Current_routine = NULL; + // call run(string) but without the scheduling + load(string("recipe! interactive [\n") + + "local-scope\n" + + "screen:&:screen <- next-ingredient\n" + + "$start-tracking-products\n" + + command + "\n" + + "$stop-tracking-products\n" + + "return screen\n" + + "]\n"); + transform_all(); + Current_routine = save_current_routine; + if (trace_count("error") > 0) return false; + // now call 'sandbox' which will run 'interactive' in a separate routine, + // and wait for it + if (Save_trace_stream) { + ++Save_callstack_depth; + trace(Save_callstack_depth+1, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_callstack_depth << end(); + assert(Save_callstack_depth < Max_depth); + } + Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox"))); + return true; +} + +//: Carefully update all state to exactly how it was -- including snapshots. + +:(before "End Globals") +bool Run_profiler_stash = false; +map<string, recipe_ordinal> Recipe_ordinal_snapshot_stash; +map<recipe_ordinal, recipe> Recipe_snapshot_stash; +map<string, type_ordinal> Type_ordinal_snapshot_stash; +map<type_ordinal, type_info> Type_snapshot_stash; +map<recipe_ordinal, map<string, int> > Name_snapshot_stash; +map<string, vector<recipe_ordinal> > Recipe_variants_snapshot_stash; +map<string, type_tree*> Type_abbreviations_snapshot_stash; +vector<scenario> Scenarios_snapshot_stash; +set<string> Scenario_names_snapshot_stash; + +:(code) +void run_code_begin(bool should_stash_snapshots) { + // stuff to undo later, in run_code_end() + Hide_errors = true; + Disable_redefine_checks = true; + Run_profiler_stash = Run_profiler; + Run_profiler = false; + if (should_stash_snapshots) + stash_snapshots(); + Save_trace_stream = Trace_stream; + Save_callstack_depth = Callstack_depth; + Callstack_depth = Initial_callstack_depth; + Trace_stream = new trace_stream; + if (Save_trace_stream) + Trace_stream->collect_depth = Save_trace_stream->collect_depth; +} + +void run_code_end() { + Hide_errors = false; + Disable_redefine_checks = false; + Run_profiler = Run_profiler_stash; + Run_profiler_stash = false; +//? ofstream fout("sandbox.log"); +//? fout << Trace_stream->readable_contents(""); +//? fout.close(); + delete Trace_stream; + Trace_stream = Save_trace_stream; + Callstack_depth = Save_callstack_depth; + Save_trace_stream = NULL; + Save_trace_file.clear(); + Save_callstack_depth = 0; + Recipe.erase(get(Recipe_ordinal, "interactive")); // keep past sandboxes from inserting errors + if (!Recipe_snapshot_stash.empty()) + unstash_snapshots(); +} + +// keep sync'd with save_snapshots and restore_snapshots +void stash_snapshots() { + assert(Recipe_ordinal_snapshot_stash.empty()); + Recipe_ordinal_snapshot_stash = Recipe_ordinal_snapshot; + assert(Recipe_snapshot_stash.empty()); + Recipe_snapshot_stash = Recipe_snapshot; + assert(Type_ordinal_snapshot_stash.empty()); + Type_ordinal_snapshot_stash = Type_ordinal_snapshot; + assert(Type_snapshot_stash.empty()); + Type_snapshot_stash = Type_snapshot; + assert(Name_snapshot_stash.empty()); + Name_snapshot_stash = Name_snapshot; + assert(Recipe_variants_snapshot_stash.empty()); + Recipe_variants_snapshot_stash = Recipe_variants_snapshot; + assert(Type_abbreviations_snapshot_stash.empty()); + Type_abbreviations_snapshot_stash = Type_abbreviations_snapshot; + assert(Scenarios_snapshot_stash.empty()); + Scenarios_snapshot_stash = Scenarios_snapshot; + assert(Scenario_names_snapshot_stash.empty()); + Scenario_names_snapshot_stash = Scenario_names_snapshot; + save_snapshots(); +} +void unstash_snapshots() { + restore_snapshots(); + Recipe_ordinal_snapshot = Recipe_ordinal_snapshot_stash; Recipe_ordinal_snapshot_stash.clear(); + Recipe_snapshot = Recipe_snapshot_stash; Recipe_snapshot_stash.clear(); + Type_ordinal_snapshot = Type_ordinal_snapshot_stash; Type_ordinal_snapshot_stash.clear(); + Type_snapshot = Type_snapshot_stash; Type_snapshot_stash.clear(); + Name_snapshot = Name_snapshot_stash; Name_snapshot_stash.clear(); + Recipe_variants_snapshot = Recipe_variants_snapshot_stash; Recipe_variants_snapshot_stash.clear(); + Type_abbreviations_snapshot = Type_abbreviations_snapshot_stash; Type_abbreviations_snapshot_stash.clear(); + Scenarios_snapshot = Scenarios_snapshot_stash; Scenarios_snapshot_stash.clear(); + Scenario_names_snapshot = Scenario_names_snapshot_stash; Scenario_names_snapshot_stash.clear(); +} + +:(before "End Mu Prelude") +load(string( +"recipe interactive [\n") + // just a dummy version to initialize the Recipe_ordinal and so on +"]\n" + +"recipe sandbox [\n" + + "local-scope\n" + +//? "$print [aaa] 10/newline\n" + + "screen:&:screen <- new-fake-screen 30, 5\n" + + "routine-id:num <- start-running interactive, screen\n" + + "limit-time routine-id, 100000/instructions\n" + + "wait-for-routine routine-id\n" + +//? "$print [bbb] 10/newline\n" + + "instructions-run:num <- number-of-instructions routine-id\n" + + "stash instructions-run [instructions run]\n" + + "sandbox-state:num <- routine-state routine-id\n" + + "completed?:bool <- equal sandbox-state, 1/completed\n" + +//? "$print [completed: ] completed? 10/newline\n" + + "output:text <- $most-recent-products\n" + +//? "$print [zzz] 10/newline\n" + +//? "$print output\n" + + "errors:text <- save-errors\n" + + "stashes:text <- save-app-trace\n" + + "$cleanup-run-sandboxed\n" + + "return output, errors, screen, stashes, completed?\n" + +"]\n"); + +//: adjust errors in the sandbox +:(before "End maybe(recipe_name) Special-cases") +if (recipe_name == "interactive") return ""; + +:(code) +void test_run_interactive_comments() { + run( + "def main [\n" + " 1:text <- new [# ab\n" + "add 2, 2]\n" + " 2:text <- run-sandboxed 1:text\n" + " 3:@:char <- copy *2:text\n" + "]\n" + ); + CHECK_TRACE_CONTENTS( + "mem: storing 52 in location 4\n" + ); +} + +:(before "End Primitive Recipe Declarations") +_START_TRACKING_PRODUCTS, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS); +:(before "End Primitive Recipe Checks") +case _START_TRACKING_PRODUCTS: { + break; +} +:(before "End Primitive Recipe Implementations") +case _START_TRACKING_PRODUCTS: { + Track_most_recent_products = true; + Call_depth_to_track_most_recent_products_at = SIZE(Current_routine->calls); + break; +} + +:(before "End Primitive Recipe Declarations") +_STOP_TRACKING_PRODUCTS, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS); +:(before "End Primitive Recipe Checks") +case _STOP_TRACKING_PRODUCTS: { + break; +} +:(before "End Primitive Recipe Implementations") +case _STOP_TRACKING_PRODUCTS: { + Track_most_recent_products = false; + break; +} + +:(before "End Primitive Recipe Declarations") +_MOST_RECENT_PRODUCTS, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS); +:(before "End Primitive Recipe Checks") +case _MOST_RECENT_PRODUCTS: { + break; +} +:(before "End Primitive Recipe Implementations") +case _MOST_RECENT_PRODUCTS: { + products.resize(1); + products.at(0).push_back(/*alloc id*/0); + products.at(0).push_back(new_mu_text(Most_recent_products)); + break; +} + +:(before "End Primitive Recipe Declarations") +SAVE_ERRORS, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "save-errors", SAVE_ERRORS); +:(before "End Primitive Recipe Checks") +case SAVE_ERRORS: { + break; +} +:(before "End Primitive Recipe Implementations") +case SAVE_ERRORS: { + products.resize(1); + products.at(0).push_back(/*alloc id*/0); + products.at(0).push_back(trace_error_contents()); + break; +} + +:(before "End Primitive Recipe Declarations") +SAVE_APP_TRACE, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE); +:(before "End Primitive Recipe Checks") +case SAVE_APP_TRACE: { + break; +} +:(before "End Primitive Recipe Implementations") +case SAVE_APP_TRACE: { + products.resize(1); + products.at(0).push_back(/*alloc id*/0); + products.at(0).push_back(trace_app_contents()); + break; +} + +:(before "End Primitive Recipe Declarations") +_CLEANUP_RUN_SANDBOXED, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "$cleanup-run-sandboxed", _CLEANUP_RUN_SANDBOXED); +:(before "End Primitive Recipe Checks") +case _CLEANUP_RUN_SANDBOXED: { + break; +} +:(before "End Primitive Recipe Implementations") +case _CLEANUP_RUN_SANDBOXED: { + run_code_end(); + break; +} + +:(code) +void test_run_interactive_converts_result_to_text() { + // try to interactively add 2 and 2 + run( + "def main [\n" + " 10:text <- new [add 2, 2]\n" + " 20:text <- run-sandboxed 10:text\n" + " 30:@:char <- copy *20:text\n" + "]\n" + ); + // first letter in the output should be '4' in utf-8 + CHECK_TRACE_CONTENTS( + "mem: storing 52 in location 31\n" + ); +} + +void test_run_interactive_ignores_products_in_nested_functions() { + run( + "def main [\n" + " 10:text <- new [foo]\n" + " 20:text <- run-sandboxed 10:text\n" + " 30:@:char <- copy *20:text\n" + "]\n" + "def foo [\n" + " 40:num <- copy 1234\n" + " {\n" + " break\n" + " reply 5678\n" + " }\n" + "]\n" + ); + // no product should have been tracked + CHECK_TRACE_CONTENTS( + "mem: storing 0 in location 30\n" + ); +} + +void test_run_interactive_ignores_products_in_previous_instructions() { + run( + "def main [\n" + " 10:text <- new [\n" + " add 1, 1\n" // generates a product + " foo]\n" // no products + " 20:text <- run-sandboxed 10:text\n" + " 30:@:char <- copy *20:text\n" + "]\n" + "def foo [\n" + " 40:num <- copy 1234\n" + " {\n" + " break\n" + " reply 5678\n" + " }\n" + "]\n" + ); + // no product should have been tracked + CHECK_TRACE_CONTENTS( + "mem: storing 0 in location 30\n" + ); +} + +void test_run_interactive_remembers_products_before_final_label() { + run( + "def main [\n" + " 10:text <- new [\n" + " add 1, 1\n" // generates a product + " +foo]\n" // no products + " 20:text <- run-sandboxed 10:text\n" + " 30:@:char <- copy *20:text\n" + "]\n" + "def foo [\n" + " 40:num <- copy 1234\n" + " {\n" + " break\n" + " reply 5678\n" + " }\n" + "]\n" + ); + // product tracked + CHECK_TRACE_CONTENTS( + "mem: storing 50 in location 31\n" + ); +} + +void test_run_interactive_returns_text() { + // try to interactively add 2 and 2 + run( + "def main [\n" + " 1:text <- new [\n" + " x:text <- new [a]\n" + " y:text <- new [b]\n" + " z:text <- append x:text, y:text\n" + " ]\n" + " 10:text <- run-sandboxed 1:text\n" +//? " $print 10:text 10/newline\n" + " 20:@:char <- copy *10:text\n" + "]\n" + ); + // output contains "ab" + CHECK_TRACE_CONTENTS( + "mem: storing 97 in location 21\n" + "mem: storing 98 in location 22\n" + ); +} + +void test_run_interactive_returns_errors() { + run( + "def main [\n" + // run a command that generates an error + " 10:text <- new [x:num <- copy 34\n" + "get x:num, foo:offset]\n" + " 20:text, 30:text <- run-sandboxed 10:text\n" + " 40:@:char <- copy *30:text\n" + "]\n" + ); + // error should be "unknown element foo in container number" + CHECK_TRACE_CONTENTS( + "mem: storing 117 in location 41\n" + "mem: storing 110 in location 42\n" + "mem: storing 107 in location 43\n" + "mem: storing 110 in location 44\n" + // ... + ); +} + +void test_run_interactive_with_comment() { + run( + "def main [\n" + // 2 instructions, with a comment after the first + " 10:text <- new [a:num <- copy 0 # abc\n" + "b:num <- copy 0\n" + "]\n" + " 20:text, 30:text <- run-sandboxed 10:text\n" + "]\n" + ); + // no errors + // skip alloc id + CHECK_TRACE_CONTENTS( + "mem: storing 0 in location 30\n" + "mem: storing 0 in location 31\n" + ); +} + +:(after "Running One Instruction") +if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at + && !current_instruction().is_label + && current_instruction().name != "$stop-tracking-products") { + Most_recent_products = ""; +} +:(before "End Running One Instruction") +if (Track_most_recent_products && SIZE(Current_routine->calls) == Call_depth_to_track_most_recent_products_at) { + Most_recent_products = track_most_recent_products(current_instruction(), products); +//? cerr << "most recent products: " << Most_recent_products << '\n'; +} +:(code) +string track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) { + ostringstream out; + for (int i = 0; i < SIZE(products); ++i) { + // A sandbox can print a string result, but only if it is actually saved + // to a variable in the sandbox, because otherwise the results are + // reclaimed before the sandbox sees them. So you get these interactions + // in the sandbox: + // + // new [abc] + // => <address> + // + // x:text <- new [abc] + // => abc + if (i < SIZE(instruction.products)) { + if (is_mu_text(instruction.products.at(i))) { + if (SIZE(products.at(i)) != 2) continue; // weak silent check for address + out << read_mu_text(products.at(i).at(/*skip alloc id*/1)) << '\n'; + continue; + } + } + for (int j = 0; j < SIZE(products.at(i)); ++j) + out << no_scientific(products.at(i).at(j)) << ' '; + out << '\n'; + } + return out.str(); +} + +:(code) +string strip_comments(string in) { + ostringstream result; + for (int i = 0; i < SIZE(in); ++i) { + if (in.at(i) != '#') { + result << in.at(i); + } + else { + while (i+1 < SIZE(in) && in.at(i+1) != '\n') + ++i; + } + } + return result.str(); +} + +int stringified_value_of_location(int address) { + // convert to string + ostringstream out; + out << no_scientific(get_or_insert(Memory, address)); + return new_mu_text(out.str()); +} + +int trace_error_contents() { + if (!Trace_stream) return 0; + ostringstream out; + for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { + if (p->label != "error") continue; + out << p->contents; + if (*--p->contents.end() != '\n') out << '\n'; + } + string result = out.str(); + truncate(result); + if (result.empty()) return 0; + return new_mu_text(result); +} + +int trace_app_contents() { + if (!Trace_stream) return 0; + ostringstream out; + for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { + if (p->depth != App_depth) continue; + out << p->contents; + if (*--p->contents.end() != '\n') out << '\n'; + } + string result = out.str(); + if (result.empty()) return 0; + truncate(result); + return new_mu_text(result); +} + +void truncate(string& x) { + if (SIZE(x) > 1024) { + x.erase(1024); + *x.rbegin() = '\n'; + *++x.rbegin() = '.'; + *++++x.rbegin() = '.'; + } +} + +//: simpler version of run-sandboxed: doesn't do any running, just loads +//: recipes and reports errors. + +:(before "End Primitive Recipe Declarations") +RELOAD, +:(before "End Primitive Recipe Numbers") +put(Recipe_ordinal, "reload", RELOAD); +:(before "End Primitive Recipe Checks") +case RELOAD: { + if (SIZE(inst.ingredients) != 1) { + raise << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end(); + break; + } + if (!is_mu_text(inst.ingredients.at(0))) { + raise << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); + break; + } + break; +} +:(before "End Primitive Recipe Implementations") +case RELOAD: { + restore_non_recipe_snapshots(); + string code = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1)); + run_code_begin(/*should_stash_snapshots*/false); + routine* save_current_routine = Current_routine; + Current_routine = NULL; + Sandbox_mode = true; + vector<recipe_ordinal> recipes_reloaded = load(code); + transform_all(); + Trace_stream->newline(); // flush trace + Sandbox_mode = false; + Current_routine = save_current_routine; + products.resize(1); + products.at(0).push_back(/*alloc id*/0); + products.at(0).push_back(trace_error_contents()); + run_code_end(); // wait until we're done with the trace contents + break; +} + +:(code) +void test_reload_loads_function_definitions() { + run( + "def main [\n" + " local-scope\n" + " x:text <- new [recipe foo [\n" + " 1:num/raw <- copy 34\n" + " ]]\n" + " reload x\n" + " run-sandboxed [foo]\n" + " 2:num/raw <- copy 1:num/raw\n" + "]\n" + ); + CHECK_TRACE_CONTENTS( + "mem: storing 34 in location 2\n" + ); +} + +void test_reload_continues_past_error() { + run( + "def main [\n" + " local-scope\n" + " x:text <- new [recipe foo [\n" + " get 1234:num, foo:offset\n" + " ]]\n" + " reload x\n" + " 1:num/raw <- copy 34\n" + "]\n" + ); + CHECK_TRACE_CONTENTS( + "mem: storing 34 in location 1\n" + ); +} + +void test_reload_can_repeatedly_load_container_definitions() { + // define a container and try to create it (merge requires knowing container size) + run( + "def main [\n" + " local-scope\n" + " x:text <- new [\n" + " container foo [\n" + " x:num\n" + " y:num\n" + " ]\n" + " recipe bar [\n" + " local-scope\n" + " x:foo <- merge 34, 35\n" + " ]\n" + " ]\n" + // save warning addresses in locations of type 'number' to avoid + // spurious changes to them due to 'abandon' + " 10:text/raw <- reload x\n" + " 20:text/raw <- reload x\n" + "]\n" + ); + // no errors on either load + CHECK_TRACE_CONTENTS( + "mem: storing 0 in location 10\n" + "mem: storing 0 in location 11\n" + "mem: storing 0 in location 20\n" + "mem: storing 0 in location 21\n" + ); +} |