// // Assert_No_Relative: C // // Check to make sure there are no relative values in an array, maybe deeply. // // !!! What if you have an ANY-ARRAY! inside your array at a position N, // but there is a relative value in the VAL_ARRAY() of that value at an // index earlier than N? This currently considers that an error since it // checks the whole array...which is more conservative (asserts on more // cases). But should there be a flag to ask to honor the index? // void Assert_No_Relative(REBARR *array, REBOOL deep) { RELVAL *item = ARR_HEAD(array); while (NOT_END(item)) { if (IS_RELATIVE(item)) { Debug_Fmt("Array contained relative item and wasn't supposed to."); PROBE_MSG(item, "relative item"); Panic_Array(array); } if (!IS_VOID_OR_SAFE_TRASH(item) && ANY_ARRAY(item) && deep) Assert_No_Relative(VAL_ARRAY(item), deep); ++item; } }
// // Panic_Value_Debug: C // // This is a debug-only "error generator", which will hunt through all the // series allocations and panic on the series that contains the value (if // it can find it). This will allow those using Address Sanitizer or // Valgrind to know a bit more about where the value came from. // // Additionally, if it happens to be a void or trash, LOGIC!, BAR!, or NONE! // it will dump out where the initialization happened if that information // was stored. // ATTRIBUTE_NO_RETURN void Panic_Value_Debug( const RELVAL *v, const char *file, int line ) { REBSER *containing = Try_Find_Containing_Series_Debug(v); printf("PANIC VALUE called from %s:%d\n", file, line); fflush(stdout); switch (VAL_TYPE_RAW(v)) { case REB_MAX_VOID: case REB_BLANK: case REB_LOGIC: case REB_BAR: printf( "REBVAL init on tick #%d at %s:%d\n", cast(unsigned int, v->extra.do_count), v->payload.track.filename, v->payload.track.line ); fflush(stdout); break; } printf("Kind=%d\n", cast(int, VAL_TYPE_RAW(v))); fflush(stdout); if (containing) { printf("Containing series for value pointer found, panicking it:\n"); fflush(stdout); Panic_Series(containing); } printf("No containing series for value...panicking to make stack dump:\n"); fflush(stdout); Panic_Array(EMPTY_ARRAY); }
// // 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 }