apport-hackers team mailing list archive
-
apport-hackers team
-
Mailing list archive
-
Message #00041
[Merge] lp:~mdz/apport/java into lp:apport
Matt Zimmerman has proposed merging lp:~mdz/apport/java into lp:apport.
Requested reviews:
Apport upstream developers (apport-hackers)
Related bugs:
#548877 Trap uncaught exceptions in Java programs
https://bugs.launchpad.net/bugs/548877
This branch provides the basic components for handling uncaught Java exceptions, and capturing problem reports for them.
It includes:
- the com.ubuntu.apport.ApportUncaughtExceptionHandler class, which can be set as the uncaught exception handler for a Java program (perhaps eventually by default)
- a java_uncaught_exception script, which receives data from ApportUncaughtExceptionHandler and creates a problem report
- a simple "crash" class for testing purposes
- java/README, which explains the basic architecture
- test/java, which should generate crash reports if everything is working (not a proper test suite yet)
I had to hack setup.py a bit to build the Java parts, and am not sure whether I did that in the right way. Thanks for reviewing.
--
https://code.launchpad.net/~mdz/apport/java/+merge/42517
Your team Apport upstream developers is requested to review the proposed merge of lp:~mdz/apport/java into lp:apport.
=== added file 'data/java_uncaught_exception'
--- data/java_uncaught_exception 1970-01-01 00:00:00 +0000
+++ data/java_uncaught_exception 2010-12-02 17:37:53 +0000
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+#
+# Receive details from ApportUncaughtExceptionHandler, generate and save a
+# problem report
+#
+# Copyright 2010 Matt Zimmerman <mdz@xxxxxxxxxx>
+#
+
+import sys
+
+#from apport import unicode_gettext as _
+
+def make_title(report):
+ lines = report['StackTrace'].split('\n')
+ message = lines[0].strip()
+ stackframe = lines[1].strip()
+ return '%s in %s' % (message, stackframe)
+
+def main():
+ from apport.packaging_impl import impl as packaging
+ if not packaging.enabled():
+ return -1
+
+ # read from the JVM process a sequence of key, value delimited by null
+ # bytes
+ items = sys.stdin.read().split('\0')
+ d = dict()
+ while items:
+ key = items.pop(0)
+ if not items: break
+ value = items.pop(0)
+ d[key] = value
+
+ # create report
+ import apport.report
+ import os
+
+ report = apport.report.Report(type='Crash')
+ # assume our parent is the JVM process
+ report.pid = os.getppid()
+
+ report.add_os_info()
+ report.add_proc_info()
+ # these aren't relevant because the crash was in bytecode
+ del report['ProcMaps']
+ del report['ProcStatus']
+ report.add_user_info()
+
+ # add in data which was fed to us from the JVM process
+ for key, value in d.items():
+ report[key] = value
+
+ # Add an ExecutablePath pointing to the file where the main class resides
+ if 'MainClassUrl' in report:
+ import urlparse
+ url = report['MainClassUrl']
+
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
+
+ if scheme == 'jar':
+ # path is then a URL to the jar file
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(path)
+ if '!/' in path:
+ path = path.split('!/', 1)[0]
+
+ if scheme == 'file':
+ report['ExecutablePath'] = path
+ else:
+ # Program at some non-file URL crashed. Give up.
+ return
+
+ report['Title'] = make_title(report)
+
+ report.write(open(apport.fileutils.make_report_path(report), 'w'))
+
+if __name__ == '__main__':
+ main()
=== added directory 'java'
=== added file 'java/README'
--- java/README 1970-01-01 00:00:00 +0000
+++ java/README 2010-12-02 17:37:53 +0000
@@ -0,0 +1,5 @@
+apport.jar contains the necessary class(es) for trapping uncaught Java
+exceptions. crash.class and crash.jar are used only by the test suite.
+
+The crash handler, when invoked, opens a pipe to the java_uncaught_exception
+script, and feeds it key/value pairs containing the relevant JVM state.
=== added file 'java/TODO'
--- java/TODO 1970-01-01 00:00:00 +0000
+++ java/TODO 2010-12-02 17:37:53 +0000
@@ -0,0 +1,3 @@
+Make the test suite work
+Make "setup.py clean" clean the Java bits
+Enable the exception handler by default when apport is enabled
=== added directory 'java/com'
=== added directory 'java/com/ubuntu'
=== added directory 'java/com/ubuntu/apport'
=== added file 'java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java'
--- java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java 1970-01-01 00:00:00 +0000
+++ java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java 2010-12-02 17:37:53 +0000
@@ -0,0 +1,98 @@
+package com.ubuntu.apport;
+
+/*
+ * Apport handler for uncaught Java exceptions
+ *
+ * Copyright 2010 Matt Zimmerman <mdz@xxxxxxxxxx>
+ */
+
+import java.io.*;
+import java.util.HashMap;
+
+public class ApportUncaughtExceptionHandler
+ implements java.lang.Thread.UncaughtExceptionHandler {
+
+ /* Write out an apport problem report with details of the
+ * exception, then print it in the usual canonical format */
+ public void uncaughtException(Thread t, Throwable e) {
+ //System.out.println("uncaughtException");
+ if (e instanceof ThreadDeath)
+ return;
+
+ HashMap problemReport = getProblemReport(t, e);
+ //System.out.println("got problem report");
+
+ try {
+ Process p = new ProcessBuilder("/usr/share/apport/java_uncaught_exception").start();
+ //System.out.println("started process");
+
+ OutputStream os = p.getOutputStream();
+ writeProblemReport(os, problemReport);
+ //System.out.println("wrote problem report");
+
+ os.close();
+
+ try {
+ p.waitFor();
+ } catch (InterruptedException ignore) {
+ // ignored
+ }
+
+ } catch (java.io.IOException ioe) {
+ // ignored
+ }
+
+ System.err.print("Exception in thread \""
+ + t.getName() + "\" ");
+ e.printStackTrace(System.err);
+ }
+
+ public HashMap getProblemReport(Thread t, Throwable e) {
+ HashMap problemReport = new HashMap();
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ problemReport.put("StackTrace", sw.toString());
+
+ problemReport.put("MainClassUrl", mainClassUrl(e));
+
+ return problemReport;
+ }
+
+ public void writeProblemReport(OutputStream os, HashMap pr)
+ throws IOException {
+
+ StringWriter sw = new StringWriter();
+ for(Object o : pr.keySet()) {
+ String key = (String)o;
+ String value = (String)pr.get(o);
+ sw.write(key);
+ sw.write("\0");
+ sw.write(value);
+ sw.write("\0");
+ }
+ os.write(sw.toString().getBytes());
+ }
+
+ public static String mainClassUrl(Throwable e) {
+ StackTraceElement[] stacktrace = e.getStackTrace();
+ String className = stacktrace[stacktrace.length-1].getClassName();
+
+ if (!className.startsWith("/")) {
+ className = "/" + className;
+ }
+ className = className.replace('.', '/');
+ className = className + ".class";
+
+ java.net.URL classUrl =
+ new ApportUncaughtExceptionHandler().getClass().getResource(className);
+
+ return classUrl.toString();
+ }
+
+ /* Install this handler as the default uncaught exception handler */
+ public static void install() {
+ Thread.setDefaultUncaughtExceptionHandler(new ApportUncaughtExceptionHandler());
+ }
+}
=== added file 'java/crash.java'
--- java/crash.java 1970-01-01 00:00:00 +0000
+++ java/crash.java 2010-12-02 17:37:53 +0000
@@ -0,0 +1,8 @@
+import com.ubuntu.apport.*;
+
+class crash {
+ public static void main(String[] args) {
+ com.ubuntu.apport.ApportUncaughtExceptionHandler.install();
+ throw new RuntimeException("Can't catch this");
+ }
+}
=== modified file 'setup.py'
--- setup.py 2009-11-24 12:24:07 +0000
+++ setup.py 2010-12-02 17:37:53 +0000
@@ -4,6 +4,57 @@
import os.path, shutil, sys
from distutils.version import StrictVersion
+# Add Java build step
+from distutils.command.build import build
+from distutils.core import Command
+import subprocess
+
+class build_java_subdir(Command):
+ description = 'Compile java components of Apport'
+
+ user_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ oldwd = os.getcwd()
+ os.chdir('java')
+
+ try:
+ subprocess.check_call(['javac'] + glob('com/ubuntu/apport/*.java'))
+ subprocess.check_call(['jar','cvf', 'apport.jar'] +
+ glob('com/ubuntu/apport/*.class'))
+ subprocess.check_call(['javac','crash.java'])
+ subprocess.check_call(['jar','cvf', 'crash.jar', 'crash.class'])
+ except OSError, e:
+ if e.errno == os.errno.ENOENT:
+ print "Java prerequisites are not installed; skipping"
+ else:
+ raise e
+ finally:
+ os.chdir(oldwd)
+
+have_java = False
+try:
+ with open('/dev/null', 'w') as null:
+ subprocess.check_call(['javac', '-version'], stderr=null, stdout=null)
+ have_java = True
+
+except OSError, e:
+ have_java = False
+
+optional_data_files = []
+cmdclass = {}
+
+if have_java:
+ build.sub_commands.append(('build_java_subdir', None))
+ optional_data_files.append(('share/java', ['java/apport.jar']))
+ cmdclass.update({'build_java_subdir' : build_java_subdir})
+
try:
import DistUtilsExtra.auto
except ImportError:
@@ -41,6 +92,7 @@
('share/apport', glob('kde/*.ui')), #TODO: use pykdeuic modules
('share/apport/testsuite/', glob('test/*')),
('share/doc/apport/', glob('doc/*.txt')),
- ('lib/pm-utils/sleep.d/', glob('pm-utils/sleep.d/*'))
- ],
+ ('lib/pm-utils/sleep.d/', glob('pm-utils/sleep.d/*')),
+ ] + optional_data_files,
+ cmdclass=cmdclass
)
=== added file 'test/java'
--- test/java 1970-01-01 00:00:00 +0000
+++ test/java 2010-12-02 17:37:53 +0000
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+mydir=$(dirname $0)
+srcdir=$mydir/..
+
+# XXX - this is a mess in so many ways -mdz
+# This only works if /usr/share/apport/java_uncaught_exception is installed
+# ...and it's run from the source tree
+# It also dumps crash reports in /var/crash
+# ...and doesn't even check if it worked!
+
+# Should produce a crash report for $srcdir/java/crash.class
+java -classpath $srcdir/java/apport.jar:$srcdir/java crash || true
+
+# Should produce a crash report for $srcdir/java/crash.jar
+java -classpath $srcdir/java/apport.jar:$srcdir/java/crash.jar crash || true
Follow ups