diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/indexing/APTUtils.java b/java/java.source.base/src/org/netbeans/modules/java/source/indexing/APTUtils.java index 8d75d598ef4e..4ef5b1dab86f 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/indexing/APTUtils.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/indexing/APTUtils.java @@ -18,6 +18,8 @@ */ package org.netbeans.modules.java.source.indexing; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.util.Abort; import com.sun.tools.javac.util.ClientCodeException; import com.sun.tools.javac.util.Context; @@ -28,12 +30,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; +import java.security.CodeSource; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -43,6 +48,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.WeakHashMap; @@ -959,7 +965,7 @@ private void save() { } } - private static final class ErrorToleratingProcessor implements Processor { + private final class ErrorToleratingProcessor implements Processor { private final Processor delegate; private ProcessingEnvironment processingEnv; @@ -1003,18 +1009,16 @@ public void init(ProcessingEnvironment processingEnv) { throw err; } catch (Throwable t) { initFailed = true; - StringBuilder exception = new StringBuilder(); - exception.append(t.getMessage()).append("\n"); - for (StackTraceElement ste : t.getStackTrace()) { - exception.append(ste).append("\n"); - } - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, Bundle.ERR_ProcessorException(delegate.getClass().getName(), exception.toString())); + classLoaderCache = null; // in case of static initializer failure (e.g lombok); clear the loader so that next time it fails again in the same way + Enter enter = Enter.instance(((JavacProcessingEnvironment) processingEnv).getContext()); + Element topLevel = enter.getEnvs().iterator().hasNext() ? enter.getEnvs().iterator().next().enclClass.sym + : null; + reportError(processingEnv, t, topLevel); } this.processingEnv = processingEnv; } @Override - @Messages("ERR_ProcessorException=Annotation processor {0} failed with an exception: {1}") public boolean process(Set annotations, RoundEnvironment roundEnv) { if (initFailed || processFailed) { return false; @@ -1026,17 +1030,40 @@ public boolean process(Set annotations, RoundEnvironment throw err; } catch (Throwable t) { processFailed = true; + classLoaderCache = null; // in case of static initializer failure (e.g lombok); clear the loader so that next time it fails again in the same way Element el = roundEnv.getRootElements().isEmpty() ? null : roundEnv.getRootElements().iterator().next(); - StringBuilder exception = new StringBuilder(); - exception.append(t.getMessage()).append("\n"); - for (StackTraceElement ste : t.getStackTrace()) { - exception.append(ste).append("\n"); - } - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, Bundle.ERR_ProcessorException(delegate.getClass().getName(), exception.toString()), el); + reportError(processingEnv, t, el); return false; } } + @Messages({ + "# {0} - processor name", + "# {1} - processor jar", + "# {2} - processor exception", + "# {3} - javac version", + "ERR_ProcessorException=An annotation processor failed on embedded javac {3}.\n class: {0}\n location: {1}\n{2}", + "# {0} - processor name", + "# {1} - processor jar", + "# {2} - processor exception", + "# {3} - javac version", + "ERR_ProcessorException_UpdateAdvice=An annotation processor failed on embedded javac {3}. Try updating to a newer version.\n class: {0}\n location: {1}\n{2}" + }) + private void reportError(ProcessingEnvironment processingEnv, Throwable t, Element targetEl) { + StringWriter exception = new StringWriter(); + try (PrintWriter pw = new PrintWriter(exception)) { + t.printStackTrace(pw); + } + String name = delegate.getClass().getName(); + CodeSource codeSource = delegate.getClass().getProtectionDomain().getCodeSource(); + String location = Objects.toString(codeSource != null ? codeSource.getLocation() : null, "none"); + int javacVersion = SourceVersion.latest().runtimeVersion().feature(); + String message = name.startsWith(JavacParser.LOMBOK_ANNOTATION_PROCESSOR_PREFIX) + ? Bundle.ERR_ProcessorException_UpdateAdvice(name, location, exception.toString(), javacVersion) + : Bundle.ERR_ProcessorException(name, location, exception.toString(), javacVersion); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, targetEl); + } + @Override public Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { if (initFailed) { diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/parsing/JavacParser.java b/java/java.source.base/src/org/netbeans/modules/java/source/parsing/JavacParser.java index b6726dba379c..8376238449f4 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/parsing/JavacParser.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/parsing/JavacParser.java @@ -155,6 +155,7 @@ public class JavacParser extends Parser { private static final boolean DISABLE_PARAMETER_NAMES_READING = Boolean.getBoolean("org.netbeans.modules.java.source.parsing.JavacParser.no_parameter_names"); //NOI18N private static final Set> HUGE_SNAPSHOTS = new HashSet<>(); private static final LowMemoryWatcher LOW_MEMORY_WATCHER = LowMemoryWatcher.getInstance(); + public static final String LOMBOK_ANNOTATION_PROCESSOR_PREFIX = "lombok."; public static final String LOMBOK_DETECTED = "lombokDetected"; /** @@ -997,7 +998,7 @@ private static JavacTaskImpl createJavacTask( aptEnabled = false; } else { for (Processor p : processors) { - if ("lombok.core.AnnotationProcessor".equals(p.getClass().getName())) { + if (p.getClass().getName().startsWith(LOMBOK_ANNOTATION_PROCESSOR_PREFIX)) { options.add("-XD" + LOMBOK_DETECTED); break; } diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/APTUtilsTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/APTUtilsTest.java index f96c0cf39326..a047bd7de475 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/APTUtilsTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/APTUtilsTest.java @@ -34,13 +34,19 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.swing.event.ChangeListener; +import javax.tools.JavaCompiler; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.classpath.JavaClassPathConstants; import org.netbeans.api.java.queries.AnnotationProcessingQuery; import org.netbeans.api.java.queries.AnnotationProcessingQuery.Trigger; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.TestUtilities; import org.netbeans.junit.MockServices; import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.source.NoJavacHelper; import org.netbeans.modules.parsing.api.indexing.IndexingManager; import org.netbeans.modules.parsing.impl.indexing.CacheFolder; import org.netbeans.spi.java.classpath.ClassPathFactory; @@ -53,7 +59,6 @@ import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.ChangeSupport; -import org.openide.util.Lookup; /** * @@ -176,6 +181,251 @@ public void testRefreshedIndexOnceAfterAnnotationProcessingChange() throws Inter } + public void testBrokenAP() throws Exception { + APQ.result.setAnnotationProcessingEnabled(EnumSet.of(Trigger.ON_SCAN, Trigger.IN_EDITOR), + Set.of("AnnotationProcessor")); + { + //broken init: + FileObject processorClassesDir = + compileProcessor(""" + import java.util.Set; + import javax.annotation.processing.AbstractProcessor; + import javax.annotation.processing.ProcessingEnvironment; + import javax.annotation.processing.RoundEnvironment; + import javax.annotation.processing.SupportedAnnotationTypes; + import javax.lang.model.element.TypeElement; + + @SupportedAnnotationTypes("*") + public class AnnotationProcessor extends AbstractProcessor { + + @Override + public void init(ProcessingEnvironment processingEnv) { + throw new IllegalStateException("Broken."); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throw new IllegalStateException("Should not get here."); + } + + } + """); + try { + processorPath.add(processorClassesDir); + FileObject test = FileUtil.createData(root1, "Test.java"); + TestUtilities.copyStringToFile(test, + """ + public class Test{} + """); + JavaSource source = JavaSource.forFileObject(test); + source.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); + List messages = + cc.getDiagnostics() + .stream() + .map(d -> d.getMessage(null)) + .toList(); + String firstLine = firstLine(Bundle.ERR_ProcessorException("AnnotationProcessor", "", "Broken.", NoJavacHelper.REQUIRED_JAVAC_VERSION)); + if (messages.stream().noneMatch(msg -> msg.contains(firstLine))) { + fail("Expected error not found, all messages: " + messages); + } + if (messages.stream().anyMatch(msg -> msg.contains("Should not get here"))) { + fail("Unexpected error found, all messages: " + messages); + } + }, true); + } finally { + processorPath.remove(processorClassesDir); + } + } + + { + //broken process: + FileObject processorClassesDir = + compileProcessor(""" + import java.util.Set; + import javax.annotation.processing.AbstractProcessor; + import javax.annotation.processing.ProcessingEnvironment; + import javax.annotation.processing.RoundEnvironment; + import javax.annotation.processing.SupportedAnnotationTypes; + import javax.lang.model.element.TypeElement; + + @SupportedAnnotationTypes("*") + public class AnnotationProcessor extends AbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throw new IllegalStateException("Broken."); + } + + } + """); + try { + processorPath.add(processorClassesDir); + FileObject test = FileUtil.createData(root1, "Test.java"); + TestUtilities.copyStringToFile(test, + """ + public class Test{} + """); + JavaSource source = JavaSource.forFileObject(test); + source.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); + List messages = + cc.getDiagnostics() + .stream() + .map(d -> d.getMessage(null)) + .toList(); + String firstLine = firstLine(Bundle.ERR_ProcessorException("AnnotationProcessor", "", "Broken.", NoJavacHelper.REQUIRED_JAVAC_VERSION)); + if (messages.stream().noneMatch(msg -> msg.contains(firstLine))) { + fail("Expected error not found, all messages: " + messages); + } + if (messages.stream().anyMatch(msg -> msg.contains("Should not get here"))) { + fail("Unexpected error found, all messages: " + messages); + } + }, true); + } finally { + processorPath.remove(processorClassesDir); + } + } + } + + public void testBrokenAPLombok() throws Exception { + APQ.result.setAnnotationProcessingEnabled(EnumSet.of(Trigger.ON_SCAN, Trigger.IN_EDITOR), + Set.of("lombok.core.AnnotationProcessor")); + //lombok: + { + //broken init: + FileObject processorClassesDir = + compileProcessor(""" + package lombok.core; + import java.util.Set; + import javax.annotation.processing.AbstractProcessor; + import javax.annotation.processing.ProcessingEnvironment; + import javax.annotation.processing.RoundEnvironment; + import javax.annotation.processing.SupportedAnnotationTypes; + import javax.lang.model.element.TypeElement; + + @SupportedAnnotationTypes("*") + public class AnnotationProcessor extends AbstractProcessor { + + @Override + public void init(ProcessingEnvironment processingEnv) { + throw new IllegalStateException("Broken."); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throw new IllegalStateException("Should not get here."); + } + + } + """); + try { + processorPath.add(processorClassesDir); + FileObject test = FileUtil.createData(root1, "Test.java"); + TestUtilities.copyStringToFile(test, + """ + public class Test{} + """); + JavaSource source = JavaSource.forFileObject(test); + source.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); + List messages = + cc.getDiagnostics() + .stream() + .map(d -> d.getMessage(null)) + .toList(); + String firstLine = firstLine(Bundle.ERR_ProcessorException("AnnotationProcessor", "", "Broken.", NoJavacHelper.REQUIRED_JAVAC_VERSION)); + if (messages.stream().noneMatch(msg -> msg.contains(firstLine))) { + fail("Expected error not found, all messages: " + messages); + } + if (messages.stream().anyMatch(msg -> msg.contains("Should not get here"))) { + fail("Unexpected error found, all messages: " + messages); + } + }, true); + } finally { + processorPath.remove(processorClassesDir); + } + } + + { + //broken process: + FileObject processorClassesDir = + compileProcessor(""" + package lombok.core; + import java.util.Set; + import javax.annotation.processing.AbstractProcessor; + import javax.annotation.processing.ProcessingEnvironment; + import javax.annotation.processing.RoundEnvironment; + import javax.annotation.processing.SupportedAnnotationTypes; + import javax.lang.model.element.TypeElement; + + @SupportedAnnotationTypes("*") + public class AnnotationProcessor extends AbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + throw new IllegalStateException("Broken."); + } + + } + """); + try { + processorPath.add(processorClassesDir); + FileObject test = FileUtil.createData(root1, "Test.java"); + TestUtilities.copyStringToFile(test, + """ + public class Test{} + """); + JavaSource source = JavaSource.forFileObject(test); + source.runUserActionTask(cc -> { + cc.toPhase(JavaSource.Phase.RESOLVED); + List messages = + cc.getDiagnostics() + .stream() + .map(d -> d.getMessage(null)) + .toList(); + String firstLine = firstLine(Bundle.ERR_ProcessorException("AnnotationProcessor", "", "Broken.", NoJavacHelper.REQUIRED_JAVAC_VERSION)); + if (messages.stream().noneMatch(msg -> msg.contains(firstLine))) { + fail("Expected error not found, all messages: " + messages); + } + if (messages.stream().anyMatch(msg -> msg.contains("Should not get here"))) { + fail("Unexpected error found, all messages: " + messages); + } + }, true); + } finally { + processorPath.remove(processorClassesDir); + } + } + } + + private String firstLine(String m) { + int newLine = m.indexOf('\n'); + if (newLine == (-1)) return m; + return m.substring(0, newLine); + } + + private FileObject compileProcessor(String processorCode) throws Exception { + FileObject wdFO = FileUtil.toFileObject(getWorkDir()); + FileObject processorSrcDir = FileUtil.createFolder(wdFO, "processor-src"); + FileObject processorClassesDir = FileUtil.createFolder(wdFO, "processor-classes"); + FileObject processorFO = FileUtil.createData(processorSrcDir, "AnnotationProcessor.java"); + TestUtilities.copyStringToFile(processorFO, + processorCode); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) { + boolean result = + compiler.getTask(null, + null, + null, + List.of("-d", FileUtil.toFile(processorClassesDir).getAbsolutePath(), + "--release", "21"), + null, + fm.getJavaFileObjects(FileUtil.toPath(processorFO))) + .call(); + assertTrue(result); + } + + return processorClassesDir; + } + private static final class MockHandler extends Handler { static FileObject currentRoot; @@ -324,10 +574,16 @@ public static final class MAP implements AnnotationProcessingQuery.Result { private final ChangeSupport listeners = new ChangeSupport(this); private volatile Set mode = Collections.emptySet(); + private volatile Set apsToRun = null; void setAnnotationProcessingEnabled(Set newMode) { + setAnnotationProcessingEnabled(newMode, null); + } + + void setAnnotationProcessingEnabled(Set newMode, Set newApsToRun) { this.mode = newMode; + this.apsToRun = newApsToRun; listeners.fireChange(); } @@ -338,7 +594,7 @@ public Set annotationProcessingEnabled() { @Override public Iterable annotationProcessorsToRun() { - return null; + return apsToRun; } @Override @@ -374,4 +630,7 @@ public AnnotationProcessingQuery.Result getAnnotationProcessingOptions(FileObjec } + static { + System.setProperty("SourcePath.no.source.filter", "true"); + } } diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/CrashingAPTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/CrashingAPTest.java index 832f9ed3bf9d..f8d4d570652d 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/CrashingAPTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/indexing/CrashingAPTest.java @@ -56,6 +56,7 @@ import org.netbeans.api.java.source.SourceUtils; import org.netbeans.api.java.source.Task; import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.source.NoJavacHelper; import org.netbeans.modules.java.source.TestUtil; import org.netbeans.modules.java.source.usages.IndexUtil; import org.netbeans.spi.java.classpath.ClassPathProvider; @@ -136,8 +137,8 @@ public void run(CompilationController parameter) throws IOException { .stream() .map(d -> d.getMessage(null)) .map(m -> firstLine(m)) - .collect(Collectors.toList()); - List expected = Arrays.asList(Bundle.ERR_ProcessorException("org.netbeans.modules.java.source.indexing.CrashingAPTest$TestAP", "Crash")); + .toList(); + List expected = Arrays.asList(firstLine(Bundle.ERR_ProcessorException("org.netbeans.modules.java.source.indexing.CrashingAPTest$TestAP", "", "Crash", NoJavacHelper.REQUIRED_JAVAC_VERSION))); assertEquals(expected, messages); } },true);