// // Do_Breakpoint_Throws: C // // A call to Do_Breakpoint_Throws does delegation to a hook in the host, which // (if registered) will generally start an interactive session for probing the // environment at the break. The `resume` native cooperates by being able to // give back a value (or give back code to run to produce a value) that the // call to breakpoint returns. // // RESUME has another feature, which is to be able to actually unwind and // simulate a return /AT a function *further up the stack*. (This may be // switched to a feature of a "STEP OUT" command at some point.) // REBOOL Do_Breakpoint_Throws( REBVAL *out, REBOOL interrupted, // Ctrl-C (as opposed to a BREAKPOINT) const REBVAL *default_value, REBOOL do_default ) { REBVAL *target = NONE_VALUE; REBVAL temp; VAL_INIT_WRITABLE_DEBUG(&temp); if (!PG_Breakpoint_Quitting_Hook) { // // Host did not register any breakpoint handler, so raise an error // about this as early as possible. // fail (Error(RE_HOST_NO_BREAKPOINT)); } // We call the breakpoint hook in a loop, in order to keep running if any // inadvertent FAILs or THROWs occur during the interactive session. // Only a conscious call of RESUME speaks the protocol to break the loop. // while (TRUE) { struct Reb_State state; REBCTX *error; push_trap: PUSH_TRAP(&error, &state); // The host may return a block of code to execute, but cannot // while evaluating do a THROW or a FAIL that causes an effective // "resumption". Halt is the exception, hence we PUSH_TRAP and // not PUSH_UNHALTABLE_TRAP. QUIT is also an exception, but a // desire to quit is indicated by the return value of the breakpoint // hook (which may or may not decide to request a quit based on the // QUIT command being run). // // The core doesn't want to get involved in presenting UI, so if // an error makes it here and wasn't trapped by the host first that // is a bug in the host. It should have done its own PUSH_TRAP. // if (error) { #if !defined(NDEBUG) REBVAL error_value; VAL_INIT_WRITABLE_DEBUG(&error_value); Val_Init_Error(&error_value, error); PROBE_MSG(&error_value, "Error not trapped during breakpoint:"); Panic_Array(CTX_VARLIST(error)); #endif // In release builds, if an error managed to leak out of the // host's breakpoint hook somehow...just re-push the trap state // and try it again. // goto push_trap; } // Call the host's breakpoint hook. // if (PG_Breakpoint_Quitting_Hook(&temp, interrupted)) { // // If a breakpoint hook returns TRUE that means it wants to quit. // The value should be the /WITH value (as in QUIT/WITH) // assert(!THROWN(&temp)); *out = *ROOT_QUIT_NATIVE; CONVERT_NAME_TO_THROWN(out, &temp, FALSE); return TRUE; // TRUE = threw } // If a breakpoint handler returns FALSE, then it should have passed // back a "resume instruction" triggered by a call like: // // resume/do [fail "This is how to fail from a breakpoint"] // // So now that the handler is done, we will allow any code handed back // to do whatever FAIL it likes vs. trapping that here in a loop. // DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); // Decode and process the "resume instruction" { struct Reb_Frame *frame; REBVAL *mode; REBVAL *payload; assert(IS_GROUP(&temp)); assert(VAL_LEN_HEAD(&temp) == RESUME_INST_MAX); mode = VAL_ARRAY_AT_HEAD(&temp, RESUME_INST_MODE); payload = VAL_ARRAY_AT_HEAD(&temp, RESUME_INST_PAYLOAD); target = VAL_ARRAY_AT_HEAD(&temp, RESUME_INST_TARGET); // The first thing we need to do is determine if the target we // want to return to has another breakpoint sandbox blocking // us. If so, what we need to do is actually retransmit the // resume instruction so it can break that wall, vs. transform // it into an EXIT/FROM that would just get intercepted. // if (!IS_NONE(target)) { #if !defined(NDEBUG) REBOOL found = FALSE; #endif for (frame = FS_TOP; frame != NULL; frame = frame->prior) { if (frame->mode != CALL_MODE_FUNCTION) continue; if ( frame != FS_TOP && FUNC_CLASS(frame->func) == FUNC_CLASS_NATIVE && ( FUNC_CODE(frame->func) == &N_pause || FUNC_CODE(frame->func) == &N_breakpoint ) ) { // We hit a breakpoint (that wasn't this call to // breakpoint, at the current FS_TOP) before finding // the sought after target. Retransmit the resume // instruction so that level will get it instead. // *out = *ROOT_RESUME_NATIVE; CONVERT_NAME_TO_THROWN(out, &temp, FALSE); return TRUE; // TRUE = thrown } if (IS_FRAME(target)) { if (NOT(frame->flags & DO_FLAG_FRAME_CONTEXT)) continue; if ( VAL_CONTEXT(target) == AS_CONTEXT(frame->data.context) ) { // Found a closure matching the target before we // reached a breakpoint, no need to retransmit. // #if !defined(NDEBUG) found = TRUE; #endif break; } } else { assert(IS_FUNCTION(target)); if (frame->flags & DO_FLAG_FRAME_CONTEXT) continue; if (VAL_FUNC(target) == frame->func) { // // Found a function matching the target before we // reached a breakpoint, no need to retransmit. // #if !defined(NDEBUG) found = TRUE; #endif break; } } } // RESUME should not have been willing to use a target that // is not on the stack. // #if !defined(NDEBUG) assert(found); #endif } if (IS_NONE(mode)) { // // If the resume instruction had no /DO or /WITH of its own, // then it doesn't override whatever the breakpoint provided // as a default. (If neither the breakpoint nor the resume // provided a /DO or a /WITH, result will be UNSET.) // goto return_default; // heeds `target` } assert(IS_LOGIC(mode)); if (VAL_LOGIC(mode)) { if (DO_VAL_ARRAY_AT_THROWS(&temp, payload)) { // // Throwing is not compatible with /AT currently. // if (!IS_NONE(target)) fail (Error_No_Catch_For_Throw(&temp)); // Just act as if the BREAKPOINT call itself threw // *out = temp; return TRUE; // TRUE = thrown } // Ordinary evaluation result... } else temp = *payload; } // The resume instruction will be GC'd. // goto return_temp; } DEAD_END; return_default: if (do_default) { if (DO_VAL_ARRAY_AT_THROWS(&temp, default_value)) { // // If the code throws, we're no longer in the sandbox...so we // bubble it up. Note that breakpoint runs this code at its // level... so even if you request a higher target, any throws // will be processed as if they originated at the BREAKPOINT // frame. To do otherwise would require the EXIT/FROM protocol // to add support for DO-ing at the receiving point. // *out = temp; return TRUE; // TRUE = thrown } } else temp = *default_value; // generally UNSET! if no /WITH return_temp: // The easy case is that we just want to return from breakpoint // directly, signaled by the target being NONE!. // if (IS_NONE(target)) { *out = temp; return FALSE; // FALSE = not thrown } // If the target is a function, then we're looking to simulate a return // from something up the stack. This uses the same mechanic as // definitional returns--a throw named by the function or closure frame. // // !!! There is a weak spot in definitional returns for FUNCTION! that // they can only return to the most recent invocation; which is a weak // spot of FUNCTION! in general with stack relative variables. Also, // natives do not currently respond to definitional returns...though // they can do so just as well as FUNCTION! can. // *out = *target; CONVERT_NAME_TO_THROWN(out, &temp, TRUE); return TRUE; // TRUE = thrown }
// // Panic_Core: C // // (va_list by pointer: http://stackoverflow.com/a/3369762/211160) // // Print a failure message and abort. The code adapts to several // different load stages of the system, and uses simpler ways to // report the error when the boot has not progressed enough to // use the more advanced modes. This allows the same interface // to be used for `panic Error_XXX(...)` and `fail (Error_XXX(...))`. // ATTRIBUTE_NO_RETURN void Panic_Core(REBCNT id, REBSER *maybe_frame, va_list *args) { char title[PANIC_TITLE_SIZE]; char message[PANIC_MESSAGE_SIZE]; title[0] = '\0'; message[0] = '\0'; if (maybe_frame) { assert(id == 0); id = ERR_NUM(maybe_frame); } // We are crashing, so a legitimate time to be disabling the garbage // collector. (It won't be turned back on.) GC_Disabled++; if (Reb_Opts && Reb_Opts->crash_dump) { Dump_Info(); Dump_Stack(0, 0); } strncat(title, "PANIC #", PANIC_TITLE_SIZE - 1); Form_Int(b_cast(title + strlen(title)), id); // !!! no bounding... strncat(message, Str_Panic_Directions, PANIC_MESSAGE_SIZE - 1); #if !defined(NDEBUG) // In debug builds, we may have the file and line number to report if // the call to Panic_Core originated from the `panic` macro. But we // will not if the panic is being called from a Make_Error call that // is earlier than errors can be made... if (TG_Erroring_C_File) { Form_Args( b_cast(message + strlen(message)), PANIC_MESSAGE_SIZE - 1 - strlen(message), "C Source File %s, Line %d\n", TG_Erroring_C_File, TG_Erroring_C_Line, NULL ); } #endif if (PG_Boot_Phase < BOOT_LOADED) { strncat(message, title, PANIC_MESSAGE_SIZE - 1); strncat( message, "\n** Boot Error: (string table not decompressed yet)", PANIC_MESSAGE_SIZE - 1 ); } else if (PG_Boot_Phase < BOOT_ERRORS && id < RE_INTERNAL_MAX) { // We are panic'ing on one of the errors that can occur during // boot (e.g. before Make_Error() be assured to run). So we use // the C string constant that was formed by %make-boot.r and // compressed in the boot block. // // Note: These strings currently do not allow arguments. const char *format = cs_cast(BOOT_STR(RS_ERROR, id - RE_INTERNAL_FIRST)); assert(args && !maybe_frame); strncat(message, "\n** Boot Error: ", PANIC_MESSAGE_SIZE - 1); Form_Args_Core( b_cast(message + strlen(message)), PANIC_MESSAGE_SIZE - 1 - strlen(message), format, args ); } else if (PG_Boot_Phase < BOOT_ERRORS && id >= RE_INTERNAL_MAX) { strncat(message, title, PANIC_MESSAGE_SIZE - 1); strncat( message, "\n** Boot Error: (error object table not initialized yet)", PANIC_MESSAGE_SIZE - 1 ); } else { // The system should be theoretically able to make and mold errors. // // !!! If you're trying to panic *during* error molding this // is obviously not going to not work. All errors pertaining to // molding errors should audited to be in the Boot: category. REBVAL error; if (maybe_frame) { assert(!args); Val_Init_Error(&error, maybe_frame); } else { // We aren't explicitly passed a Rebol ERROR! object, but we // consider it "safe" to make one since we're past BOOT_ERRORS Val_Init_Error(&error, Make_Error_Core(id, args)); } Form_Args( b_cast(message + strlen(message)), PANIC_MESSAGE_SIZE - 1 - strlen(message), "%v", &error, NULL ); } OS_CRASH(cb_cast(Str_Panic_Title), cb_cast(message)); // Note that since we crash, we never return so that the caller can run // a va_end on the passed-in args. This is illegal in the general case: // // http://stackoverflow.com/a/587139/211160 DEAD_END; }
// // RL_Start: C // // Evaluate the default boot function. // // Returns: // Zero on success, otherwise indicates an error occurred. // Arguments: // bin - optional startup code (compressed), can be null // len - length of above bin // flags - special flags // Notes: // This function completes the startup sequence by calling // the sys/start function. // RL_API int RL_Start(REBYTE *bin, REBINT len, REBYTE *script, REBINT script_len, REBCNT flags) { REBVAL *val; REBSER *ser; struct Reb_State state; REBCTX *error; int error_num; REBVAL result; VAL_INIT_WRITABLE_DEBUG(&result); if (bin) { ser = Decompress(bin, len, -1, FALSE, FALSE); if (!ser) return 1; val = CTX_VAR(Sys_Context, SYS_CTX_BOOT_HOST); Val_Init_Binary(val, ser); } if (script && script_len > 4) { /* a 4-byte long payload type at the beginning */ i32 ptype = 0; REBYTE *data = script + sizeof(ptype); script_len -= sizeof(ptype); memcpy(&ptype, script, sizeof(ptype)); if (ptype == 1) {/* COMPRESSed data */ ser = Decompress(data, script_len, -1, FALSE, FALSE); } else { ser = Make_Binary(script_len); if (ser == NULL) { OS_FREE(script); return 1; } memcpy(BIN_HEAD(ser), data, script_len); } OS_FREE(script); val = CTX_VAR(Sys_Context, SYS_CTX_BOOT_EMBEDDED); Val_Init_Binary(val, ser); } PUSH_UNHALTABLE_TRAP(&error, &state); // The first time through the following code 'error' will be NULL, but... // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! if (error) { // // !!! We are not allowed to ask for a print operation that can take // arbitrarily long without allowing for cancellation via Ctrl-C, // but here we are wanting to print an error. If you're printing // out an error and get a halt, it won't print the halt. // REBCTX *halt_error; // Save error for WHY? // REBVAL *last = Get_System(SYS_STATE, STATE_LAST_ERROR); Val_Init_Error(last, error); PUSH_UNHALTABLE_TRAP(&halt_error, &state); // The first time through the following code 'error' will be NULL, but... // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! if (halt_error) { assert(ERR_NUM(halt_error) == RE_HALT); return ERR_NUM(halt_error); } Print_Value(last, 1024, FALSE); DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); // !!! When running in a script, whether or not the Rebol interpreter // just exits in an error case with a bad error code or breaks you // into the console to debug the environment should be controlled by // a command line option. Defaulting to returning an error code // seems better, because kicking into an interactive session can // cause logging systems to hang. // For RE_HALT and all other errors we return the error // number. Error numbers are not set in stone (currently), but // are never zero...which is why we can use 0 for success. // return ERR_NUM(error); } if (Apply_Only_Throws( &result, Sys_Func(SYS_CTX_FINISH_RL_START), END_VALUE )) { #if !defined(NDEBUG) if (LEGACY(OPTIONS_EXIT_FUNCTIONS_ONLY)) fail (Error_No_Catch_For_Throw(&result)); #endif if ( IS_FUNCTION_AND(&result, FUNC_CLASS_NATIVE) && ( VAL_FUNC_CODE(&result) == &N_quit || VAL_FUNC_CODE(&result) == &N_exit ) ) { int status; CATCH_THROWN(&result, &result); status = Exit_Status_From_Value(&result); DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); Shutdown_Core(); OS_EXIT(status); DEAD_END; } fail (Error_No_Catch_For_Throw(&result)); } DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); // The convention in the API was to return 0 for success. We use the // convention (as for FINISH_INIT_CORE) that any non-UNSET! result from // FINISH_RL_START indicates something went wrong. if (IS_UNSET(&result)) error_num = 0; // no error else { assert(FALSE); // should not happen (raise an error instead) Debug_Fmt("** finish-rl-start returned non-NONE!:"); Debug_Fmt("%r", &result); error_num = RE_MISC; } return error_num; }
// // RL_Do_String: C // // Load a string and evaluate the resulting block. // // Returns: // The datatype of the result if a positive number (or 0 if the // type has no representation in the "RXT" API). An error code // if it's a negative number. Two negative numbers are reserved // for non-error conditions: -1 for halting (e.g. Escape), and // -2 is reserved for exiting with exit_status set. // // Arguments: // text - A null terminated UTF-8 (or ASCII) string to transcode // into a block and evaluate. // flags - set to zero for now // result - value returned from evaluation, if NULL then result // will be returned on the top of the stack // // Notes: // This API was from before Rebol's open sourcing and had little // vetting and few clients. The one client it did have was the // "sample" console code (which wound up being the "only" // console code for quite some time). // RL_API int RL_Do_String( int *exit_status, const REBYTE *text, REBCNT flags, RXIARG *out ) { REBARR *code; struct Reb_State state; REBCTX *error; REBVAL result; VAL_INIT_WRITABLE_DEBUG(&result); // assumes it can only be run at the topmost level where // the data stack is completely empty. // assert(DSP == 0); PUSH_UNHALTABLE_TRAP(&error, &state); // The first time through the following code 'error' will be NULL, but... // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! if (error) { // Save error for WHY? REBVAL *last = Get_System(SYS_STATE, STATE_LAST_ERROR); Val_Init_Error(last, error); if (ERR_NUM(error) == RE_HALT) return -1; // !!! Revisit hardcoded # if (out) Value_To_RXI(out, last); else DS_PUSH(last); return -ERR_NUM(error); } code = Scan_Source(text, LEN_BYTES(text)); PUSH_GUARD_ARRAY(code); // Bind into lib or user spaces? if (flags) { // Top words will be added to lib: Bind_Values_Set_Midstream_Shallow(ARR_HEAD(code), Lib_Context); Bind_Values_Deep(ARR_HEAD(code), Lib_Context); } else { REBCTX *user = VAL_CONTEXT(Get_System(SYS_CONTEXTS, CTX_USER)); REBVAL vali; VAL_INIT_WRITABLE_DEBUG(&vali); SET_INTEGER(&vali, CTX_LEN(user) + 1); Bind_Values_All_Deep(ARR_HEAD(code), user); Resolve_Context(user, Lib_Context, &vali, FALSE, FALSE); } if (Do_At_Throws(&result, code, 0)) { DROP_GUARD_ARRAY(code); if ( IS_FUNCTION_AND(&result, FUNC_CLASS_NATIVE) && ( VAL_FUNC_CODE(&result) == &N_quit || VAL_FUNC_CODE(&result) == &N_exit ) ) { CATCH_THROWN(&result, &result); DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); *exit_status = Exit_Status_From_Value(&result); return -2; // Revisit hardcoded # } fail (Error_No_Catch_For_Throw(&result)); } DROP_GUARD_ARRAY(code); DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); if (out) Value_To_RXI(out, &result); else DS_PUSH(&result); return Reb_To_RXT[VAL_TYPE_0(&result)]; }
// // Do_String() // // This is a version of a routine that was offered by the RL_Api, which has // been expanded here in order to permit the necessary customizations for // interesting REPL behavior w.r.t. binding, error handling, and response // to throws. // // !!! Now that this code has been moved into the host, the convoluted // integer-return-scheme can be eliminated and the code integrated more // clearly into the surrounding calls. // int Do_String( int *exit_status, REBVAL *out, const REBYTE *text, REBOOL at_breakpoint ) { struct Reb_State state; REBCTX *error; // Breakpoint REPLs are nested, and we may wish to jump out of them to // the topmost level via a HALT. However, all other errors need to be // confined, so that if one is doing evaluations during the pause of // a breakpoint an error doesn't "accidentally resume" by virtue of // jumping the stack out of the REPL. // // The topmost layer REPL, however, needs to catch halts in order to // keep control and not crash out. // if (at_breakpoint) PUSH_TRAP(&error, &state); else PUSH_UNHALTABLE_TRAP(&error, &state); // The first time through the following code 'error' will be NULL, but... // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! if (error) { // Save error for WHY? REBVAL *last = Get_System(SYS_STATE, STATE_LAST_ERROR); if (ERR_NUM(error) == RE_HALT) { assert(!at_breakpoint); return -1; // !!! Revisit hardcoded # } Val_Init_Error(out, error); *last = *out; return -cast(REBINT, ERR_NUM(error)); } REBARR *code = Scan_UTF8_Managed(text, LEN_BYTES(text)); // Where code ends up being bound when loaded at the REPL prompt should // be more generally configurable. (It may be, for instance, that one // wants to run something with it not bound at all.) Such choices // must come from this REPL host...not from the interpreter itself. { // First the scanned code is bound into the user context with a // fallback to the lib context. // // !!! This code is very old, and is how the REPL has bound since // R3-Alpha. It comes from RL_Do_String, but should receive a modern // review of why it's written exactly this way. // REBCTX *user_ctx = VAL_CONTEXT(Get_System(SYS_CONTEXTS, CTX_USER)); REBVAL vali; SET_INTEGER(&vali, CTX_LEN(user_ctx) + 1); Bind_Values_All_Deep(ARR_HEAD(code), user_ctx); Resolve_Context(user_ctx, Lib_Context, &vali, FALSE, FALSE); // If we're stopped at a breakpoint, the REPL should have a concept // of what stack level it is inspecting (conveyed by the |#|>> in the // prompt). This does a binding pass using the function for that // stack level, just the way a body is bound during Make_Function() // if (at_breakpoint) { REBVAL level; SET_INTEGER(&level, HG_Stack_Level); REBFRM *frame = Frame_For_Stack_Level(NULL, &level, FALSE); assert(frame); // Need to manage because it may be no words get bound into it, // and we're not putting it into a FRAME! value, so it might leak // otherwise if it's reified. // REBCTX *frame_ctx = Context_For_Frame_May_Reify_Managed(frame); Bind_Values_Deep(ARR_HEAD(code), frame_ctx); } // !!! This was unused code that used to be in Do_String from // RL_Api. It was an alternative path under `flags` which said // "Bind into lib or user spaces?" and then "Top words will be // added to lib". Is it relevant in any way? // /* Bind_Values_Set_Midstream_Shallow(ARR_HEAD(code), Lib_Context); Bind_Values_Deep(ARR_HEAD(code), Lib_Context); */ } if (Do_At_Throws(out, code, 0, SPECIFIED)) { // `code` will be GC protected if (at_breakpoint) { if ( IS_FUNCTION(out) && VAL_FUNC_DISPATCHER(out) == &N_resume ) { // // This means we're done with the embedded REPL. We want to // resume and may be returning a piece of code that will be // run by the finishing BREAKPOINT command in the target // environment. // // We'll never return a halt, so we reuse -1 (in this very // temporary scheme built on the very clunky historical REPL, // which will not last much longer...fingers crossed.) // DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); CATCH_THROWN(out, out); *exit_status = -1; return -1; } if ( IS_FUNCTION(out) && VAL_FUNC_DISPATCHER(out) == &N_quit ) { // // It would be frustrating if the system did not respond to // a QUIT and forced you to do `resume/with [quit]`. So // this is *not* caught, rather passed back up with the // special -2 status code. // DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); CATCH_THROWN(out, out); *exit_status = -2; return -2; } } else { // We are at the top level REPL, where we catch QUIT and for // now, also EXIT as meaning you want to leave. // if ( IS_FUNCTION(out) && ( VAL_FUNC_DISPATCHER(out) == &N_quit || VAL_FUNC_DISPATCHER(out) == &N_exit ) ) { DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); CATCH_THROWN(out, out); *exit_status = Exit_Status_From_Value(out); return -2; // Revisit hardcoded # } } fail (Error_No_Catch_For_Throw(out)); } DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); return 0; }