7373
7474import org .graalvm .collections .EconomicMap ;
7575import org .graalvm .collections .EconomicSet ;
76+ import org .graalvm .collections .MapCursor ;
7677import org .graalvm .collections .Pair ;
7778import org .graalvm .compiler .options .OptionKey ;
7879import org .graalvm .compiler .options .OptionValues ;
@@ -103,6 +104,7 @@ public class NativeImageClassLoaderSupport {
103104 private final EconomicMap <URI , EconomicSet <String >> classes ;
104105 private final EconomicMap <URI , EconomicSet <String >> packages ;
105106 private final EconomicSet <String > emptySet ;
107+ private final EconomicSet <URI > builderURILocations ;
106108
107109 private final ClassPathClassLoader classPathClassLoader ;
108110 private final ClassLoader classLoader ;
@@ -124,6 +126,7 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St
124126 classes = EconomicMap .create ();
125127 packages = EconomicMap .create ();
126128 emptySet = EconomicSet .create ();
129+ builderURILocations = EconomicSet .create ();
127130
128131 classPathClassLoader = new ClassPathClassLoader (Util .verifyClassPathAndConvertToURLs (classpath ), defaultSystemClassLoader );
129132
@@ -142,6 +145,7 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St
142145 .map (Path ::of )
143146 .map (Util ::toRealPath )
144147 .collect (Collectors .toUnmodifiableList ());
148+ buildcp .stream ().map (Path ::toUri ).forEach (builderURILocations ::add );
145149
146150 imagemp = Arrays .stream (modulePath )
147151 .map (Path ::of )
@@ -650,10 +654,16 @@ private void initModule(ModuleReference moduleReference) {
650654 }
651655 try (ModuleReader moduleReader = moduleReference .open ()) {
652656 Module module = optionalModule .get ();
657+ var container = moduleReference .location ().orElseThrow ();
658+ if (ModuleLayer .boot ().equals (module .getLayer ())) {
659+ builderURILocations .add (container );
660+ }
653661 moduleReader .list ().forEach (moduleResource -> {
654- if (moduleResource .endsWith (CLASS_EXTENSION )) {
655- currentlyProcessedEntry = moduleReferenceLocation + "/" + moduleResource ;
656- executor .execute (() -> handleClassFileName (moduleReference .location ().orElseThrow (), module , moduleResource , '/' ));
662+ char fileSystemSeparatorChar = '/' ;
663+ String className = extractClassName (moduleResource , fileSystemSeparatorChar );
664+ if (className != null ) {
665+ currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource ;
666+ executor .execute (() -> handleClassFileName (container , module , className ));
657667 }
658668 entriesProcessed .increment ();
659669 });
@@ -715,9 +725,10 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
715725 public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) {
716726 assert !excludes .contains (file .getParent ()) : "Visiting file '" + file + "' with excluded parent directory" ;
717727 String fileName = root .relativize (file ).toString ();
718- if (fileName .endsWith (CLASS_EXTENSION )) {
728+ String className = extractClassName (fileName , fileSystemSeparatorChar );
729+ if (className != null ) {
719730 currentlyProcessedEntry = file .toUri ().toString ();
720- executor .execute (() -> handleClassFileName (container , null , fileName , fileSystemSeparatorChar ));
731+ executor .execute (() -> handleClassFileName (container , null , className ));
721732 }
722733 entriesProcessed .increment ();
723734 return FileVisitResult .CONTINUE ;
@@ -739,35 +750,34 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) {
739750
740751 /**
741752 * Take a file name from a possibly-multi-versioned jar file and remove the versioning
742- * information. See https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html for
743- * the specification of the versioning strings.
744- *
753+ * information. See
754+ * <a href="https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html">...</a>
755+ * for the specification of the versioning strings.
756+ * <p>
745757 * Then, depend on the JDK class loading mechanism to prefer the appropriately-versioned
746758 * class when the class is loaded. The same class name be loaded multiple times, but each
747759 * request will return the same appropriately-versioned class. If a higher-versioned class
748760 * is not available in a lower-versioned JDK, a ClassNotFoundException will be thrown, which
749761 * will be handled appropriately.
750762 */
751- private String strippedClassFileName (String fileName ) {
752- final String versionedPrefix = "META-INF/versions/" ;
753- final String versionedSuffix = "/" ;
763+ private String extractClassName (String fileName , char fileSystemSeparatorChar ) {
764+ if (!fileName .endsWith (CLASS_EXTENSION )) {
765+ return null ;
766+ }
767+ String versionedPrefix = "META-INF/versions/" ;
768+ String versionedSuffix = "/" ;
754769 String result = fileName ;
755770 if (fileName .startsWith (versionedPrefix )) {
756771 final int versionedSuffixIndex = fileName .indexOf (versionedSuffix , versionedPrefix .length ());
757772 if (versionedSuffixIndex >= 0 ) {
758773 result = fileName .substring (versionedSuffixIndex + versionedSuffix .length ());
759774 }
760775 }
761- return result .substring (0 , result .length () - CLASS_EXTENSION .length ());
776+ String strippedClassFileName = result .substring (0 , result .length () - CLASS_EXTENSION .length ());
777+ return strippedClassFileName .equals ("module-info" ) ? null : strippedClassFileName .replace (fileSystemSeparatorChar , '.' );
762778 }
763779
764- private void handleClassFileName (URI container , Module module , String fileName , char fileSystemSeparatorChar ) {
765- String strippedClassFileName = strippedClassFileName (fileName );
766- if (strippedClassFileName .equals ("module-info" )) {
767- return ;
768- }
769-
770- String className = strippedClassFileName .replace (fileSystemSeparatorChar , '.' );
780+ private void handleClassFileName (URI container , Module module , String className ) {
771781 synchronized (classes ) {
772782 EconomicSet <String > classNames = classes .get (container );
773783 if (classNames == null ) {
@@ -800,4 +810,37 @@ private void handleClassFileName(URI container, Module module, String fileName,
800810 }
801811 }
802812 }
813+
814+ public void reportBuilderClassesInApplication () {
815+ EconomicMap <URI , EconomicSet <String >> builderClasses = EconomicMap .create ();
816+ EconomicMap <URI , EconomicSet <String >> applicationClasses = EconomicMap .create ();
817+ MapCursor <URI , EconomicSet <String >> classesEntries = classes .getEntries ();
818+ while (classesEntries .advance ()) {
819+ var destinationMap = builderURILocations .contains (classesEntries .getKey ()) ? builderClasses : applicationClasses ;
820+ destinationMap .put (classesEntries .getKey (), classesEntries .getValue ());
821+ }
822+ boolean tolerateViolations = SubstrateOptions .TolerateBuilderClassesOnImageClasspath .getValue (parsedHostedOptions );
823+ MapCursor <URI , EconomicSet <String >> applicationClassesEntries = applicationClasses .getEntries ();
824+ while (applicationClassesEntries .advance ()) {
825+ var applicationClassContainer = applicationClassesEntries .getKey ();
826+ for (String applicationClass : applicationClassesEntries .getValue ()) {
827+ MapCursor <URI , EconomicSet <String >> builderClassesEntries = builderClasses .getEntries ();
828+ while (builderClassesEntries .advance ()) {
829+ var builderClassContainer = builderClassesEntries .getKey ();
830+ if (builderClassesEntries .getValue ().contains (applicationClass )) {
831+ String message = String .format ("Class-path entry %s contains class %s. This class is part of the image builder itself (in %s) and must not be passed via -cp." ,
832+ applicationClassContainer , applicationClass , builderClassContainer );
833+ if (!tolerateViolations ) {
834+ String errorMessage = String .join (" " , message ,
835+ "This can be caused by a fat-jar that illegally includes svm.jar (or graal-sdk.jar) due to its build-time dependency on it." ,
836+ "As a temporary workaround, %s allows turning this error into a warning." );
837+ throw UserError .abort (errorMessage , SubstrateOptionsParser .commandArgument (SubstrateOptions .TolerateBuilderClassesOnImageClasspath , "+" ));
838+ } else {
839+ System .out .println ("Warning: " + message );
840+ }
841+ }
842+ }
843+ }
844+ }
845+ }
803846}
0 commit comments