1 /*
  2  * Copyright (c) 2014, 2024, 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 /*
 26  * @test
 27  * @summary Class-Path: attribute in MANIFEST file
 28  * @requires vm.cds
 29  * @library /test/lib
 30  * @compile test-classes/Hello.java
 31  * @run driver/timeout=240 ClassPathAttr
 32  */
 33 
 34 import jdk.test.lib.Platform;
 35 import jdk.test.lib.cds.CDSTestUtils;
 36 import jdk.test.lib.process.OutputAnalyzer;
 37 import java.io.File;
 38 import java.nio.file.Files;
 39 import java.nio.file.FileAlreadyExistsException;
 40 import java.nio.file.StandardCopyOption;
 41 import java.nio.file.Paths;
 42 
 43 
 44 public class ClassPathAttr {
 45 
 46   public static void main(String[] args) throws Exception {
 47     testNormalOps();
 48     testNonExistentJars();
 49     testClassPathAttrJarOnCP();
 50   }
 51 
 52   static void testNormalOps() throws Exception {
 53     buildCpAttr("cpattr1", "cpattr1.mf", "CpAttr1", "CpAttr1");
 54     buildCpAttr("cpattr1_long", "cpattr1_long.mf", "CpAttr1", "CpAttr1");
 55     buildCpAttr("cpattr2", "cpattr2.mf", "CpAttr2", "CpAttr2");
 56     buildCpAttr("cpattr3", "cpattr3.mf", "CpAttr3", "CpAttr2", "CpAttr3");
 57     buildCpAttr("cpattr4", "cpattr4.mf", "CpAttr4",
 58         "CpAttr2", "CpAttr3", "CpAttr4", "CpAttr5");
 59     buildCpAttr("cpattr5_123456789_223456789_323456789_423456789_523456789_623456789", "cpattr5_extra_long.mf", "CpAttr5", "CpAttr5");
 60 
 61     String[] classlist = { "CpAttr1", "CpAttr2", "CpAttr3", "CpAttr4", "CpAttr5"};
 62     String jar4 = TestCommon.getTestJar("cpattr4.jar");
 63     for (int i=1; i<=2; i++) {
 64       String jar1 = TestCommon.getTestJar("cpattr1.jar");
 65       if (i == 2) {
 66         // Test case #2 -- same as #1, except we use cpattr1_long.jar, which has a super-long
 67         // Class-Path: attribute.
 68         jar1 = TestCommon.getTestJar("cpattr1_long.jar");
 69       }
 70       String cp = jar1 + File.pathSeparator + jar4;
 71 
 72       TestCommon.testDump(cp, classlist);
 73 
 74       TestCommon.run(
 75           "-cp", cp,
 76           "CpAttr1")
 77         .assertNormalExit();
 78 
 79       // Logging test for class+path.
 80       TestCommon.run(
 81           "-Xlog:class+path",
 82           "-cp", cp,
 83           "CpAttr1")
 84         .assertNormalExit(output -> {
 85             output.shouldMatch("checking shared classpath entry: .*cpattr2.jar");
 86             output.shouldMatch("checking shared classpath entry: .*cpattr3.jar");
 87           });
 88 
 89       // Test handling of forward slash ('/') file separator when locating entries
 90       // in the classpath entry on Windows.
 91       // Skip the following test when CDS dynamic dump is enabled due to some
 92       // issue when converting a relative path to real path.
 93       if (Platform.isWindows() && !CDSTestUtils.DYNAMIC_DUMP) {
 94           // Test with relative path
 95           // Find the index to the dir before the jar file.
 96           int idx = jar1.lastIndexOf(File.separator);
 97           idx = jar1.substring(0, idx - 1).lastIndexOf(File.separator);
 98           // Setup jar directory and names.
 99           String jarDir = jar1.substring(0, idx);
100           String jar1Name = jar1.substring(idx + 1);
101           String jar4Name = jar4.substring(idx + 1);
102           String newCp = jar1Name.replace("\\", "/") + File.pathSeparator + jar4Name.replace("\\", "/");
103 
104           OutputAnalyzer out = TestCommon.testDump(jarDir, newCp, classlist, "-Xlog:class+path=info");
105           if (i == 1) {
106               out.shouldMatch("opened:.*cpattr1.jar"); // first jar on -cp
107           } else {
108               // first jar on -cp with long Class-Path: attribute
109               out.shouldMatch("opened:.*cpattr1_long.jar");
110           }
111           // one of the jar in the Class-Path: attribute of cpattr1.jar
112           out.shouldMatch("opened:.*cpattr2.jar");
113 
114           TestCommon.runWithRelativePath(
115               jarDir.replace("\\", "/"),
116               "-Xlog:class+path,class+load",
117               "-cp", newCp,
118               "CpAttr1")
119             .assertNormalExit(output -> {
120                 output.shouldMatch("checking shared classpath entry: .*cpattr2.jar");
121                 output.shouldMatch("checking shared classpath entry: .*cpattr3.jar");
122               });
123 
124           // Go one directory up.
125           int idx2 = jar1.substring(0, idx - 1).lastIndexOf(File.separator);
126           if (idx2 != -1) {
127               // Setup jar directory and names.
128               jarDir = jar1.substring(0, idx2);
129               // Set relative path to jar containing '\' and '/' file separators
130               // e.g. d1\d2/A.jar
131               jar1Name = jar1.substring(idx2 + 1).replace("\\", "/");
132               jar4Name = jar4.substring(idx2 + 1).replace("\\", "/");
133               jar1Name = jar1Name.replaceFirst("/", "\\\\");
134               jar4Name = jar4Name.replaceFirst("/", "\\\\");
135 
136               newCp = jar1Name + File.pathSeparator + jar4Name;
137               out = TestCommon.testDump(jarDir, newCp, classlist, "-Xlog:class+path=info");
138               if (i == 1) {
139                   out.shouldMatch("opened:.*cpattr1.jar"); // first jar on -cp
140               } else {
141                   // first jar on -cp with long Class-Path: attribute
142                   out.shouldMatch("opened:.*cpattr1_long.jar");
143               }
144               // one of the jar in the Class-Path: attribute of cpattr1.jar
145               out.shouldMatch("opened:.*cpattr2.jar");
146 
147               TestCommon.runWithRelativePath(
148                   jarDir.replace("\\", "/"),
149                   "-Xlog:class+path,class+load",
150                   "-cp", newCp,
151                   "CpAttr1")
152                 .assertNormalExit(output -> {
153                     output.shouldMatch("checking shared classpath entry: .*cpattr2.jar");
154                     output.shouldMatch("checking shared classpath entry: .*cpattr3.jar");
155                   });
156           }
157       }
158     }
159 
160     // test duplicate jars in the "Class-path" attribute in the jar manifest
161     buildCpAttr("cpattr_dup", "cpattr_dup.mf", "CpAttr1", "CpAttr1");
162     String cp = TestCommon.getTestJar("cpattr_dup.jar") + File.pathSeparator + jar4;
163     TestCommon.testDump(cp, classlist);
164 
165     TestCommon.run(
166         "-cp", cp,
167         "CpAttr1")
168       .assertNormalExit();
169   }
170 
171   static void testNonExistentJars() throws Exception {
172     buildCpAttr("cpattr6", "cpattr6.mf", "CpAttr6", "CpAttr6");
173 
174     String cp = TestCommon.getTestJar("cpattr6.jar");
175     String nonExistPath = CDSTestUtils.getOutputDir() + File.separator + "cpattrX.jar";
176     (new File(nonExistPath)).delete();
177 
178     TestCommon.testDump(cp, TestCommon.list("CpAttr6"),
179         "-Xlog:class+path");
180 
181     TestCommon.run(
182         "-Xlog:class+path",
183         "-cp", cp,
184         "CpAttr6")
185       .assertNormalExit(output -> {
186           output.shouldMatch("should be non-existent: .*cpattrX.jar");
187         });
188 
189     // Now make nonExistPath exist. CDS still loads, but archived non-system classes will not be used.
190     Files.copy(Paths.get(cp), Paths.get(nonExistPath),
191                StandardCopyOption.REPLACE_EXISTING);
192 
193     TestCommon.run(
194         "-Xlog:class+path",
195         "-cp", cp,
196         "CpAttr6")
197       .assertNormalExit(output -> {
198           output.shouldMatch("Archived non-system classes are disabled because the file .*cpattrX.jar exists");
199         });
200   }
201 
202   static void testClassPathAttrJarOnCP() throws Exception {
203     String helloJar = JarBuilder.getOrCreateHelloJar();
204     String jar1 = TestCommon.getTestJar("cpattr1.jar");
205     String cp = jar1 + File.pathSeparator + helloJar;
206 
207     // The cpattr1.jar contains "Class-Path: cpattr2.jar".
208     // The cpattr2.jar contains "Class-Path: cpattr3.jar cpattr5_123456789_223456789_323456789_42345678.jar".
209     // With -cp cpattr1:hello.jar, the following shared paths should be stored in the CDS archive:
210     // cpattr1.jar:cpattr2.jar:cpattr3.jar:cpattr5_123456789_223456789_323456789_42345678.jari:hello.jar
211     TestCommon.testDump(cp, TestCommon.list("Hello"), "-Xlog:class+path");
212 
213     // Run with the same -cp apattr1.jar:hello.jar. The Hello class should be
214     // loaded from the archive.
215     TestCommon.run("-Xlog:class+path,class+load",
216                    "-cp", cp,
217                    "Hello")
218               .assertNormalExit(output -> {
219                   output.shouldContain("Hello source: shared objects file");
220                 });
221 
222     // Run with -cp apattr1.jar:cpattr2.jar:hello.jar. App classpath mismatch should be detected.
223     String jar2 = TestCommon.getTestJar("cpattr2.jar");
224     cp = jar1 + File.pathSeparator + jar2 + File.pathSeparator + helloJar;
225     TestCommon.run("-Xlog:class+path,class+load",
226                    "-cp", cp,
227                    "Hello")
228               .assertAbnormalExit(output -> {
229                   output.shouldMatch(".*APP classpath mismatch, actual: -Djava.class.path=.*cpattr1.jar.*cpattr2.jar.*hello.jar")
230               .shouldContain("Unable to use shared archive.");
231                 });
232 
233     // Run with different -cp cpattr2.jar:hello.jar. App classpath mismatch should be detected.
234     cp = jar2 + File.pathSeparator + helloJar;
235     TestCommon.run("-Xlog:class+path,class+load",
236                    "-cp", cp,
237                    "Hello")
238               .assertAbnormalExit(output -> {
239                   output.shouldMatch(".*APP classpath mismatch, actual: -Djava.class.path=.*cpattr2.jar.*hello.jar")
240               .shouldContain("Unable to use shared archive.");
241                 });
242 
243     // Dumping with -cp cpattr1.jar:cpattr2.jar:hello.jar
244     // The cpattr2.jar is from the Class-Path: attribute of cpattr1.jar.
245     cp = jar1 + File.pathSeparator + jar2 + File.pathSeparator + helloJar;
246     TestCommon.testDump(cp, TestCommon.list("Hello"), "-Xlog:class+path");
247 
248     // Run with the same -cp as dump time. The Hello class should be loaded from the archive.
249     TestCommon.run("-Xlog:class+path,class+load",
250                    "-cp", cp,
251                    "Hello")
252               .assertNormalExit(output -> {
253                   output.shouldContain("Hello source: shared objects file");
254                 });
255 
256   }
257 
258   private static void buildCpAttr(String jarName, String manifest, String enclosingClassName, String ...testClassNames) throws Exception {
259     String jarClassesDir = CDSTestUtils.getOutputDir() + File.separator + jarName + "_classes";
260     try { Files.createDirectory(Paths.get(jarClassesDir)); } catch (FileAlreadyExistsException e) { }
261 
262     JarBuilder.compile(jarClassesDir, System.getProperty("test.src") + File.separator +
263         "test-classes" + File.separator + enclosingClassName + ".java");
264     JarBuilder.buildWithManifest(jarName, manifest, jarClassesDir, testClassNames);
265   }
266 }