# HG changeset patch # User jjg # Date 1301065133 25200 # Node ID 88cd61b4e5aa7628bdddea44f5e6ea103abe9eec # Parent f847fe5cad3dc5bf841de733dcc013cdd485d638 6437138: JSR 199: Compiler doesn't diagnose crash in user code 6482554: uncaught exception from annotation processor not reported through JavaCompiler.CompilationTask.call Reviewed-by: mcimadamore diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/api/ClientCodeWrapper.java Fri Mar 25 07:58:53 2011 -0700 @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package com.sun.tools.javac.api; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.NestingKind; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; + +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.tools.javac.util.ClientCodeException; +import com.sun.tools.javac.util.Context; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.lang.model.element.Modifier; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject.Kind; + +/** + * Wrap objects to enable unchecked exceptions to be caught and handled. + * + * For each method, exceptions are handled as follows: + * + * + * The intent is that ClientCodeException can be caught at an appropriate point + * in the program and can be distinguished from any unanticipated unchecked + * exceptions arising in the main body of the code (i.e. bugs.) When the + * ClientCodeException has been caught, either a suitable message can be + * generated, or if appropriate, the original cause can be rethrown. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ClientCodeWrapper { + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Trusted { } + + public static ClientCodeWrapper instance(Context context) { + ClientCodeWrapper instance = context.get(ClientCodeWrapper.class); + if (instance == null) + instance = new ClientCodeWrapper(context); + return instance; + } + + /** + * A map to cache the results of whether or not a specific classes can + * be "trusted", and thus does not need to be wrapped. + */ + Map, Boolean> trustedClasses; + + protected ClientCodeWrapper(Context context) { + trustedClasses = new HashMap, Boolean>(); + } + + public JavaFileManager wrap(JavaFileManager fm) { + if (isTrusted(fm)) + return fm; + return new WrappedJavaFileManager(fm); + } + + public FileObject wrap(FileObject fo) { + if (isTrusted(fo)) + return fo; + return new WrappedFileObject(fo); + } + + FileObject unwrap(FileObject fo) { + if (fo instanceof WrappedFileObject) + return ((WrappedFileObject) fo).clientFileObject; + else + return fo; + } + + public JavaFileObject wrap(JavaFileObject fo) { + if (isTrusted(fo)) + return fo; + return new WrappedJavaFileObject(fo); + } + + public Iterable wrapJavaFileObjects(Iterable list) { + List wrapped = new ArrayList(); + for (JavaFileObject fo : list) + wrapped.add(wrap(fo)); + return Collections.unmodifiableList(wrapped); + } + + JavaFileObject unwrap(JavaFileObject fo) { + if (fo instanceof WrappedJavaFileObject) + return ((JavaFileObject) ((WrappedJavaFileObject) fo).clientFileObject); + else + return fo; + } + + DiagnosticListener wrap(DiagnosticListener dl) { + if (isTrusted(dl)) + return dl; + return new WrappedDiagnosticListener(dl); + } + + TaskListener wrap(TaskListener tl) { + if (isTrusted(tl)) + return tl; + return new WrappedTaskListener(tl); + } + + protected boolean isTrusted(Object o) { + Class c = o.getClass(); + Boolean trusted = trustedClasses.get(c); + if (trusted == null) { + trusted = c.getName().startsWith("com.sun.tools.javac.") + || c.isAnnotationPresent(Trusted.class); + trustedClasses.put(c, trusted); + } + return trusted; + } + + // + + // FIXME: all these classes should be converted to use multi-catch when + // that is available in the bootstrap compiler. + + protected class WrappedJavaFileManager implements JavaFileManager { + protected JavaFileManager clientJavaFileManager; + WrappedJavaFileManager(JavaFileManager clientJavaFileManager) { + clientJavaFileManager.getClass(); // null check + this.clientJavaFileManager = clientJavaFileManager; + } + + @Override + public ClassLoader getClassLoader(Location location) { + try { + return clientJavaFileManager.getClassLoader(location); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { + try { + return wrapJavaFileObjects(clientJavaFileManager.list(location, packageName, kinds, recurse)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + try { + return clientJavaFileManager.inferBinaryName(location, unwrap(file)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean isSameFile(FileObject a, FileObject b) { + try { + return clientJavaFileManager.isSameFile(unwrap(a), unwrap(b)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean handleOption(String current, Iterator remaining) { + try { + return clientJavaFileManager.handleOption(current, remaining); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean hasLocation(Location location) { + try { + return clientJavaFileManager.hasLocation(location); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + try { + return wrap(clientJavaFileManager.getJavaFileForInput(location, className, kind)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + try { + return wrap(clientJavaFileManager.getJavaFileForOutput(location, className, kind, unwrap(sibling))); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + try { + return wrap(clientJavaFileManager.getFileForInput(location, packageName, relativeName)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { + try { + return wrap(clientJavaFileManager.getFileForOutput(location, packageName, relativeName, unwrap(sibling))); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void flush() throws IOException { + try { + clientJavaFileManager.flush(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void close() throws IOException { + try { + clientJavaFileManager.close(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public int isSupportedOption(String option) { + try { + return clientJavaFileManager.isSupportedOption(option); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedFileObject implements FileObject { + protected FileObject clientFileObject; + WrappedFileObject(FileObject clientFileObject) { + clientFileObject.getClass(); // null check + this.clientFileObject = clientFileObject; + } + + @Override + public URI toUri() { + try { + return clientFileObject.toUri(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public String getName() { + try { + return clientFileObject.getName(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public InputStream openInputStream() throws IOException { + try { + return clientFileObject.openInputStream(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public OutputStream openOutputStream() throws IOException { + try { + return clientFileObject.openOutputStream(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + try { + return clientFileObject.openReader(ignoreEncodingErrors); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + try { + return clientFileObject.getCharContent(ignoreEncodingErrors); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Writer openWriter() throws IOException { + try { + return clientFileObject.openWriter(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public long getLastModified() { + try { + return clientFileObject.getLastModified(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean delete() { + try { + return clientFileObject.delete(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedJavaFileObject extends WrappedFileObject implements JavaFileObject { + WrappedJavaFileObject(JavaFileObject clientJavaFileObject) { + super(clientJavaFileObject); + } + + @Override + public Kind getKind() { + try { + return ((JavaFileObject)clientFileObject).getKind(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + try { + return ((JavaFileObject)clientFileObject).isNameCompatible(simpleName, kind); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public NestingKind getNestingKind() { + try { + return ((JavaFileObject)clientFileObject).getNestingKind(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Modifier getAccessLevel() { + try { + return ((JavaFileObject)clientFileObject).getAccessLevel(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedDiagnosticListener implements DiagnosticListener { + protected DiagnosticListener clientDiagnosticListener; + WrappedDiagnosticListener(DiagnosticListener clientDiagnosticListener) { + clientDiagnosticListener.getClass(); // null check + this.clientDiagnosticListener = clientDiagnosticListener; + } + + @Override + public void report(Diagnostic diagnostic) { + try { + clientDiagnosticListener.report(diagnostic); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedTaskListener implements TaskListener { + protected TaskListener clientTaskListener; + WrappedTaskListener(TaskListener clientTaskListener) { + clientTaskListener.getClass(); // null check + this.clientTaskListener = clientTaskListener; + } + + @Override + public void started(TaskEvent ev) { + try { + clientTaskListener.started(ev); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void finished(TaskEvent ev) { + try { + clientTaskListener.finished(ev); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + // +} diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java --- a/langtools/src/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java Fri Mar 25 07:39:30 2011 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/api/JavacTaskImpl.java Fri Mar 25 07:58:53 2011 -0700 @@ -65,7 +65,7 @@ * @author Jonathan Gibbons */ public class JavacTaskImpl extends JavacTask { - private JavacTool tool; + private ClientCodeWrapper ccw; private Main compilerMain; private JavaCompiler compiler; private Locale locale; @@ -80,12 +80,11 @@ private Integer result = null; - JavacTaskImpl(JavacTool tool, - Main compilerMain, + JavacTaskImpl(Main compilerMain, String[] args, Context context, List fileObjects) { - this.tool = tool; + this.ccw = ClientCodeWrapper.instance(context); this.compilerMain = compilerMain; this.args = args; this.context = context; @@ -94,17 +93,15 @@ // null checks compilerMain.getClass(); args.getClass(); - context.getClass(); fileObjects.getClass(); } - JavacTaskImpl(JavacTool tool, - Main compilerMain, + JavacTaskImpl(Main compilerMain, Iterable flags, Context context, Iterable classes, Iterable fileObjects) { - this(tool, compilerMain, toArray(flags, classes), context, toList(fileObjects)); + this(compilerMain, toArray(flags, classes), context, toList(fileObjects)); } static private String[] toArray(Iterable flags, Iterable classes) { @@ -131,7 +128,7 @@ if (!used.getAndSet(true)) { initContext(); notYetEntered = new HashMap(); - compilerMain.setFatalErrors(true); + compilerMain.setAPIMode(true); result = compilerMain.compile(args, context, fileObjects, processors); cleanup(); return result == 0; @@ -185,32 +182,10 @@ if (context.get(TaskListener.class) != null) context.put(TaskListener.class, (TaskListener)null); if (taskListener != null) - context.put(TaskListener.class, wrap(taskListener)); + context.put(TaskListener.class, ccw.wrap(taskListener)); //initialize compiler's default locale context.put(Locale.class, locale); } - // where - private TaskListener wrap(final TaskListener tl) { - tl.getClass(); // null check - return new TaskListener() { - public void started(TaskEvent e) { - try { - tl.started(e); - } catch (Throwable t) { - throw new ClientCodeException(t); - } - } - - public void finished(TaskEvent e) { - try { - tl.finished(e); - } catch (Throwable t) { - throw new ClientCodeException(t); - } - } - - }; - } void cleanup() { if (compiler != null) diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/api/JavacTool.java --- a/langtools/src/share/classes/com/sun/tools/javac/api/JavacTool.java Fri Mar 25 07:39:30 2011 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/api/JavacTool.java Fri Mar 25 07:58:53 2011 -0700 @@ -49,6 +49,7 @@ import com.sun.tools.javac.main.Main; import com.sun.tools.javac.main.RecognizedOptions.GrumpyHelper; import com.sun.tools.javac.main.RecognizedOptions; +import com.sun.tools.javac.util.ClientCodeException; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; @@ -162,38 +163,45 @@ Iterable classes, Iterable compilationUnits) { - final String kindMsg = "All compilation units must be of SOURCE kind"; - if (options != null) - for (String option : options) - option.getClass(); // null check - if (classes != null) { - for (String cls : classes) - if (!SourceVersion.isName(cls)) // implicit null check - throw new IllegalArgumentException("Not a valid class name: " + cls); - } - if (compilationUnits != null) { - for (JavaFileObject cu : compilationUnits) { - if (cu.getKind() != JavaFileObject.Kind.SOURCE) // implicit null check - throw new IllegalArgumentException(kindMsg); - } - } + try { + Context context = new Context(); + ClientCodeWrapper ccw = ClientCodeWrapper.instance(context); - Context context = new Context(); - - if (diagnosticListener != null) - context.put(DiagnosticListener.class, diagnosticListener); + final String kindMsg = "All compilation units must be of SOURCE kind"; + if (options != null) + for (String option : options) + option.getClass(); // null check + if (classes != null) { + for (String cls : classes) + if (!SourceVersion.isName(cls)) // implicit null check + throw new IllegalArgumentException("Not a valid class name: " + cls); + } + if (compilationUnits != null) { + compilationUnits = ccw.wrapJavaFileObjects(compilationUnits); // implicit null check + for (JavaFileObject cu : compilationUnits) { + if (cu.getKind() != JavaFileObject.Kind.SOURCE) + throw new IllegalArgumentException(kindMsg); + } + } - if (out == null) - context.put(Log.outKey, new PrintWriter(System.err, true)); - else - context.put(Log.outKey, new PrintWriter(out, true)); + if (diagnosticListener != null) + context.put(DiagnosticListener.class, ccw.wrap(diagnosticListener)); + + if (out == null) + context.put(Log.outKey, new PrintWriter(System.err, true)); + else + context.put(Log.outKey, new PrintWriter(out, true)); - if (fileManager == null) - fileManager = getStandardFileManager(diagnosticListener, null, null); - context.put(JavaFileManager.class, fileManager); - processOptions(context, fileManager, options); - Main compiler = new Main("javacTask", context.get(Log.outKey)); - return new JavacTaskImpl(this, compiler, options, context, classes, compilationUnits); + if (fileManager == null) + fileManager = getStandardFileManager(diagnosticListener, null, null); + fileManager = ccw.wrap(fileManager); + context.put(JavaFileManager.class, fileManager); + processOptions(context, fileManager, options); + Main compiler = new Main("javacTask", context.get(Log.outKey)); + return new JavacTaskImpl(compiler, options, context, classes, compilationUnits); + } catch (ClientCodeException ex) { + throw new RuntimeException(ex.getCause()); + } } private static void processOptions(Context context, diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/main/Main.java --- a/langtools/src/share/classes/com/sun/tools/javac/main/Main.java Fri Mar 25 07:39:30 2011 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/main/Main.java Fri Mar 25 07:58:53 2011 -0700 @@ -65,9 +65,11 @@ PrintWriter out; /** - * If true, any command line arg errors will cause an exception. + * If true, certain errors will cause an exception, such as command line + * arg errors, or exceptions in user provided code. */ - boolean fatalErrors; + boolean apiMode; + /** Result codes. */ @@ -163,7 +165,7 @@ /** Report a usage error. */ void error(String key, Object... args) { - if (fatalErrors) { + if (apiMode) { String msg = getLocalizedString(key, args); throw new PropagatedException(new IllegalStateException(msg)); } @@ -192,8 +194,8 @@ this.options = options; } - public void setFatalErrors(boolean fatalErrors) { - this.fatalErrors = fatalErrors; + public void setAPIMode(boolean apiMode) { + this.apiMode = apiMode; } /** Process command line arguments: store all command line options @@ -440,7 +442,9 @@ } catch (FatalError ex) { feMessage(ex); return EXIT_SYSERR; - } catch(AnnotationProcessingError ex) { + } catch (AnnotationProcessingError ex) { + if (apiMode) + throw new RuntimeException(ex.getCause()); apMessage(ex); return EXIT_SYSERR; } catch (ClientCodeException ex) { @@ -458,7 +462,13 @@ bugMessage(ex); return EXIT_ABNORMAL; } finally { - if (comp != null) comp.close(); + if (comp != null) { + try { + comp.close(); + } catch (ClientCodeException ex) { + throw new RuntimeException(ex.getCause()); + } + } filenames = null; options = null; } diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java --- a/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Fri Mar 25 07:39:30 2011 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Fri Mar 25 07:58:53 2011 -0700 @@ -67,6 +67,7 @@ import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Abort; import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.ClientCodeException; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Convert; import com.sun.tools.javac.util.FatalError; @@ -432,6 +433,8 @@ log.error("proc.processor.cant.instantiate", processorName); return false; } + } catch(ClientCodeException e) { + throw e; } catch(Throwable t) { throw new AnnotationProcessingError(t); } @@ -527,6 +530,8 @@ supportedOptionNames.add(optionName); } + } catch (ClientCodeException e) { + throw e; } catch (Throwable t) { throw new AnnotationProcessingError(t); } @@ -790,6 +795,8 @@ ex.printStackTrace(new PrintWriter(out)); log.error("proc.cant.access", ex.sym, ex.getDetailValue(), out.toString()); return false; + } catch (ClientCodeException e) { + throw e; } catch (Throwable t) { throw new AnnotationProcessingError(t); } diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/src/share/classes/com/sun/tools/javac/util/Log.java --- a/langtools/src/share/classes/com/sun/tools/javac/util/Log.java Fri Mar 25 07:39:30 2011 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/util/Log.java Fri Mar 25 07:58:53 2011 -0700 @@ -425,13 +425,8 @@ */ protected void writeDiagnostic(JCDiagnostic diag) { if (diagListener != null) { - try { - diagListener.report(diag); - return; - } - catch (Throwable t) { - throw new ClientCodeException(t); - } + diagListener.report(diag); + return; } PrintWriter writer = getWriterForDiagnosticType(diag.getType()); diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/test/tools/javac/api/T6437138.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/api/T6437138.java Fri Mar 25 07:58:53 2011 -0700 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6437138 + * @summary JSR 199: Compiler doesn't diagnose crash in user code + */ + +import java.net.URI; +import java.util.Arrays; +import javax.tools.*; +import static javax.tools.JavaFileObject.Kind.*; + + +public class T6437138 { + static class JFO extends SimpleJavaFileObject { + public JFO(URI uri, JavaFileObject.Kind kind) { + super(uri, kind); + } + // getCharContent not impl, will throw UnsupportedOperationException + } + + public static void main(String... arg) throws Exception { + try { + JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + JavaFileObject jfo = new JFO(new URI("JFOTest04.java"),SOURCE); + JavaCompiler.CompilationTask ct = javac.getTask(null,null,null,null, + null, Arrays.asList(jfo)); + ct.call(); + throw new Exception("no exception thrown by JavaCompiler.CompilationTask"); + } catch (RuntimeException e) { + if (e.getCause() instanceof UnsupportedOperationException) { + System.err.println("RuntimeException(UnsupportedOperationException) caught as expected"); + return; + } + throw new Exception("unexpected exception caught", e); + } + } +} + diff -r f847fe5cad3d -r 88cd61b4e5aa langtools/test/tools/javac/api/TestClientCodeWrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/api/TestClientCodeWrapper.java Fri Mar 25 07:58:53 2011 -0700 @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6437138 6482554 + * @summary JSR 199: Compiler doesn't diagnose crash in user code + * @library ../lib + * @build JavacTestingAbstractProcessor TestClientCodeWrapper + * @run main TestClientCodeWrapper + */ + +import java.io.*; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.*; +import javax.annotation.processing.*; +import javax.lang.model.*; +import javax.lang.model.element.*; +import javax.tools.*; +import com.sun.source.util.*; +import com.sun.tools.javac.api.*; +import javax.tools.JavaFileObject.Kind; + +public class TestClientCodeWrapper extends JavacTestingAbstractProcessor { + public static void main(String... args) throws Exception { + new TestClientCodeWrapper().run(); + } + + /** + * Run a series of compilations, each with a different user-provided object + * configured to throw an exception when a specific method is invoked. + * Then, verify the exception is thrown as expected. + * + * Some methods are not invoked from the compiler, and are excluded from the test. + */ + void run() throws Exception { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + defaultFileManager = compiler.getStandardFileManager(null, null, null); + + for (Method m: getMethodsExcept(JavaFileManager.class, "close", "getJavaFileForInput")) { + test(m); + } + + for (Method m: getMethodsExcept(FileObject.class, "delete")) { + test(m); + } + + for (Method m: getMethods(JavaFileObject.class)) { + test(m); + } + + for (Method m: getMethodsExcept(Processor.class, "getCompletions")) { + test(m); + } + + for (Method m: DiagnosticListener.class.getDeclaredMethods()) { + test(m); + } + + for (Method m: TaskListener.class.getDeclaredMethods()) { + test(m); + } + + if (errors > 0) + throw new Exception(errors + " errors occurred"); + } + + /** Get a sorted set of the methods declared on a class. */ + Set getMethods(Class clazz) { + return getMethodsExcept(clazz, new String[0]); + } + + /** Get a sorted set of the methods declared on a class, excluding + * specified methods by name. */ + Set getMethodsExcept(Class clazz, String... exclude) { + Set methods = new TreeSet(new Comparator() { + public int compare(Method m1, Method m2) { + return m1.toString().compareTo(m2.toString()); + } + }); + Set e = new HashSet(Arrays.asList(exclude)); + for (Method m: clazz.getDeclaredMethods()) { + if (!e.contains(m.getName())) + methods.add(m); + } + return methods; + } + + /** + * Test a method in a user supplied component, to verify javac's handling + * of any exceptions thrown by that method. + */ + void test(Method m) throws Exception { + testNum++; + + File extDirs = new File("empty-extdirs"); + extDirs.mkdirs(); + + File testClasses = new File("test" + testNum); + testClasses.mkdirs(); + defaultFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(testClasses)); + + System.err.println("test " + testNum + ": " + + m.getDeclaringClass().getSimpleName() + "." + m.getName()); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + List javacOptions = Arrays.asList( + "-extdirs", extDirs.getPath(), // for use by filemanager handleOption + "-processor", TestClientCodeWrapper.class.getName() + ); + + List classes = Collections.emptyList(); + + JavacTool tool = JavacTool.create(); + try { + JavacTask task = tool.getTask(pw, + getFileManager(m, defaultFileManager), + getDiagnosticListener(m, pw), + javacOptions, + classes, + getCompilationUnits(m)); + + if (isDeclaredIn(m, Processor.class)) + task.setProcessors(getProcessors(m)); + + if (isDeclaredIn(m, TaskListener.class)) + task.setTaskListener(getTaskListener(m, pw)); + + boolean ok = task.call(); + error("compilation " + (ok ? "succeeded" : "failed") + " unexpectedly"); + } catch (RuntimeException e) { + System.err.println("caught " + e); + if (e.getClass() == RuntimeException.class) { + Throwable cause = e.getCause(); + if (cause instanceof UserError) { + String expect = m.getName(); + String found = cause.getMessage(); + checkEqual("exception messaqe", expect, found); + } else { + cause.printStackTrace(System.err); + error("Unexpected exception: " + cause); + } + } else { + e.printStackTrace(System.err); + error("Unexpected exception: " + e); + } + } + + pw.close(); + String out = sw.toString(); + System.err.println(out); + } + + /** Get a file manager to use for the test compilation. */ + JavaFileManager getFileManager(Method m, JavaFileManager defaultFileManager) { + return isDeclaredIn(m, JavaFileManager.class, FileObject.class, JavaFileObject.class) + ? new UserFileManager(m, defaultFileManager) + : defaultFileManager; + } + + /** Get a diagnostic listener to use for the test compilation. */ + DiagnosticListener getDiagnosticListener(Method m, PrintWriter out) { + return isDeclaredIn(m, DiagnosticListener.class) + ? new UserDiagnosticListener(m, out) + : null; + } + + /** Get a set of file objects to use for the test compilation. */ + Iterable getCompilationUnits(Method m) { + File testSrc = new File(System.getProperty("test.src")); + File thisSrc = new File(testSrc, TestClientCodeWrapper.class.getName() + ".java"); + Iterable files = defaultFileManager.getJavaFileObjects(thisSrc); + if (isDeclaredIn(m, FileObject.class, JavaFileObject.class)) + return Arrays.asList(new UserFileObject(m, files.iterator().next())); + else + return files; + } + + /** Get a set of annotation processors to use for the test compilation. */ + Iterable getProcessors(Method m) { + return Arrays.asList(new UserProcessor(m)); + } + + /** Get a task listener to use for the test compilation. */ + TaskListener getTaskListener(Method m, PrintWriter out) { + return new UserTaskListener(m, out); + } + + /** Check if two values are .equal, and report an error if not. */ + void checkEqual(String label, T expect, T found) { + if (!expect.equals(found)) + error("Unexpected value for " + label + ": " + found + "; expected: " + expect); + } + + /** Report an error. */ + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + /** Check if a method is declared in any of a set of classes */ + static boolean isDeclaredIn(Method m, Class... classes) { + Class dc = m.getDeclaringClass(); + for (Class c: classes) { + if (c == dc) return true; + } + return false; + } + + /** Throw an intentional error if the method has a given name. */ + static void throwUserExceptionIfNeeded(Method m, String name) { + if (m != null && m.getName().equals(name)) + throw new UserError(name); + } + + StandardJavaFileManager defaultFileManager; + int testNum; + int errors; + + //-------------------------------------------------------------------------- + + /** + * Processor used to trigger use of methods not normally used by javac. + */ + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + boolean firstRound = false; + for (Element e: roundEnv.getRootElements()) { + if (e.getSimpleName().contentEquals(TestClientCodeWrapper.class.getSimpleName())) + firstRound = true; + } + if (firstRound) { + try { + FileObject f1 = filer.getResource(StandardLocation.CLASS_PATH, "", + TestClientCodeWrapper.class.getName() + ".java"); + f1.openInputStream().close(); + f1.openReader(false).close(); + + FileObject f2 = filer.createResource( + StandardLocation.CLASS_OUTPUT, "", "f2.txt", (Element[]) null); + f2.openOutputStream().close(); + + FileObject f3 = filer.createResource( + StandardLocation.CLASS_OUTPUT, "", "f3.txt", (Element[]) null); + f3.openWriter().close(); + + JavaFileObject f4 = filer.createSourceFile("f4", (Element[]) null); + f4.openWriter().close(); + f4.getNestingKind(); + f4.getAccessLevel(); + + messager.printMessage(Diagnostic.Kind.NOTE, "informational note", + roundEnv.getRootElements().iterator().next()); + + } catch (IOException e) { + throw new UserError(e); + } + } + return true; + } + + //-------------------------------------------------------------------------- + + // + + static class UserError extends Error { + private static final long serialVersionUID = 1L; + UserError(String msg) { + super(msg); + } + UserError(Throwable t) { + super(t); + } + } + + static class UserFileManager extends ForwardingJavaFileManager { + Method fileManagerMethod; + Method fileObjectMethod; + + UserFileManager(Method m, JavaFileManager delegate) { + super(delegate); + if (isDeclaredIn(m, JavaFileManager.class)) { + fileManagerMethod = m; + } else if (isDeclaredIn(m, FileObject.class, JavaFileObject.class)) { + fileObjectMethod = m; + } else + assert false; + } + + @Override + public ClassLoader getClassLoader(Location location) { + throwUserExceptionIfNeeded(fileManagerMethod, "getClassLoader"); + return super.getClassLoader(location); + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "list"); + return wrap(super.list(location, packageName, kinds, recurse)); + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + throwUserExceptionIfNeeded(fileManagerMethod, "inferBinaryName"); + return super.inferBinaryName(location, unwrap(file)); + } + + @Override + public boolean isSameFile(FileObject a, FileObject b) { + throwUserExceptionIfNeeded(fileManagerMethod, "isSameFile"); + return super.isSameFile(unwrap(a), unwrap(b)); + } + + @Override + public boolean handleOption(String current, Iterator remaining) { + throwUserExceptionIfNeeded(fileManagerMethod, "handleOption"); + return super.handleOption(current, remaining); + } + + @Override + public boolean hasLocation(Location location) { + throwUserExceptionIfNeeded(fileManagerMethod, "hasLocation"); + return super.hasLocation(location); + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "getJavaFileForInput"); + return wrap(super.getJavaFileForInput(location, className, kind)); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "getJavaFileForOutput"); + return wrap(super.getJavaFileForOutput(location, className, kind, sibling)); + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "getFileForInput"); + return wrap(super.getFileForInput(location, packageName, relativeName)); + } + + @Override + public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "getFileForOutput"); + return wrap(super.getFileForOutput(location, packageName, relativeName, sibling)); + } + + @Override + public void flush() throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "flush"); + super.flush(); + } + + @Override + public void close() throws IOException { + throwUserExceptionIfNeeded(fileManagerMethod, "close"); + super.close(); + } + + @Override + public int isSupportedOption(String option) { + throwUserExceptionIfNeeded(fileManagerMethod, "isSupportedOption"); + return super.isSupportedOption(option); + } + + public FileObject wrap(FileObject fo) { + if (fileObjectMethod == null) + return fo; + return new UserFileObject(fileObjectMethod, (JavaFileObject)fo); + } + + FileObject unwrap(FileObject fo) { + if (fo instanceof UserFileObject) + return ((UserFileObject) fo).unwrap(); + else + return fo; + } + + public JavaFileObject wrap(JavaFileObject fo) { + if (fileObjectMethod == null) + return fo; + return new UserFileObject(fileObjectMethod, fo); + } + + public Iterable wrap(Iterable list) { + List wrapped = new ArrayList(); + for (JavaFileObject fo : list) + wrapped.add(wrap(fo)); + return Collections.unmodifiableList(wrapped); + } + + JavaFileObject unwrap(JavaFileObject fo) { + if (fo instanceof UserFileObject) + return ((UserFileObject) fo).unwrap(); + else + return fo; + } + } + + static class UserFileObject extends ForwardingJavaFileObject { + Method method; + + UserFileObject(Method m, JavaFileObject delegate) { + super(delegate); + assert isDeclaredIn(m, FileObject.class, JavaFileObject.class); + this.method = m; + } + + JavaFileObject unwrap() { + return fileObject; + } + + @Override + public Kind getKind() { + throwUserExceptionIfNeeded(method, "getKind"); + return super.getKind(); + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + throwUserExceptionIfNeeded(method, "isNameCompatible"); + return super.isNameCompatible(simpleName, kind); + } + + @Override + public NestingKind getNestingKind() { + throwUserExceptionIfNeeded(method, "getNestingKind"); + return super.getNestingKind(); + } + + @Override + public Modifier getAccessLevel() { + throwUserExceptionIfNeeded(method, "getAccessLevel"); + return super.getAccessLevel(); + } + + @Override + public URI toUri() { + throwUserExceptionIfNeeded(method, "toUri"); + return super.toUri(); + } + + @Override + public String getName() { + throwUserExceptionIfNeeded(method, "getName"); + return super.getName(); + } + + @Override + public InputStream openInputStream() throws IOException { + throwUserExceptionIfNeeded(method, "openInputStream"); + return super.openInputStream(); + } + + @Override + public OutputStream openOutputStream() throws IOException { + throwUserExceptionIfNeeded(method, "openOutputStream"); + return super.openOutputStream(); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + throwUserExceptionIfNeeded(method, "openReader"); + return super.openReader(ignoreEncodingErrors); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + throwUserExceptionIfNeeded(method, "getCharContent"); + return super.getCharContent(ignoreEncodingErrors); + } + + @Override + public Writer openWriter() throws IOException { + throwUserExceptionIfNeeded(method, "openWriter"); + return super.openWriter(); + } + + @Override + public long getLastModified() { + throwUserExceptionIfNeeded(method, "getLastModified"); + return super.getLastModified(); + } + + @Override + public boolean delete() { + throwUserExceptionIfNeeded(method, "delete"); + return super.delete(); + } + + } + + static class UserProcessor extends JavacTestingAbstractProcessor { + Method method; + + UserProcessor(Method m) { + assert isDeclaredIn(m, Processor.class); + method = m; + } + + @Override + public Set getSupportedOptions() { + throwUserExceptionIfNeeded(method, "getSupportedOptions"); + return super.getSupportedOptions(); + } + + @Override + public Set getSupportedAnnotationTypes() { + throwUserExceptionIfNeeded(method, "getSupportedAnnotationTypes"); + return super.getSupportedAnnotationTypes(); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + throwUserExceptionIfNeeded(method, "getSupportedSourceVersion"); + return super.getSupportedSourceVersion(); + } + + @Override + public void init(ProcessingEnvironment processingEnv) { + throwUserExceptionIfNeeded(method, "init"); + super.init(processingEnv); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throwUserExceptionIfNeeded(method, "process"); + return true; + } + + @Override + public Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { + throwUserExceptionIfNeeded(method, "getCompletions"); + return super.getCompletions(element, annotation, member, userText); + } + } + + static class UserDiagnosticListener implements DiagnosticListener { + Method method; + PrintWriter out; + + UserDiagnosticListener(Method m, PrintWriter out) { + assert isDeclaredIn(m, DiagnosticListener.class); + this.method = m; + this.out = out; + } + + @Override + public void report(Diagnostic diagnostic) { + throwUserExceptionIfNeeded(method, "report"); + out.println("report: " + diagnostic); + } + } + + static class UserTaskListener implements TaskListener { + Method method; + PrintWriter out; + + UserTaskListener(Method m, PrintWriter out) { + assert isDeclaredIn(m, TaskListener.class); + this.method = m; + this.out = out; + } + + @Override + public void started(TaskEvent e) { + throwUserExceptionIfNeeded(method, "started"); + out.println("started: " + e); + } + + @Override + public void finished(TaskEvent e) { + throwUserExceptionIfNeeded(method, "finished"); + out.println("finished: " + e); + } + } + + // +}