A new version of Kernel was added to project The Inbox: http://source.squeak.org/inbox/Kernel-jar.1551.mcz
==================== Summary ====================
Name: Kernel-jar.1551 Author: jar Time: 9 January 2024, 10:28:51.614546 pm UUID: 51c45b0b-53ac-064c-8a39-917b0f726809 Ancestors: Kernel-jar.1550
for consideration: stepOver bug fix using Christoph's #nextRunUntilErrorOrReturnFromCalleeContext method.
The issue is: when you debug and step through to ^2 and then step over ^2 in [^2] ensure: []
the non local return ^2 will jump over the guard contexts installed temporarily while executing the #stepOver and the computation will runaway and end up with BCR error. Both events are wrong: the step over should stop at the initial doit context and the computation should end gracefully without the BCR error.
In the current trunk there's a "fix" or rather an unclean "hack" that forces recomputing the closest unwind context and stops the debugged process fromo "running away". However, as Christoph noted, this hack violates the idea that simulation mimics the VM behavior.
Here I'm proposing an alternative approach: to move the guard contexts to the correct position in the sender chain right before the non local return is about to be executed.
With this approach simulation mimics the VM behavior as requested.
=============== Diff against Kernel-jar.1550 ===============
Item was added: + ----- Method: Context>>findNextRunUntilErrorOrReturnFromCalleeContextUpTo: (in category 'private-exceptions') ----- + findNextRunUntilErrorOrReturnFromCalleeContextUpTo: aContext + + | ctx callee | + "Don't shoot in your own foot when reaching here straight from #jump in #runUntilErrorReturnFrom:" + self sender selector + caseOf: { + [#runUntilErrorOrReturnFrom:] -> [^ nil]. + [#contextOn:do:] -> [(self sender sender tempAt: 2) outerContext ifNotNil: [:blockCtx | blockCtx selector = #runUntilErrorOrReturnFrom: ifTrue: [^ nil]]]. + [#contextEnsure:] -> [(self sender sender tempAt: 1) outerContext ifNotNil: [:blockCtx | blockCtx selector = #runUntilErrorOrReturnFrom: ifTrue: [^ nil]]] } + otherwise: []. + + ctx := self. + [callee := ctx. (ctx := ctx nextHandlerContext) == nil or: [ctx == aContext]] whileFalse: + [(((ctx tempAt: 1) == UnhandledError) and: + [(ctx tempAt: 2) outerContext selector = #runUntilErrorOrReturnFrom:]) ifTrue: [^callee]]. + ^nil!
Item was added: + ----- Method: Context>>moveSimulationGuardsUnder: (in category 'private-debugger') ----- + moveSimulationGuardsUnder: aContext + "Move simulation guard contexts under the callee of self in case self (return context) is under aContext." + + | importantContext guards head tail callee | + importantContext := thisContext nextRunUntilErrorOrReturnFromCalleeContext ifNil: [^ self]. + guards := importantContext nextHandlerContext. + (aContext hasSender: guards) ifTrue: [^ self]. "guard contexts are under aContext, no need to interrupt" + + self assert: (aContext hasSender: self). + head := importantContext findContextSuchThat: [:c | c sender == guards]. "callee of guard" + tail := guards sender sender. + callee := tail findContextSuchThat: [:c | c sender == self]. "callee of self" + guards cut: tail. + head privSender: tail. + callee insertSender: guards + !
Item was added: + ----- Method: Context>>nextRunUntilErrorOrReturnFromCalleeContext (in category 'private-exceptions') ----- + nextRunUntilErrorOrReturnFromCalleeContext + + ^ self findNextRunUntilErrorOrReturnFromCalleeContextUpTo: self!
Item was changed: ----- Method: Context>>resumeEvaluating: (in category 'controlling') ----- resumeEvaluating: aBlock "Unwind thisContext to self and resume with value as result of last send. Execute unwind blocks when unwinding. ASSUMES self is a sender of thisContext"
+ ^self resumeEvaluating: aBlock through: thisContext! - ^self resumeEvaluating: aBlock through: nil!
Item was changed: ----- Method: Context>>resumeEvaluating:through: (in category 'controlling') ----- + resumeEvaluating: aBlock through: aContext - resumeEvaluating: aBlock through: firstUnwindCtxtOrNil "Unwind thisContext to self and resume with value as result of last send. Execute unwind blocks when unwinding. ASSUMES self is a sender of thisContext."
self isDead ifTrue: [self cannotReturn: aBlock value to: self]. + self moveSimulationGuardsUnder: aContext. + aContext unwindTo: self safely: false. - (firstUnwindCtxtOrNil ifNil: thisContext) unwindTo: self safely: false. thisContext terminateTo: self. ^aBlock value!
Item was changed: ----- Method: Context>>return:from: (in category 'instruction decoding') ----- return: value from: aSender "For simulation. Roll back self to aSender and return value from it. Execute any unwind blocks on the way. ASSUMES aSender is a sender of self"
| newTop | newTop := aSender sender. + (aSender isDead or: [newTop isNil or: [newTop isDead]]) ifTrue: [ + ^self pc: nil; send: #cannotReturn: to: self with: {value}]. + (self findNextUnwindContextUpTo: newTop) ifNotNil: [:unwindProtectCtxt | + ^self send: #aboutToReturn:through: to: self with: {value. unwindProtectCtxt}]. - (aSender isDead or: [newTop isNil or: [newTop isDead]]) ifTrue: - [^self pc: nil; send: #cannotReturn: to: self with: {value}]. - (self findNextUnwindContextUpTo: newTop) ifNotNil: - "Send #aboutToReturn:through: with nil as the second argument to avoid this bug: - Cannot #stepOver '^2' in example '[^2] ensure: []'. - See http://lists.squeakfoundation.org/pipermail/squeak-dev/2022-June/220975.html" - [^self send: #aboutToReturn:through: to: self with: {value. nil}]. self releaseTo: newTop. + ^newTop push: value! - newTop ifNotNil: [newTop push: value]. - ^newTop!
squeak-dev@lists.squeakfoundation.org