diff --git a/packages/debugger/lib/controller/sagas/index.js b/packages/debugger/lib/controller/sagas/index.js index 522c3f6600e..2e7e80bb12c 100644 --- a/packages/debugger/lib/controller/sagas/index.js +++ b/packages/debugger/lib/controller/sagas/index.js @@ -78,9 +78,12 @@ function* advance(action) { */ function* stepNext() { const starting = yield select(controller.current.location); + const isStartingGenerated = yield select( + controller.current.isAnyFrameGenerated + ); const allowInternal = yield select(controller.stepIntoInternalSources); - let upcoming, finished; + let upcoming, finished, isUpcomingGenerated; do { // advance at least once step @@ -88,6 +91,7 @@ function* stepNext() { // and check the next source range upcoming = yield select(controller.current.location); + isUpcomingGenerated = yield select(controller.current.isAnyFrameGenerated); finished = yield select(controller.current.trace.finished); @@ -98,8 +102,8 @@ function* stepNext() { //don't stop on an internal source unless allowInternal is on or //we started in an internal source (!allowInternal && - upcoming.source.internal && - !starting.source.internal) || + (upcoming.source.internal || isUpcomingGenerated) && + !(starting.source.internal || isStartingGenerated)) || upcoming.sourceRange.length === 0 || upcoming.source.id === undefined || (upcoming.node && isDeliberatelySkippedNodeType(upcoming.node)) || diff --git a/packages/debugger/lib/controller/selectors/index.js b/packages/debugger/lib/controller/selectors/index.js index a8f8552311d..7a591673fc6 100644 --- a/packages/debugger/lib/controller/selectors/index.js +++ b/packages/debugger/lib/controller/selectors/index.js @@ -7,6 +7,7 @@ import * as Codec from "@truffle/codec"; import evm from "lib/evm/selectors"; import sourcemapping from "lib/sourcemapping/selectors"; +import stacktrace from "lib/stacktrace/selectors"; import data from "lib/data/selectors"; import trace from "lib/trace/selectors"; @@ -137,6 +138,24 @@ const controller = createSelectorTree({ ) }, + /** + * controller.current.callstack + */ + callstack: createLeaf([stacktrace.current.callstack.preupdated], identity), + + /** + * controller.current.isAnyFrameGenerated + * + * This selector checks whether there are any generated sources + * stackframes on the callstack. We should regard ourselves + * as still inside a generated source until there are none. + * (We only consider generated sources here, not other sorts of internal + * sources, to prevent potential problems.) + */ + isAnyFrameGenerated: createLeaf(["./callstack"], callstack => + callstack.some(frame => frame.sourceIsGenerated) + ), + /** * controller.current.trace */ diff --git a/packages/debugger/lib/stacktrace/actions/index.js b/packages/debugger/lib/stacktrace/actions/index.js index 4e88bff61fb..d9dd0c47f3c 100644 --- a/packages/debugger/lib/stacktrace/actions/index.js +++ b/packages/debugger/lib/stacktrace/actions/index.js @@ -1,11 +1,18 @@ export const JUMP_IN = "STACKTRACE_JUMP_IN"; -export function jumpIn(location, functionNode, contractNode, sourceIsInternal) { +export function jumpIn( + location, + functionNode, + contractNode, + sourceIsInternal, + sourceIsGenerated +) { return { type: JUMP_IN, location, functionNode, contractNode, - sourceIsInternal + sourceIsInternal, + sourceIsGenerated }; } diff --git a/packages/debugger/lib/stacktrace/reducers.js b/packages/debugger/lib/stacktrace/reducers.js index 540f1b50c0c..8db98c85fd0 100644 --- a/packages/debugger/lib/stacktrace/reducers.js +++ b/packages/debugger/lib/stacktrace/reducers.js @@ -10,7 +10,13 @@ function callstack(state = [], action) { let newFrame; switch (action.type) { case actions.JUMP_IN: - const { location, functionNode, contractNode, sourceIsInternal } = action; + const { + location, + functionNode, + contractNode, + sourceIsInternal, + sourceIsGenerated + } = action; newFrame = { type: "internal", calledFromLocation: location, @@ -27,7 +33,8 @@ function callstack(state = [], action) { ? contractNode.name : undefined, combineWithNextInternal: false, - sourceIsInternal + sourceIsInternal, + sourceIsGenerated //note we don't currently account for getters because currently //we can't; fallback, receive, constructors, & modifiers also remain //unaccounted for at present diff --git a/packages/debugger/lib/stacktrace/sagas/index.js b/packages/debugger/lib/stacktrace/sagas/index.js index 6a37752f5c9..6a866b650a0 100644 --- a/packages/debugger/lib/stacktrace/sagas/index.js +++ b/packages/debugger/lib/stacktrace/sagas/index.js @@ -63,12 +63,16 @@ function* stacktraceSaga() { const nextLocation = yield select(stacktrace.next.location); const nextParent = yield select(stacktrace.next.contractNode); const nextSourceIsInternal = yield select(stacktrace.next.sourceIsInternal); + const nextSourceIsGenerated = yield select( + stacktrace.next.sourceIsGenerated + ); yield put( actions.jumpIn( currentLocation, nextLocation.node, nextParent, - nextSourceIsInternal + nextSourceIsInternal, + nextSourceIsGenerated ) ); positionUpdated = true; diff --git a/packages/debugger/lib/stacktrace/selectors/index.js b/packages/debugger/lib/stacktrace/selectors/index.js index b7746af6faf..bf11ae9a8bb 100644 --- a/packages/debugger/lib/stacktrace/selectors/index.js +++ b/packages/debugger/lib/stacktrace/selectors/index.js @@ -111,6 +111,15 @@ function createMultistepSelectors(stepSelector) { source => source.id === undefined || source.internal ), + /** + * .sourceIsGenerated + * only specifically generated sources, not unmapped code or anything! + */ + sourceIsGenerated: createLeaf( + ["./location/source"], + source => source.internal + ), + /** * .strippedLocation */ @@ -156,9 +165,31 @@ let stacktrace = createSelectorTree({ */ current: { /** - * stacktrace.current.callstack + * stacktrace.current.callstack (namespace) */ - callstack: createLeaf(["/state"], state => state.proc.callstack), + callstack: { + /** + * stacktrace.current.callstack (selector) + */ + _: createLeaf(["/state"], state => state.proc.callstack), + + /** + * stacktrace.current.callstack.preupdated + * This selector reflects the callstack as it actually is at the current + * moment, rather than carrying around additional error information on top + * in case it turns out to be relevant -- it's been "preupdated" assuming + * we don't want the error info on top, which in certain cases, we don't. + */ + preupdated: createLeaf( + ["./_", "/current/returnCounter"], + (callstack, returnCounter) => + popNWhere( + callstack, + returnCounter, + frame => frame.type === "external" + ) + ) + }, /** * stacktrace.current.returnCounter @@ -398,18 +429,14 @@ let stacktrace = createSelectorTree({ */ report: createLeaf( [ - "./callstack", + "./callstack/preupdated", "./returnCounter", "./lastPosition", "/current/strippedLocation" ], (callstack, returnCounter, lastPosition, currentLocation) => generateReport( - popNWhere( - callstack, - returnCounter, - frame => frame.type === "external" - ), + callstack, currentLocation || lastPosition, null, undefined