This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 88b9ceac6540c751df290a394bc871260ddd277c
Author: opencode <[email protected]>
AuthorDate: Sun Oct 18 21:09:41 2026 +0200

    Stop the Ant compiler System.err capture when compilation fails unexpectedly
    
    AntCompiler.generateClass() starts a per-thread System.err capture with
    SystemLogHandler.setThread() but only stopped the capture on the
    straight-line path after javac had run. If configuring or executing the
    javac task failed with an unexpected exception, such as a BuildException
    when creating the task or a RuntimeException while configuring it, the
    exception escaped without the capture being stopped.
    
    Since the capture is installed behind a JVM-global System.err wrapper,
    all later System.err output on the failed thread was then silently
    discarded into the abandoned buffer, which kept growing for the life of
    that (pooled) thread.
    
    Stop the capture in a finally block so the thread state is always
    cleaned up. The content and ordering of the error report are unchanged
    on the paths where compilation completes.
    
    Add TestAntCompiler to verify the capture is not left installed when
    compilation fails unexpectedly.
---
 java/org/apache/jasper/compiler/AntCompiler.java   | 153 +++++++++++----------
 .../apache/jasper/compiler/TestAntCompiler.java    |  76 ++++++++++
 2 files changed, 154 insertions(+), 75 deletions(-)

