diff options
Diffstat (limited to 'archive/2.vm/101run_sandboxed.cc')
-rw-r--r-- | archive/2.vm/101run_sandboxed.cc | 711 |
1 files changed, 0 insertions, 711 deletions
diff --git a/archive/2.vm/101run_sandboxed.cc b/archive/2.vm/101run_sandboxed.cc deleted file mode 100644 index e464bbe3..00000000 --- a/archive/2.vm/101run_sandboxed.cc +++ /dev/null @@ -1,711 +0,0 @@ -//: 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" - ); -} |