/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-13 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.mode.java.runner; import processing.app.*; import processing.app.exec.StreamRedirectThread; import processing.core.*; import processing.mode.java.JavaBuild; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.io.*; import java.util.*; import com.sun.jdi.*; import com.sun.jdi.connect.*; import com.sun.jdi.event.*; import com.sun.jdi.request.*; /** * Runs a compiled sketch. As of release 0136, all sketches are run externally * to the environment so that a debugging interface can be used. This opens up * future options for a decent debugger, but in the meantime fixes several * problems with output and error streams, messages getting lost on Mac OS X, * the run/stop buttons not working, libraries not shutting down, exceptions * not coming through, exceptions being printed twice, having to force quit * if you make a bad while() loop, and so on. */ public class Runner implements MessageConsumer { // private boolean presenting; // Object that listens for error messages or exceptions. protected RunnerListener listener; // Running remote VM protected VirtualMachine vm; // Thread transferring remote error stream to our error stream protected Thread errThread = null; // Thread transferring remote output stream to our output stream protected Thread outThread = null; // Mode for tracing the Trace program (default= 0 off) // protected int debugTraceMode = 0; // Do we want to watch assignments to fields // protected boolean watchFields = false; // // Class patterns for which we don't want events // protected String[] excludes = { // "java.*", "javax.*", "sun.*", "com.sun.*", // "apple.*", // "processing.*" // }; protected SketchException exception; protected Editor editor; protected JavaBuild build; protected Process process; public Runner(JavaBuild build, RunnerListener listener) throws SketchException { this.listener = listener; // this.sketch = sketch; this.build = build; if (listener instanceof Editor) { this.editor = (Editor) listener; // } else { // System.out.println("actually it's a " + listener.getClass().getName()); } // Make sure all the imported libraries will actually run with this setup. int bits = Base.getNativeBits(); for (Library library : build.getImportedLibraries()) { if (!library.supportsArch(PApplet.platform, bits)) { System.err.println(library.getName() + " does not run in " + bits + "-bit mode."); int opposite = (bits == 32) ? 64 : 32; if (Base.isMacOS()) { //if (library.supportsArch(PConstants.MACOSX, opposite)) { // should always be true throw new SketchException("To use " + library.getName() + ", " + "switch to " + opposite + "-bit mode in Preferences."); //} } else { throw new SketchException(library.getName() + " is only compatible " + "with the " + opposite + "-bit download of Processing."); //throw new SketchException(library.getName() + " does not run in " + bits + "-bit mode."); // "To use this library, switch to 32-bit mode in Preferences." (OS X) // "To use this library, you must use the 32-bit version of Processing." } } } } public void launch(boolean presenting) { if (launchVirtualMachine(presenting)) { generateTrace(); } } // public void launch(String appletClassName, boolean presenting) { // this.appletClassName = appletClassName; public boolean launchVirtualMachine(boolean presenting) { // this.presenting = presenting; String[] vmParams = getMachineParams(); String[] sketchParams = getSketchParams(presenting); int port = 8000 + (int) (Math.random() * 1000); String portStr = String.valueOf(port); // Older (Java 1.5 and earlier) version, go figure // String jdwpArg = "-Xrunjdwp:transport=dt_socket,address=" + portStr + ",server=y,suspend=y"; // String debugArg = "-Xdebug"; // Newer (Java 1.5+) version that uses JVMTI String jdwpArg = "-agentlib:jdwp=transport=dt_socket,address=" + portStr + ",server=y,suspend=y"; String[] commandArgs = null; if (!Base.isMacOS()) { commandArgs = new String[] { //"java", Base.getJavaPath(), jdwpArg }; } else { // Decided to just set this to 1.6 only, because otherwise it's gonna // be a shitshow if folks are getting Apple's 1.6 with 32-bit and // Oracle's 1.7 when run in 64-bit mode. ("Why does my sketch suck in // 64-bit? Why is retina broken?) // The --request flag will prompt to install Apple's 1.6 JVM if none is // available. We're specifying 1.6 so that we can get support for both // 32- and 64-bit, because Oracle won't be releasing Java 1.7 in 32-bit. // Helpfully, the --request flag is not present on Mac OS X 10.6 // (luckily it is also not needed, because 1.6 is installed by default) // but it requires an additional workaround to not use that flag, // otherwise will see an error about an unsupported option. The flag is // available with 10.7 and 10.8, the only other supported versions of // OS X at this point, because we require 10.6.8 and higher. That also // means we don't need to check for any other OS versions, the user is // a douchebag and modifies Info.plist to get around the restriction. if (false) { if (System.getProperty("os.version").startsWith("10.6")) { commandArgs = new String[] { "/usr/libexec/java_home", "--version", "1.6", "--exec", "java", "-d" + Base.getNativeBits(), jdwpArg }; } else { // for 10.7, 10.8, etc commandArgs = new String[] { "/usr/libexec/java_home", "--request", // install on-demand "--version", "1.6", "--exec", "java", "-d" + Base.getNativeBits(), // debugArg, jdwpArg }; } } else { // testing jdk-7u40 commandArgs = new String[] { //"/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/Contents/Home/bin/java", Base.getJavaPath(), jdwpArg }; } } commandArgs = PApplet.concat(commandArgs, vmParams); commandArgs = PApplet.concat(commandArgs, sketchParams); // PApplet.println(commandArgs); // commandArg.setValue(commandArgs); launchJava(commandArgs); // try { // Thread.sleep(2000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // boolean available = false; // while (!available) { // try { // Socket socket = new Socket((String) null, port); //// socket.close(); // // this should mean we're all set? // available = true; // // } catch (IOException e) { // System.out.println("waiting"); // //e.printStackTrace(); // try { // Thread.sleep(100); // } catch (InterruptedException e1) { // e1.printStackTrace(); // } // } // } AttachingConnector connector = (AttachingConnector) findConnector("com.sun.jdi.SocketAttach"); //PApplet.println(connector); // gets the defaults Map arguments = connector.defaultArguments(); // Connector.Argument addressArg = // (Connector.Argument)arguments.get("address"); // addressArg.setValue(addr); Connector.Argument portArg = (Connector.Argument)arguments.get("port"); portArg.setValue(portStr); // Connector.Argument timeoutArg = // (Connector.Argument)arguments.get("timeout"); // timeoutArg.setValue("10000"); //PApplet.println(connector); // prints the current //com.sun.tools.jdi.AbstractLauncher al; //com.sun.tools.jdi.RawCommandLineLauncher rcll; //System.out.println(PApplet.javaVersion); // http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch try { // boolean available = false; // while (!available) { while (true) { try { vm = connector.attach(arguments); // vm = connector.attach(arguments); if (vm != null) { // generateTrace(); // available = true; return true; } } catch (IOException e) { // System.out.println("waiting"); // e.printStackTrace(); try { Thread.sleep(100); } catch (InterruptedException e1) { e1.printStackTrace(); } } } // } catch (IOException exc) { // throw new Error("Unable to launch target VM: " + exc); } catch (IllegalConnectorArgumentsException exc) { throw new Error("Internal error: " + exc); } } protected String[] getMachineParams() { ArrayList params = new ArrayList(); //params.add("-Xint"); // interpreted mode //params.add("-Xprof"); // profiler //params.add("-Xaprof"); // allocation profiler //params.add("-Xrunhprof:cpu=samples"); // old-style profiler // TODO change this to use run.args = true, run.args.0, run.args.1, etc. // so that spaces can be included in the arg names String options = Preferences.get("run.options"); if (options.length() > 0) { String pieces[] = PApplet.split(options, ' '); for (int i = 0; i < pieces.length; i++) { String p = pieces[i].trim(); if (p.length() > 0) { params.add(p); } } } // params.add("-Djava.ext.dirs=nuffing"); if (Preferences.getBoolean("run.options.memory")) { params.add("-Xms" + Preferences.get("run.options.memory.initial") + "m"); params.add("-Xmx" + Preferences.get("run.options.memory.maximum") + "m"); } if (Base.isMacOS()) { params.add("-Xdock:name=" + build.getSketchClassName()); // params.add("-Dcom.apple.mrj.application.apple.menu.about.name=" + // sketch.getMainClassName()); } // sketch.libraryPath might be "" // librariesClassPath will always have sep char prepended params.add("-Djava.library.path=" + build.getJavaLibraryPath() + File.pathSeparator + System.getProperty("java.library.path")); params.add("-cp"); params.add(build.getClassPath()); // params.add(sketch.getClassPath() + // File.pathSeparator + // Base.librariesClassPath); // enable assertions // http://dev.processing.org/bugs/show_bug.cgi?id=1188 params.add("-ea"); //PApplet.println(PApplet.split(sketch.classPath, ':')); String outgoing[] = new String[params.size()]; params.toArray(outgoing); // PApplet.println(outgoing); // PApplet.println(PApplet.split(outgoing[0], ":")); // PApplet.println(); // PApplet.println("class path"); // PApplet.println(PApplet.split(outgoing[2], ":")); return outgoing; //return (String[]) params.toArray(); // System.out.println("sketch class path"); // PApplet.println(PApplet.split(sketch.classPath, ';')); // System.out.println(); // System.out.println("libraries class path"); // PApplet.println(PApplet.split(Base.librariesClassPath, ';')); // System.out.println(); } protected String[] getSketchParams(boolean presenting) { ArrayList params = new ArrayList(); // It's dangerous to add your own main() to your code, // but if you've done it, we'll respect your right to hang yourself. // http://processing.org/bugs/bugzilla/1446.html if (build.getFoundMain()) { params.add(build.getSketchClassName()); } else { params.add("processing.core.PApplet"); // get the stored device index (starts at 0) int runDisplay = Preferences.getInteger("run.display"); // If there was a saved location (this guy has been run more than once) // then the location will be set to the last position of the sketch window. // This will be passed to the PApplet runner using something like // --location=30,20 // Otherwise, the editor location will be passed, and the applet will // figure out where to place itself based on the editor location. // --editor-location=150,20 if (editor != null) { // if running processing-cmd, don't do placement GraphicsDevice editorDevice = editor.getGraphicsConfiguration().getDevice(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] devices = ge.getScreenDevices(); // Make sure the display set in Preferences actually exists GraphicsDevice runDevice = editorDevice; if (runDisplay >= 0 && runDisplay < devices.length) { runDevice = devices[runDisplay]; } else { runDevice = editorDevice; for (int i = 0; i < devices.length; i++) { if (devices[i] == runDevice) { runDisplay = i; break; // Don't set the pref, might be a temporary thing. Users can // open/close Preferences to reset the device themselves. // Preferences.setInteger("run.display", runDisplay); } } } Point windowLocation = editor.getSketchLocation(); // if (windowLocation != null) { // // could check to make sure the sketch location is on the device // // that's specified in Preferences, but that's going to be annoying // // if you move a sketch to another window, then it keeps jumping // // back to the specified window. //// Rectangle screenRect = //// runDevice.getDefaultConfiguration().getBounds(); // } if (windowLocation == null) { if (editorDevice == runDevice) { // If sketches are to be shown on the same display as the editor, // provide the editor location so the sketch's main() can place it. Point editorLocation = editor.getLocation(); params.add(PApplet.ARGS_EDITOR_LOCATION + "=" + editorLocation.x + "," + editorLocation.y); } else { // The sketch's main() will set a location centered on the new // display. It has to happen in main() because the width/height // of the sketch are not known here. // Set a location centered on the other display // Rectangle screenRect = // runDevice.getDefaultConfiguration().getBounds(); // int runX = // params.add(PApplet.ARGS_LOCATION + "=" + runX + "," + runY); } } else { params.add(PApplet.ARGS_LOCATION + "=" + windowLocation.x + "," + windowLocation.y); } params.add(PApplet.ARGS_EXTERNAL); } params.add(PApplet.ARGS_DISPLAY + "=" + runDisplay); if (presenting) { params.add(PApplet.ARGS_FULL_SCREEN); // if (Preferences.getBoolean("run.present.exclusive")) { // params.add(PApplet.ARGS_EXCLUSIVE); // } params.add(PApplet.ARGS_STOP_COLOR + "=" + Preferences.get("run.present.stop.color")); params.add(PApplet.ARGS_BGCOLOR + "=" + Preferences.get("run.present.bgcolor")); } params.add(build.getSketchClassName()); params.add(PApplet.ARGS_SKETCH_FOLDER + "=" + build.getSketchPath()); // Adding sketch path in the end coz it's likely to // contain spaces and things go wrong on UNIX systems. } // String outgoing[] = new String[params.size()]; // params.toArray(outgoing); // return outgoing; return params.toArray(new String[0]); } protected void launchJava(final String[] args) { new Thread(new Runnable() { public void run() { // PApplet.println("java starting"); process = PApplet.exec(args); try { // PApplet.println("java waiting"); int result = process.waitFor(); // PApplet.println("java done waiting"); if (result != 0) { String[] errorStrings = PApplet.loadStrings(process.getErrorStream()); String[] inputStrings = PApplet.loadStrings(process.getInputStream()); // PApplet.println("launchJava stderr:"); // PApplet.println(errorStrings); // PApplet.println("launchJava stdout:"); PApplet.printArray(inputStrings); if (errorStrings != null && errorStrings.length > 1) { if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) { Base.showWarning("Way Too High", "Please lower the value for \u201Cmaximum available memory\u201D in the\n" + "Preferences window. For more information, read Help \u2192 Troubleshooting.", null); } else { for (String err : errorStrings) { System.err.println(err); } System.err.println("Using startup command: " + PApplet.join(args, " ")); } } else { //exc.printStackTrace(); System.err.println("Could not run the sketch (Target VM failed to initialize)."); if (Preferences.getBoolean("run.options.memory")) { // Only mention this if they've even altered the memory setup System.err.println("Make sure that you haven't set the maximum available memory too high."); } System.err.println("For more information, read revisions.txt and Help \u2192 Troubleshooting."); } // changing this to separate editor and listener [091124] //if (editor != null) { listener.statusError("Could not run the sketch."); //} // return null; } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } /* protected VirtualMachine launchVirtualMachine(String[] vmParams, String[] classParams) { //vm = launchTarget(sb.toString()); LaunchingConnector connector = (LaunchingConnector) findConnector("com.sun.jdi.RawCommandLineLaunch"); //PApplet.println(connector); // gets the defaults //Map arguments = connectorArguments(connector, mainArgs); Map arguments = connector.defaultArguments(); Connector.Argument commandArg = (Connector.Argument)arguments.get("command"); // Using localhost instead of 127.0.0.1 sometimes causes a // "Transport Error 202" error message when trying to run. // http://dev.processing.org/bugs/show_bug.cgi?id=895 // String addr = "127.0.0.1:" + (8000 + (int) (Math.random() * 1000)); //String addr = "localhost:" + (8000 + (int) (Math.random() * 1000)); // Better yet, host is not needed, so using just the port for the address String addr = "" + (8000 + (int) (Math.random() * 1000)); String commandArgs = "java -Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y "; if (Base.isMacOS()) { // Decided to just set this to 1.6 only, because otherwise it's gonna // be a shitshow if folks are getting Apple's 1.6 with 32-bit and // Oracle's 1.7 when run in 64-bit mode. ("Why does my sketch suck in // 64-bit? Why is retina broken?) // The --request flag will prompt to install Apple's 1.6 JVM if none is // available. We're specifying 1.6 so that we can get support for both // 32- and 64-bit, because Oracle won't be releasing Java 1.7 in 32-bit. // Helpfully, the --request flag is not present on Mac OS X 10.6 // (luckily it is also not needed, because 1.6 is installed by default) // but it requires an additional workaround to not use that flag, // otherwise will see an error about an unsupported option. The flag is // available with 10.7 and 10.8, the only other supported versions of // OS X at this point, because we require 10.6.8 and higher. That also // means we don't need to check for any other OS versions, unless // is a douchebag and modifies Info.plist to get around the restriction. addr = "" + (8000 + (int) (Math.random() * 1000)); commandArgs = "/usr/libexec/java_home " + (System.getProperty("os.version").startsWith("10.6") ? "" : "--request ") + "--version 1.6 " + "--exec java " + "-d" + Base.getNativeBits() + " " + "-Xrunjdwp:transport=dt_socket,address=" + addr + ",suspend=y "; } for (int i = 0; i < vmParams.length; i++) { commandArgs = addArgument(commandArgs, vmParams[i], ' '); } if (classParams != null) { for (int i = 0; i < classParams.length; i++) { commandArgs = addArgument(commandArgs, classParams[i], ' '); } } System.out.println("commandArgs is " + commandArgs); commandArg.setValue(commandArgs); Connector.Argument addressArg = (Connector.Argument)arguments.get("address"); addressArg.setValue(addr); //PApplet.println(connector); // prints the current //com.sun.tools.jdi.AbstractLauncher al; //com.sun.tools.jdi.RawCommandLineLauncher rcll; //System.out.println(PApplet.javaVersion); // http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch try { return connector.launch(arguments); } catch (IOException exc) { throw new Error("Unable to launch target VM: " + exc); } catch (IllegalConnectorArgumentsException exc) { throw new Error("Internal error: " + exc); } catch (VMStartException exc) { Process p = exc.process(); //System.out.println(p); String[] errorStrings = PApplet.loadStrings(p.getErrorStream()); //String[] inputStrings = PApplet.loadStrings(p.getInputStream()); if (errorStrings != null && errorStrings.length > 1) { if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) { Base.showWarning("Way Too High", "Please lower the value for \u201Cmaximum available memory\u201D in the\n" + "Preferences window. For more information, read Help \u2192 Troubleshooting.", exc); } else { PApplet.println(errorStrings); System.err.println("Using startup command:"); PApplet.println(arguments); } } else { exc.printStackTrace(); System.err.println("Could not run the sketch (Target VM failed to initialize)."); if (Preferences.getBoolean("run.options.memory")) { // Only mention this if they've even altered the memory setup System.err.println("Make sure that you haven't set the maximum available memory too high."); } System.err.println("For more information, read revisions.txt and Help \u2192 Troubleshooting."); } // changing this to separate editor and listener [091124] //if (editor != null) { listener.statusError("Could not run the sketch."); //} return null; } } private static boolean hasWhitespace(String string) { int length = string.length(); for (int i = 0; i < length; i++) { if (Character.isWhitespace(string.charAt(i))) { return true; } } return false; } private static String addArgument(String string, String argument, char sep) { if (hasWhitespace(argument) || argument.indexOf(',') != -1) { // Quotes were stripped out for this argument, add 'em back. StringBuffer buffer = new StringBuffer(string); buffer.append('"'); for (int i = 0; i < argument.length(); i++) { char c = argument.charAt(i); if (c == '"') { buffer.append('\\'); // buffer.append("\\\\"); } buffer.append(c); } buffer.append('"'); buffer.append(sep); return buffer.toString(); } else { return string + argument + String.valueOf(sep); } } */ /** * Generate the trace. * Enable events, start thread to display events, * start threads to forward remote error and output streams, * resume the remote VM, wait for the final event, and shutdown. */ protected void generateTrace() { //vm.setDebugTraceMode(debugTraceMode); // vm.setDebugTraceMode(VirtualMachine.TRACE_ALL); // vm.setDebugTraceMode(VirtualMachine.TRACE_NONE); // formerly, seems to have no effect // For internal debugging PrintWriter writer = null; // Calling this seems to set something internally to make the // Eclipse JDI wake up. Without it, an ObjectCollectedException // is thrown on excReq.enable(). No idea why this works, // but at least exception handling has returned. (Suspect that it may // block until all or at least some threads are available, meaning // that the app has launched and we have legit objects to talk to). vm.allThreads(); // The bug may not have been noticed because the test suite waits for // a thread to be available, and queries it by calling allThreads(). // See org.eclipse.debug.jdi.tests.AbstractJDITest for the example. EventRequestManager mgr = vm.eventRequestManager(); // get only the uncaught exceptions ExceptionRequest excReq = mgr.createExceptionRequest(null, false, true); // System.out.println(excReq); // this version reports all exceptions, caught or uncaught // ExceptionRequest excReq = mgr.createExceptionRequest(null, true, true); // suspend so we can step excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL); // excReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); // excReq.setSuspendPolicy(EventRequest.SUSPEND_NONE); // another option? excReq.enable(); Thread eventThread = new Thread() { public void run() { try { boolean connected = true; while (connected) { EventQueue eventQueue = vm.eventQueue(); // remove() blocks until event(s) available EventSet eventSet = eventQueue.remove(); // listener.vmEvent(eventSet); for (Event event : eventSet) { // System.out.println("EventThread.handleEvent -> " + event); if (event instanceof ExceptionEvent) { // for (ThreadReference thread : vm.allThreads()) { // System.out.println("thread : " + thread); //// thread.suspend(); // } exceptionEvent((ExceptionEvent) event); } else if (event instanceof VMDisconnectEvent) { connected = false; } } } // } catch (VMDisconnectedException e) { // Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect"); } catch (Exception e) { System.err.println("crashed in event thread due to " + e.getMessage()); // Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e); e.printStackTrace(); } } }; eventThread.start(); errThread = new MessageSiphon(process.getErrorStream(), this).getThread(); outThread = new StreamRedirectThread("JVM stdout Reader", process.getInputStream(), System.out); errThread.start(); outThread.start(); vm.resume(); // Shutdown begins when event thread terminates try { if (eventThread != null) eventThread.join(); // is this the problem? // System.out.println("in here"); // Bug #852 tracked to this next line in the code. // http://dev.processing.org/bugs/show_bug.cgi?id=852 errThread.join(); // Make sure output is forwarded // System.out.println("and then"); outThread.join(); // before we exit // System.out.println("finished join for errThread and outThread"); // At this point, disable the run button. // This happens when the sketch is exited by hitting ESC, // or the user manually closes the sketch window. // TODO this should be handled better, should it not? if (editor != null) { editor.deactivateRun(); } } catch (InterruptedException exc) { // we don't interrupt } //System.out.println("and leaving"); if (writer != null) writer.close(); } protected Connector findConnector(String connectorName) { // List connectors = // com.sun.jdi.Bootstrap.virtualMachineManager().allConnectors(); List connectors = org.eclipse.jdi.Bootstrap.virtualMachineManager().allConnectors(); // // debug: code to list available connectors // Iterator iter2 = connectors.iterator(); // while (iter2.hasNext()) { // Connector connector = (Connector)iter2.next(); // System.out.println("connector name is " + connector.name()); // } for (Object c : connectors) { Connector connector = (Connector) c; // System.out.println(connector.name()); // } // Iterator iter = connectors.iterator(); // while (iter.hasNext()) { // Connector connector = (Connector)iter.next(); if (connector.name().equals(connectorName)) { return connector; } } Base.showError("Compiler Error", "findConnector() failed to find " + connectorName + " inside Runner", null); return null; // Not reachable } public void exceptionEvent(ExceptionEvent event) { ObjectReference or = event.exception(); ReferenceType rt = or.referenceType(); String exceptionName = rt.name(); //Field messageField = Throwable.class.getField("detailMessage"); Field messageField = rt.fieldByName("detailMessage"); // System.out.println("field " + messageField); Value messageValue = or.getValue(messageField); // System.out.println("mess val " + messageValue); //"java.lang.ArrayIndexOutOfBoundsException" int last = exceptionName.lastIndexOf('.'); String message = exceptionName.substring(last + 1); if (messageValue != null) { String messageStr = messageValue.toString(); if (messageStr.startsWith("\"")) { messageStr = messageStr.substring(1, messageStr.length() - 1); } message += ": " + messageStr; } // System.out.println("mess type " + messageValue.type()); //StringReference messageReference = (StringReference) messageValue.type(); // First just report the exception and its placement reportException(message, or, event.thread()); // Then try to pretty it up with a better message handleCommonErrors(exceptionName, message, listener); if (editor != null) { editor.deactivateRun(); } } /** * Provide more useful explanations of common error messages, perhaps with * a short message in the status area, and (if necessary) a longer message * in the console. * * @param exceptionClass Class name causing the error (with full package name) * @param message The message from the exception * @param listener The Editor or command line interface that's listening for errors * @return true if the error was purtified, false otherwise */ public static boolean handleCommonErrors(final String exceptionClass, final String message, final RunnerListener listener) { if (exceptionClass.equals("java.lang.OutOfMemoryError")) { if (message.contains("exceeds VM budget")) { // TODO this is a kludge for Android, since there's no memory preference listener.statusError("OutOfMemoryError: This code attempts to use more memory than available."); System.err.println("An OutOfMemoryError means that your code is either using up too much memory"); System.err.println("because of a bug (e.g. creating an array that's too large, or unintentionally"); System.err.println("loading thousands of images), or simply that it's trying to use more memory"); System.err.println("than what is supported by the current device."); } else { listener.statusError("OutOfMemoryError: You may need to increase the memory setting in Preferences."); System.err.println("An OutOfMemoryError means that your code is either using up too much memory"); System.err.println("because of a bug (e.g. creating an array that's too large, or unintentionally"); System.err.println("loading thousands of images), or that your sketch may need more memory to run."); System.err.println("If your sketch uses a lot of memory (for instance if it loads a lot of data files)"); System.err.println("you can increase the memory available to your sketch using the Preferences window."); } } else if (exceptionClass.equals("java.lang.UnsatisfiedLinkError")) { listener.statusError("A library used by this sketch is not installed properly."); System.err.println("A library relies on native code that's not available."); System.err.println("Or only works properly when the sketch is run as a " + ((Base.getNativeBits() == 32) ? "64-bit " : "32-bit ") + " application."); } else if (exceptionClass.equals("java.lang.StackOverflowError")) { listener.statusError("StackOverflowError: This sketch is attempting too much recursion."); System.err.println("A StackOverflowError means that you have a bug that's causing a function"); System.err.println("to be called recursively (it's calling itself and going in circles),"); System.err.println("or you're intentionally calling a recursive function too much,"); System.err.println("and your code should be rewritten in a more efficient manner."); } else if (exceptionClass.equals("java.lang.UnsupportedClassVersionError")) { listener.statusError("UnsupportedClassVersionError: A library is using code compiled with an unsupported version of Java."); System.err.println("This version of Processing only supports libraries and JAR files compiled for Java 1.6 or earlier."); System.err.println("A library used by this sketch was compiled for Java 1.7 or later, "); System.err.println("and needs to be recompiled to be compatible with Java 1.6."); } else if (exceptionClass.equals("java.lang.NoSuchMethodError") || exceptionClass.equals("java.lang.NoSuchFieldError")) { listener.statusError(exceptionClass.substring(10) + ": " + "You may be using a library that's incompatible " + "with this version of Processing."); } else { return false; } return true; } // TODO: This may be called more than one time per error in the VM, // presumably because exceptions might be wrapped inside others, // and this will fire for both. protected void reportException(String message, ObjectReference or, ThreadReference thread) { listener.statusError(findException(message, or, thread)); } /** * Move through a list of stack frames, searching for references to code * found in the current sketch. Return with a RunnerException that contains * the location of the error, or if nothing is found, just return with a * RunnerException that wraps the error message itself. */ protected SketchException findException(String message, ObjectReference or, ThreadReference thread) { try { // use to dump the stack for debugging // for (StackFrame frame : thread.frames()) { // System.out.println("frame: " + frame); // } List frames = thread.frames(); for (StackFrame frame : frames) { try { Location location = frame.location(); String filename = null; filename = location.sourceName(); int lineNumber = location.lineNumber() - 1; SketchException rex = build.placeException(message, filename, lineNumber); if (rex != null) { return rex; } } catch (AbsentInformationException e) { // Any of the thread.blah() methods can throw an AbsentInformationEx // if that bit of data is missing. If so, just write out the error // message to the console. //e.printStackTrace(); // not useful exception = new SketchException(message); exception.hideStackTrace(); listener.statusError(exception); } } } catch (IncompatibleThreadStateException e) { // This shouldn't happen, but if it does, print the exception in case // it's something that needs to be debugged separately. e.printStackTrace(); } // before giving up, try to extract from the throwable object itself // since sometimes exceptions are re-thrown from a different context try { // assume object reference is Throwable, get stack trace Method method = ((ClassType) or.referenceType()).concreteMethodByName("getStackTrace", "()[Ljava/lang/StackTraceElement;"); ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); // iterate through stack frames and pull filename and line number for each for (Value val: result.getValues()) { ObjectReference ref = (ObjectReference)val; method = ((ClassType) ref.referenceType()).concreteMethodByName("getFileName", "()Ljava/lang/String;"); StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); String filename = strref.value(); method = ((ClassType) ref.referenceType()).concreteMethodByName("getLineNumber", "()I"); IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); int lineNumber = intval.intValue() - 1; SketchException rex = build.placeException(message, filename, lineNumber); if (rex != null) { return rex; } } // for (Method m : ((ClassType) or.referenceType()).allMethods()) { // System.out.println(m + " | " + m.signature() + " | " + m.genericSignature()); // } // Implemented for 2.0b9, writes a stack trace when there's an internal error inside core. method = ((ClassType) or.referenceType()).concreteMethodByName("printStackTrace", "()V"); // System.err.println("got method " + method); or.invokeMethod(thread, method, new ArrayList(), ObjectReference.INVOKE_SINGLE_THREADED); } catch (Exception e) { e.printStackTrace(); } // Give up, nothing found inside the pile of stack frames SketchException rex = new SketchException(message); // exception is being created /here/, so stack trace is not useful rex.hideStackTrace(); return rex; } public void close() { // TODO make sure stop() has already been called to exit the sketch // TODO actually kill off the vm here if (vm != null) { try { vm.exit(0); } catch (com.sun.jdi.VMDisconnectedException vmde) { // if the vm has disconnected on its own, ignore message //System.out.println("harmless disconnect " + vmde.getMessage()); // TODO shouldn't need to do this, need to do more cleanup } vm = null; } } // made synchronized for 0087 // attempted to remove synchronized for 0136 to fix bug #775 (no luck tho) // http://dev.processing.org/bugs/show_bug.cgi?id=775 synchronized public void message(String s) { // System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length()); // this eats the CRLFs on the lines.. oops.. do it later //if (s.trim().length() == 0) return; // this is PApplet sending a message (via System.out.println) // that signals that the applet has been quit. if (s.indexOf(PApplet.EXTERNAL_STOP) == 0) { //System.out.println("external: quit"); if (editor != null) { // editor.internalCloseRunner(); // [091124] // editor.handleStop(); // prior to 0192 editor.internalCloseRunner(); // 0192 } return; } // this is the PApplet sending us a message that the applet // is being moved to a new window location if (s.indexOf(PApplet.EXTERNAL_MOVE) == 0) { String nums = s.substring(s.indexOf(' ') + 1).trim(); int space = nums.indexOf(' '); int left = Integer.parseInt(nums.substring(0, space)); int top = Integer.parseInt(nums.substring(space + 1)); // this is only fired when connected to an editor editor.setSketchLocation(new Point(left, top)); //System.out.println("external: move to " + left + " " + top); return; } // these are used for debugging, in case there are concerns // that some errors aren't coming through properly // if (s.length() > 2) { // System.err.println(newMessage); // System.err.println("message " + s.length() + ":" + s); // } // always shove out the message, since it might not fall under // the same setup as we're expecting System.err.print(s); //System.err.println("[" + s.length() + "] " + s); System.err.flush(); } }