Skip to content

Commit

Permalink
Support for drop to frame
Browse files Browse the repository at this point in the history
Note that it can skip some frames as StepInto does this intentionally
  • Loading branch information
mpociecha committed Nov 18, 2014
1 parent 1dfe6b9 commit d5e0798
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 32 deletions.
@@ -1,13 +1,14 @@
package org.scalaide.debug.internal

import org.scalaide.core.testsetup.TestProjectSetup
import org.scalaide.core.testsetup.SDTTestUtils
import org.junit.Test
import org.junit.Before
import org.junit.After
import org.eclipse.core.resources.IncrementalProjectBuilder
import org.eclipse.core.runtime.NullProgressMonitor
import org.junit.After
import org.junit.AfterClass
import org.junit.Assert._
import org.junit.Before
import org.junit.Test
import org.scalaide.core.testsetup.SDTTestUtils
import org.scalaide.core.testsetup.TestProjectSetup

object ScalaDebugSteppingTest extends TestProjectSetup("debug", bundleName = "org.scala-ide.sdt.debug.tests") with ScalaDebugRunningTest {

Expand Down Expand Up @@ -602,4 +603,69 @@ class ScalaDebugSteppingTest {
session.stepInto
session.checkStackFrame("stepping.MaxArgs$class", "manyArgs(Lstepping/MaxArgs;DDDDDDDDDDDDDDDDDDDDDD)D", 105)
}

@Test
def canDropToFrame() {
session = initDebugSession("SimpleStepping")

session.runToLine(TYPENAME_SIMPLE_STEPPING, 12)

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 12)

session.currentStackFrames.dropRight(1).foreach { frame =>
assertTrue("Should be able to drop to frame", frame.canDropToFrame())
}

assertFalse("Shouldn't be able to drop to the last frame", session.currentStackFrames.last.canDropToFrame())

// just check also the top stack frame when we stop in other place than the beginning of a method
session.runToLine(TYPENAME_SIMPLE_STEPPING, 13)
session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 13)

assertTrue("Should be able to drop to frame", session.currentStackFrames.head.canDropToFrame())
}

@Test
def dropToFrame() {

def checkNumberOfFrames(count: Int): Unit =
assertEquals("Wrong number of stack frames", count, session.currentStackFrames.size)

session = initDebugSession("SimpleStepping")

session.runToLine(TYPENAME_SIMPLE_STEPPING, 13)

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 13)
checkNumberOfFrames(5)

// return to the beginning of current method
session dropToFrame session.currentStackFrame

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 12)
checkNumberOfFrames(5)

// drop to the same place
session dropToFrame session.currentStackFrame

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 12)
checkNumberOfFrames(5)

// step back - 2 levels
session dropToFrame session.currentStackFrames(2)

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "mainTest()V", 17)
checkNumberOfFrames(3)

// check that an application will be resumed from the correct place
session.stepInto()
session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "foo()V", 8)
session.runToLine(TYPENAME_SIMPLE_STEPPING, 13)
session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "bar()V", 13)

// step back - 1 level
session dropToFrame session.currentStackFrames(1)

session.checkStackFrame(TYPENAME_SIMPLE_STEPPING, "foo()V", 8)
checkNumberOfFrames(4)
}
}
Expand Up @@ -135,6 +135,7 @@ class ScalaDebugTestSession private(launchConfiguration: ILaunchConfiguration) e
var state = NOT_LAUNCHED
var debugTarget: ScalaDebugTarget = null
var currentStackFrame: ScalaStackFrame = null
def currentStackFrames: Seq[ScalaStackFrame] = Option(currentStackFrame).map(_.thread.getScalaStackFrames).getOrElse(Nil)

/**
* Add a breakpoint at the specified location,
Expand Down Expand Up @@ -233,6 +234,17 @@ class ScalaDebugTestSession private(launchConfiguration: ILaunchConfiguration) e
assertEquals("Bad state after resumeToCompletion", TERMINATED, state)
}

def dropToFrame(stackFrame: ScalaStackFrame) {
assertEquals("Bad state before dropToFrame", SUSPENDED, state)

setActionRequested
stackFrame.dropToFrame()

waitUntilSuspended

assertEquals("Bad state after dropToFrame", SUSPENDED, state)
}

def terminate() {
if ((state ne NOT_LAUNCHED) && (state ne TERMINATED)) {
debugTarget.terminate()
Expand Down
Expand Up @@ -370,6 +370,8 @@ abstract class ScalaDebugTarget private (val virtualMachine: VirtualMachine, lau
*/
private[model] def getScalaThreads: List[ScalaThread] = threads

