1 /*
  2  * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /**
 25  * @test
 26  * @enablePreview
 27  * @library /test/lib
 28  * @modules jdk.compiler
 29  * @build jdk.test.lib.compiler.CompilerUtils
 30  * @run junit/othervm BadProvidersTest
 31  * @summary Basic test of ServiceLoader with bad provider and bad provider
 32  *          factories deployed on the module path
 33  */
 34 
 35 import java.lang.classfile.ClassFile;
 36 import java.lang.constant.ClassDesc;
 37 import java.lang.constant.MethodTypeDesc;
 38 import java.lang.module.Configuration;
 39 import java.lang.module.ModuleFinder;
 40 import java.lang.reflect.AccessFlag;
 41 import java.nio.file.Files;
 42 import java.nio.file.Path;
 43 import java.nio.file.Paths;
 44 import java.nio.file.StandardCopyOption;
 45 import java.util.List;
 46 import java.util.ServiceConfigurationError;
 47 import java.util.ServiceLoader;
 48 import java.util.ServiceLoader.Provider;
 49 import java.util.Set;
 50 import java.util.stream.Collectors;
 51 
 52 import jdk.test.lib.compiler.CompilerUtils;
 53 
 54 
 55 import static java.lang.classfile.ClassFile.ACC_PUBLIC;
 56 import static java.lang.classfile.ClassFile.ACC_STATIC;
 57 import static java.lang.constant.ConstantDescs.CD_Object;
 58 import static java.lang.constant.ConstantDescs.INIT_NAME;
 59 import static java.lang.constant.ConstantDescs.MTD_void;
 60 
 61 import org.junit.jupiter.api.Assertions;
 62 import static org.junit.jupiter.api.Assertions.*;
 63 import org.junit.jupiter.api.Test;
 64 import org.junit.jupiter.params.ParameterizedTest;
 65 import org.junit.jupiter.params.provider.ValueSource;
 66 
 67 /**
 68  * Basic test of `provides S with PF` and `provides S with P` where the provider
 69  * factory or provider
 70  */
 71 public class BadProvidersTest {
 72 
 73     private static final String TEST_SRC = System.getProperty("test.src");
 74 
 75     private static final Path USER_DIR   = Paths.get(System.getProperty("user.dir"));
 76     private static final Path SRC_DIR    = Paths.get(TEST_SRC, "modules");
 77 
 78     private static final Path BADFACTORIES_DIR = Paths.get(TEST_SRC, "badfactories");
 79     private static final Path BADPROVIDERS_DIR = Paths.get(TEST_SRC, "badproviders");
 80 
 81     private static final String TEST1_MODULE = "test1";
 82     private static final String TEST2_MODULE = "test2";
 83 
 84     private static final String TEST_SERVICE = "p.Service";
 85 
 86     /**
 87      * Compiles a module, returning a module path with the compiled module.
 88      */
 89     private Path compileTest(String moduleName) throws Exception {
 90         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 91         Path output = Files.createDirectory(dir.resolve(moduleName));
 92         boolean compiled = CompilerUtils.compile(SRC_DIR.resolve(moduleName), output);
 93         assertTrue(compiled);
 94         return dir;
 95     }
 96 
 97     /**
 98      * Resolves a test module and loads it into its own layer. ServiceLoader
 99      * is then used to load all providers.
100      */
101     private List<Provider> loadProviders(Path mp, String moduleName) throws Exception {
102         ModuleFinder finder = ModuleFinder.of(mp);
103 
104         ModuleLayer bootLayer = ModuleLayer.boot();
105 
106         Configuration cf = bootLayer.configuration()
107                 .resolveAndBind(finder, ModuleFinder.of(), Set.of(moduleName));
108 
109         ClassLoader scl = ClassLoader.getSystemClassLoader();
110 
111         ModuleLayer layer = ModuleLayer.boot().defineModulesWithOneLoader(cf, scl);
112 
113         Class<?> service = layer.findLoader(moduleName).loadClass(TEST_SERVICE);
114 
115         return ServiceLoader.load(layer, service)
116                 .stream()
117                 .collect(Collectors.toList());
118     }
119 
120     @Test
121     public void sanityTest1() throws Exception {
122         Path mods = compileTest(TEST1_MODULE);
123         List<Provider> list = loadProviders(mods, TEST1_MODULE);
124         assertTrue(list.size() == 1);
125 
126         // the provider is a singleton, enforced by the provider factory
127         Object p1 = list.get(0).get();
128         Object p2 = list.get(0).get();
129         assertTrue(p1 != null);
130         assertTrue(p1 == p2);
131     }
132 
133     @Test
134     public void sanityTest2() throws Exception {
135         Path mods = compileTest(TEST2_MODULE);
136         List<Provider> list = loadProviders(mods, TEST2_MODULE);
137         assertTrue(list.size() == 1);
138         Object p = list.get(0).get();
139         assertTrue(p != null);
140     }
141 
142     @ParameterizedTest
143     @ValueSource(strings = { "classnotpublic", "methodnotpublic", "badreturntype",
144             "returnsnull", "throwsexception" })
145     public void testBadFactory(String testName) throws Exception {
146         Path mods = compileTest(TEST1_MODULE);
147 
148         // compile the bad factory
149         Path source = BADFACTORIES_DIR.resolve(testName);
150         Path output = Files.createTempDirectory(USER_DIR, "tmp");
151         boolean compiled = CompilerUtils.compile(source, output);
152         assertTrue(compiled);
153 
154         // copy the compiled class into the module
155         Path classFile = Paths.get("p", "ProviderFactory.class");
156         Files.copy(output.resolve(classFile),
157                 mods.resolve(TEST1_MODULE).resolve(classFile),
158                 StandardCopyOption.REPLACE_EXISTING);
159 
160         Assertions.assertThrows(ServiceConfigurationError.class,
161                 // load providers and instantiate each one
162                 () -> loadProviders(mods, TEST1_MODULE).forEach(Provider::get)
163         );
164     }
165 
166     @ParameterizedTest
167     @ValueSource(strings = { "notpublic", "ctornotpublic", "notasubtype", "throwsexception" })
168     public void testBadProvider(String testName) throws Exception {
169         Path mods = compileTest(TEST2_MODULE);
170 
171         // compile the bad provider
172         Path source = BADPROVIDERS_DIR.resolve(testName);
173         Path output = Files.createTempDirectory(USER_DIR, "tmp");
174         boolean compiled = CompilerUtils.compile(source, output);
175         assertTrue(compiled);
176 
177         // copy the compiled class into the module
178         Path classFile = Paths.get("p", "Provider.class");
179         Files.copy(output.resolve(classFile),
180                 mods.resolve(TEST2_MODULE).resolve(classFile),
181                 StandardCopyOption.REPLACE_EXISTING);
182 
183         Assertions.assertThrows(ServiceConfigurationError.class,
184                 // load providers and instantiate each one
185                 () -> loadProviders(mods, TEST2_MODULE).forEach(Provider::get)
186         );
187     }
188 
189 
190     /**
191      * Test a service provider that defines more than one no-args
192      * public static "provider" method.
193      */
194     @Test
195     public void testWithTwoFactoryMethods() throws Exception {
196         Path mods = compileTest(TEST1_MODULE);
197 
198         var bytes = ClassFile.of().build(ClassDesc.of("p", "ProviderFactory"), clb -> {
199             clb.withSuperclass(CD_Object);
200             clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY);
201 
202             var providerFactory$1 = ClassDesc.of("p", "ProviderFactory$1");
203 
204             // public static p.Service provider()
205             clb.withMethodBody("provider", MethodTypeDesc.of(ClassDesc.of("p", "Service")),
206                     ACC_PUBLIC | ACC_STATIC, cob -> {
207                         cob.new_(providerFactory$1);
208                         cob.dup();
209                         cob.invokespecial(providerFactory$1, INIT_NAME, MTD_void);
210                         cob.areturn();
211                     });
212 
213             // public static p.ProviderFactory$1 provider()
214             clb.withMethodBody("provider", MethodTypeDesc.of(providerFactory$1),
215                     ACC_PUBLIC | ACC_STATIC, cob -> {
216                         cob.new_(providerFactory$1);
217                         cob.dup();
218                         cob.invokespecial(providerFactory$1, INIT_NAME, MTD_void);
219                         cob.areturn();
220                     });
221         });
222 
223         // write the class bytes into the compiled module directory
224         Path classFile = mods.resolve(TEST1_MODULE)
225                 .resolve("p")
226                 .resolve("ProviderFactory.class");
227         Files.write(classFile, bytes);
228 
229         Assertions.assertThrows(ServiceConfigurationError.class,
230                 // load providers and instantiate each one
231                 () -> loadProviders(mods, TEST1_MODULE).forEach(Provider::get)
232         );
233     }
234 
235 }
236