diff --git a/java/org/apache/jasper/compiler/AntCompiler.java 
b/java/org/apache/jasper/compiler/AntCompiler.java
index 5c2dce7b40..a00f9f6ed2 100644
--- a/java/org/apache/jasper/compiler/AntCompiler.java
+++ b/java/org/apache/jasper/compiler/AntCompiler.java
@@ -166,92 +166,95 @@ public class AntCompiler extends Compiler {
         // Start capturing the System.err output for this thread
         SystemLogHandler.setThread();
 
-        // Initializing javac task
-        getProject();
-        Javac javac = (Javac) project.createTask("javac");
-
-        // Initializing classpath
-        Path path = new Path(project);
-        path.setPath(System.getProperty("java.class.path"));
-        info.append("    
cp=").append(System.getProperty("java.class.path")).append("\n");
-        StringTokenizer tokenizer = new StringTokenizer(classpath, 
File.pathSeparator);
-        while (tokenizer.hasMoreElements()) {
-            String pathElement = tokenizer.nextToken();
-            File repository = new File(pathElement);
-            path.setLocation(repository);
-            info.append("    cp=").append(repository).append("\n");
-        }
-
-        if (log.isTraceEnabled()) {
-            log.trace("Using classpath: " + 
System.getProperty("java.class.path") + File.pathSeparator + classpath);
-        }
-
-        // Initializing sourcepath
-        Path srcPath = new Path(project);
-        srcPath.setLocation(options.getScratchDir());
-
-        info.append("    work 
dir=").append(options.getScratchDir()).append("\n");
-
-        // Initialize and set java extensions
-        String exts = System.getProperty("java.ext.dirs");
-        if (exts != null) {
-            Path extdirs = new Path(project);
-            extdirs.setPath(exts);
-            javac.setExtdirs(extdirs);
-            info.append("    extension dir=").append(exts).append("\n");
-        }
-
-        // Configure the compiler object
-        javac.setEncoding(javaEncoding);
-        javac.setClasspath(path);
-        javac.setDebug(ctxt.getOptions().getClassDebugInfo());
-        javac.setSrcdir(srcPath);
-        javac.setTempdir(options.getScratchDir());
-        javac.setFork(ctxt.getOptions().getFork());
-        info.append("    srcDir=").append(srcPath).append("\n");
-
-        // Set the Java compiler to use
-        if (options.getCompiler() != null) {
-            javac.setCompiler(options.getCompiler());
-            info.append("    
compiler=").append(options.getCompiler()).append("\n");
-        }
+        BuildException be = null;
+        String errorCapture;
+        try {
+            // Initializing javac task
+            getProject();
+            Javac javac = (Javac) project.createTask("javac");
+
+            // Initializing classpath
+            Path path = new Path(project);
+            path.setPath(System.getProperty("java.class.path"));
+            info.append("    
cp=").append(System.getProperty("java.class.path")).append("\n");
+            StringTokenizer tokenizer = new StringTokenizer(classpath, 
File.pathSeparator);
+            while (tokenizer.hasMoreElements()) {
+                String pathElement = tokenizer.nextToken();
+                File repository = new File(pathElement);
+                path.setLocation(repository);
+                info.append("    cp=").append(repository).append("\n");
+            }
 
-        if (options.getCompilerTargetVM() != null) {
-            javac.setTarget(options.getCompilerTargetVM());
-            info.append("   
compilerTargetVM=").append(options.getCompilerTargetVM()).append("\n");
-        }
+            if (log.isTraceEnabled()) {
+                log.trace("Using classpath: " + 
System.getProperty("java.class.path") + File.pathSeparator + classpath);
+            }
 
-        if (options.getCompilerSourceVM() != null) {
-            javac.setSource(options.getCompilerSourceVM());
-            info.append("   
compilerSourceVM=").append(options.getCompilerSourceVM()).append("\n");
-        }
+            // Initializing sourcepath
+            Path srcPath = new Path(project);
+            srcPath.setLocation(options.getScratchDir());
 
-        // Build includes path
-        PatternSet.NameEntry includes = javac.createInclude();
+            info.append("    work 
dir=").append(options.getScratchDir()).append("\n");
 
-        includes.setName(ctxt.getJavaPath());
-        info.append("    include=").append(ctxt.getJavaPath()).append("\n");
+            // Initialize and set java extensions
+            String exts = System.getProperty("java.ext.dirs");
+            if (exts != null) {
+                Path extdirs = new Path(project);
+                extdirs.setPath(exts);
+                javac.setExtdirs(extdirs);
+                info.append("    extension dir=").append(exts).append("\n");
+            }
 
-        BuildException be = null;
+            // Configure the compiler object
+            javac.setEncoding(javaEncoding);
+            javac.setClasspath(path);
+            javac.setDebug(ctxt.getOptions().getClassDebugInfo());
+            javac.setSrcdir(srcPath);
+            javac.setTempdir(options.getScratchDir());
+            javac.setFork(ctxt.getOptions().getFork());
+            info.append("    srcDir=").append(srcPath).append("\n");
+
+            // Set the Java compiler to use
+            if (options.getCompiler() != null) {
+                javac.setCompiler(options.getCompiler());
+                info.append("    
compiler=").append(options.getCompiler()).append("\n");
+            }
 
-        try {
-            if (ctxt.getOptions().getFork()) {
-                javac.execute();
-            } else {
-                synchronized (javacLock) {
+            if (options.getCompilerTargetVM() != null) {
+                javac.setTarget(options.getCompilerTargetVM());
+                info.append("   
compilerTargetVM=").append(options.getCompilerTargetVM()).append("\n");
+            }
+
+            if (options.getCompilerSourceVM() != null) {
+                javac.setSource(options.getCompilerSourceVM());
+                info.append("   
compilerSourceVM=").append(options.getCompilerSourceVM()).append("\n");
+            }
+
+            // Build includes path
+            PatternSet.NameEntry includes = javac.createInclude();
+
+            includes.setName(ctxt.getJavaPath());
+            info.append("    
include=").append(ctxt.getJavaPath()).append("\n");
+
+            try {
+                if (ctxt.getOptions().getFork()) {
                     javac.execute();
+                } else {
+                    synchronized (javacLock) {
+                        javac.execute();
+                    }
                 }
+            } catch (BuildException e) {
+                be = e;
+                log.error(Localizer.getMessage("jsp.error.javac"), e);
+                log.error(Localizer.getMessage("jsp.error.javac.env") + 
info.toString());
             }
-        } catch (BuildException e) {
-            be = e;
-            log.error(Localizer.getMessage("jsp.error.javac"), e);
-            log.error(Localizer.getMessage("jsp.error.javac.env") + 
info.toString());
-        }
 
-        errorReport.append(logger.getReport());
+            errorReport.append(logger.getReport());
+        } finally {
+            // Stop capturing the System.err output for this thread
+            errorCapture = SystemLogHandler.unsetThread();
+        }
 
-        // Stop capturing the System.err output for this thread
-        String errorCapture = SystemLogHandler.unsetThread();
         if (errorCapture != null) {
             errorReport.append(System.lineSeparator());
             errorReport.append(errorCapture);
diff --git a/test/org/apache/jasper/compiler/TestAntCompiler.java 
b/test/org/apache/jasper/compiler/TestAntCompiler.java
new file mode 100644
index 0000000000..76b939c65c
--- /dev/null
+++ b/test/org/apache/jasper/compiler/TestAntCompiler.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.easymock.EasyMock;
+
+public class TestAntCompiler {
+
+    @After
+    public void clearCapture() {
+        // Never leave the System.err capture installed on the test thread,
+        // even if an assertion below fails
+        AntCompiler.SystemLogHandler.unsetThread();
+    }
+
+    /*
+     * A compilation that fails unexpectedly, with the exception escaping
+     * generateClass(), must not leave the per-thread System.err capture
+     * installed. Otherwise, later output to System.err on the (pooled) thread
+     * would be silently discarded for the life of that thread.
+     */
+    @Test
+    public void testErrorCaptureClearedWhenCompilationFails() throws Exception 
{
+        Options options = EasyMock.createMock(Options.class);
+        
EasyMock.expect(options.getJavaEncoding()).andReturn("UTF-8").anyTimes();
+        EasyMock.expect(options.getCompiler()).andReturn(null).anyTimes();
+        EasyMock.replay(options);
+
+        JspCompilationContext ctxt = EasyMock.createMock(
+                JspCompilationContext.class);
+        EasyMock.expect(ctxt.getOptions()).andReturn(options).anyTimes();
+        EasyMock.expect(ctxt.getServletJavaFileName())
+                .andReturn("none_jsp.java").anyTimes();
+        // A null class path triggers an unexpected exception while the
+        // javac task is being configured, after the capture has started
+        EasyMock.expect(ctxt.getClassPath()).andReturn(null).anyTimes();
+        EasyMock.replay(ctxt);
+
+        AntCompiler compiler = new AntCompiler();
+        compiler.init(ctxt, null);
+
+        try {
+            compiler.generateClass(null);
+            Assert.fail("Compilation was expected to fail");
+        } catch (Exception expected) {
+            // Ignore
+        }
+
+        // The capture must not be active on this thread any more
+        Assert.assertNull(AntCompiler.SystemLogHandler.streams.get());
+        Assert.assertNull(AntCompiler.SystemLogHandler.data.get());
+        Assert.assertNull(AntCompiler.SystemLogHandler.unsetThread());
+
+        EasyMock.verify(options, ctxt);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to