private[model] def canPopFrames: Boolean = running && virtualMachine.canPopFrames()

}

private[model] object ScalaDebugTargetActor {
Expand Down
@@ -1,16 +1,17 @@
package org.scalaide.debug.internal.model

import scala.collection.JavaConverters.asScalaBufferConverter
import scala.reflect.NameTransformer
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.InvalidStackFrameException
import com.sun.jdi.Method
import com.sun.jdi.NativeMethodException
import com.sun.jdi.StackFrame
import org.eclipse.debug.core.model.IDropToFrame
import org.eclipse.debug.core.model.IRegisterGroup
import org.eclipse.debug.core.model.IStackFrame
import org.eclipse.debug.core.model.IThread
import org.eclipse.debug.core.model.IVariable
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Method
import com.sun.jdi.StackFrame
import com.sun.jdi.InvalidStackFrameException
import com.sun.jdi.NativeMethodException
import scala.collection.JavaConverters.asScalaBufferConverter
import scala.reflect.NameTransformer

object ScalaStackFrame {

Expand Down Expand Up @@ -78,7 +79,8 @@ object ScalaStackFrame {
* This class is NOT thread safe. 'stackFrame' variable can be 're-bound' at any time.
* Instances have be created through its companion object.
*/
class ScalaStackFrame private (val thread: ScalaThread, @volatile var stackFrame: StackFrame) extends ScalaDebugElement(thread.getDebugTarget) with IStackFrame {
class ScalaStackFrame private (val thread: ScalaThread, @volatile var stackFrame: StackFrame)
extends ScalaDebugElement(thread.getDebugTarget) with IStackFrame with IDropToFrame {
import ScalaStackFrame._

// Members declared in org.eclipse.debug.core.model.IStackFrame
Expand Down Expand Up @@ -119,8 +121,15 @@ class ScalaStackFrame private (val thread: ScalaThread, @volatile var stackFrame
override def resume(): Unit = thread.resume()
override def suspend(): Unit = ???

// Members declared in org.eclipse.debug.core.model.IDropToFrame

override def canDropToFrame(): Boolean = thread.canDropToFrame(this)
override def dropToFrame(): Unit = thread.dropToFrame(this)

// ---

def isNative = stackFrame.location().method().isNative()

import org.scalaide.debug.internal.JDIUtil._
import scala.util.control.Exception
import Exception.Catch
Expand Down
@@ -1,27 +1,28 @@
package org.scalaide.debug.internal.model

import scala.collection.JavaConverters.asScalaBufferConverter
import org.scalaide.debug.internal.command.ScalaStepOver
import org.scalaide.debug.internal.command.ScalaStep
import org.eclipse.debug.core.model.IThread
import org.eclipse.debug.core.model.IBreakpoint
import com.sun.jdi.ClassType
import com.sun.jdi.IncompatibleThreadStateException
import com.sun.jdi.Method
import com.sun.jdi.ObjectCollectedException
import com.sun.jdi.ObjectReference
import com.sun.jdi.ThreadReference
import com.sun.jdi.Value
import com.sun.jdi.VMCannotBeModifiedException
import com.sun.jdi.VMDisconnectedException
import com.sun.jdi.ObjectCollectedException
import org.eclipse.debug.core.DebugEvent
import com.sun.jdi.Value
import com.sun.jdi.ObjectReference
import com.sun.jdi.Method
import org.scalaide.debug.internal.command.ScalaStepInto
import org.scalaide.debug.internal.command.ScalaStepReturn
import org.eclipse.debug.core.model.IThread
import org.eclipse.debug.core.model.IBreakpoint
import org.eclipse.debug.core.model.IStackFrame
import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame
import org.scalaide.debug.internal.BaseDebuggerActor
import org.scalaide.debug.internal.JDIUtil._
import scala.actors.Future
import com.sun.jdi.ClassType
import com.sun.jdi.VMCannotBeModifiedException
import org.eclipse.debug.core.model.IStackFrame
import com.sun.jdi.IncompatibleThreadStateException
import org.scalaide.debug.internal.command.ScalaStepOver
import org.scalaide.debug.internal.command.ScalaStep
import org.scalaide.debug.internal.command.ScalaStepInto
import org.scalaide.debug.internal.command.ScalaStepReturn
import org.scalaide.logging.HasLogger
import scala.actors.Future
import scala.collection.JavaConverters.asScalaBufferConverter

class ThreadNotSuspendedException extends Exception

Expand Down Expand Up @@ -50,9 +51,7 @@ abstract class ScalaThread private (target: ScalaDebugTarget, private[model] val
override def canStepReturn: Boolean = suspended // TODO: need real logic
override def isStepping: Boolean = ???

override def stepInto(): Unit = {
wrapJDIException("Exception while performing `step into`") { ScalaStepInto(stackFrames.head).step() }
}
override def stepInto(): Unit = stepIntoFrame(stackFrames.head)
override def stepOver(): Unit = {
wrapJDIException("Exception while performing `step over`") { ScalaStepOver(stackFrames.head).step() }
}
Expand Down Expand Up @@ -87,6 +86,7 @@ abstract class ScalaThread private (target: ScalaDebugTarget, private[model] val

override def getPriority: Int = ???
override def getStackFrames: Array[IStackFrame] = stackFrames.toArray
final def getScalaStackFrames: List[ScalaStackFrame] = stackFrames
override def getTopStackFrame: IStackFrame = stackFrames.headOption.getOrElse(null)
override def hasStackFrames: Boolean = !stackFrames.isEmpty

Expand Down Expand Up @@ -140,6 +140,38 @@ abstract class ScalaThread private (target: ScalaDebugTarget, private[model] val
processMethodInvocationResult(syncSend(companionActor, InvokeStaticMethod(classType, method, args.toList)))
}

private def stepIntoFrame(stackFrame: => ScalaStackFrame): Unit =
wrapJDIException("Exception while performing `step into`") { ScalaStepInto(stackFrame).step() }

/**
* It's not possible to drop the bottom stack frame. Moreover all dropped frames and also
* the one below the target frame can't be native.
*/
private[model] def canDropToFrame(frame: ScalaStackFrame): Boolean = {
val frames = stackFrames
val indexOfFrame = frames.indexOf(frame)

val atLeastLastButOne = frames.size >= indexOfFrame + 2
def notNative = !frames.take(indexOfFrame + 2).exists(_.isNative)
canPopFrames && atLeastLastButOne && notNative
}

private[model] def canPopFrames: Boolean = isSuspended && target.canPopFrames

private[model] def dropToFrame(frame: ScalaStackFrame): Unit = companionActor ! DropToFrame(frame)

/**
* Removes all top stack frames starting from a given one and performs StepInto to reach the given frame again.
* FOR THE COMPANION ACTOR ONLY.
*/
private[model] def dropToFrameInternal(frame: ScalaStackFrame): Unit =
if (canDropToFrame(frame)) {
val frames = stackFrames
val startFrameForStepInto = frames(frames.indexOf(frame) + 1)
threadRef.popFrames(frame.stackFrame)
stepIntoFrame(startFrameForStepInto)
}

private def processMethodInvocationResult(res: Option[Any]): Value = res match {
case Some(Right(null)) =>
null
Expand Down Expand Up @@ -219,6 +251,7 @@ private[model] object ScalaThreadActor {
case class ResumeFromScala(step: Option[ScalaStep], eventDetail: Int)
case class InvokeMethod(objectReference: ObjectReference, method: Method, args: List[Value])
case class InvokeStaticMethod(classType: ClassType, method: Method, args: List[Value])
case class DropToFrame(frame: ScalaStackFrame)
case object TerminatedFromScala

def apply(thread: ScalaThread): BaseDebuggerActor = {
Expand Down Expand Up @@ -249,6 +282,7 @@ private[model] class ScalaThreadActor private(thread: ScalaThread) extends BaseD
currentStep = step
thread.resume(eventDetail)
thread.threadRef.resume()
case DropToFrame(frame) => thread.dropToFrameInternal(frame)
case InvokeMethod(objectReference, method, args) =>
reply(
if (!thread.isSuspended) {
Expand Down

0 comments on commit d5e0798

Please sign in to comment.