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 172dc23438c8b841683c1fa452df3cbe957568bd
Author: opencode <[email protected]>
AuthorDate: Thu Oct 8 17:31:20 2026 +0200

    Report service provider loading failures caused by a LinkageError via the
    documented IOException contract.
    
    WebappServiceLoader.loadServices() loads each provider with
    Class.forName(name, true, loader), which initializes the class. A defective
    provider can therefore fail with a LinkageError, for example a
    NoClassDefFoundError caused by a missing transitive dependency, an
    ExceptionInInitializerError caused by a failing static initialiser or a
    ClassFormatError caused by a corrupt class file. None of these were caught.
    The documented contract of load() is to report any problem loading a service
    as an IOException and its only caller,
    ContextConfig.processServletContainerInitializers(), only handles that
    exception. An escaped Error bypassed the clean "the 
ServletContainerInitializers
    for this application could not be loaded" failure path and aborted context
    configuration via the generic Throwable handling in LifecycleBase.
    
    Catch LinkageError alongside the existing exceptions and wrap it in an
    IOException so the failure follows the documented contract and the existing
    controlled context failure path. A test using a provider whose static
    initialiser fails verifies the behaviour.
---
 .../catalina/startup/WebappServiceLoader.java      |  5 +++
 .../catalina/startup/TestWebappServiceLoader.java  | 20 ++++++++++++
 .../TesterServiceWithFailingInitializer.java       | 38 ++++++++++++++++++++++
 3 files changed, 63 insertions(+)

diff --git a/java/org/apache/catalina/startup/WebappServiceLoader.java 
b/java/org/apache/catalina/startup/WebappServiceLoader.java
index bb8f60c000..baaaf35ce6 100644
--- a/java/org/apache/catalina/startup/WebappServiceLoader.java
+++ b/java/org/apache/catalina/startup/WebappServiceLoader.java
@@ -232,6 +232,11 @@ public class WebappServiceLoader<T> {
                 
services.add(serviceType.cast(clazz.getConstructor().newInstance()));
             } catch (ReflectiveOperationException | ClassCastException e) {
                 throw new IOException(e);
+            } catch (LinkageError e) {
+                // Loading with initialization enabled can fail with an Error, 
for example a missing
+                // transitive dependency or a failing static initializer. 
Report those via the
+                // documented IOException contract rather than letting the 
Error escape.
+                throw new IOException(e);
             }
         }
         return Collections.unmodifiableList(services);
diff --git a/test/org/apache/catalina/startup/TestWebappServiceLoader.java 
b/test/org/apache/catalina/startup/TestWebappServiceLoader.java
index e7fe0e7269..cc3c2356c7 100644
--- a/test/org/apache/catalina/startup/TestWebappServiceLoader.java
+++ b/test/org/apache/catalina/startup/TestWebappServiceLoader.java
@@ -172,6 +172,26 @@ public class TestWebappServiceLoader {
         }
     }
 
+    @Test
+    public void testServiceInitializationFailsWithLinkageError() throws 
Exception {
+        Class<?> sci = TesterServiceWithFailingInitializer.class;
+        loader = new WebappServiceLoader<>(context);
+        cl.loadClass(sci.getName());
+        EasyMock.expectLastCall()
+                .andReturn(sci);
+        LinkedHashSet<String> names = new LinkedHashSet<>();
+        names.add(sci.getName());
+        control.replay();
+        try {
+            loader.loadServices(ServletContainerInitializer.class, names);
+            Assert.fail("Expected IOException");
+        } catch (IOException ioe) {
+            assertThat(ioe.getCause(), instanceOf(LinkageError.class));
+        } finally {
+            control.verify();
+        }
+    }
+
     @Test
     public void testServiceCannotBeConstructed() throws Exception {
         Class<?> sci = Integer.class;
diff --git 
a/test/org/apache/catalina/startup/TesterServiceWithFailingInitializer.java 
b/test/org/apache/catalina/startup/TesterServiceWithFailingInitializer.java
new file mode 100644
index 0000000000..a99f3a945c
--- /dev/null
+++ b/test/org/apache/catalina/startup/TesterServiceWithFailingInitializer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.catalina.startup;
+
+import java.util.Set;
+
+import jakarta.servlet.ServletContainerInitializer;
+import jakarta.servlet.ServletContext;
+
+public class TesterServiceWithFailingInitializer implements 
ServletContainerInitializer {
+
+    static {
+        // Force an ExceptionInInitializerError (a LinkageError) when this
+        // class is initialized by Class.forName(name, true, loader).
+        if (Boolean.TRUE) {
+            throw new IllegalStateException("Test failure in static 
initializer");
+        }
+    }
+
+    @Override
+    public void onStartup(Set<Class<?>> c, ServletContext ctx) {
+        // Not reached
+    }
+}


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

Reply via email to