This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit be00e40bf0e9cba623d33ffef9be3acc755a4a0a 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 57a8c47e85..9f78b12539 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 2666fc1c37..9b5b89702b 100644 --- a/test/org/apache/catalina/startup/TestWebappServiceLoader.java +++ b/test/org/apache/catalina/startup/TestWebappServiceLoader.java @@ -171,6 +171,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]
