Bug 533983 - [Python *] improve debugging support
improves py4j debugger performance by pre-filtering call dispatches in
Python itself to avoid IPC overhead.
Change-Id: Iccefedfac627522b648f93d410c722d14b4bcc70
Signed-off-by: Martin Kloesch <martin@kmh-solutions.com>
diff --git a/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java b/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
index be9e710..e67544b 100644
--- a/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
+++ b/plugins/org.eclipse.ease.lang.javascript.rhino.debugger/src/org/eclipse/ease/lang/javascript/rhino/debugger/RhinoDebugger.java
@@ -22,6 +22,7 @@
import org.eclipse.ease.debugging.AbstractEaseDebugger;
import org.eclipse.ease.debugging.EaseDebugFrame;
import org.eclipse.ease.debugging.IScriptDebugFrame;
+import org.eclipse.ease.debugging.IScriptRegistry;
import org.eclipse.ease.lang.javascript.rhino.RhinoScriptEngine;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
@@ -150,6 +151,7 @@
return "";
}
+ @Override
public Map<String, Object> getVariables() {
final Map<String, Object> result = getEngine().getVariables(fScope);
@@ -211,14 +213,22 @@
@Override
public void notify(final IScriptEngine engine, final Script script, final int status) {
+ final IScriptRegistry scriptRegistry = getDispatcher();
switch (status) {
case SCRIPT_START:
// fall through
case SCRIPT_INJECTION_START:
+ if (scriptRegistry != null) {
+ scriptRegistry.put(script);
+ }
fLastScript = script;
break;
+ case SCRIPT_END:
+ // TODO: Check if scripts should be removed to lower memory usage
+ break;
+
case ENGINE_END:
fFrameToSource.clear();
fLastScript = null;
@@ -228,7 +238,6 @@
// unknown event
break;
}
-
super.notify(engine, script, status);
}
}
diff --git a/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java b/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
index d4ea533..dbe73ba 100644
--- a/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
+++ b/plugins/org.eclipse.ease.lang.python.jython.debugger/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebuggerEngine.java
@@ -21,11 +21,11 @@
import org.eclipse.ease.ScriptObjectType;
import org.eclipse.ease.debugging.EaseDebugFrame;
import org.eclipse.ease.debugging.ScriptStackTrace;
-import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
import org.eclipse.ease.debugging.model.EaseDebugVariable;
import org.eclipse.ease.debugging.model.EaseDebugVariable.Type;
import org.eclipse.ease.lang.python.debugger.IPythonDebugEngine;
import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+import org.eclipse.ease.lang.python.debugger.PythonEventDispatchJob;
import org.eclipse.ease.lang.python.debugger.ResourceHelper;
import org.eclipse.ease.lang.python.debugger.model.PythonDebugTarget;
import org.eclipse.ease.lang.python.jython.JythonScriptEngine;
@@ -99,7 +99,7 @@
setDebugger(debugger);
- new EventDispatchJob(target, debugger);
+ new PythonEventDispatchJob(target, debugger);
}
@Override
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
index fd34845..d1ad350 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.ease.lang.python.py4j/META-INF/MANIFEST.MF
@@ -3,6 +3,7 @@
Bundle-Name: Py4j
Bundle-SymbolicName: org.eclipse.ease.lang.python.py4j;singleton:=true
Bundle-Version: 0.7.0.qualifier
+Bundle-Vendor: Eclipse.org
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.eclipse.ui;bundle-version="[3.7.0,4.0.0)",
org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)",
@@ -16,3 +17,5 @@
org.eclipse.ease.lang.python.py4j.internal.ui;x-internal:=true
Bundle-Activator: org.eclipse.ease.lang.python.py4j.internal.Activator
Bundle-ActivationPolicy: lazy
+Bundle-ClassPath: .
+Eclipse-BundleShape: dir
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
index d49576c..4fcfe5b 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
+++ b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/ease_py4j_main.py
@@ -8,14 +8,13 @@
# Contributors:
# Jonah Graham (Kichwa Coders) - initial API and implementation
###############################################################################
-
+import sys
if __name__ == '__main__':
# To be able to import all the py4j and related items, we need to add to the PYTHONPATH
# all the correct paths. Because we may be launched with -E, the command line provides
# all the paths to what we need.
# sys.argv[1] - required - the port to conenct o
# sys.argv[2:] - optional - paths to prepend on sys.path
- import sys
sys.path[0:0] = sys.argv[2:]
import code
@@ -24,8 +23,7 @@
from py4j.clientserver import ClientServer, JavaParameters, PythonParameters
from py4j.java_collections import MapConverter, ListConverter, SetConverter
from py4j.java_gateway import JavaObject, JavaClass
-from py4j.protocol import Py4JJavaError, get_command_part
-import sys
+from py4j.protocol import Py4JJavaError
import threading
import __main__
import ast
@@ -33,7 +31,6 @@
from six import integer_types
from six import string_types
except ImportError:
- import sys
if sys.version_info.major == 2:
integer_types = (int, long)
string_types = (basestring, )
@@ -61,6 +58,7 @@
'''
builtins.__dict__.update({name: value})
+
# To ease some debugging of the py4j engine itself it is useful to turn logging on,
# uncomment the following lines for one way to do that
# import logging
@@ -69,7 +67,7 @@
# logger.addHandler(logging.StreamHandler())
def convert_value(
value,
- gw,
+ gw=None,
integer_types=integer_types,
string_types=string_types,
dict_types=dict,
@@ -84,6 +82,9 @@
:param value: Value to be converted.
:param gw: py4j gateway necessary for converters.
'''
+ if not gw:
+ gw = gateway._gateway_client
+
# None will be mapped to Java null
if value is None:
return value
@@ -271,12 +272,16 @@
self.locals['net'] = gateway.jvm.net
patch_builtins('net', gateway.jvm.net)
-
+
self.locals['jvm'] = gateway.jvm
patch_builtins('jvm', gateway.jvm.jvm)
self.locals['gateway'] = gateway
+ patch_builtins('gateway', gateway)
+
self.locals['py4j'] = py4j
+ patch_builtins('py4j', py4j)
+
sys.displayhook = self.displayhook
self.display_data = None
self.except_data = None
@@ -343,6 +348,9 @@
def wait_on_shutdown(self):
self.shutdown_event.wait()
+ def addSearchPath(self, path):
+ sys.path.append(path)
+
class Java:
implements = ['org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine']
@@ -364,6 +372,7 @@
timer.setDaemon(True)
timer.start()
+
def main(argv):
port = int(argv[1])
engine = ScriptEngineExecute()
@@ -395,4 +404,6 @@
if __name__ == '__main__':
+ # Will be patched via builtins
+ gateway = None
main(sys.argv)
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py
new file mode 100644
index 0000000..c4654a9
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/pysrc/py4jdb.py
@@ -0,0 +1,460 @@
+'''
+Copyright (c) 2018 Martin Kloesch
+All rights reserved. This program and the accompanying materials
+are made available under the terms of the Eclipse Public License v1.0
+which accompanies this distribution, and is available at
+http://www.eclipse.org/legal/epl-v10.html
+
+Contributors:
+ * Martin Kloesch - initial API and implementation
+'''
+
+# Python std library imports
+import ast
+import bdb
+import functools
+import re
+import sys
+import threading
+
+# : Regular expression if we are dealing with internal module.
+INTERNAL_CHECKER = re.compile(r'^<.+>$')
+
+
+class PyFrame:
+ '''
+ Python implementation of IPyFrame used for exchanging frame data
+ with eclipse.
+
+ Simply wraps standard python frame to more easily usable format.
+
+ Python frame documentation is available here:
+ https://docs.python.org/3/reference/datamodel.html#types
+ '''
+
+ def __init__(self, frame):
+ '''
+ Constructor only stores frame to member.
+
+ :param frame: Python frame to be converted.
+ '''
+ self._frame = frame
+
+ def _valid_frame(self):
+ '''
+ Utility method checking if the current frame is valid
+
+ A frame is valid if all information is present and
+ it's file is not ignored.
+
+ :returns: ``True`` if frame is valid.
+ '''
+ return self._frame and not ignore_frame(self._frame)
+
+ def getFilename(self):
+ '''
+ Returns the filename of the frame.
+
+ If no frame was set, a dummy value will be returned.
+
+ :returns: Filename of frame or dummy value.
+ '''
+ if self._valid_frame():
+ return self._frame.f_code.co_filename
+ else:
+ return '__no_frame__'
+
+ def getLineNumber(self):
+ '''
+ Returns the linenumber of the frame.
+
+ If no frame was set, -1 is returned.
+
+ :returns: Line number of frame or -1.
+ '''
+ if self._valid_frame():
+ return self._frame.f_lineno
+ else:
+ return -1
+
+ def getParent(self):
+ '''
+ Returns the parent of the frame.
+
+ If no frame was set or the frame does not have a parent
+ `None` is returned.
+
+ :returns: Parent frame in stack or `None`
+ '''
+ if self._valid_frame():
+ return PyFrame(self._frame.f_back)
+ else:
+ return None
+
+ def getVariable(self, name):
+ '''
+ Get a variable from the current frame.
+
+ :param: name: variable name to look up
+ :return: variable or <code>null</code>
+ '''
+ if self._valid_frame():
+ if name in self._frame.f_locals:
+ return convert_value(self._frame.f_locals[name])
+
+ if name in self._frame.f_globals:
+ return convert_value(self._frame.f_globals[name])
+
+ return None
+
+ def getVariables(self):
+ '''
+ Get variables visible from current frame.
+
+ :return: variableName -> variableContent
+ '''
+ variables = {}
+ if self._valid_frame():
+ # TODO: Differentiate between global and local variables
+ variables.update(self._frame.f_globals)
+ variables.update(self._frame.f_locals)
+
+ return convert_value(variables)
+
+ def setVariable(self, name, value):
+ '''
+ Sets new value for a variable only if it exists.
+
+ :param name: Name of variable to be updated.
+ :param value: New value for variable.
+ '''
+ if self._valid_frame():
+ if name in self._frame.f_locals:
+ self._frame.f_locals[name] = value
+
+ elif name in self._frame.f_globals:
+ self._frame.f_globals[name] = value
+
+ def toString(self):
+ '''
+ Required override to avoid problems in py4j bridge.
+ '''
+ return '{}: #{:d}'.format(self.getFilename(), self.getLineNumber())
+
+ class Java:
+ implements = ['org.eclipse.ease.lang.python.debugger.IPyFrame']
+
+
+def ignore_frame(frame, first=True):
+ '''
+ Utility to check if a frame should be ignored.
+
+ Current reasons to ignore a frame:
+ * It is the top entry of the stack and it is a standard module.
+ e.g. <string>
+ * It is part of the py4j library or has py4j in its call chain.
+
+ Recursively checks trace until at bottom of stack.
+
+ :param frame: Frame to check if it should be ignored.
+ :param first: Flag to signalize if it is the first call.
+ :returns: `True` if the frame should be ignored.
+ '''
+ # End of frame
+ if not frame:
+ return False
+
+ # ignore frames that originate from the self.run() method
+ if first:
+ if frame.f_code.co_filename == '(none)':
+ return True
+
+ # Check if we are in the standard modules
+ if INTERNAL_CHECKER.match(frame.f_code.co_filename):
+ # Only ignore standard module if its the top of the stack
+ return first
+
+ # TODO: Think of better way to identify py4j library
+ if 'py4j' in frame.f_code.co_filename.lower():
+ return True
+
+ return ignore_frame(frame.f_back, False)
+
+
+class CodeTracer(bdb.Bdb):
+ '''
+ Eclipse Debugger class.
+ '''
+
+ def __init__(self, *args, **kwargs):
+ '''
+ Constructor initializes instance variables.
+ '''
+ bdb.Bdb.__init__(self, *args, **kwargs)
+
+ # Debugger for communication with Java world
+ self._debugger = None
+
+ # Caches for currently executed code parts
+ self._current_frame = None
+ self._current_file = None
+
+ # Async continuation handling
+ self._continue_event = threading.Event()
+ self._continue_func = lambda: None
+
+ # TODO: Think about a better way to handle step return
+ self._return_hack = False
+
+ def setDebugger(self, debugger):
+ '''
+ Setter method for self._debugger.
+
+ :param org.eclipse.ease.lang.python.debugger.PythonDebugger debugger:
+ PythonDebugger object to handling communication with Eclipse.
+ '''
+ self._debugger = debugger
+
+ def trace_dispatch(self, frame, event, arg):
+ '''
+ Called for every executed line of source code.
+
+ Checks if line is of interest and dispatches the call for
+ further investigation.
+
+ :param frame: Current stack frame.
+ :param event: Type of dispatch event that occurred.
+ :param arg: Optional argument for dispatching.
+ '''
+ # Pre-filter to avoid issues with library internals
+ if not ignore_frame(frame):
+ bdb.Bdb.trace_dispatch(self, frame, event, arg)
+ return self.trace_dispatch
+
+ def user_line(self, frame):
+ '''
+ Called when debugger thinks line is of interest.
+
+ :param frame: Current stack frame with line.
+ '''
+ self._continue_func = self.set_continue
+ self.dispatch(frame, 'line')
+
+ def user_call(self, frame, argument_list):
+ '''
+ Called when debugger thinks call is of interest.
+
+ :param frame: Current stack frame with call information.
+ :param argument_list: ignored
+ '''
+ self.dispatch(frame, 'call')
+
+ def user_return(self, frame, return_value):
+ '''
+ Called when debugger thinks returning from function is of interest.
+
+ :param frame: Current stack frame with return information.
+ :param return_value: ignored
+ '''
+ self._continue_func = self.set_step
+ self.dispatch(frame, 'return')
+
+ def user_exception(self, frame, exc_info):
+ '''
+ Called when debugger thinks thrown exception is of interest.
+
+ :param frame: Current stack frame with exception information.
+ :param exc_info: ignored
+ '''
+ # Cache exception
+ if self._debugger:
+ self._debugger.setExceptionStackTrace(PyFrame(frame))
+
+ self._continue_func = self.set_continue
+ self.dispatch(frame, 'exception')
+
+ def dispatch(self, frame, dispatch_type):
+ '''
+ Dispatches the given frame to the interested debugger.
+
+ :param frame: Current stack frame to be dispatched.
+ :param dispatch_type: Type of dispatch event (call, line, ...)
+ '''
+ if not self._debugger:
+ return
+
+ # Set up caches for asynchronous continuation
+ self._current_frame = frame
+ self._continue_event.set()
+
+ # Check if we need to fake the current frame for Eclipse
+ if self._return_hack:
+ frame = frame.f_back
+ self._return_hack = False
+
+ # Dispatch call to Java
+ self._debugger.traceDispatch(PyFrame(frame), dispatch_type)
+
+ # Make asynchronous call synchronous again
+ self._continue_event.wait()
+ self._continue_func()
+
+ def suspend(self):
+ '''
+ Suspend the execution to wait for asynchronous callbacks
+ from Java.
+ '''
+ self._continue_event.clear()
+
+ def resume(self, resume_type):
+ '''
+ Resume execution by calling the given resume function.
+
+ :param resume_type: Desired resume function identifier.
+ '''
+ frame = self._current_frame
+
+ # Step into
+ if resume_type == 1:
+ self._continue_func = self.set_step
+
+ # Step over
+ elif resume_type == 2:
+ self._continue_func = functools.partial(self.set_next, frame)
+
+ # Step return
+ elif resume_type == 4:
+ self._return_hack = True
+ self._continue_func = functools.partial(self.set_return, frame)
+
+ # Continue
+ else:
+ self._continue_func = self.set_continue
+
+ self._continue_event.set()
+
+ def set_continue(self):
+ '''
+ FIXME: Override of bdb.Bdb.set_continue
+ Original stops debugging when no more breakpoints in file.
+ This means we cannot add breakpoints later on.
+ '''
+ self._set_stopinfo(self.botframe, None, -1)
+
+ def setBreakpoint(self, breakpoint):
+ '''
+ Interface method for Java to have simpler method of setting
+ a breakpoint.
+
+ FIXME: This is a copy of bdb.Bdb.set_break
+ set_break performs OS level checks to see if file exists.
+
+ :param breakpoint: Breakpoint to be set.
+ '''
+ filename = self.canonic(breakpoint.getFilename())
+ lineno = breakpoint.getLineno()
+
+ if filename not in self.breaks:
+ self.breaks[filename] = []
+ linenos = self.breaks[filename]
+ if lineno not in linenos:
+ linenos .append(lineno)
+ bdb.Breakpoint(filename, lineno, 0, None, None)
+
+ def removeBreakpoint(self, breakpoint):
+ '''
+ Interface method for Java to have simpler method of removing
+ a breakpoint.
+
+ :param breakpoint: Breakpoint to be removed.
+ '''
+ filename = self.canonic(breakpoint.getFilename())
+ lineno = breakpoint.getLineno()
+ self.clear_break(filename, lineno)
+
+ def stop_here(self, frame):
+ '''
+ Hijacks frame check to see if currently executed file has changed.
+
+ If file changed new breakpoints will be loaded.
+
+ :param frame: Current stack frame to check for file change.
+ '''
+ if self._debugger:
+ file = frame.f_code.co_filename
+ if file != self._current_file:
+ self._current_file = file
+ self.clear_all_file_breaks(file)
+
+ breakpoints = self._debugger.getBreakpoints(file)
+ for breakpoint in breakpoints:
+ self.setBreakpoint(breakpoint)
+
+ # If we have breakpoints we want to let the debugger know
+ if breakpoints:
+ return True
+
+ return bdb.Bdb.stop_here(self, frame)
+
+ def run(self, script, filename):
+ '''
+ Executes the script given using the bdb.Bdb.run method.
+
+ :param script: Script to be executed.
+ :param filename: Filename for easier identification of dynamic code.
+ '''
+ # Compile code for better inspection
+ code = '{}\n'.format(script.getCode())
+ ast_ = ast.parse(code, filename, 'exec')
+ final_expr = None
+ for field_ in ast.iter_fields(ast_):
+ if 'body' != field_[0]:
+ continue
+
+ # Check if last item is an expression
+ if len(field_[1]) > 0:
+ if isinstance(field_[1][-1], ast.Expr):
+ final_expr = ast.Expression()
+ popped = field_[1].pop(-1)
+ final_expr.body = popped.value
+
+ # Run code up to last expression
+ return_value = None
+ compiled = compile(ast_, filename, 'exec')
+ bdb.Bdb.run(self, compiled)
+ self._keep_debugging()
+
+ # Check if last expression still needs to be run
+ if final_expr:
+ compiled = compile(final_expr, filename, 'eval')
+ return_value = self.runeval(compiled)
+ self._keep_debugging()
+
+ return return_value
+
+ def _keep_debugging(self):
+ '''
+ Resets debugger state to be able to continue debugging after
+ script injection.
+ '''
+ sys.settrace(self.trace_dispatch)
+ self.quitting = False
+
+ def toString(self):
+ '''
+ Required override to avoid problems in py4j bridge.
+ '''
+ return str(self)
+
+ def equals(self, other):
+ '''
+ Required override to avoid problems in py4j bridge.
+ '''
+ return self is other
+
+ class Java:
+ implements = ['org.eclipse.ease.lang.python.py4j.internal.ICodeTraceFilter']
+
+
+# Set up connection between eclipse and python
+CodeTracer()
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java
new file mode 100644
index 0000000..b00eabe
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/ICodeTraceFilter.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j.internal;
+
+import org.eclipse.ease.IExecutionListener;
+import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
+import org.eclipse.ease.lang.python.debugger.ICodeTracer;
+import org.eclipse.ease.lang.python.debugger.PythonBreakpoint;
+import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+
+/**
+ * Extension of {@link ICodeTracer} performing pre-filtering before code gets to actual tracer.
+ */
+public interface ICodeTraceFilter extends ICodeTracer, IEventProcessor, IExecutionListener {
+ /**
+ * Sets the {@link PythonDebugger} to be used by the code trace filter to perform callbacks.
+ *
+ * @param debugger
+ * {@link PythonDebugger} for callbacks.
+ */
+ void setDebugger(PythonDebugger debugger);
+
+ /**
+ * Sets a breakpoint in the trace filter.
+ *
+ * @param breakpoint
+ * Breakpoint to be set.
+ */
+ void setBreakpoint(PythonBreakpoint breakpoint);
+
+ /**
+ * Removes a breakpoint from the trace filter.
+ *
+ * @param breakpoint
+ * Breakpoint to be removed.
+ */
+ void removeBreakpoint(PythonBreakpoint breakpoint);
+
+ /**
+ * Resume execution after filter has notified us that execution might need to be stopped.
+ *
+ * @param resumeType
+ * Resume type for execution continuation.
+ */
+ void resume(int resumeType);
+
+ /**
+ * Suspend execution after filter has notified us that execution might need to be stopped.
+ */
+ void suspend();
+}
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java
new file mode 100644
index 0000000..8b2050d
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebugger.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.debug.core.DebugEvent;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.ease.ExitException;
+import org.eclipse.ease.IDebugEngine;
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.events.debugger.IDebuggerEvent;
+import org.eclipse.ease.lang.python.debugger.IPythonScriptRegistry;
+import org.eclipse.ease.lang.python.debugger.PythonBreakpoint;
+import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+
+/**
+ * Extension of {@link PythonDebugger} with additional {@link ICodeTraceFilter} to lower amount of trace dispatches.
+ */
+public class Py4jDebugger extends PythonDebugger {
+ /**
+ * Extended code tracer doing pre-filtering.
+ */
+ private ICodeTraceFilter fTraceFilter;
+
+ /**
+ * @see PythonDebugger#PythonDebugger(IDebugEngine, boolean)
+ */
+ public Py4jDebugger(IDebugEngine engine, boolean showDynamicCode) {
+ super(engine, showDynamicCode);
+ }
+
+ /**
+ * Sets extended code tracer doing pre-filtering of dispatch calls..
+ *
+ * @param traceFilter
+ * Extended code tracer.
+ */
+ public void setTraceFilter(ICodeTraceFilter traceFilter) {
+ fTraceFilter = traceFilter;
+ }
+
+ /**
+ * Returns list of all breakpoints in given file.
+ *
+ * @param filename
+ * Filename to get all breakpoints for.
+ * @return List of breakpoints in given file.
+ */
+ public List<PythonBreakpoint> getBreakpoints(String filename) {
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry != null) {
+
+ final Script script = registry.getScript(filename);
+ if (script != null) {
+ final List<IBreakpoint> breakpoints = fBreakpoints.get(script);
+ if (breakpoints != null) {
+ final List<PythonBreakpoint> pythonBreakpoints = new ArrayList<>();
+ for (final IBreakpoint breakpoint : breakpoints) {
+ final int lineno = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+ pythonBreakpoints.add(new PythonBreakpoint(filename, lineno));
+ }
+ return pythonBreakpoints;
+ }
+ }
+ }
+
+ return Collections.<PythonBreakpoint> emptyList();
+
+ }
+
+ @Override
+ protected void suspend(IDebuggerEvent event) {
+ fTraceFilter.suspend();
+ super.suspend(event);
+
+ }
+
+ @Override
+ protected void resume(int resumeType, Object thread) {
+ super.resume(resumeType, thread);
+ fTraceFilter.resume(resumeType);
+ }
+
+ @Override
+ public Object execute(Script script) {
+ try {
+ String reference = script.getTitle();
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry != null) {
+ registry.put(script);
+ reference = registry.getReference(script);
+ }
+ return fTraceFilter.run(script, reference);
+
+ } catch (final Exception e) {
+ /*
+ * When terminating, #handleEvent sets resume type to STEP_END, which causes #traceDispatch to raise an ExitException. The ExitException is
+ * propagated back to python, and eventually ends up back here at Exit method. However Py4J does not support propogating the same type of exception
+ * across the Python/Java barrier, so what ends up being thrown is a Py4JException instead.
+ *
+ * Therefore we catch that case and re-raise the expected ExitException()
+ */
+ if (getThreadState(getThread()).fResumeType == DebugEvent.STEP_END) {
+ throw new ExitException("Debug aborted by user");
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ protected void breakpointAdded(final Script script, final IBreakpoint breakpoint) {
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry == null) {
+ return;
+ }
+
+ final String reference = registry.getReference(script);
+ if (reference != null) {
+ final int linenumber = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+
+ if (linenumber != -1) {
+ fTraceFilter.setBreakpoint(new PythonBreakpoint(reference, linenumber));
+ }
+ }
+ }
+
+ @Override
+ protected void breakpointRemoved(final Script script, final IBreakpoint breakpoint) {
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry == null) {
+ return;
+ }
+
+ final String reference = registry.getReference(script);
+ if (reference != null) {
+ final int linenumber = breakpoint.getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
+
+ if (linenumber != -1) {
+ fTraceFilter.removeBreakpoint(new PythonBreakpoint(reference, linenumber));
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
index 07b6391..ed18c3d 100644
--- a/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
+++ b/plugins/org.eclipse.ease.lang.python.py4j/src/org/eclipse/ease/lang/python/py4j/internal/Py4jDebuggerEngine.java
@@ -13,16 +13,18 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.ease.Script;
import org.eclipse.ease.ScriptEngineException;
+import org.eclipse.ease.debugging.EaseDebugFrame;
import org.eclipse.ease.debugging.ScriptStackTrace;
-import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
import org.eclipse.ease.debugging.model.EaseDebugVariable;
import org.eclipse.ease.lang.python.debugger.IPythonDebugEngine;
import org.eclipse.ease.lang.python.debugger.PythonDebugger;
+import org.eclipse.ease.lang.python.debugger.PythonEventDispatchJob;
import org.eclipse.ease.lang.python.debugger.ResourceHelper;
import org.eclipse.ease.lang.python.debugger.model.PythonDebugTarget;
@@ -36,10 +38,15 @@
public static final String ENGINE_ID = "org.eclipse.ease.lang.python.py4j.debugger.engine";
- private PythonDebugger fDebugger = null;
+ private Py4jDebugger fDebugger = null;
+ private ICodeTraceFilter fPyDebugger = null;
@Override
public void setDebugger(final PythonDebugger debugger) {
+ setDebugger((Py4jDebugger) debugger);
+ }
+
+ private void setDebugger(final Py4jDebugger debugger) {
fDebugger = debugger;
}
@@ -51,10 +58,12 @@
if (fDebugger != null) {
try {
// load python part of debugger
- final InputStream stream = ResourceHelper.getResourceStream("org.eclipse.ease.lang.python", "pysrc/edb.py");
+ final InputStream stream = ResourceHelper.getResourceStream("org.eclipse.ease.lang.python.py4j", "pysrc/py4jdb.py");
+ fPyDebugger = (ICodeTraceFilter) super.internalExecute(new Script("Load Python debugger", stream), null);
- internalSetVariable(PythonDebugger.PYTHON_DEBUGGER_VARIABLE, fDebugger);
- super.internalExecute(new Script("Load Python debugger", stream), null);
+ // Connect both sides
+ fPyDebugger.setDebugger(fDebugger);
+ fDebugger.setTraceFilter(fPyDebugger);
} catch (final Throwable e) {
throw new ScriptEngineException("Failed to load Python Debugger", e);
@@ -75,17 +84,16 @@
final PythonDebugTarget target = new PythonDebugTarget(launch, suspendOnStartup, suspendOnScriptLoad, showDynamicCode);
launch.addDebugTarget(target);
- final PythonDebugger debugger = new PythonDebugger(this, showDynamicCode);
+ final Py4jDebugger debugger = new Py4jDebugger(this, showDynamicCode);
setDebugger(debugger);
- new EventDispatchJob(target, debugger);
+ new PythonEventDispatchJob(target, debugger);
}
@Override
public ScriptStackTrace getExceptionStackTrace() {
- // FIXME to be implemented
- return null;
+ return getExceptionStackTrace(getThread());
}
@Override
@@ -101,11 +109,17 @@
@Override
public Collection<EaseDebugVariable> getVariables(Object scope) {
- // FIXME to be implemented correctly on the scope
+ final Map<String, Object> variablesMap;
+ if (scope instanceof EaseDebugFrame) {
+ final EaseDebugFrame frame = (EaseDebugFrame) scope;
+ variablesMap = frame.getVariables();
+ } else {
+ variablesMap = getVariables();
+ }
final Collection<EaseDebugVariable> variables = new ArrayList<>();
- for (final Entry<String, Object> entry : getVariables().entrySet())
+ for (final Entry<String, Object> entry : variablesMap.entrySet())
variables.add(createVariable(entry.getKey(), entry.getValue()));
return variables;
diff --git a/plugins/org.eclipse.ease.lang.python/pysrc/edb.py b/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
index fe9a706..5ac2d02 100644
--- a/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
+++ b/plugins/org.eclipse.ease.lang.python/pysrc/edb.py
@@ -12,14 +12,13 @@
# Python std library imports
import sys
-import __main__
import re
import ast
-from pygments.lexers._scilab_builtins import variables_kw
# : Regular expression if we are dealing with internal module.
_pyease_INTERNAL_CHECKER = re.compile(r"^<.+>$")
+
class _pyease_PyFrame:
'''
Python implementation of IPyFrame used for exchanging frame data
@@ -30,6 +29,7 @@
Python frame documentation is available here:
https://docs.python.org/3/reference/datamodel.html#types
'''
+
def __init__(self, frame):
'''
Constructor only stores frame to member.
@@ -177,7 +177,7 @@
if len(field_[1]) > 0:
if isinstance(field_[1][-1], ast.Expr):
final_expr = ast.Expression()
- popped = field_[1].pop(len(field_[1]) - 1); # Jython pop() will not accept -1 as parameter
+ popped = field_[1].pop(len(field_[1]) - 1); # Jython pop() will not accept -1 as parameter
final_expr.body = popped.value
return_value = None
@@ -224,7 +224,6 @@
# Check parent in stack
return self.ignore_frame(frame.f_back, False)
-
class Java:
implements = ['org.eclipse.ease.lang.python.debugger.ICodeTracer']
@@ -233,4 +232,4 @@
# Set up connection between eclipse and python
_pyease_eclipse_python_debugger = _pyease_CodeTracer()
_pyease_eclipse_python_debugger.set_debugger(_pyease_debugger)
-_pyease_debugger.setCodeTracer(_pyease_eclipse_python_debugger)
\ No newline at end of file
+_pyease_debugger.setCodeTracer(_pyease_eclipse_python_debugger)
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java
new file mode 100644
index 0000000..e7221a2
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/IPythonScriptRegistry.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.IScriptRegistry;
+
+/**
+ * Extension of {@link IScriptRegistry} to also add mapping from {@link Script} used in EASE world to {@link String} used in Python world.
+ */
+public interface IPythonScriptRegistry extends IScriptRegistry {
+
+ /**
+ * Return the {@link Script} identified by this reference {@link String}.
+ *
+ * @param reference
+ * Reference {@link String} to get {@link Script} for.
+ * @return {@link Script} identified by reference {@link String} or {@code null} if no mapping found.
+ */
+ Script getScript(String reference);
+
+ /**
+ * Return the reference {@link String} for this {@link Script}.
+ *
+ * @param script
+ * {@link Script} to get reference {@link String} for.
+ * @return Reference {@link String} for {@link Script} or {@code null} if no mapping found.
+ */
+ String getReference(Script script);
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java
new file mode 100644
index 0000000..325a94a
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonBreakpoint.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+/**
+ * Helper class to have simpler data exchange format between EASE breakpoint information and Python counterpart.
+ *
+ * FIXME: Could reuse functionality from org.python.pydev.debug.model.PyBreakpoint.
+ */
+public class PythonBreakpoint {
+ private final String fFilename;
+ private final int fLineno;
+
+ /**
+ * Constructor only stores parameters to members.
+ *
+ * @param filename
+ * Filename for the breakpoint.
+ * @param lineno
+ * Linenumber for the breakpoint.
+ */
+ public PythonBreakpoint(final String filename, final int lineno) {
+ fFilename = filename;
+ fLineno = lineno;
+ }
+
+ /**
+ * Returns the filename for the breakpoint.
+ *
+ * @return breakpoint's filename.
+ */
+ public String getFilename() {
+ return fFilename;
+ }
+
+ /**
+ * Returns the line number for the breakpoint.
+ *
+ * @return breakpoint's line number.
+ */
+ public int getLineno() {
+ return fLineno;
+ }
+
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
index cd2cf7e..cd97951 100644
--- a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonDebugger.java
@@ -12,10 +12,7 @@
package org.eclipse.ease.lang.python.debugger;
import java.io.File;
-import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
-import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.debug.core.DebugEvent;
@@ -27,6 +24,7 @@
import org.eclipse.ease.debugging.AbstractEaseDebugger;
import org.eclipse.ease.debugging.EaseDebugFrame;
import org.eclipse.ease.debugging.IScriptDebugFrame;
+import org.eclipse.ease.debugging.IScriptRegistry;
import org.eclipse.ease.debugging.ScriptStackTrace;
import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
@@ -52,7 +50,7 @@
* {@link IPyFrame} with information about the current execution frame.
*/
public PythonDebugFrame(final IPyFrame frame) {
- super(fScriptRegistry.get(frame.getFilename()), frame.getLineNumber(), TYPE_FILE);
+ super(getScriptRegistry() != null ? getScriptRegistry().getScript(frame.getFilename()) : null, frame.getLineNumber(), TYPE_FILE);
fFrame = frame;
}
@@ -129,14 +127,19 @@
* Top frame of stack.
* @return Stack based on given {@link IPyFrame}
*/
- private ScriptStackTrace getStacktrace(final IPyFrame origin) {
+ protected ScriptStackTrace getStacktrace(final IPyFrame origin) {
final ScriptStackTrace trace = new ScriptStackTrace();
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry == null) {
+ return trace;
+ }
+
IPyFrame frame = origin;
while (frame != null) {
- final Script script = fScriptRegistry.get(frame.getFilename());
+ final Script script = registry.getScript(frame.getFilename());
if (script != null) {
- if (isTrackedScript(fScriptRegistry.get(frame.getFilename())))
+ if (isTrackedScript(registry.getScript(frame.getFilename())))
trace.add(new PythonDebugFrame(frame));
}
@@ -155,7 +158,12 @@
* Type of trace step that occurred (ignored).
*/
public void traceDispatch(final IPyFrame frame, final String type) {
- final Script script = fScriptRegistry.get(frame.getFilename());
+ Script script = null;
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry != null) {
+ script = registry.getScript(frame.getFilename());
+ }
+
if (script != null) {
if (isTrackedScript(script)) {
@@ -192,7 +200,13 @@
*/
public Object execute(final Script script) {
try {
- return fCodeTracer.run(script, registerScript(script));
+ String reference = script.getTitle();
+ final IPythonScriptRegistry registry = getScriptRegistry();
+ if (registry != null) {
+ registry.put(script);
+ reference = registry.getReference(script);
+ }
+ return fCodeTracer.run(script, reference);
} catch (final Exception e) {
/*
@@ -210,39 +224,17 @@
}
/**
- * Map from custom filename to actual {@link Script} for easily identifying different scripts.
- * <p>
- * For each new file a unique filename is created using {@link #getHash(Script, Set)}.
- */
- private final Map<String, Script> fScriptRegistry = new HashMap<>();
-
- private String registerScript(final Script script) {
- final String reference = getHash(script, fScriptRegistry.keySet());
- fScriptRegistry.put(reference, script);
-
- return reference;
- }
-
- /**
- * Creates a unique filename for the given {@link Script}.
+ * Returns the {@link IPythonScriptRegistry} used by the debugger.
*
- * @param script
- * {@link Script} to get unique filename for.
- * @param existingKeys
- * Existing keys to avoid duplicates.
- * @return Unique filename for given {@link Script}.
+ * If debugger has been set up incorrectly (e.g. script registry not tailored for python) this will return {@code null}.
+ *
+ * @return {@link IPythonScriptRegistry} or {@code null}.
*/
- private static String getHash(final Script script, final Set<String> existingKeys) {
- final StringBuilder buffer = new StringBuilder("__ref_");
- buffer.append(script.isDynamic() ? "dyn" : script.getCommand().toString());
- buffer.append("_");
-
- for (int index = 0; index < 10; index++)
- buffer.append((char) ('a' + new Random().nextInt(26)));
-
- if (existingKeys.contains(buffer.toString()))
- return getHash(script, existingKeys);
-
- return buffer.toString();
+ public IPythonScriptRegistry getScriptRegistry() {
+ final IScriptRegistry scriptRegistry = super.getScriptRegistry();
+ if (scriptRegistry instanceof IPythonScriptRegistry) {
+ return (IPythonScriptRegistry) scriptRegistry;
+ }
+ return null;
}
}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java
new file mode 100644
index 0000000..aa4d111
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonEventDispatchJob.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.IScriptRegistry;
+import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
+import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
+
+/**
+ * Extension of {@link EventDispatchJob} using {@link IPythonScriptRegistry} rather than {@link IScriptRegistry}.
+ */
+public class PythonEventDispatchJob extends EventDispatchJob implements IPythonScriptRegistry {
+ /**
+ * {@link IPythonScriptRegistry} to be used instead of parent's {@link IScriptRegistry}.
+ */
+ private final IPythonScriptRegistry fScriptRegistry;
+
+ /**
+ * Constructor creating new {@link PythonScriptRegistry} for overload.
+ *
+ * @see EventDispatchJob#EventDispatchJob(IEventProcessor, IEventProcessor, IScriptRegistry)
+ */
+ public PythonEventDispatchJob(IEventProcessor host, IEventProcessor debugger) {
+ super(host, debugger, new PythonScriptRegistry());
+ fScriptRegistry = (IPythonScriptRegistry) getScriptRegistry();
+ }
+
+ @Override
+ public Script getScript(String reference) {
+ return fScriptRegistry.getScript(reference);
+ }
+
+ @Override
+ public String getReference(Script script) {
+ return fScriptRegistry.getReference(script);
+ }
+
+}
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java
new file mode 100644
index 0000000..abc8dfb
--- /dev/null
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/debugger/PythonScriptRegistry.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.debugger;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import org.eclipse.ease.Script;
+import org.eclipse.ease.debugging.ScriptRegistry;
+
+/**
+ * Default implementation of {@link IPythonScriptRegistry} simply using maps to perform 1:1 mapping.
+ */
+public class PythonScriptRegistry extends ScriptRegistry implements IPythonScriptRegistry {
+
+ /** List of framework files that should be ignored by script registry. */
+ private static final List<String> IGNORED_FILES = Arrays.asList(new String[] { "cp1252.py", "bdb.py", "ast.py", "threading.py" });
+
+ /** Lookup from {@link Script} in EASE world to {@link String} in Python world */
+ private final Map<Script, String> fScriptMap = new HashMap<>();
+
+ /** Reverse lookup from {@link String} in Python world to {@link Script} in EASE world */
+ private final Map<String, Script> fStringMap = new HashMap<>();
+
+ @Override
+ public void put(Script script) {
+ // IResource <-> Script mapping
+ super.put(script);
+
+ // Script <-> String mapping
+ final String reference = uid(script);
+ fStringMap.put(reference, script);
+ fScriptMap.put(script, reference);
+
+ }
+
+ @Override
+ public Script getScript(String reference) {
+ Script script = fStringMap.get(reference);
+ if (script == null) {
+ final File file = new File(reference);
+ if (file.exists()) {
+ if (!IGNORED_FILES.contains(file.getName())) {
+ script = new Script(file);
+ put(script);
+ }
+ }
+ }
+
+ return script;
+ }
+
+ @Override
+ public String getReference(Script script) {
+ return fScriptMap.get(script);
+ }
+
+ /**
+ * Creates a unique reference identifier for the given script.
+ *
+ * @param script
+ * Script to get unique identifier for.
+ * @return Unique identifer for script.
+ */
+ private String uid(final Script script) {
+ final Set<String> existingKeys = fStringMap.keySet();
+ final StringBuilder buffer = new StringBuilder("__ref_");
+ buffer.append(script.isDynamic() ? "dyn" : script.getCommand().toString());
+ buffer.append("_");
+
+ for (int index = 0; index < 10; index++)
+ buffer.append((char) ('a' + new Random().nextInt(26)));
+
+ if (existingKeys.contains(buffer.toString()))
+ return uid(script);
+
+ return buffer.toString();
+ }
+
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
index 905e15f..bdd24bf 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
@@ -71,9 +71,11 @@
private EventDispatchJob fDispatcher;
+ private IScriptRegistry fScriptRegistry;
+
private IDebugEngine fEngine;
- private final Map<Script, List<IBreakpoint>> fBreakpoints = new HashMap<>();
+ protected final Map<Script, List<IBreakpoint>> fBreakpoints = new HashMap<>();
private final boolean fShowDynamicCode;
@@ -92,6 +94,15 @@
}
/**
+ * Protected getter for event dispatch job.
+ *
+ * @return {@link #fDispatcher}.
+ */
+ protected EventDispatchJob getDispatcher() {
+ return fDispatcher;
+ }
+
+ /**
* Setter method for dispatcher.
*
* @param dispatcher
@@ -100,6 +111,28 @@
@Override
public void setDispatcher(final EventDispatchJob dispatcher) {
fDispatcher = dispatcher;
+
+ // TODO: Use setScriptRegistry explicitly
+ setScriptRegistry(dispatcher);
+ }
+
+ /**
+ * Setter method for script registry for lookups between different types of file identifications.
+ *
+ * @param registry
+ * Script registry to be used.
+ */
+ public void setScriptRegistry(final IScriptRegistry registry) {
+ fScriptRegistry = registry;
+ }
+
+ /**
+ * Protected getter for subclasses to have access to {@link #fScriptRegistry}.
+ *
+ * @return {@link #fScriptRegistry}
+ */
+ protected IScriptRegistry getScriptRegistry() {
+ return fScriptRegistry;
}
/**
@@ -127,10 +160,15 @@
if (!fBreakpoints.containsKey(script))
fBreakpoints.put(script, new ArrayList<IBreakpoint>());
- if (((BreakpointRequest) event).getMode() == BreakpointRequest.Mode.ADD)
- fBreakpoints.get(script).add(((BreakpointRequest) event).getBreakpoint());
- else
- fBreakpoints.get(script).remove(((BreakpointRequest) event).getBreakpoint());
+ if (((BreakpointRequest) event).getMode() == BreakpointRequest.Mode.ADD) {
+ final IBreakpoint breakpoint = ((BreakpointRequest) event).getBreakpoint();
+ fBreakpoints.get(script).add(breakpoint);
+ breakpointAdded(script, breakpoint);
+ } else {
+ final IBreakpoint breakpoint = ((BreakpointRequest) event).getBreakpoint();
+ fBreakpoints.get(script).remove(breakpoint);
+ breakpointRemoved(script, breakpoint);
+ }
}
} else if (event instanceof TerminateRequest) {
@@ -149,6 +187,34 @@
}
}
+ /**
+ * Callback triggered when breakpoint has been added.
+ *
+ * No-op implementations can be overridden by subclasses.
+ *
+ * @param script
+ * Script this breakpoint belongs to.
+ * @param breakpoint
+ * Breakpoint information.
+ */
+ protected void breakpointAdded(final Script script, final IBreakpoint breakpoint) {
+
+ }
+
+ /**
+ * Callback triggered when breakpoint has been removed.
+ *
+ * No-op implementations can be overridden by subclasses.
+ *
+ * @param script
+ * Script this breakpoint belonged to.
+ * @param breakpoint
+ * Breakpoint information.
+ */
+ protected void breakpointRemoved(final Script script, final IBreakpoint breakpoint) {
+
+ }
+
protected void suspend(final IDebuggerEvent event) {
// only suspend if there possibly exists someone to wake us up again
@@ -239,7 +305,7 @@
}
}
- private void resume(final int resumeType, Object thread) {
+ protected void resume(final int resumeType, Object thread) {
// called by engine thread
final ThreadState threadState = fThreadStates.get(thread);
@@ -331,7 +397,14 @@
return null;
}
- protected boolean isTrackedScript(final Script script) {
+ /**
+ * Checks if the given script is tracked by debugger.
+ *
+ * @param script
+ * Script to be checked.
+ * @return {@code true} if script is being tracked by debugger.
+ */
+ public boolean isTrackedScript(final Script script) {
return (!script.isDynamic()) || fShowDynamicCode;
}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java
new file mode 100644
index 0000000..0132f36
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/IScriptRegistry.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.debugging;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.ease.Script;
+
+/**
+ * Simple interface for mapping between data types in different debugger realms.
+ *
+ * The eclipse framework uses {@link IResource} objects to identify files while EASE relies on {@link Script} objects.
+ */
+public interface IScriptRegistry {
+ /**
+ * Add a new {@link Script} to the registry and store its mapping.
+ *
+ * @param script
+ * Script to be stored in registry.
+ */
+ public void put(Script script);
+
+ /**
+ * Return the {@link Script} identified by this {@link IResource}.
+ *
+ * @param resource
+ * {@link IResource} to get {@link Script} for.
+ * @return {@link Script} identified by {@link IResource} or {@code null} if no mapping found.
+ */
+ public Script getScript(IResource resource);
+
+ /**
+ * Get the {@link IResource} identified by this {@link Script}.
+ *
+ * @param resource
+ * {@link Script} to get {@link IResource} for.
+ * @return {@link IResource} identified by {@link Script} or {@code null} if no mapping found.
+ */
+ public IResource getResource(Script script);
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java
new file mode 100644
index 0000000..5a78e88
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/ScriptRegistry.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.debugging;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.ease.Script;
+
+/**
+ * Default implementation of {@link IScriptRegistry} simply using maps to perform 1:1 mapping.
+ */
+public class ScriptRegistry implements IScriptRegistry {
+ /** Lookup from {@link IResource} in eclipse world to {@link Script} in EASE world */
+ private final Map<IResource, Script> fResourceMap = new HashMap<>();
+
+ /** Reverse lookup from {@link Script} in EASE world to {@link IResource} in eclipse world */
+ private final Map<Script, IResource> fScriptMap = new HashMap<>();
+
+ @Override
+ public Script getScript(IResource resource) {
+ return fResourceMap.get(resource);
+ }
+
+ @Override
+ public IResource getResource(Script script) {
+ return fScriptMap.get(script);
+ }
+
+ @Override
+ public void put(Script script) {
+ if (script.getCommand() instanceof IResource) {
+ fResourceMap.put((IResource) script.getCommand(), script);
+ fScriptMap.put(script, (IResource) script.getCommand());
+ }
+ }
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
index 8bd730b..b488c34 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/dispatcher/EventDispatchJob.java
@@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -20,14 +21,17 @@
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.ease.Activator;
import org.eclipse.ease.Logger;
+import org.eclipse.ease.Script;
import org.eclipse.ease.debugging.DebugTracer;
+import org.eclipse.ease.debugging.IScriptRegistry;
+import org.eclipse.ease.debugging.ScriptRegistry;
import org.eclipse.ease.debugging.events.IDebugEvent;
import org.eclipse.ease.debugging.events.debugger.IDebuggerEvent;
import org.eclipse.ease.debugging.events.model.IModelRequest;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.swt.widgets.Display;
-public class EventDispatchJob extends Job {
+public class EventDispatchJob extends Job implements IScriptRegistry {
/** Cached events. */
private final List<IDebugEvent> fEvents = new ArrayList<>();
@@ -41,6 +45,9 @@
/** Debug engine. */
private final IEventProcessor fDebugger;
+ /** Script registry for performing script lookups */
+ private final IScriptRegistry fScriptRegistry;
+
/**
* Creates a new dispatcher. The dispatcher automatically attaches to the host and the debugger. Further the job get automatically scheduled.
*
@@ -50,6 +57,20 @@
* debugger implementation
*/
public EventDispatchJob(final IEventProcessor host, final IEventProcessor debugger) {
+ this(host, debugger, new ScriptRegistry());
+ }
+
+ /**
+ * Protected constructor for externally setting {@link IScriptRegistry}.
+ *
+ * @param host
+ * debug model
+ * @param debugger
+ * debugger implementation
+ * @param scriptRegistry
+ * IScriptRegistry to be used (must not be {@code null})
+ */
+ protected EventDispatchJob(final IEventProcessor host, final IEventProcessor debugger, final IScriptRegistry scriptRegistry) {
super(debugger + " event dispatcher");
fModel = host;
@@ -58,10 +79,19 @@
fModel.setDispatcher(this);
fDebugger.setDispatcher(this);
+ fScriptRegistry = scriptRegistry;
+
setSystem(true);
schedule();
}
+ /**
+ * Protected getter for the {@link #fScriptRegistry}.
+ */
+ protected IScriptRegistry getScriptRegistry() {
+ return fScriptRegistry;
+ }
+
public void addEvent(final IDebugEvent event) {
synchronized (fEvents) {
DebugTracer.debug("Dispatcher", "[+] " + event);
@@ -135,4 +165,19 @@
fEvents.notifyAll();
}
}
+
+ @Override
+ public Script getScript(IResource resource) {
+ return fScriptRegistry.getScript(resource);
+ }
+
+ @Override
+ public IResource getResource(Script script) {
+ return fScriptRegistry.getResource(script);
+ }
+
+ @Override
+ public void put(Script script) {
+ fScriptRegistry.put(script);
+ }
}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
index cd63d0e..ae80c63 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugElement.java
@@ -61,7 +61,8 @@
@Override
public synchronized void terminate() {
- getDebugTarget().getProcess().terminate();
+ if (getDebugTarget().getProcess() != null)
+ getDebugTarget().getProcess().terminate();
}
@Override
@@ -78,17 +79,22 @@
@Override
public boolean canDisconnect() {
- return getDebugTarget().getProcess().canDisconnect();
+ if (getDebugTarget().getProcess() != null)
+ return getDebugTarget().getProcess().canDisconnect();
+ return false;
}
@Override
public void disconnect() {
- getDebugTarget().getProcess().disconnect();
+ if (getDebugTarget().getProcess() != null)
+ getDebugTarget().getProcess().disconnect();
}
@Override
public boolean isDisconnected() {
- return getDebugTarget().getProcess().isDisconnected();
+ if (getDebugTarget().getProcess() != null)
+ return getDebugTarget().getProcess().isDisconnected();
+ return false;
}
// ************************************************************
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
index 85c942b..9afc1d3 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
@@ -101,8 +101,11 @@
else if ((((ScriptReadyEvent) event).isRoot()) && (getDebugTarget().isSuspendOnStartup()))
debugThread.stepInto();
- // default resume request
- debugThread.resume();
+ else {
+ if (!debugThread.getState().equals(State.STEPPING)) {
+ debugThread.resume();
+ }
+ }
} else if (event instanceof ThreadCreatedEvent) {
EaseDebugThread debugThread = findDebugThread(((ThreadCreatedEvent) event).getThread());
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
index a9bbe66..589c1b4 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugTarget.java
@@ -28,6 +28,7 @@
import org.eclipse.ease.Activator;
import org.eclipse.ease.Script;
import org.eclipse.ease.debugging.DebugTracer;
+import org.eclipse.ease.debugging.IScriptRegistry;
import org.eclipse.ease.debugging.dispatcher.EventDispatchJob;
import org.eclipse.ease.debugging.dispatcher.IEventProcessor;
import org.eclipse.ease.debugging.events.IDebugEvent;
@@ -49,6 +50,8 @@
private EventDispatchJob fDispatcher;
+ private IScriptRegistry fScriptRegistry;
+
private EaseDebugProcess fProcess = null;
private final ILaunch fLaunch;
@@ -137,6 +140,19 @@
@Override
public void setDispatcher(final EventDispatchJob dispatcher) {
fDispatcher = dispatcher;
+
+ // TODO: Use setScriptRegistry explicitly
+ setScriptRegistry(dispatcher);
+ }
+
+ /**
+ * Setter method for script registry for lookups between different types of file identifications.
+ *
+ * @param registry
+ * Script registry to be used.
+ */
+ public void setScriptRegistry(final IScriptRegistry registry) {
+ fScriptRegistry = registry;
}
@Override
@@ -250,6 +266,14 @@
private synchronized void handleBreakpointChange(final IBreakpoint breakpoint, final Mode mode) {
final IResource affectedResource = breakpoint.getMarker().getResource();
+ // Try to perform lookup via script registry (Thread independent)
+ if (fScriptRegistry != null) {
+ final Script script = fScriptRegistry.getScript(affectedResource);
+ if (script != null) {
+ fireDispatchEvent(new BreakpointRequest(script, breakpoint, mode));
+ return;
+ }
+ }
// see if we are affected by this breakpoint
for (final EaseDebugThread thread : getThreads()) {
for (final IStackFrame frame : thread.getStackFrames()) {
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
index 4bbdf6e..47fdf90 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugValue.java
@@ -169,6 +169,13 @@
fVariables.add(variable);
}
+ } else if (getValue() instanceof Collection<?>) {
+ final Collection<?> asCollection = (Collection<?>) getValue();
+ int index = 0;
+ for (final Object value : asCollection) {
+ final EaseDebugVariable variable = new EaseDebugVariable(String.format("[%d]", index++), value, fParent, null);
+ fVariables.add(variable);
+ }
} else {
// populate generic Java fields
diff --git a/tests/org.eclipse.ease.lang.python.py4j.test/build.properties b/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
index b107977..ab59e59 100644
--- a/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
+++ b/tests/org.eclipse.ease.lang.python.py4j.test/build.properties
@@ -1,3 +1,4 @@
source.. = src/
bin.includes = META-INF/,\
- .
+ .,\
+ fragment.xml
diff --git a/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
new file mode 100644
index 0000000..7b5e43a
--- /dev/null
+++ b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Martin Kloesch and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Martin Kloesch - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.lang.python.py4j;
+
+import static org.junit.Assume.assumeTrue;
+
+import java.io.IOException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.model.IBreakpoint;
+import org.eclipse.debug.core.model.LineBreakpoint;
+import org.eclipse.ease.lang.python.py4j.internal.Py4jDebuggerEngine;
+import org.eclipse.ease.testhelper.AbstractDebugTest;
+
+public class Py4jDebugTest extends AbstractDebugTest {
+
+ @Override
+ protected String getEngineId() {
+ return Py4jDebuggerEngine.ENGINE_ID;
+ }
+
+ @Override
+ protected String getScriptSource() throws IOException {
+ return readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py");
+ }
+
+ @Override
+ protected LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
+ final IMarker marker = file.createMarker("org.python.pydev.debug.pyStopBreakpointMarker");
+ marker.setAttribute("org.eclipse.debug.core.enabled", true);
+ marker.setAttribute("org.eclipse.debug.core.id", "org.python.pydev.debug");
+ marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+
+ final LineBreakpoint breakpoint = new LineBreakpoint() {
+
+ @Override
+ public String getModelIdentifier() {
+ return "org.python.pydev.debug";
+ }
+ };
+
+ breakpoint.setMarker(marker);
+
+ DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
+
+ return breakpoint;
+ }
+
+ @Override
+ protected IBreakpoint[] getBreakpoints() throws CoreException {
+ return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints("org.python.pydev.debug");
+ }
+
+ @Override
+ public void nativeObjectVariable() throws CoreException, IOException {
+ // TODO: Currently not possible to access native Python variables
+ assumeTrue(false);
+ }
+
+ @Override
+ public void nativeArrayVariable() throws CoreException, IOException {
+ // TODO: Currently not possible to access native Python variables
+ assumeTrue(false);
+ }
+}
diff --git a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
index 2eccab5..b5cd86b 100644
--- a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
+++ b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
@@ -56,7 +56,7 @@
public abstract class AbstractDebugTest extends WorkspaceTestHelper {
- private static final int TEST_TIMEOUT = 5000;
+ private static final int TEST_TIMEOUT = 7000;
private interface IDebugElementProvider {
EaseDebugElement getDebugElement();