--- old/make/mapfiles/libjava/mapfile-vers 2017-03-21 13:43:51.614638588 +0000 +++ new/make/mapfiles/libjava/mapfile-vers 2017-03-21 13:43:51.414624863 +0000 @@ -128,7 +128,6 @@ Java_java_lang_Class_isInstance; Java_java_lang_Class_registerNatives; Java_java_lang_ClassLoader_findBootstrapClass; - Java_java_lang_ClassLoader_defineClass0; Java_java_lang_ClassLoader_defineClass1; Java_java_lang_ClassLoader_defineClass2; Java_java_lang_ClassLoader_findBuiltinLib; --- old/src/java.base/share/classes/java/lang/Class.java 2017-03-21 13:43:52.116673037 +0000 +++ new/src/java.base/share/classes/java/lang/Class.java 2017-03-21 13:43:51.914659175 +0000 @@ -64,9 +64,9 @@ import jdk.internal.HotSpotIntrinsicCandidate; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; -import jdk.internal.loader.ResourceHelper; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; +import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.ConstantPool; import jdk.internal.reflect.Reflection; @@ -2563,11 +2563,11 @@ Module module = getModule(); if (module.isNamed()) { - if (!ResourceHelper.isSimpleResource(name)) { + if (Resources.canEncapsulate(name)) { Module caller = Reflection.getCallerClass().getModule(); if (caller != module) { Set packages = module.getDescriptor().packages(); - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); if (packages.contains(pn) && !module.isOpen(pn, caller)) { // resource is in package not open to caller return null; @@ -2665,11 +2665,11 @@ Module module = getModule(); if (module.isNamed()) { - if (!ResourceHelper.isSimpleResource(name)) { + if (Resources.canEncapsulate(name)) { Module caller = Reflection.getCallerClass().getModule(); if (caller != module) { Set packages = module.getDescriptor().packages(); - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); if (packages.contains(pn) && !module.isOpen(pn, caller)) { // resource is in package not open to caller return null; --- old/src/java.base/share/classes/java/lang/ClassLoader.java 2017-03-21 13:43:52.698712976 +0000 +++ new/src/java.base/share/classes/java/lang/ClassLoader.java 2017-03-21 13:43:52.493698908 +0000 @@ -983,7 +983,7 @@ { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); - Class c = defineClass1(name, b, off, len, protectionDomain, source); + Class c = defineClass1(this, name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; } @@ -1075,17 +1075,17 @@ protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); - Class c = defineClass2(name, b, b.position(), len, protectionDomain, source); + Class c = defineClass2(this, name, b, b.position(), len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; } - private native Class defineClass1(String name, byte[] b, int off, int len, - ProtectionDomain pd, String source); + static native Class defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, + ProtectionDomain pd, String source); - private native Class defineClass2(String name, java.nio.ByteBuffer b, - int off, int len, ProtectionDomain pd, - String source); + static native Class defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b, + int off, int len, ProtectionDomain pd, + String source); // true if the name is null or has the potential to be a valid binary name private boolean checkName(String name) { --- old/src/java.base/share/classes/java/lang/RuntimePermission.java 2017-03-21 13:43:53.247750651 +0000 +++ new/src/java.base/share/classes/java/lang/RuntimePermission.java 2017-03-21 13:43:53.053737338 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,7 @@ package java.lang; import java.security.*; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.StringTokenizer; +import java.lang.module.ModuleFinder; /** * This class is for runtime permissions. A {@code RuntimePermission} @@ -265,6 +263,16 @@ * * * + * defineClass + * Define a class with + * {@link java.lang.invoke.MethodHandles.Lookup#defineClass(byte[]) + * Lookup.defineClass}. + * This grants code with a suitably privileged {@code Lookup} object + * permission to define classes in the same package as the {@code Lookup}'s + * lookup class. + * + * + * * accessDeclaredMembers * Access to the declared members of a class * This grants code permission to query a class for its public, @@ -359,6 +367,14 @@ * See {@link java.lang.System.LoggerFinder java.lang.System.LoggerFinder} * for more information. * + * + * + * accessSystemModules + * Access system modules in the runtime image. + * This grants the permission to access resources in the + * {@linkplain ModuleFinder#ofSystem system modules} in the runtime image. + * + * * * * @implNote --- old/src/java.base/share/classes/java/lang/StackStreamFactory.java 2017-03-21 13:43:53.753785374 +0000 +++ new/src/java.base/share/classes/java/lang/StackStreamFactory.java 2017-03-21 13:43:53.555771787 +0000 @@ -982,13 +982,6 @@ } private static boolean isReflectionFrame(Class c) { - if (c.getName().startsWith("jdk.internal.reflect") && - !MethodAccessor.class.isAssignableFrom(c) && - !ConstructorAccessor.class.isAssignableFrom(c)) { - throw new InternalError("Not jdk.internal.reflect.MethodAccessor" - + " or jdk.internal.reflect.ConstructorAccessor: " - + c.toString()); - } // ## should filter all @Hidden frames? return c == Method.class || c == Constructor.class || --- old/src/java.base/share/classes/java/lang/System.java 2017-03-21 13:43:54.269820784 +0000 +++ new/src/java.base/share/classes/java/lang/System.java 2017-03-21 13:43:54.073807334 +0000 @@ -43,6 +43,7 @@ import java.lang.reflect.Module; import java.net.URL; import java.security.AccessControlContext; +import java.security.ProtectionDomain; import java.util.Properties; import java.util.PropertyPermission; import java.util.Map; @@ -1845,6 +1846,39 @@ } /** + * Logs an exception/error at initialization time to stdout or stderr. + * + * @param printToStderr to print to stderr rather than stdout + * @param printStackTrace to print the stack trace + * @param msg the message to print before the exception, can be {@code null} + * @param e the exception or error + */ + private static void logInitException(boolean printToStderr, + boolean printStackTrace, + String msg, + Throwable e) { + if (VM.initLevel() < 1) { + throw new InternalError("system classes not initialized"); + } + PrintStream log = (printToStderr) ? err : out; + if (msg != null) { + log.println(msg); + } + if (printStackTrace) { + e.printStackTrace(log); + } else { + log.println(e); + for (Throwable suppressed : e.getSuppressed()) { + log.println("Suppressed: " + suppressed); + } + Throwable cause = e.getCause(); + if (cause != null) { + log.println("Caused by: " + cause); + } + } + } + + /** * Initialize the system class. Called after thread initialization. */ private static void initPhase1() { @@ -1922,13 +1956,25 @@ /* * Invoked by VM. Phase 2 module system initialization. * Only classes in java.base can be loaded in this phase. + * + * @param printToStderr print exceptions to stderr rather than stdout + * @param printStackTrace print stack trace when exception occurs + * + * @return JNI_OK for success, JNI_ERR for failure */ - private static void initPhase2() { - // initialize the module system - System.bootLayer = ModuleBootstrap.boot(); + private static int initPhase2(boolean printToStderr, boolean printStackTrace) { + try { + bootLayer = ModuleBootstrap.boot(); + } catch (Exception | Error e) { + logInitException(printToStderr, printStackTrace, + "Error occurred during initialization of boot layer", e); + return -1; // JNI_ERR + } // module system initialized VM.initLevel(2); + + return 0; // JNI_OK } /* @@ -2034,6 +2080,9 @@ public ConcurrentHashMap createOrGetClassLoaderValueMap(ClassLoader cl) { return cl.createOrGetClassLoaderValueMap(); } + public Class defineClass(ClassLoader loader, String name, byte[] b, ProtectionDomain pd, String source) { + return ClassLoader.defineClass1(loader, name, b, 0, b.length, pd, source); + } public Class findBootstrapClassOrNull(ClassLoader cl, String name) { return cl.findBootstrapClassOrNull(name); } --- old/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2017-03-21 13:43:54.802857360 +0000 +++ new/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2017-03-21 13:43:54.599843430 +0000 @@ -25,6 +25,9 @@ package java.lang.invoke; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.module.IllegalAccessLogger; +import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.vm.annotation.ForceInline; @@ -43,6 +46,9 @@ import java.lang.reflect.Module; import java.lang.reflect.ReflectPermission; import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -191,6 +197,12 @@ } if ((lookup.lookupModes() & Lookup.MODULE) == 0) throw new IllegalAccessException("lookup does not have MODULE lookup mode"); + if (!callerModule.isNamed() && targetModule.isNamed()) { + IllegalAccessLogger logger = IllegalAccessLogger.illegalAccessLogger(); + if (logger != null) { + logger.logIfOpenedByBackdoor(lookup, targetClass); + } + } return new Lookup(targetClass); } @@ -855,6 +867,112 @@ return new Lookup(lookupClass(), newModes); } + /** + * Defines a class to the same class loader and in the same runtime package and + * {@linkplain java.security.ProtectionDomain protection domain} as this lookup's + * {@linkplain #lookupClass() lookup class}. + * + *

The {@linkplain #lookupModes() lookup modes} for this lookup must include + * {@link #PACKAGE PACKAGE} access as default (package) members will be + * accessible to the class. The {@code PACKAGE} lookup mode serves to authenticate + * that the lookup object was created by a caller in the runtime package (or derived + * from a lookup originally created by suitably privileged code to a target class in + * the runtime package). The lookup modes cannot include {@link #PRIVATE PRIVATE} + * access. A lookup with {@code PRIVATE} access can be downgraded to drop this lookup + * mode with the {@linkplain #dropLookupMode(int) dropLookupMode} method.

+ * + *

The {@code bytes} parameter is the class bytes of a valid class file (as defined + * by the The Java Virtual Machine Specification) with a class name in the + * same package as the lookup class.

+ * + *

This method does not run the class initializer. The class initializer may + * run at a later time, as detailed in section 12.4 of the The Java Language + * Specification.

+ * + *

If there is a security manager, its {@code checkPermission} method is first called + * to check {@code RuntimePermission("defineClass")}.

+ * + * @param bytes the class bytes + * @return the {@code Class} object for the class + * @throws IllegalArgumentException the bytes are for a class in a different package + * to the lookup class + * @throws IllegalAccessException if this lookup does not have {@code PACKAGE} access + * @throws UnsupportedOperationException if the lookup class has {@code PRIVATE} access + * @throws LinkageError if the class is malformed ({@code ClassFormatError}), cannot be + * verified ({@code VerifyError}), is already defined, or another linkage error occurs + * @throws SecurityException if denied by the security manager + * @throws NullPointerException if {@code bytes} is {@code null} + * @since 9 + * @spec JPMS + * @see Lookup#privateLookupIn + * @see Lookup#dropLookupMode + * @see ClassLoader#defineClass(String,byte[],int,int,ProtectionDomain) + */ + public Class defineClass(byte[] bytes) throws IllegalAccessException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(new RuntimePermission("defineClass")); + if (hasPrivateAccess()) + throw new UnsupportedOperationException("PRIVATE access not supported"); + if ((lookupModes() & PACKAGE) == 0) + throw new IllegalAccessException("Lookup does not have PACKAGE access"); + assert (lookupModes() & (MODULE|PUBLIC)) != 0; + + // parse class bytes to get class name (in internal form) + bytes = bytes.clone(); + String name; + try { + ClassReader reader = new ClassReader(bytes); + name = reader.getClassName(); + } catch (RuntimeException e) { + // ASM exceptions are poorly specified + ClassFormatError cfe = new ClassFormatError(); + cfe.initCause(e); + throw cfe; + } + + // get package and class name in binary form + String cn, pn; + int index = name.lastIndexOf('/'); + if (index == -1) { + cn = name; + pn = ""; + } else { + cn = name.replace('/', '.'); + pn = cn.substring(0, index); + } + if (!pn.equals(lookupClass.getPackageName())) { + throw new IllegalArgumentException("Class not in same package as lookup class"); + } + + // invoke the class loader's defineClass method + ClassLoader loader = lookupClass.getClassLoader(); + ProtectionDomain pd = (loader != null) ? lookupClassProtectionDomain() : null; + String source = "__Lookup_defineClass__"; + Class clazz = SharedSecrets.getJavaLangAccess().defineClass(loader, cn, bytes, pd, source); + assert clazz.getClassLoader() == lookupClass.getClassLoader() + && clazz.getPackageName().equals(lookupClass.getPackageName()) + && protectionDomain(clazz) == lookupClassProtectionDomain(); + return clazz; + } + + private ProtectionDomain lookupClassProtectionDomain() { + ProtectionDomain pd = cachedProtectionDomain; + if (pd == null) { + cachedProtectionDomain = pd = protectionDomain(lookupClass); + } + return pd; + } + + private ProtectionDomain protectionDomain(Class clazz) { + PrivilegedAction pa = clazz::getProtectionDomain; + return AccessController.doPrivileged(pa); + } + + // cached protection domain + private volatile ProtectionDomain cachedProtectionDomain; + + // Make sure outer class is initialized first. static { IMPL_NAMES.getClass(); } @@ -1948,7 +2066,7 @@ /** * Returns {@code true} if this lookup has {@code PRIVATE} access. - * @return {@code true} if this lookup has {@code PRIVATE} acesss. + * @return {@code true} if this lookup has {@code PRIVATE} access. * @since 9 */ public boolean hasPrivateAccess() { --- old/src/java.base/share/classes/java/lang/module/Configuration.java 2017-03-21 13:43:55.408898946 +0000 +++ new/src/java.base/share/classes/java/lang/module/Configuration.java 2017-03-21 13:43:55.211885428 +0000 @@ -112,11 +112,9 @@ // module constraints on target private final String osName; private final String osArch; - private final String osVersion; String osName() { return osName; } String osArch() { return osArch; } - String osVersion() { return osVersion; } private Configuration() { this.parents = Collections.emptyList(); @@ -125,7 +123,6 @@ this.nameToModule = Collections.emptyMap(); this.osName = null; this.osArch = null; - this.osVersion = null; } private Configuration(List parents, @@ -152,7 +149,6 @@ this.osName = resolver.osName(); this.osArch = resolver.osArch(); - this.osVersion = resolver.osVersion(); } /** @@ -281,6 +277,7 @@ * observability-related reasons:

* *
    + * *
  • A root module, or a direct or transitive dependency, is not * found.

  • * @@ -289,13 +286,6 @@ * descriptor ({@code module-info.class}) or two versions of the same * module are found in the same directory.

    * - *
  • A module with the required name is found but the module - * requires a different {@link ModuleDescriptor#osName() operating - * system}, {@link ModuleDescriptor#osArch() architecture}, or {@link - * ModuleDescriptor#osVersion() version} to other modules that have - * been resolved for the new configuration or modules in the parent - * configurations.

  • - * *
* *

Post-resolution consistency checks may fail with {@code @@ -306,6 +296,10 @@ *

  • A cycle is detected, say where module {@code m1} requires * module {@code m2} and {@code m2} requires {@code m1}.

  • * + *
  • A module reads two or more modules with the same name. This + * includes the case where a module reads another with the same name as + * itself.

  • + * *
  • Two or more modules in the configuration export the same * package to a module that reads both. This includes the case where a * module {@code M} containing package {@code p} reads another module @@ -319,8 +313,9 @@ * * * @implNote In the implementation then observability of modules may depend - * on referential integrity checks that ensure different builds of tightly - * coupled modules are not combined in the same configuration. + * on referential integrity or other checks that ensure different builds of + * tightly coupled modules or modules for specific operating systems or + * architectures are not combined in the same configuration. * * @param before * The before module finder to find modules --- old/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java 2017-03-21 13:43:55.917933875 +0000 +++ new/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java 2017-03-21 13:43:55.718920220 +0000 @@ -179,8 +179,10 @@ private final Set mods; private final String name; private final Version compiledVersion; + private final String rawCompiledVersion; - private Requires(Set ms, String mn, Version v) { + private Requires(Set ms, String mn, Version v, String vs) { + assert v == null || vs == null; if (ms.isEmpty()) { ms = Collections.emptySet(); } else { @@ -189,12 +191,14 @@ this.mods = ms; this.name = mn; this.compiledVersion = v; + this.rawCompiledVersion = vs; } private Requires(Set ms, String mn, Version v, boolean unused) { this.mods = ms; this.name = mn; this.compiledVersion = v; + this.rawCompiledVersion = null; } /** @@ -218,13 +222,33 @@ /** * Returns the version of the module if recorded at compile-time. * - * @return The version of the module if recorded at compile-time + * @return The version of the module if recorded at compile-time, + * or an empty {@code Optional} if no version was recorded or + * the version string recorded is {@linkplain Version#parse(String) + * unparseable} */ public Optional compiledVersion() { return Optional.ofNullable(compiledVersion); } /** + * Returns the string with the possibly-unparseable version of the module + * if recorded at compile-time. + * + * @return The string containing the version of the module if recorded + * at compile-time + * + * @see #compiledVersion() + */ + public Optional rawCompiledVersion() { + if (compiledVersion != null) { + return Optional.of(compiledVersion.toString()); + } else { + return Optional.ofNullable(rawCompiledVersion); + } + } + + /** * Compares this module dependence to another. * *

    Two {@code Requires} objects are compared by comparing their @@ -236,7 +260,10 @@ * recorded at compile-time are compared. When comparing the versions * recorded at compile-time then a dependence that has a recorded * version is considered to succeed a dependence that does not have a - * recorded version.

    + * recorded version. If both recorded versions are {@linkplain + * Version#parse(String) unparseable} then the {@linkplain + * #rawCompiledVersion() raw version strings} are compared + * lexicographically.

    * * @param that * The module dependence to compare @@ -262,6 +289,10 @@ c = compare(this.compiledVersion, that.compiledVersion); if (c != 0) return c; + // rawCompiledVersion + c = compare(this.rawCompiledVersion, that.rawCompiledVersion); + if (c != 0) return c; + return 0; } @@ -289,7 +320,8 @@ return false; Requires that = (Requires)ob; return name.equals(that.name) && mods.equals(that.mods) - && Objects.equals(compiledVersion, that.compiledVersion); + && Objects.equals(compiledVersion, that.compiledVersion) + && Objects.equals(rawCompiledVersion, that.rawCompiledVersion); } /** @@ -306,6 +338,8 @@ int hash = name.hashCode() * 43 + mods.hashCode(); if (compiledVersion != null) hash = hash * 43 + compiledVersion.hashCode(); + if (rawCompiledVersion != null) + hash = hash * 43 + rawCompiledVersion.hashCode(); return hash; } @@ -774,7 +808,7 @@ /** * Returns the fully qualified class name of the service type. * - * @return The fully qualified class name of the service type. + * @return The fully qualified class name of the service type */ public String service() { return service; } @@ -1199,6 +1233,7 @@ private final String name; private final Version version; + private final String rawVersionString; private final Set modifiers; private final boolean open; // true if modifiers contains OPEN private final boolean automatic; // true if modifiers contains AUTOMATIC @@ -1209,12 +1244,10 @@ private final Set provides; private final Set packages; private final String mainClass; - private final String osName; - private final String osArch; - private final String osVersion; private ModuleDescriptor(String name, Version version, + String rawVersionString, Set modifiers, Set requires, Set exports, @@ -1222,13 +1255,12 @@ Set uses, Set provides, Set packages, - String mainClass, - String osName, - String osArch, - String osVersion) + String mainClass) { + assert version == null || rawVersionString == null; this.name = name; this.version = version; + this.rawVersionString = rawVersionString; this.modifiers = emptyOrUnmodifiableSet(modifiers); this.open = modifiers.contains(Modifier.OPEN); this.automatic = modifiers.contains(Modifier.AUTOMATIC); @@ -1242,9 +1274,6 @@ this.packages = emptyOrUnmodifiableSet(packages); this.mainClass = mainClass; - this.osName = osName; - this.osArch = osArch; - this.osVersion = osVersion; } /** @@ -1261,13 +1290,11 @@ Set provides, Set packages, String mainClass, - String osName, - String osArch, - String osVersion, int hashCode, boolean unused) { this.name = name; this.version = version; + this.rawVersionString = null; this.modifiers = modifiers; this.open = modifiers.contains(Modifier.OPEN); this.automatic = modifiers.contains(Modifier.AUTOMATIC); @@ -1278,9 +1305,6 @@ this.provides = provides; this.packages = packages; this.mainClass = mainClass; - this.osName = osName; - this.osArch = osArch; - this.osVersion = osVersion; this.hash = hashCode; } @@ -1394,18 +1418,36 @@ /** *

    Returns the module version.

    * - * @return This module's version + * @return This module's version, or an empty {@code Optional} if the + * module does not have a version or the version is + * {@linkplain Version#parse(String) unparseable} */ public Optional version() { return Optional.ofNullable(version); } /** + *

    Returns the string with the possibly-unparseable version of the + * module

    + * + * @return The string containing the version of the module + * + * @see #version() + */ + public Optional rawVersion() { + if (version != null) { + return Optional.of(version.toString()); + } else { + return Optional.ofNullable(rawVersionString); + } + } + + /** *

    Returns a string containing the module name and, if present, its * version.

    * * @return A string containing the module name and, if present, its - * version. + * version */ public String toNameAndVersion() { if (version != null) { @@ -1425,41 +1467,12 @@ } /** - * Returns the operating system name if the module is operating system - * specific. - * - * @return The operating system name or an empty {@code Optional} - * if the module is not operating system specific - */ - public Optional osName() { - return Optional.ofNullable(osName); - } - - /** - * Returns the operating system architecture if the module is operating - * system architecture specific. - * - * @return The operating system architecture or an empty {@code Optional} - * if the module is not operating system architecture specific - */ - public Optional osArch() { - return Optional.ofNullable(osArch); - } - - /** - * Returns the operating system version if the module is operating - * system version specific. - * - * @return The operating system version or an empty {@code Optional} - * if the module is not operating system version specific - */ - public Optional osVersion() { - return Optional.ofNullable(osVersion); - } - - /** * Returns the set of packages in the module. * + *

    The set of packages includes all exported and open packages, as well + * as the packages of any service providers, and the package for the main + * class.

    + * * @return A possibly-empty unmodifiable set of the packages in the module */ public Set packages() { @@ -1518,9 +1531,7 @@ final Set uses = new HashSet<>(); final Map provides = new HashMap<>(); Version version; - String osName; - String osArch; - String osVersion; + String rawVersionString; String mainClass; /** @@ -1604,24 +1615,21 @@ Objects.requireNonNull(compiledVersion); if (strict) mn = requireModuleName(mn); - return requires(new Requires(ms, mn, compiledVersion)); + return requires(new Requires(ms, mn, compiledVersion, null)); } /* package */Builder requires(Set ms, String mn, - String compiledVersion) { - Version v = null; + String rawCompiledVersion) { + Requires r; try { - v = Version.parse(compiledVersion); + Version v = Version.parse(rawCompiledVersion); + r = new Requires(ms, mn, v, null); } catch (IllegalArgumentException e) { - // for now, drop un-parsable version when non-strict if (strict) throw e; + r = new Requires(ms, mn, null, rawCompiledVersion); } - if (v == null) { - return requires(ms, mn); - } else { - return requires(ms, mn, v); - } + return requires(r); } /** @@ -1646,7 +1654,7 @@ public Builder requires(Set ms, String mn) { if (strict) mn = requireModuleName(mn); - return requires(new Requires(ms, mn, null)); + return requires(new Requires(ms, mn, null, null)); } /** @@ -1952,7 +1960,7 @@ * a class in a named package * @throws IllegalStateException * If a dependency on the service type has already been declared - * or this is a builder for an an automatic module + * or this is a builder for an automatic module */ public Builder uses(String service) { if (automatic) @@ -2068,6 +2076,7 @@ */ public Builder version(Version v) { version = requireNonNull(v); + rawVersionString = null; return this; } @@ -2086,18 +2095,15 @@ * @see Version#parse(String) */ public Builder version(String vs) { - Version v; - if (strict) { - v = Version.parse(vs); - } else { - try { - v = Version.parse(vs); - } catch (IllegalArgumentException ignore) { - // for now, ignore when non-strict - return this; - } + try { + version = Version.parse(vs); + rawVersionString = null; + } catch (IllegalArgumentException e) { + if (strict) throw e; + version = null; + rawVersionString = vs; } - return version(v); + return this; } /** @@ -2132,60 +2138,6 @@ } /** - * Sets the operating system name. - * - * @param name - * The operating system name - * - * @return This builder - * - * @throws IllegalArgumentException - * If {@code name} is {@code null} or the empty String - */ - public Builder osName(String name) { - if (name == null || name.isEmpty()) - throw new IllegalArgumentException("OS name is null or empty"); - osName = name; - return this; - } - - /** - * Sets the operating system architecture. - * - * @param arch - * The operating system architecture - * - * @return This builder - * - * @throws IllegalArgumentException - * If {@code name} is {@code null} or the empty String - */ - public Builder osArch(String arch) { - if (arch == null || arch.isEmpty()) - throw new IllegalArgumentException("OS arch is null or empty"); - osArch = arch; - return this; - } - - /** - * Sets the operating system version. - * - * @param version - * The operating system version - * - * @return This builder - * - * @throws IllegalArgumentException - * If {@code name} is {@code null} or the empty String - */ - public Builder osVersion(String version) { - if (version == null || version.isEmpty()) - throw new IllegalArgumentException("OS version is null or empty"); - osVersion = version; - return this; - } - - /** * Builds and returns a {@code ModuleDescriptor} from its components. * *

    The module will require "{@code java.base}" even if the dependence @@ -2208,6 +2160,7 @@ && !this.requires.containsKey("java.base")) { requires.add(new Requires(Set.of(Requires.Modifier.MANDATED), "java.base", + null, null)); } @@ -2215,6 +2168,7 @@ return new ModuleDescriptor(name, version, + rawVersionString, modifiers, requires, exports, @@ -2222,10 +2176,7 @@ uses, provides, packages, - mainClass, - osName, - osArch, - osVersion); + mainClass); } } @@ -2237,9 +2188,11 @@ * module names lexicographically. Where the module names are equal then the * module versions are compared. When comparing the module versions then a * module descriptor with a version is considered to succeed a module - * descriptor that does not have a version. Where the module names are equal - * and the versions are equal (or not present in both), then the set of - * modifiers are compared. Sets of modifiers are compared by comparing + * descriptor that does not have a version. If both versions are {@linkplain + * Version#parse(String) unparseable} then the {@linkplain #rawVersion() + * raw version strings} are compared lexicographically. Where the module names + * are equal and the versions are equal (or not present in both), then the + * set of modifiers are compared. Sets of modifiers are compared by comparing * a binary value computed for each set. If a modifier is present * in the set then the bit at the position of its ordinal is {@code 1} * in the binary value, otherwise {@code 0}. If the two set of modifiers @@ -2263,6 +2216,9 @@ c = compare(this.version, that.version); if (c != 0) return c; + c = compare(this.rawVersionString, that.rawVersionString); + if (c != 0) return c; + long v1 = modsValue(this.modifiers()); long v2 = modsValue(that.modifiers()); c = Long.compare(v1, v2); @@ -2289,15 +2245,6 @@ c = compare(this.mainClass, that.mainClass); if (c != 0) return c; - c = compare(this.osName, that.osName); - if (c != 0) return c; - - c = compare(this.osArch, that.osArch); - if (c != 0) return c; - - c = compare(this.osVersion, that.osVersion); - if (c != 0) return c; - return 0; } @@ -2333,10 +2280,8 @@ && uses.equals(that.uses) && provides.equals(that.provides) && Objects.equals(version, that.version) - && Objects.equals(mainClass, that.mainClass) - && Objects.equals(osName, that.osName) - && Objects.equals(osArch, that.osArch) - && Objects.equals(osVersion, that.osVersion)); + && Objects.equals(rawVersionString, that.rawVersionString) + && Objects.equals(mainClass, that.mainClass)); } /** @@ -2361,10 +2306,8 @@ hc = hc * 43 + uses.hashCode(); hc = hc * 43 + provides.hashCode(); hc = hc * 43 + Objects.hashCode(version); + hc = hc * 43 + Objects.hashCode(rawVersionString); hc = hc * 43 + Objects.hashCode(mainClass); - hc = hc * 43 + Objects.hashCode(osName); - hc = hc * 43 + Objects.hashCode(osArch); - hc = hc * 43 + Objects.hashCode(osVersion); if (hc == 0) hc = -1; hash = hc; @@ -2713,8 +2656,8 @@ public void requires(ModuleDescriptor.Builder builder, Set ms, String mn, - String compiledVersion) { - builder.requires(ms, mn, compiledVersion); + String rawCompiledVersion) { + builder.requires(ms, mn, rawCompiledVersion); } @Override @@ -2762,9 +2705,6 @@ Set provides, Set packages, String mainClass, - String osName, - String osArch, - String osVersion, int hashCode) { return new ModuleDescriptor(name, version, @@ -2776,9 +2716,6 @@ provides, packages, mainClass, - osName, - osArch, - osVersion, hashCode, false); } --- old/src/java.base/share/classes/java/lang/module/ModuleFinder.java 2017-03-21 13:43:56.505974226 +0000 +++ new/src/java.base/share/classes/java/lang/module/ModuleFinder.java 2017-03-21 13:43:56.304960433 +0000 @@ -25,8 +25,6 @@ package java.lang.module; -import java.io.File; -import java.io.FilePermission; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -43,9 +41,9 @@ import java.util.Set; import jdk.internal.module.ModuleBootstrap; +import jdk.internal.module.ModulePatcher; import jdk.internal.module.ModulePath; import jdk.internal.module.SystemModuleFinder; -import sun.security.action.GetPropertyAction; /** * A finder of modules. A {@code ModuleFinder} is used to find modules during @@ -146,9 +144,9 @@ * *

    If there is a security manager set then its {@link * SecurityManager#checkPermission(Permission) checkPermission} method is - * invoked to check that the caller has been granted {@link FilePermission} - * to recursively read the directory that is the value of the system - * property {@code java.home}.

    + * invoked to check that the caller has been granted + * {@link RuntimePermission RuntimePermission("accessSystemModules")} + * to access the system modules.

    * * @return A {@code ModuleFinder} that locates the system modules * @@ -156,26 +154,29 @@ * If denied by the security manager */ static ModuleFinder ofSystem() { - String home; - SecurityManager sm = System.getSecurityManager(); if (sm != null) { - PrivilegedAction pa = new GetPropertyAction("java.home"); - home = AccessController.doPrivileged(pa); - Permission p = new FilePermission(home + File.separator + "-", "read"); - sm.checkPermission(p); + sm.checkPermission(new RuntimePermission("accessSystemModules")); + PrivilegedAction pa = ModuleFinder::privilegedOfSystem; + return AccessController.doPrivileged(pa); } else { - home = System.getProperty("java.home"); + return privilegedOfSystem(); } + } + /** + * Returns a module finder that locates the system modules. This method + * assumes it has permissions to access the runtime image. + */ + private static ModuleFinder privilegedOfSystem() { + String home = System.getProperty("java.home"); Path modules = Paths.get(home, "lib", "modules"); if (Files.isRegularFile(modules)) { return SystemModuleFinder.getInstance(); } else { - Path mlib = Paths.get(home, "modules"); - if (Files.isDirectory(mlib)) { - // exploded build may be patched - return ModulePath.of(ModuleBootstrap.patcher(), mlib); + Path dir = Paths.get(home, "modules"); + if (Files.isDirectory(dir)) { + return privilegedOf(ModuleBootstrap.patcher(), dir); } else { throw new InternalError("Unable to detect the run-time image"); } @@ -183,6 +184,26 @@ } /** + * Returns a module finder that locates the system modules in an exploded + * image. The image may be patched. + */ + private static ModuleFinder privilegedOf(ModulePatcher patcher, Path dir) { + ModuleFinder finder = ModulePath.of(patcher, dir); + return new ModuleFinder() { + @Override + public Optional find(String name) { + PrivilegedAction> pa = () -> finder.find(name); + return AccessController.doPrivileged(pa); + } + @Override + public Set findAll() { + PrivilegedAction> pa = finder::findAll; + return AccessController.doPrivileged(pa); + } + }; + } + + /** * Returns a module finder that locates modules on the file system by * searching a sequence of directories and/or packaged modules. * @@ -201,7 +222,7 @@ * *

    If an element is a path to a directory of modules then each entry in * the directory is a packaged module or the top-level directory of an - * exploded module. It it an error if a directory contains more than one + * exploded module. It is an error if a directory contains more than one * module with the same name. If an element is a path to a directory, and * that directory contains a file named {@code module-info.class}, then the * directory is treated as an exploded module rather than a directory of --- old/src/java.base/share/classes/java/lang/module/ModuleReader.java 2017-03-21 13:43:57.017009293 +0000 +++ new/src/java.base/share/classes/java/lang/module/ModuleReader.java 2017-03-21 13:43:56.816995568 +0000 @@ -48,7 +48,11 @@ *

    A resource in a module is identified by an abstract name that is a * '{@code /}'-separated path string. For example, module {@code java.base} may * have a resource "{@code java/lang/Object.class}" that, by convention, is the - * class file for {@code java.lang.Object}.

    + * class file for {@code java.lang.Object}. A module reader may treat + * directories in the module content as resources (whether it does or not is + * module reader specific). Where the module content contains a directory + * that can be located as a resource then its name ends with a slash ('/'). The + * directory can also be located with a name that drops the trailing slash.

    * *

    A {@code ModuleReader} is {@linkplain ModuleReference#open open} upon * creation and is closed by invoking the {@link #close close} method. Failure @@ -80,6 +84,9 @@ /** * Finds a resource, returning a URI to the resource in the module. * + *

    If the module reader can determine that the name locates a directory + * then the resulting URI will end with a slash ('/').

    + * * @param name * The name of the resource to open for reading * @@ -140,7 +147,7 @@ * * @apiNote This method is intended for high-performance class loading. It * is not capable (or intended) to read arbitrary large resources that - * could potentially be 2GB or larger. The rational for using this method + * could potentially be 2GB or larger. The rationale for using this method * in conjunction with the {@code release} method is to allow module reader * implementations manage buffers in an efficient manner. * @@ -195,7 +202,9 @@ /** * Lists the contents of the module, returning a stream of elements that - * are the names of all resources in the module. + * are the names of all resources in the module. Whether the stream of + * elements includes names corresponding to directories in the module is + * module reader specific. * *

    In lazy implementations then an {@code IOException} may be thrown * when using the stream to list the module contents. If this occurs then --- old/src/java.base/share/classes/java/lang/module/Resolver.java 2017-03-21 13:43:57.520043810 +0000 +++ new/src/java.base/share/classes/java/lang/module/Resolver.java 2017-03-21 13:43:57.323030292 +0000 @@ -47,6 +47,7 @@ import jdk.internal.module.ModuleHashes; import jdk.internal.module.ModuleReferenceImpl; +import jdk.internal.module.ModuleTarget; /** * The resolver used by {@link Configuration#resolve} and {@link @@ -69,11 +70,9 @@ // module constraints on target platform private String osName; private String osArch; - private String osVersion; String osName() { return osName; } String osArch() { return osArch; } - String osVersion() { return osVersion; } /** * @throws IllegalArgumentException if there are more than one parent and @@ -110,16 +109,6 @@ } } } - value = parent.osVersion(); - if (value != null) { - if (osVersion == null) { - osVersion = value; - } else { - if (!value.equals(osVersion)) { - failParentConflict("OS version", osVersion, value); - } - } - } } } @@ -318,13 +307,15 @@ * the target platform with the constraints of other modules. */ private void addFoundModule(ModuleReference mref) { - ModuleDescriptor descriptor = mref.descriptor(); - nameToReference.put(descriptor.name(), mref); + String mn = mref.descriptor().name(); + + if (mref instanceof ModuleReferenceImpl) { + ModuleTarget target = ((ModuleReferenceImpl)mref).moduleTarget(); + if (target != null) + checkTargetConstraints(mn, target); + } - if (descriptor.osName().isPresent() - || descriptor.osArch().isPresent() - || descriptor.osVersion().isPresent()) - checkTargetConstraints(descriptor); + nameToReference.put(mn, mref); } /** @@ -332,58 +323,44 @@ * conflict with the constraints of other modules resolved so far or * modules in parent configurations. */ - private void checkTargetConstraints(ModuleDescriptor descriptor) { - String value = descriptor.osName().orElse(null); + private void checkTargetConstraints(String mn, ModuleTarget target) { + String value = target.osName(); if (value != null) { if (osName == null) { osName = value; } else { if (!value.equals(osName)) { - failTargetConstraint(descriptor); + failTargetConstraint(mn, target); } } } - value = descriptor.osArch().orElse(null); + value = target.osArch(); if (value != null) { if (osArch == null) { osArch = value; } else { if (!value.equals(osArch)) { - failTargetConstraint(descriptor); - } - } - } - value = descriptor.osVersion().orElse(null); - if (value != null) { - if (osVersion == null) { - osVersion = value; - } else { - if (!value.equals(osVersion)) { - failTargetConstraint(descriptor); + failTargetConstraint(mn, target); } } } } - private void failTargetConstraint(ModuleDescriptor md) { - String s1 = targetAsString(osName, osArch, osVersion); - String s2 = targetAsString(md); - findFail("Module %s has constraints on target platform that conflict" + - " with other modules: %s, %s", md.name(), s1, s2); + private void failTargetConstraint(String mn, ModuleTarget target) { + String s1 = targetAsString(osName, osArch); + String s2 = targetAsString(target.osName(), target.osArch()); + findFail("Module %s has constraints on target platform (%s) that" + + " conflict with other modules: %s", mn, s1, s2); } - private String targetAsString(ModuleDescriptor descriptor) { - String osName = descriptor.osName().orElse(null); - String osArch = descriptor.osArch().orElse(null); - String osVersion = descriptor.osVersion().orElse(null); - return targetAsString(osName, osArch, osVersion); + private String targetAsString(ModuleTarget target) { + return targetAsString(target.osName(), target.osArch()); } - private String targetAsString(String osName, String osArch, String osVersion) { + private String targetAsString(String osName, String osArch) { return new StringJoiner("-") .add(Objects.toString(osName, "*")) .add(Objects.toString(osArch, "*")) - .add(Objects.toString(osVersion, "*")) .toString(); } @@ -712,16 +689,30 @@ /** - * Checks the readability graph to ensure that no two modules export the - * same package to a module. This includes the case where module M has - * a local package P and M reads another module that exports P to M. - * Also checks the uses/provides of module M to ensure that it reads a - * module that exports the package of the service type to M. + * Checks the readability graph to ensure that + *

      + *
    1. A module does not read two or more modules with the same name. + * This includes the case where a module reads another another with the + * same name as itself.

    2. + *
    3. Two or more modules in the configuration don't export the same + * package to a module that reads both. This includes the case where a + * module {@code M} containing package {@code p} reads another module + * that exports {@code p} to {@code M}.

    4. + *
    5. A module {@code M} doesn't declare that it "{@code uses p.S}" + * or "{@code provides p.S with ...}" but package {@code p} is neither + * in module {@code M} nor exported to {@code M} by any module that + * {@code M} reads.

    6. + *
    */ private void checkExportSuppliers(Map> graph) { for (Map.Entry> e : graph.entrySet()) { ModuleDescriptor descriptor1 = e.getKey().descriptor(); + String name1 = descriptor1.name(); + + // the names of the modules that are read (including self) + Set names = new HashSet<>(); + names.add(name1); // the map of packages that are local or exported to descriptor1 Map packageToExporter = new HashMap<>(); @@ -737,9 +728,20 @@ for (ResolvedModule endpoint : reads) { ModuleDescriptor descriptor2 = endpoint.descriptor(); + String name2 = descriptor2.name(); + if (descriptor2 != descriptor1 && !names.add(name2)) { + if (name2.equals(name1)) { + resolveFail("Module %s reads another module named %s", + name1, name1); + } else{ + resolveFail("Module %s reads more than one module named %s", + name1, name2); + } + } + if (descriptor2.isAutomatic()) { // automatic modules read self and export all packages - if (descriptor2 != descriptor1){ + if (descriptor2 != descriptor1) { for (String source : descriptor2.packages()) { ModuleDescriptor supplier = packageToExporter.putIfAbsent(source, descriptor2); --- old/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java 2017-03-21 13:43:58.047079975 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java 2017-03-21 13:43:57.847066250 +0000 @@ -28,9 +28,12 @@ import java.lang.annotation.Annotation; import java.security.AccessController; +import jdk.internal.misc.VM; +import jdk.internal.module.IllegalAccessLogger; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; +import sun.security.action.GetPropertyAction; /** * The {@code AccessibleObject} class is the base class for {@code Field}, @@ -288,27 +291,20 @@ if (callerModule == Object.class.getModule()) return true; if (!declaringModule.isNamed()) return true; - // package is open to caller - String pn = packageName(declaringClass); - if (declaringModule.isOpen(pn, callerModule)) { - dumpStackIfOpenedReflectively(declaringModule, pn, callerModule); - return true; - } - - // package is exported to caller - boolean isExported = declaringModule.isExported(pn, callerModule); - boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers()); + String pn = declaringClass.getPackageName(); int modifiers; if (this instanceof Executable) { modifiers = ((Executable) this).getModifiers(); } else { modifiers = ((Field) this).getModifiers(); } - if (isExported && isClassPublic) { + // class is public and package is exported to caller + boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers()); + if (isClassPublic && declaringModule.isExported(pn, callerModule)) { // member is public if (Modifier.isPublic(modifiers)) { - dumpStackIfExportedReflectively(declaringModule, pn, callerModule); + logIfExportedByBackdoor(caller, declaringClass); return true; } @@ -316,11 +312,17 @@ if (Modifier.isProtected(modifiers) && Modifier.isStatic(modifiers) && isSubclassOf(caller, declaringClass)) { - dumpStackIfExportedReflectively(declaringModule, pn, callerModule); + logIfExportedByBackdoor(caller, declaringClass); return true; } } + // package is open to caller + if (declaringModule.isOpen(pn, callerModule)) { + logIfOpenedByBackdoor(caller, declaringClass); + return true; + } + if (throwExceptionIfDenied) { // not accessible String msg = "Unable to make "; @@ -333,7 +335,7 @@ msg += "opens"; msg += " " + pn + "\" to " + callerModule; InaccessibleObjectException e = new InaccessibleObjectException(msg); - if (Reflection.printStackTraceWhenAccessFails()) { + if (printStackTraceWhenAccessFails()) { e.printStackTrace(System.err); } throw e; @@ -351,48 +353,35 @@ return false; } - private void dumpStackIfOpenedReflectively(Module module, - String pn, - Module other) { - dumpStackIfExposedReflectively(module, pn, other, true); - } - - private void dumpStackIfExportedReflectively(Module module, - String pn, - Module other) { - dumpStackIfExposedReflectively(module, pn, other, false); - } - - private void dumpStackIfExposedReflectively(Module module, - String pn, - Module other, - boolean open) - { - if (Reflection.printStackTraceWhenAccessSucceeds() - && !module.isStaticallyExportedOrOpen(pn, other, open)) - { - String msg = other + " allowed to invoke setAccessible on "; - if (this instanceof Field) - msg += "field "; - msg += this; - new Exception(msg) { - private static final long serialVersionUID = 42L; - public String toString() { - return "WARNING: " + getMessage(); - } - }.printStackTrace(System.err); + private void logIfOpenedByBackdoor(Class caller, Class declaringClass) { + Module callerModule = caller.getModule(); + Module targetModule = declaringClass.getModule(); + // callerModule is null during early startup + if (callerModule != null && !callerModule.isNamed() && targetModule.isNamed()) { + IllegalAccessLogger logger = IllegalAccessLogger.illegalAccessLogger(); + if (logger != null) { + logger.logIfOpenedByBackdoor(caller, declaringClass, this::toShortString); + } + } + } + + private void logIfExportedByBackdoor(Class caller, Class declaringClass) { + Module callerModule = caller.getModule(); + Module targetModule = declaringClass.getModule(); + // callerModule is null during early startup + if (callerModule != null && !callerModule.isNamed() && targetModule.isNamed()) { + IllegalAccessLogger logger = IllegalAccessLogger.illegalAccessLogger(); + if (logger != null) { + logger.logIfExportedByBackdoor(caller, declaringClass, this::toShortString); + } } } /** - * Returns the package name of the given class. + * Returns a short descriptive string to describe this object in log messages. */ - private static String packageName(Class c) { - while (c.isArray()) { - c = c.getComponentType(); - } - String pn = c.getPackageName(); - return (pn != null) ? pn : ""; + String toShortString() { + return toString(); } /** @@ -409,6 +398,7 @@ * it should use {@link #canAccess(Object)}. * * @revised 9 + * @spec JPMS */ @Deprecated(since="9") public boolean isAccessible() { @@ -483,10 +473,7 @@ } else { targetClass = Modifier.isStatic(modifiers) ? null : obj.getClass(); } - return Reflection.verifyMemberAccess(caller, - declaringClass, - targetClass, - modifiers); + return verifyAccess(caller, declaringClass, targetClass, modifiers); } /** @@ -527,7 +514,7 @@ return AnnotatedElement.super.isAnnotationPresent(annotationClass); } - /** + /** * @throws NullPointerException {@inheritDoc} * @since 1.8 */ @@ -598,8 +585,21 @@ Class targetClass, int modifiers) throws IllegalAccessException { + if (!verifyAccess(caller, memberClass, targetClass, modifiers)) { + IllegalAccessException e = Reflection.newIllegalAccessException( + caller, memberClass, targetClass, modifiers); + if (printStackTraceWhenAccessFails()) { + e.printStackTrace(System.err); + } + throw e; + } + } + + final boolean verifyAccess(Class caller, Class memberClass, + Class targetClass, int modifiers) + { if (caller == memberClass) { // quick check - return; // ACCESS IS OK + return true; // ACCESS IS OK } Object cache = securityCheckCache; // read volatile if (targetClass != null // instance member or constructor @@ -610,26 +610,31 @@ Class[] cache2 = (Class[]) cache; if (cache2[1] == targetClass && cache2[0] == caller) { - return; // ACCESS IS OK + return true; // ACCESS IS OK } // (Test cache[1] first since range check for [1] // subsumes range check for [0].) } } else if (cache == caller) { // Non-protected case (or targetClass == memberClass or static member). - return; // ACCESS IS OK + return true; // ACCESS IS OK } // If no return, fall through to the slow path. - slowCheckMemberAccess(caller, memberClass, targetClass, modifiers); + return slowVerifyAccess(caller, memberClass, targetClass, modifiers); } // Keep all this slow stuff out of line: - void slowCheckMemberAccess(Class caller, Class memberClass, - Class targetClass, int modifiers) - throws IllegalAccessException + private boolean slowVerifyAccess(Class caller, Class memberClass, + Class targetClass, int modifiers) { - Reflection.ensureMemberAccess(caller, memberClass, targetClass, modifiers); + if (!Reflection.verifyMemberAccess(caller, memberClass, targetClass, modifiers)) { + // access denied + return false; + } + + // access okay + logIfExportedByBackdoor(caller, memberClass); // Success: Update the cache. Object cache = (targetClass != null @@ -643,5 +648,27 @@ // guarantees that the initializing stores for the cache // elements will occur before the volatile write. securityCheckCache = cache; // write volatile + return true; + } + + // true to print a stack trace when access fails + private static volatile boolean printStackWhenAccessFails; + + // true if printStack* values are initialized + private static volatile boolean printStackPropertiesSet; + + /** + * Returns true if a stack trace should be printed when access fails. + */ + private static boolean printStackTraceWhenAccessFails() { + if (!printStackPropertiesSet && VM.initLevel() >= 1) { + String s = GetPropertyAction.privilegedGetProperty( + "sun.reflect.debugModuleAccessChecks"); + if (s != null) { + printStackWhenAccessFails = !s.equalsIgnoreCase("false"); + } + printStackPropertiesSet = true; + } + return printStackWhenAccessFails; } } --- old/src/java.base/share/classes/java/lang/reflect/Constructor.java 2017-03-21 13:43:58.567115659 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Constructor.java 2017-03-21 13:43:58.368102003 +0000 @@ -38,6 +38,7 @@ import sun.reflect.generics.scope.ConstructorScope; import java.lang.annotation.Annotation; import java.lang.annotation.AnnotationFormatError; +import java.util.StringJoiner; /** * {@code Constructor} provides information about, and access to, a single @@ -360,6 +361,20 @@ sb.append(getDeclaringClass().getTypeName()); } + @Override + String toShortString() { + StringBuilder sb = new StringBuilder("constructor "); + sb.append(getDeclaringClass().getTypeName()); + sb.append('('); + StringJoiner sj = new StringJoiner(","); + for (Class parameterType : getParameterTypes()) { + sj.add(parameterType.getTypeName()); + } + sb.append(sj); + sb.append(')'); + return sb.toString(); + } + /** * Returns a string describing this {@code Constructor}, * including type parameters. The string is formatted as the --- old/src/java.base/share/classes/java/lang/reflect/Field.java 2017-03-21 13:43:59.072150314 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Field.java 2017-03-21 13:43:58.875136795 +0000 @@ -324,6 +324,11 @@ + getName()); } + @Override + String toShortString() { + return "field " + getDeclaringClass().getTypeName() + "." + getName(); + } + /** * Returns a string describing this {@code Field}, including * its generic type. The format is the access modifiers for the --- old/src/java.base/share/classes/java/lang/reflect/Layer.java 2017-03-21 13:43:59.604186821 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Layer.java 2017-03-21 13:43:59.402172959 +0000 @@ -66,9 +66,7 @@ * ResolvedModule} in the configuration. For each resolved module that is * {@link ResolvedModule#reads() read}, the {@code Module} {@link * Module#canRead reads} the corresponding run-time {@code Module}, which may - * be in the same layer or a {@link #parents() parent} layer. The {@code Module} - * {@link Module#isExported(String) exports} and {@link Module#isOpen(String) - * opens} the packages described by its {@link ModuleDescriptor}.

    + * be in the same layer or a {@link #parents() parent} layer.

    * *

    The {@link #defineModulesWithOneLoader defineModulesWithOneLoader} and * {@link #defineModulesWithManyLoaders defineModulesWithManyLoaders} methods @@ -91,6 +89,28 @@ * built-in into the Java virtual machine. The boot layer will often be * the {@link #parents() parent} when creating additional layers.

    * + *

    Each {@code Module} in a layer is created so that it {@link + * Module#isExported(String) exports} and {@link Module#isOpen(String) opens} + * the packages described by its {@link ModuleDescriptor}. Qualified exports + * (where a package is exported to a set of target modules rather than all + * modules) are reified when creating the layer as follows:

    + *
      + *
    • If module {@code X} exports a package to {@code Y}, and if the + * runtime {@code Module} {@code X} reads {@code Module} {@code Y}, then + * the package is exported to {@code Module} {@code Y} (which may be in + * the same layer as {@code X} or a parent layer).
    • + * + *
    • If module {@code X} exports a package to {@code Y}, and if the + * runtime {@code Module} {@code X} does not read {@code Y} then target + * {@code Y} is located as if by invoking {@link #findModule(String) + * findModule} to find the module in the layer or its parent layers. If + * {@code Y} is found then the package is exported to the instance of + * {@code Y} that was found. If {@code Y} is not found then the qualified + * export is ignored.
    • + *
    + * + *

    Qualified opens are handled in same way as qualified exports.

    + * *

    As when creating a {@code Configuration}, * {@link ModuleDescriptor#isAutomatic() automatic} modules receive special * treatment when creating a layer. An automatic module is created in the @@ -193,7 +213,7 @@ } private void ensureInLayer(Module source) { - if (!layer.modules().contains(source)) + if (source.getLayer() != layer) throw new IllegalArgumentException(source + " not in layer"); } @@ -220,9 +240,8 @@ * @see Module#addReads */ public Controller addReads(Module source, Module target) { - Objects.requireNonNull(source); - Objects.requireNonNull(target); ensureInLayer(source); + Objects.requireNonNull(target); Modules.addReads(source, target); return this; } @@ -248,9 +267,9 @@ * @see Module#addOpens */ public Controller addOpens(Module source, String pn, Module target) { - Objects.requireNonNull(source); - Objects.requireNonNull(target); ensureInLayer(source); + Objects.requireNonNull(pn); + Objects.requireNonNull(target); Modules.addOpens(source, pn, target); return this; } @@ -408,8 +427,8 @@ * * *

    In addition, a layer cannot be created if the configuration contains - * a module named "{@code java.base}" or a module with a package name - * starting with "{@code java.}".

    + * a module named "{@code java.base}", or a module contains a package named + * "{@code java}" or a package with a name starting with "{@code java.}".

    * *

    If there is a security manager then the class loader created by * this method will load classes and resources with privileges that are @@ -418,7 +437,7 @@ * @param cf * The configuration for the layer * @param parentLayers - * The list parent layers in search order + * The list of parent layers in search order * @param parentLoader * The parent class loader for the class loader created by this * method; may be {@code null} for the bootstrap class loader @@ -485,7 +504,7 @@ * @param cf * The configuration for the layer * @param parentLayers - * The list parent layers in search order + * The list of parent layers in search order * @param parentLoader * The parent class loader for each of the class loaders created by * this method; may be {@code null} for the bootstrap class loader @@ -497,8 +516,10 @@ * the parent layers, including order * @throws LayerInstantiationException * If the layer cannot be created because the configuration contains - * a module named "{@code java.base}" or a module with a package - * name starting with "{@code java.}" + * a module named "{@code java.base}" or a module contains a package + * named "{@code java}" or a package with a name starting with + * "{@code java.}" + * * @throws SecurityException * If {@code RuntimePermission("createClassLoader")} or * {@code RuntimePermission("getClassLoader")} is denied by @@ -558,10 +579,11 @@ * *

    In addition, a layer cannot be created if the configuration contains * a module named "{@code java.base}", a configuration contains a module - * with a package name starting with "{@code java.}" is mapped to a class - * loader other than the {@link ClassLoader#getPlatformClassLoader() - * platform class loader}, or the function to map a module name to a class - * loader returns {@code null}.

    + * with a package named "{@code java}" or a package name starting with + * "{@code java.}" and the module is mapped to a class loader other than + * the {@link ClassLoader#getPlatformClassLoader() platform class loader}, + * or the function to map a module name to a class loader returns + * {@code null}.

    * *

    If the function to map a module name to class loader throws an error * or runtime exception then it is propagated to the caller of this method. @@ -575,7 +597,7 @@ * @param cf * The configuration for the layer * @param parentLayers - * The list parent layers in search order + * The list of parent layers in search order * @param clf * The function to map a module name to a class loader * @@ -754,10 +776,16 @@ * @return A possibly-empty unmodifiable set of the modules in this layer */ public Set modules() { - return Collections.unmodifiableSet( - nameToModule.values().stream().collect(Collectors.toSet())); + Set modules = this.modules; + if (modules == null) { + this.modules = modules = + Collections.unmodifiableSet(new HashSet<>(nameToModule.values())); + } + return modules; } + private volatile Set modules; + /** * Returns the module with the given name in this layer, or if not in this @@ -776,6 +804,8 @@ */ public Optional findModule(String name) { Objects.requireNonNull(name); + if (this == EMPTY_LAYER) + return Optional.empty(); Module m = nameToModule.get(name); if (m != null) return Optional.of(m); --- old/src/java.base/share/classes/java/lang/reflect/Method.java 2017-03-21 13:44:00.121222299 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Method.java 2017-03-21 13:43:59.926208918 +0000 @@ -42,6 +42,7 @@ import java.lang.annotation.Annotation; import java.lang.annotation.AnnotationFormatError; import java.nio.ByteBuffer; +import java.util.StringJoiner; /** * A {@code Method} provides information about, and access to, a single method @@ -416,6 +417,21 @@ sb.append(getName()); } + @Override + String toShortString() { + StringBuilder sb = new StringBuilder("method "); + sb.append(getDeclaringClass().getTypeName()).append('.'); + sb.append(getName()); + sb.append('('); + StringJoiner sj = new StringJoiner(","); + for (Class parameterType : getParameterTypes()) { + sj.add(parameterType.getTypeName()); + } + sb.append(sj); + sb.append(')'); + return sb.toString(); + } + /** * Returns a string describing this {@code Method}, including * type parameters. The string is formatted as the method access --- old/src/java.base/share/classes/java/lang/reflect/Module.java 2017-03-21 13:44:00.636257640 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Module.java 2017-03-21 13:44:00.436243916 +0000 @@ -39,8 +39,10 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -51,11 +53,11 @@ import jdk.internal.loader.BuiltinClassLoader; import jdk.internal.loader.BootLoader; -import jdk.internal.loader.ResourceHelper; import jdk.internal.misc.JavaLangAccess; import jdk.internal.misc.JavaLangReflectModuleAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.module.ServicesCatalog; +import jdk.internal.module.Resources; import jdk.internal.org.objectweb.asm.AnnotationVisitor; import jdk.internal.org.objectweb.asm.Attribute; import jdk.internal.org.objectweb.asm.ClassReader; @@ -369,28 +371,19 @@ * If {@code syncVM} is {@code true} then the VM is notified. */ private void implAddReads(Module other, boolean syncVM) { - Objects.requireNonNull(other); - - // nothing to do - if (other == this || !this.isNamed()) - return; - - // check if we already read this module - Set reads = this.reads; - if (reads != null && reads.contains(other)) - return; - - // update VM first, just in case it fails - if (syncVM) { - if (other == ALL_UNNAMED_MODULE) { - addReads0(this, null); - } else { - addReads0(this, other); + if (!canRead(other)) { + // update VM first, just in case it fails + if (syncVM) { + if (other == ALL_UNNAMED_MODULE) { + addReads0(this, null); + } else { + addReads0(this, other); + } } - } - // add reflective read - reflectivelyReads.putIfAbsent(this, other, Boolean.TRUE); + // add reflective read + reflectivelyReads.putIfAbsent(this, other, Boolean.TRUE); + } } @@ -553,7 +546,7 @@ * Returns {@code true} if this module exports or opens a package to * the given module via its module declaration. */ - boolean isStaticallyExportedOrOpen(String pn, Module other, boolean open) { + private boolean isStaticallyExportedOrOpen(String pn, Module other, boolean open) { // package is open to everyone or Map> openPackages = this.openPackages; if (openPackages != null) { @@ -909,9 +902,7 @@ * Returns an array of the package names of the packages in this module. * *

    For named modules, the returned array contains an element for each - * package in the module. It may contain elements corresponding to packages - * added to the module, dynamic modules - * for example, after it was loaded. + * package in the module.

    * *

    For unnamed modules, this method is the equivalent to invoking the * {@link ClassLoader#getDefinedPackages() getDefinedPackages} method of @@ -950,15 +941,6 @@ } /** - * Add a package to this module. - * - * @apiNote This method is for Proxy use. - */ - void addPackage(String pn) { - implAddPackage(pn, true); - } - - /** * Add a package to this module without notifying the VM. * * @apiNote This method is VM white-box testing. @@ -1080,20 +1062,28 @@ // reads Set reads = new HashSet<>(); + + // name -> source Module when in parent layer + Map nameToSource = Collections.emptyMap(); + for (ResolvedModule other : resolvedModule.reads()) { Module m2 = null; if (other.configuration() == cf) { - String dn = other.reference().descriptor().name(); - m2 = nameToModule.get(dn); + // this configuration + m2 = nameToModule.get(other.name()); + assert m2 != null; } else { + // parent layer for (Layer parent: layer.parents()) { m2 = findModule(parent, other); if (m2 != null) break; } + assert m2 != null; + if (nameToSource.isEmpty()) + nameToSource = new HashMap<>(); + nameToSource.put(other.name(), m2); } - assert m2 != null; - reads.add(m2); // update VM view @@ -1107,7 +1097,7 @@ } // exports and opens - initExportsAndOpens(descriptor, nameToModule, m); + initExportsAndOpens(m, nameToSource, nameToModule, layer.parents()); } // register the modules in the boot layer @@ -1159,15 +1149,17 @@ .orElse(null); } + /** * Initialize the maps of exported and open packages for module m. */ - private static void initExportsAndOpens(ModuleDescriptor descriptor, + private static void initExportsAndOpens(Module m, + Map nameToSource, Map nameToModule, - Module m) - { + List parents) { // The VM doesn't special case open or automatic modules so need to // export all packages + ModuleDescriptor descriptor = m.getDescriptor(); if (descriptor.isOpen() || descriptor.isAutomatic()) { assert descriptor.opens().isEmpty(); for (String source : descriptor.packages()) { @@ -1187,8 +1179,7 @@ // qualified opens Set targets = new HashSet<>(); for (String target : opens.targets()) { - // only open to modules that are in this configuration - Module m2 = nameToModule.get(target); + Module m2 = findModule(target, nameToSource, nameToModule, parents); if (m2 != null) { addExports0(m, source, m2); targets.add(m2); @@ -1217,8 +1208,7 @@ // qualified exports Set targets = new HashSet<>(); for (String target : exports.targets()) { - // only export to modules that are in this configuration - Module m2 = nameToModule.get(target); + Module m2 = findModule(target, nameToSource, nameToModule, parents); if (m2 != null) { // skip qualified export if already open to m2 if (openToTargets == null || !openToTargets.contains(m2)) { @@ -1244,6 +1234,32 @@ m.exportedPackages = exportedPackages; } + /** + * Find the runtime Module with the given name. The module name is the + * name of a target module in a qualified exports or opens directive. + * + * @param target The target module to find + * @param nameToSource The modules in parent layers that are read + * @param nameToModule The modules in the layer under construction + * @param parents The parent layers + */ + private static Module findModule(String target, + Map nameToSource, + Map nameToModule, + List parents) { + Module m = nameToSource.get(target); + if (m == null) { + m = nameToModule.get(target); + if (m == null) { + for (Layer parent : parents) { + m = parent.findModule(target).orElse(null); + if (m != null) break; + } + } + } + return m; + } + // -- annotations -- @@ -1428,12 +1444,12 @@ name = name.substring(1); } - if (isNamed() && !ResourceHelper.isSimpleResource(name)) { + if (isNamed() && Resources.canEncapsulate(name)) { Module caller = Reflection.getCallerClass().getModule(); if (caller != this && caller != Object.class.getModule()) { // ignore packages added for proxies via addPackage Set packages = getDescriptor().packages(); - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); if (packages.contains(pn) && !isOpen(pn, caller)) { // resource is in package not open to caller return null; @@ -1531,24 +1547,24 @@ m.implAddReads(Module.ALL_UNNAMED_MODULE); } @Override - public void addExports(Module m, String pn, Module other) { - m.implAddExportsOrOpens(pn, other, false, true); + public void addExports(Module m, String pn) { + m.implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true); } @Override - public void addOpens(Module m, String pn, Module other) { - m.implAddExportsOrOpens(pn, other, true, true); + public void addExports(Module m, String pn, Module other) { + m.implAddExportsOrOpens(pn, other, false, true); } @Override - public void addExportsToAll(Module m, String pn) { - m.implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true); + public void addExportsToAllUnnamed(Module m, String pn) { + m.implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true); } @Override - public void addOpensToAll(Module m, String pn) { + public void addOpens(Module m, String pn) { m.implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true); } @Override - public void addExportsToAllUnnamed(Module m, String pn) { - m.implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true); + public void addOpens(Module m, String pn, Module other) { + m.implAddExportsOrOpens(pn, other, true, true); } @Override public void addOpensToAllUnnamed(Module m, String pn) { @@ -1559,10 +1575,6 @@ m.implAddUses(service); } @Override - public void addPackage(Module m, String pn) { - m.implAddPackage(pn, true); - } - @Override public ServicesCatalog getServicesCatalog(Layer layer) { return layer.getServicesCatalog(); } @@ -1574,10 +1586,6 @@ public Stream layers(ClassLoader loader) { return Layer.layers(loader); } - @Override - public boolean isStaticallyExported(Module module, String pn, Module other) { - return module.isStaticallyExportedOrOpen(pn, other, false); - } }); } } --- old/src/java.base/share/classes/java/lang/reflect/Proxy.java 2017-03-21 13:44:01.179294903 +0000 +++ new/src/java.base/share/classes/java/lang/reflect/Proxy.java 2017-03-21 13:44:00.979281178 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -359,10 +359,11 @@ * @throws NullPointerException if the {@code interfaces} array * argument or any of its elements are {@code null} * - * @deprecated Proxy classes generated in a named module are encapsulated and not - * accessible to code outside its module. - * {@link Constructor#newInstance(Object...) Constructor.newInstance} will throw - * {@code IllegalAccessException} when it is called on an inaccessible proxy class. + * @deprecated Proxy classes generated in a named module are encapsulated + * and not accessible to code outside its module. + * {@link Constructor#newInstance(Object...) Constructor.newInstance} + * will throw {@code IllegalAccessException} when it is called on + * an inaccessible proxy class. * Use {@link #newProxyInstance(ClassLoader, Class[], InvocationHandler)} * to create a proxy instance instead. * @@ -511,17 +512,19 @@ "Unnamed package cannot be added to " + m); } - // add the package to the runtime module if not exists if (m.isNamed()) { - m.addPackage(proxyPkg); + if (!m.getDescriptor().packages().contains(proxyPkg)) { + throw new InternalError(proxyPkg + " not exist in " + m.getName()); + } } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); - String proxyName = proxyPkg.isEmpty() ? proxyClassNamePrefix + num - : proxyPkg + "." + proxyClassNamePrefix + num; + String proxyName = proxyPkg.isEmpty() + ? proxyClassNamePrefix + num + : proxyPkg + "." + proxyClassNamePrefix + num; ClassLoader loader = getLoader(m); trace(proxyName, m, loader, interfaces); @@ -581,9 +584,13 @@ c.getModule().getName(), c.getName(), access, ld); } - static void trace(String cn, Module module, ClassLoader loader, List> interfaces) { + static void trace(String cn, + Module module, + ClassLoader loader, + List> interfaces) { if (isDebug()) { - System.out.format("PROXY: %s/%s defined by %s%n", module.getName(), cn, loader); + System.err.format("PROXY: %s/%s defined by %s%n", + module.getName(), cn, loader); } if (isDebug("debug")) { interfaces.stream() @@ -592,7 +599,7 @@ } private static final String DEBUG = - GetPropertyAction.privilegedGetProperty("jdk.proxy.debug", ""); + GetPropertyAction.privilegedGetProperty("jdk.proxy.debug", ""); private static boolean isDebug() { return !DEBUG.isEmpty(); @@ -603,15 +610,16 @@ // ProxyBuilder instance members start here.... - private final ClassLoader loader; private final List> interfaces; private final Module module; ProxyBuilder(ClassLoader loader, List> interfaces) { if (!VM.isModuleSystemInited()) { - throw new InternalError("Proxy is not supported until module system is fully initialized"); + throw new InternalError("Proxy is not supported until " + + "module system is fully initialized"); } if (interfaces.size() > 65535) { - throw new IllegalArgumentException("interface limit exceeded: " + interfaces.size()); + throw new IllegalArgumentException("interface limit exceeded: " + + interfaces.size()); } Set> refTypes = referencedTypes(loader, interfaces); @@ -619,7 +627,6 @@ // IAE if violates any restrictions specified in newProxyInstance validateProxyInterfaces(loader, interfaces, refTypes); - this.loader = loader; this.interfaces = interfaces; this.module = mapToModule(loader, interfaces, refTypes); assert getLoader(module) == loader; @@ -659,8 +666,8 @@ * Validate the given proxy interfaces and the given referenced types * are visible to the defining loader. * - * @throws IllegalArgumentException if it violates the restrictions specified - * in {@link Proxy#newProxyInstance} + * @throws IllegalArgumentException if it violates the restrictions + * specified in {@link Proxy#newProxyInstance} */ private static void validateProxyInterfaces(ClassLoader loader, List> interfaces, @@ -731,9 +738,9 @@ * is in the same module of the package-private interface. * * If all proxy interfaces are public and at least one in a non-exported - * package, then the proxy class is in a dynamic module in a non-exported - * package. Reads edge and qualified exports are added for - * dynamic module to access. + * package, then the proxy class is in a dynamic module in a + * non-exported package. Reads edge and qualified exports are added + * for dynamic module to access. */ private static Module mapToModule(ClassLoader loader, List> interfaces, @@ -752,11 +759,12 @@ } } - // all proxy interfaces are public and exported, the proxy class is in unnamed module - // Such proxy class is accessible to any unnamed module and named module that - // can read unnamed module + // all proxy interfaces are public and exported, the proxy class + // is in unnamed module. Such proxy class is accessible to + // any unnamed module and named module that can read unnamed module if (packagePrivateTypes.isEmpty() && modulePrivateTypes.isEmpty()) { - return loader != null ? loader.getUnnamedModule() : BootLoader.getUnnamedModule(); + return loader != null ? loader.getUnnamedModule() + : BootLoader.getUnnamedModule(); } if (packagePrivateTypes.size() > 0) { @@ -778,7 +786,8 @@ Module target = null; for (Module m : packagePrivateTypes.values()) { if (getLoader(m) != loader) { - // the specified loader is not the same class loader of the non-public interface + // the specified loader is not the same class loader + // of the non-public interface throw new IllegalArgumentException( "non-public interface is not defined by the given loader"); } @@ -799,8 +808,9 @@ return target; } - // all proxy interfaces are public and at least one in a non-exported package - // map to dynamic proxy module and add reads edge and qualified exports, if necessary + // All proxy interfaces are public and at least one in a non-exported + // package. So maps to a dynamic proxy module and add reads edge + // and qualified exports, if necessary Module target = getDynamicModule(loader); // set up proxy class access to proxy interfaces and types @@ -856,8 +866,8 @@ private static final AtomicInteger counter = new AtomicInteger(); /* - * Define a dynamic module for the generated proxy classes in a non-exported package - * named com.sun.proxy.$MODULE. + * Define a dynamic module for the generated proxy classes in + * a non-exported package named com.sun.proxy.$MODULE. * * Each class loader will have one dynamic module. */ --- old/src/java.base/share/classes/java/util/ServiceLoader.java 2017-03-21 13:44:01.715331685 +0000 +++ new/src/java.base/share/classes/java/util/ServiceLoader.java 2017-03-21 13:44:01.515317960 +0000 @@ -1007,6 +1007,7 @@ { static final String PREFIX = "META-INF/services/"; + Set providerNames = new HashSet<>(); // to avoid duplicates Enumeration configs; Iterator pending; Class nextClass; @@ -1016,7 +1017,7 @@ /** * Parse a single line from the given configuration file, adding the - * name on the line to the names list. + * name on the line to set of names if not already seen. */ private int parseLine(URL u, BufferedReader r, int lc, Set names) throws IOException @@ -1041,7 +1042,9 @@ if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } - names.add(ln); + if (providerNames.add(ln)) { + names.add(ln); + } } return lc + 1; } @@ -1072,7 +1075,7 @@ return true; } - Class clazz = null; + Class clazz; do { if (configs == null) { try { --- old/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java 2017-03-21 13:44:02.250368398 +0000 +++ new/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java 2017-03-21 13:44:02.050354673 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,7 +59,7 @@ bis.read(magic); if (magic[0] != JMOD_MAGIC_NUMBER[0] || magic[1] != JMOD_MAGIC_NUMBER[1]) { - throw new IOException("Invalid jmod file: " + file.toString()); + throw new IOException("Invalid JMOD file: " + file.toString()); } if (magic[2] > JMOD_MAJOR_VERSION || (magic[2] == JMOD_MAJOR_VERSION && magic[3] > JMOD_MINOR_VERSION)) { @@ -131,6 +131,13 @@ } /** + * Returns true if the entry is a directory in the JMOD file. + */ + public boolean isDirectory() { + return zipEntry.isDirectory(); + } + + /** * Returns the size of this entry. */ public long size() { @@ -186,12 +193,12 @@ public Entry getEntry(Section section, String name) { String entry = section.jmodDir() + "/" + name; ZipEntry ze = zipfile.getEntry(entry); - return (ze == null || ze.isDirectory()) ? null : new Entry(ze); + return (ze != null) ? new Entry(ze) : null; } /** * Opens an {@code InputStream} for reading the named entry of the given - * section in this jmod file. + * section in this JMOD file. * * @throws IOException if the named entry is not found, or I/O error * occurs when reading it @@ -201,7 +208,7 @@ { String entry = section.jmodDir() + "/" + name; ZipEntry e = zipfile.getEntry(entry); - if (e == null || e.isDirectory()) { + if (e == null) { throw new IOException(name + " not found: " + file); } return zipfile.getInputStream(e); @@ -217,11 +224,10 @@ } /** - * Returns a stream of non-directory entries in this jmod file. + * Returns a stream of entries in this JMOD file. */ public Stream stream() { return zipfile.stream() - .filter(e -> !e.isDirectory()) .map(Entry::new); } --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java 2017-03-21 13:44:02.748402572 +0000 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystemProvider.java 2017-03-21 13:44:02.551389053 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,14 +65,12 @@ } /** - * Need FilePermission ${java.home}/-", "read" to create or get jrt:/ + * Need RuntimePermission "accessSystemModules" to create or get jrt:/ */ private void checkPermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { - String home = SystemImage.RUNTIME_HOME; - FilePermission perm - = new FilePermission(home + File.separator + "-", "read"); + RuntimePermission perm = new RuntimePermission("accessSystemModules"); sm.checkPermission(perm); } } --- old/src/java.base/share/classes/jdk/internal/loader/BootLoader.java 2017-03-21 13:44:03.373445462 +0000 +++ new/src/java.base/share/classes/jdk/internal/loader/BootLoader.java 2017-03-21 13:44:03.171431600 +0000 @@ -96,6 +96,14 @@ } /** + * Returns {@code true} if there is a class path associated with the + * BootLoader. + */ + public static boolean hasClassPath() { + return ClassLoaders.bootLoader().hasClassPath(); + } + + /** * Register a module with this class loader so that its classes (and * resources) become visible via this class loader. */ @@ -188,14 +196,6 @@ } /** - * Returns {@code true} if there is a class path associated with the - * BootLoader. - */ - public static boolean hasClassPath() { - return ClassLoaders.bootLoader().hasClassPath(); - } - - /** * Helper class to define {@code Package} objects for packages in modules * defined to the boot loader. */ --- old/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java 2017-03-21 13:44:03.996488214 +0000 +++ new/src/java.base/share/classes/jdk/internal/loader/BuiltinClassLoader.java 2017-03-21 13:44:03.797474558 +0000 @@ -60,6 +60,7 @@ import jdk.internal.misc.VM; import jdk.internal.module.ModulePatcher.PatchedModuleReader; import jdk.internal.module.SystemModules; +import jdk.internal.module.Resources; /** @@ -163,6 +164,14 @@ } /** + * Returns {@code true} if there is a class path associated with this + * class loader. + */ + boolean hasClassPath() { + return ucp != null; + } + + /** * Register a module this this class loader. This has the effect of making * the types in the module visible. */ @@ -248,18 +257,24 @@ */ @Override public URL findResource(String name) { - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); LoadedModule module = packageToModule.get(pn); if (module != null) { // resource is in a package of a module defined to this loader - if (module.loader() == this - && (name.endsWith(".class") || isOpen(module.mref(), pn))) { + if (module.loader() == this) { + URL url; try { - return findResource(module.name(), name); // checks URL + url = findResource(module.name(), name); // checks URL } catch (IOException ioe) { return null; } + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + return url; + } } } else { @@ -293,15 +308,17 @@ public Enumeration findResources(String name) throws IOException { List checked = new ArrayList<>(); // list of checked URLs - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); LoadedModule module = packageToModule.get(pn); if (module != null) { // resource is in a package of a module defined to this loader - if (module.loader() == this - && (name.endsWith(".class") || isOpen(module.mref(), pn))) { - URL url = findResource(module.name(), name); // checks URL - if (url != null) { + if (module.loader() == this) { + URL url = findResource(module.name(), name); // checks URL + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { checked.add(url); } } @@ -351,11 +368,13 @@ new PrivilegedExceptionAction<>() { @Override public List run() throws IOException { - List result = new ArrayList<>(); + List result = null; for (ModuleReference mref : nameToModule.values()) { URI u = moduleReaderFor(mref).find(name).orElse(null); if (u != null) { try { + if (result == null) + result = new ArrayList<>(); result.add(u.toURL()); } catch (MalformedURLException | IllegalArgumentException e) { @@ -375,7 +394,7 @@ map = new ConcurrentHashMap<>(); this.resourceCache = new SoftReference<>(map); } - if (urls.isEmpty()) + if (urls == null) urls = Collections.emptyList(); map.putIfAbsent(name, urls); } @@ -870,14 +889,6 @@ } /** - * Returns {@code true} if there is a class path associated with this - * class loader. - */ - boolean hasClassPath() { - return ucp != null; - } - - /** * Returns {@code true} if the specified package name is sealed according to * the given manifest. */ @@ -975,7 +986,7 @@ */ private boolean isOpen(ModuleReference mref, String pn) { ModuleDescriptor descriptor = mref.descriptor(); - if (descriptor.isOpen()) + if (descriptor.isOpen() || descriptor.isAutomatic()) return true; for (ModuleDescriptor.Opens opens : descriptor.opens()) { String source = opens.source(); --- old/src/java.base/share/classes/jdk/internal/loader/Loader.java 2017-03-21 13:44:04.514523760 +0000 +++ new/src/java.base/share/classes/jdk/internal/loader/Loader.java 2017-03-21 13:44:04.319510379 +0000 @@ -60,6 +60,7 @@ import java.util.stream.Stream; import jdk.internal.misc.SharedSecrets; +import jdk.internal.module.Resources; /** @@ -356,45 +357,52 @@ @Override public URL findResource(String name) { - URL url = null; - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); LoadedModule module = localPackageToModule.get(pn); + if (module != null) { - if (name.endsWith(".class") || isOpen(module.mref(), pn)) { - try { - url = findResource(module.name(), name); - } catch (IOException ioe) { - // ignore + try { + URL url = findResource(module.name(), name); + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + return url; } + } catch (IOException ioe) { + // ignore } + } else { for (ModuleReference mref : nameToModule.values()) { try { - url = findResource(mref.descriptor().name(), name); - if (url != null) - break; + URL url = findResource(mref.descriptor().name(), name); + if (url != null) return url; } catch (IOException ioe) { // ignore } } } - return url; + + return null; } @Override public Enumeration findResources(String name) throws IOException { List urls = new ArrayList<>(); - String pn = ResourceHelper.getPackageName(name); + String pn = Resources.toPackageName(name); LoadedModule module = localPackageToModule.get(pn); if (module != null) { - if (name.endsWith(".class") || isOpen(module.mref(), pn)) { - try { - URL url = findResource(module.name(), name); - if (url != null) - urls.add(url); - } catch (IOException ioe) { - // ignore + try { + URL url = findResource(module.name(), name); + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + urls.add(url); } + } catch (IOException ioe) { + // ignore } } else { for (ModuleReference mref : nameToModule.values()) { @@ -643,7 +651,7 @@ */ private boolean isOpen(ModuleReference mref, String pn) { ModuleDescriptor descriptor = mref.descriptor(); - if (descriptor.isOpen()) + if (descriptor.isOpen() || descriptor.isAutomatic()) return true; for (ModuleDescriptor.Opens opens : descriptor.opens()) { String source = opens.source(); --- old/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java 2017-03-21 13:44:05.028559033 +0000 +++ new/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java 2017-03-21 13:44:04.829545377 +0000 @@ -33,6 +33,7 @@ import java.lang.reflect.Module; import java.net.URL; import java.security.AccessControlContext; +import java.security.ProtectionDomain; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -150,6 +151,11 @@ ConcurrentHashMap createOrGetClassLoaderValueMap(ClassLoader cl); /** + * Defines a class with the given name to a class loader. + */ + Class defineClass(ClassLoader cl, String name, byte[] b, ProtectionDomain pd, String source); + + /** * Returns a class loaded by the bootstrap class loader. */ Class findBootstrapClassOrNull(ClassLoader cl, String name); --- old/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java 2017-03-21 13:44:05.520592795 +0000 +++ new/src/java.base/share/classes/jdk/internal/misc/JavaLangModuleAccess.java 2017-03-21 13:44:05.323579276 +0000 @@ -73,7 +73,7 @@ void requires(ModuleDescriptor.Builder builder, Set ms, String mn, - String compiledVersion); + String rawCompiledVersion); /** * Returns a {@code ModuleDescriptor.Requires} of the given modifiers @@ -127,9 +127,6 @@ Set provides, Set packages, String mainClass, - String osName, - String osArch, - String osVersion, int hashCode); /** --- old/src/java.base/share/classes/jdk/internal/misc/JavaLangReflectModuleAccess.java 2017-03-21 13:44:06.019627038 +0000 +++ new/src/java.base/share/classes/jdk/internal/misc/JavaLangReflectModuleAccess.java 2017-03-21 13:44:05.821613451 +0000 @@ -66,31 +66,31 @@ void addReadsAllUnnamed(Module m); /** - * Updates module m1 to export a package to module m2. The export does - * not result in a strong reference to m2 (m2 can be GC'ed). + * Update module m to export a package to all modules. */ - void addExports(Module m1, String pkg, Module m2); + void addExports(Module m, String pn); /** - * Updates module m1 to open a package to module m2. Opening the - * package does not result in a strong reference to m2 (m2 can be GC'ed). + * Updates module m1 to export a package to module m2. The export does + * not result in a strong reference to m2 (m2 can be GC'ed). */ - void addOpens(Module m1, String pkg, Module m2); + void addExports(Module m1, String pkg, Module m2); /** - * Updates a module m to export a package to all modules. + * Updates a module m to export a package to all unnamed modules. */ - void addExportsToAll(Module m, String pkg); + void addExportsToAllUnnamed(Module m, String pkg); /** * Updates a module m to open a package to all modules. */ - void addOpensToAll(Module m, String pkg); + void addOpens(Module m, String pkg); /** - * Updates a module m to export a package to all unnamed modules. + * Updates module m1 to open a package to module m2. Opening the + * package does not result in a strong reference to m2 (m2 can be GC'ed). */ - void addExportsToAllUnnamed(Module m, String pkg); + void addOpens(Module m1, String pkg, Module m2); /** * Updates a module m to open a package to all unnamed modules. @@ -103,11 +103,6 @@ void addUses(Module m, Class service); /** - * Add a package to the given module. - */ - void addPackage(Module m, String pkg); - - /** * Returns the ServicesCatalog for the given Layer. */ ServicesCatalog getServicesCatalog(Layer layer); @@ -123,12 +118,4 @@ * given class loader. */ Stream layers(ClassLoader loader); - - /** - * Tests if a module exports a package at least {@code other} via its - * module declaration. - * - * @apiNote This is a temporary method for debugging features. - */ - boolean isStaticallyExported(Module module, String pn, Module other); } \ No newline at end of file --- old/src/java.base/share/classes/jdk/internal/module/Builder.java 2017-03-21 13:44:06.517661212 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/Builder.java 2017-03-21 13:44:06.320647693 +0000 @@ -145,9 +145,6 @@ Set provides; Version version; String mainClass; - String osName; - String osArch; - String osVersion; Builder(String name) { this.name = name; @@ -248,30 +245,6 @@ } /** - * Sets the OS name. - */ - public Builder osName(String name) { - this.osName = name; - return this; - } - - /** - * Sets the OS arch. - */ - public Builder osArch(String arch) { - this.osArch = arch; - return this; - } - - /** - * Sets the OS version. - */ - public Builder osVersion(String version) { - this.osVersion = version; - return this; - } - - /** * Returns an immutable set of the module modifiers derived from the flags. */ private Set modifiers() { @@ -305,9 +278,6 @@ provides, packages, mainClass, - osName, - osArch, - osVersion, hashCode); } } --- old/src/java.base/share/classes/jdk/internal/module/Checks.java 2017-03-21 13:44:07.018695592 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/Checks.java 2017-03-21 13:44:06.821682073 +0000 @@ -180,41 +180,38 @@ } /** - * Returns {@code true} if the last character of the given name is legal - * as the last character of a module name. - * - * @throws IllegalArgumentException if name is empty + * Returns {@code true} if a given legal module name contains an identifier + * that doesn't end with a Java letter. */ - public static boolean hasLegalModuleNameLastCharacter(String name) { - if (name.isEmpty()) - throw new IllegalArgumentException("name is empty"); - int len = name.length(); - if (isASCIIString(name)) { - char c = name.charAt(len-1); - return Character.isJavaIdentifierStart(c); - } else { - int i = 0; - int cp = -1; - while (i < len) { - cp = name.codePointAt(i); - i += Character.charCount(cp); + public static boolean hasJavaIdentifierWithTrailingDigit(String name) { + // quick scan to allow names that are just ASCII without digits + boolean needToParse = false; + int i = 0; + while (i < name.length()) { + int c = name.charAt(i); + if (c > 0x7F || (c >= '0' && c <= '9')) { + needToParse = true; + break; } - return Character.isJavaIdentifierStart(cp); + i++; } - } + if (!needToParse) + return false; - /** - * Returns true if the given string only contains ASCII characters. - */ - private static boolean isASCIIString(String s) { - int i = 0; - while (i < s.length()) { - int c = s.charAt(i); - if (c > 0x7F) - return false; - i++; + // slow path + int next; + int off = 0; + while ((next = name.indexOf('.', off)) != -1) { + int last = isJavaIdentifier(name, off, (next - off)); + if (!Character.isJavaIdentifierStart(last)) + return true; + off = next+1; } - return true; + int last = isJavaIdentifier(name, off, name.length() - off); + if (!Character.isJavaIdentifierStart(last)) + return true; + return false; + } /** --- old/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java 2017-03-21 13:44:07.518729904 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ClassFileAttributes.java 2017-03-21 13:44:07.321716385 +0000 @@ -292,11 +292,11 @@ attr.putShort(module_flags); // module_version - Version v = descriptor.version().orElse(null); - if (v == null) { + String vs = descriptor.rawVersion().orElse(null); + if (vs == null) { attr.putShort(0); } else { - int module_version_index = cw.newUTF8(v.toString()); + int module_version_index = cw.newUTF8(vs); attr.putShort(module_version_index); } @@ -320,11 +320,11 @@ attr.putShort(requires_flags); int requires_version_index; - v = r.compiledVersion().orElse(null); - if (v == null) { + vs = r.rawCompiledVersion().orElse(null); + if (vs == null) { requires_version_index = 0; } else { - requires_version_index = cw.newUTF8(v.toString()); + requires_version_index = cw.newUTF8(vs); } attr.putShort(requires_version_index); } @@ -553,8 +553,6 @@ * u2 os_name_index; * // index to CONSTANT_utf8_info structure with the OS arch * u2 os_arch_index - * // index to CONSTANT_utf8_info structure with the OS version - * u2 os_version_index; * } * * } @@ -562,17 +560,23 @@ public static class ModuleTargetAttribute extends Attribute { private final String osName; private final String osArch; - private final String osVersion; - public ModuleTargetAttribute(String osName, String osArch, String osVersion) { + public ModuleTargetAttribute(String osName, String osArch) { super(MODULE_TARGET); this.osName = osName; this.osArch = osArch; - this.osVersion = osVersion; } public ModuleTargetAttribute() { - this(null, null, null); + this(null, null); + } + + public String osName() { + return osName; + } + + public String osArch() { + return osArch; } @Override @@ -586,7 +590,6 @@ String osName = null; String osArch = null; - String osVersion = null; int name_index = cr.readUnsignedShort(off); if (name_index != 0) @@ -598,12 +601,7 @@ osArch = cr.readUTF8(off, buf); off += 2; - int version_index = cr.readUnsignedShort(off); - if (version_index != 0) - osVersion = cr.readUTF8(off, buf); - off += 2; - - return new ModuleTargetAttribute(osName, osArch, osVersion); + return new ModuleTargetAttribute(osName, osArch); } @Override @@ -625,11 +623,6 @@ arch_index = cw.newUTF8(osArch); attr.putShort(arch_index); - int version_index = 0; - if (osVersion != null && osVersion.length() > 0) - version_index = cw.newUTF8(osVersion); - attr.putShort(version_index); - return attr; } } --- old/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java 2017-03-21 13:44:08.038765587 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java 2017-03-21 13:44:07.837751794 +0000 @@ -33,6 +33,7 @@ import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.lang.reflect.Layer; +import java.lang.reflect.LayerInstantiationException; import java.lang.reflect.Module; import java.net.URI; import java.nio.file.Path; @@ -46,7 +47,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.stream.Stream; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; @@ -327,8 +327,9 @@ for (String p : descriptor.packages()) { String other = packageToModule.putIfAbsent(p, name); if (other != null) { - fail("Package " + p + " in both module " - + name + " and module " + other); + String msg = "Package " + p + " in both module " + + name + " and module " + other; + throw new LayerInstantiationException(msg); } } } @@ -359,7 +360,7 @@ PerfCounters.loadModulesTime.addElapsedTimeFrom(t5); - // --add-reads, -add-exports/-add-opens + // --add-reads, --add-exports/--add-opens addExtraReads(bootLayer); addExtraExportsAndOpens(bootLayer); @@ -514,40 +515,59 @@ * additional packages specified on the command-line. */ private static void addExtraExportsAndOpens(Layer bootLayer) { + IllegalAccessLogger.Builder builder = new IllegalAccessLogger.Builder(); // --add-exports String prefix = "jdk.module.addexports."; Map> extraExports = decode(prefix); if (!extraExports.isEmpty()) { - addExtraExportsOrOpens(bootLayer, extraExports, false); + addExtraExportsOrOpens(bootLayer, extraExports, false, builder); } // --add-opens prefix = "jdk.module.addopens."; Map> extraOpens = decode(prefix); if (!extraOpens.isEmpty()) { - addExtraExportsOrOpens(bootLayer, extraOpens, true); + addExtraExportsOrOpens(bootLayer, extraOpens, true, builder); } + // --permit-illegal-access + if (getAndRemoveProperty("jdk.module.permitIllegalAccess") != null) { + warn("--permit-illegal-access will be removed in the next major release"); + bootLayer.modules().stream().forEach(m -> { + m.getDescriptor() + .packages() + .stream() + .filter(pn -> !m.isOpen(pn)) + .forEach(pn -> { + builder.logAccessToOpenPackage(m, pn, "--permit-illegal-access"); + Modules.addOpensToAllUnnamed(m, pn); + }); + }); + } + + IllegalAccessLogger.setIllegalAccessLogger(builder.build()); } private static void addExtraExportsOrOpens(Layer bootLayer, Map> map, - boolean opens) + boolean opens, + IllegalAccessLogger.Builder builder) { String option = opens ? ADD_OPENS : ADD_EXPORTS; + //warn(map.size() + " usage(s) of " + option); for (Map.Entry> e : map.entrySet()) { // the key is $MODULE/$PACKAGE String key = e.getKey(); String[] s = key.split("/"); if (s.length != 2) - fail(unableToParse(option, "/", key)); + fail(unableToParse(option, "/", key)); String mn = s[0]; String pn = s[1]; if (mn.isEmpty() || pn.isEmpty()) - fail(unableToParse(option, "/", key)); + fail(unableToParse(option, "/", key)); // The exporting module is in the boot layer Module m; @@ -581,8 +601,10 @@ } if (allUnnamed) { if (opens) { + builder.logAccessToOpenPackage(m, pn, option); Modules.addOpensToAllUnnamed(m, pn); } else { + builder.logAccessToExportedPackage(m, pn, option); Modules.addExportsToAllUnnamed(m, pn); } } else { @@ -632,7 +654,7 @@ // value is (,)* or ()* if (!allowDuplicates && map.containsKey(key)) - fail(key + " specified more than once in " + option(prefix)); + fail(key + " specified more than once to " + option(prefix)); List values = map.computeIfAbsent(key, k -> new ArrayList<>()); int ntargets = 0; for (String s : rhs.split(regex)) { @@ -676,10 +698,6 @@ ModuleReference mref = rm.reference(); String mn = mref.descriptor().name(); - // emit warning if module name ends with a non-Java letter - if (!Checks.hasLegalModuleNameLastCharacter(mn)) - warn("Module name \"" + mn + "\" may soon be illegal"); - // emit warning if the WARN_INCUBATING module resolution bit set if (ModuleResolution.hasIncubatingWarning(mref)) { if (incubating == null) { @@ -705,7 +723,7 @@ } static void warnUnknownModule(String option, String mn) { - warn("Unknown module: " + mn + " specified in " + option); + warn("Unknown module: " + mn + " specified to " + option); } static String unableToParse(String option, String text, String value) { --- old/src/java.base/share/classes/jdk/internal/module/ModuleInfo.java 2017-03-21 13:44:08.550800722 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleInfo.java 2017-03-21 13:44:08.354787272 +0000 @@ -89,18 +89,24 @@ */ public static final class Attributes { private final ModuleDescriptor descriptor; + private final ModuleTarget target; private final ModuleHashes recordedHashes; private final ModuleResolution moduleResolution; Attributes(ModuleDescriptor descriptor, + ModuleTarget target, ModuleHashes recordedHashes, ModuleResolution moduleResolution) { this.descriptor = descriptor; + this.target = target; this.recordedHashes = recordedHashes; this.moduleResolution = moduleResolution; } public ModuleDescriptor descriptor() { return descriptor; } + public ModuleTarget target() { + return target; + } public ModuleHashes recordedHashes() { return recordedHashes; } @@ -221,8 +227,8 @@ Builder builder = null; Set allPackages = null; String mainClass = null; - String[] osValues = null; - ModuleHashes hashes = null; + ModuleTarget moduleTarget = null; + ModuleHashes moduelHashes = null; ModuleResolution moduleResolution = null; for (int i = 0; i < attributes_count ; i++) { @@ -251,12 +257,12 @@ break; case MODULE_TARGET : - osValues = readModuleTargetAttribute(in, cpool); + moduleTarget = readModuleTargetAttribute(in, cpool); break; case MODULE_HASHES : if (parseHashes) { - hashes = readModuleHashesAttribute(in, cpool); + moduelHashes = readModuleHashesAttribute(in, cpool); } else { in.skipBytes(length); } @@ -282,15 +288,10 @@ throw invalidModuleDescriptor(MODULE + " attribute not found"); } - // ModuleMainClass and ModuleTarget attributes + // ModuleMainClass attribute if (mainClass != null) { builder.mainClass(mainClass); } - if (osValues != null) { - if (osValues[0] != null) builder.osName(osValues[0]); - if (osValues[1] != null) builder.osArch(osValues[1]); - if (osValues[2] != null) builder.osVersion(osValues[2]); - } // If the ModulePackages attribute is not present then the packageFinder // is used to find the set of packages @@ -323,7 +324,10 @@ } ModuleDescriptor descriptor = builder.build(); - return new Attributes(descriptor, hashes, moduleResolution); + return new Attributes(descriptor, + moduleTarget, + moduelHashes, + moduleResolution); } /** @@ -422,7 +426,11 @@ Set targets = new HashSet<>(exports_to_count); for (int j=0; j targets = new HashSet<>(open_to_count); for (int j=0; j cw.visitAttribute(new ModuleMainClassAttribute(mc))); - // write ModuleTarget attribute if have any of OS name/arch/version - String osName = md.osName().orElse(null); - String osArch = md.osArch().orElse(null); - String osVersion = md.osVersion().orElse(null); - if (osName != null || osArch != null || osVersion != null) { - cw.visitAttribute(new ModuleTargetAttribute(osName, osArch, osVersion)); + // write ModuleTarget if there is a platform OS/arch + if (target != null) { + cw.visitAttribute(new ModuleTargetAttribute(target.osName(), + target.osArch())); } cw.visitEnd(); @@ -82,20 +80,31 @@ * Writes a module descriptor to the given output stream as a * module-info.class. */ - public static void write(ModuleDescriptor descriptor, OutputStream out) + public static void write(ModuleDescriptor descriptor, + ModuleTarget target, + OutputStream out) throws IOException { - byte[] bytes = toModuleInfo(descriptor); + byte[] bytes = toModuleInfo(descriptor, target); out.write(bytes); } /** + * Writes a module descriptor to the given output stream as a + * module-info.class. + */ + public static void write(ModuleDescriptor descriptor, OutputStream out) + throws IOException + { + write(descriptor, null, out); + } + + /** * Returns a {@code ByteBuffer} containing the given module descriptor * in module-info.class format. */ public static ByteBuffer toByteBuffer(ModuleDescriptor descriptor) { - byte[] bytes = toModuleInfo(descriptor); + byte[] bytes = toModuleInfo(descriptor, null); return ByteBuffer.wrap(bytes); } - } --- old/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java 2017-03-21 13:44:10.084905989 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java 2017-03-21 13:44:09.881892059 +0000 @@ -55,7 +55,6 @@ import java.util.stream.Stream; import jdk.internal.loader.Resource; -import jdk.internal.loader.ResourceHelper; import jdk.internal.misc.JavaLangModuleAccess; import jdk.internal.misc.SharedSecrets; import sun.net.www.ParseUtil; @@ -165,9 +164,6 @@ descriptor.version().ifPresent(builder::version); descriptor.mainClass().ifPresent(builder::mainClass); - descriptor.osName().ifPresent(builder::osName); - descriptor.osArch().ifPresent(builder::osArch); - descriptor.osVersion().ifPresent(builder::osVersion); // original + new packages builder.packages(descriptor.packages()); @@ -179,10 +175,12 @@ // return a module reference to the patched module URI location = mref.location().orElse(null); + ModuleTarget target = null; ModuleHashes recordedHashes = null; ModuleResolution mres = null; if (mref instanceof ModuleReferenceImpl) { ModuleReferenceImpl impl = (ModuleReferenceImpl)mref; + target = impl.moduleTarget(); recordedHashes = impl.recordedHashes(); mres = impl.moduleResolution(); } @@ -191,6 +189,7 @@ location, () -> new PatchedModuleReader(paths, mref), this, + target, recordedHashes, null, mres); @@ -226,7 +225,7 @@ private volatile ModuleReader delegate; /** - * Creates the ModuleReader to reads resources a patched module. + * Creates the ModuleReader to reads resources in a patched module. */ PatchedModuleReader(List patches, ModuleReference mref) { List finders = new ArrayList<>(); @@ -291,13 +290,16 @@ } /** - * Finds a resources in the patch locations. Returns null if not found. + * Finds a resources in the patch locations. Returns null if not found + * or the name is "module-info.class" as that cannot be overridden. */ private Resource findResourceInPatch(String name) throws IOException { - for (ResourceFinder finder : finders) { - Resource r = finder.find(name); - if (r != null) - return r; + if (!name.equals("module-info.class")) { + for (ResourceFinder finder : finders) { + Resource r = finder.find(name); + if (r != null) + return r; + } } return null; } @@ -478,9 +480,7 @@ @Override public Stream list() throws IOException { - return jf.stream() - .filter(e -> !e.isDirectory()) - .map(JarEntry::getName); + return jf.stream().map(JarEntry::getName); } } @@ -500,14 +500,12 @@ @Override public Resource find(String name) throws IOException { - Path path = ResourceHelper.toFilePath(name); - if (path != null) { - Path file = dir.resolve(path); - if (Files.isRegularFile(file)) { - return newResource(name, dir, file); - } + Path file = Resources.toFilePath(dir, name); + if (file != null) { + return newResource(name, dir, file); + } else { + return null; } - return null; } private Resource newResource(String name, Path top, Path file) { @@ -550,11 +548,9 @@ @Override public Stream list() throws IOException { - return Files.find(dir, Integer.MAX_VALUE, - (path, attrs) -> attrs.isRegularFile()) - .map(f -> dir.relativize(f) - .toString() - .replace(File.separatorChar, '/')); + return Files.walk(dir, Integer.MAX_VALUE) + .map(f -> Resources.toResourceName(dir, f)) + .filter(s -> s.length() > 0); } } --- old/src/java.base/share/classes/jdk/internal/module/ModulePath.java 2017-03-21 13:44:10.603941604 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModulePath.java 2017-03-21 13:44:10.403927879 +0000 @@ -513,7 +513,7 @@ String pn = packageName(cn); if (!packages.contains(pn)) { String msg = "Provider class " + cn + " not in module"; - throw new IOException(msg); + throw new InvalidModuleDescriptorException(msg); } providerClasses.add(cn); } @@ -533,7 +533,7 @@ String pn = packageName(mainClass); if (!packages.contains(pn)) { String msg = "Main-Class " + mainClass + " not in module"; - throw new IOException(msg); + throw new InvalidModuleDescriptorException(msg); } builder.mainClass(mainClass); } @@ -609,11 +609,10 @@ // no module-info.class so treat it as automatic module try { ModuleDescriptor md = deriveModuleDescriptor(jf); - attrs = new ModuleInfo.Attributes(md, null, null); - } catch (IllegalArgumentException e) { - throw new FindException( - "Unable to derive module descriptor for: " - + jf.getName(), e); + attrs = new ModuleInfo.Attributes(md, null, null, null); + } catch (RuntimeException e) { + throw new FindException("Unable to derive module descriptor for " + + jf.getName(), e); } } else { @@ -672,18 +671,18 @@ /** * Maps the name of an entry in a JAR or ZIP file to a package name. * - * @throws IllegalArgumentException if the name is a class file in - * the top-level directory of the JAR/ZIP file (and it's - * not module-info.class) + * @throws InvalidModuleDescriptorException if the name is a class file in + * the top-level directory of the JAR/ZIP file (and it's not + * module-info.class) */ private Optional toPackageName(String name) { assert !name.endsWith("/"); int index = name.lastIndexOf("/"); if (index == -1) { if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { - throw new IllegalArgumentException(name - + " found in top-level directory" - + " (unnamed package not allowed in module)"); + String msg = name + " found in top-level directory" + + " (unnamed package not allowed in module)"; + throw new InvalidModuleDescriptorException(msg); } return Optional.empty(); } @@ -701,8 +700,8 @@ * Maps the relative path of an entry in an exploded module to a package * name. * - * @throws IllegalArgumentException if the name is a class file in - * the top-level directory (and it's not module-info.class) + * @throws InvalidModuleDescriptorException if the name is a class file in + * the top-level directory (and it's not module-info.class) */ private Optional toPackageName(Path file) { assert file.getRoot() == null; @@ -711,9 +710,9 @@ if (parent == null) { String name = file.toString(); if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { - throw new IllegalArgumentException(name - + " found in top-level directory" - + " (unnamed package not allowed in module)"); + String msg = name + " found in top-level directory" + + " (unnamed package not allowed in module)"; + throw new InvalidModuleDescriptorException(msg); } return Optional.empty(); } --- old/src/java.base/share/classes/jdk/internal/module/ModuleReferenceImpl.java 2017-03-21 13:44:11.124977356 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleReferenceImpl.java 2017-03-21 13:44:10.925963700 +0000 @@ -46,6 +46,9 @@ // non-null if the module is patched private final ModulePatcher patcher; + // ModuleTarget if the module is OS/architecture specific + private final ModuleTarget target; + // the hashes of other modules recorded in this module private final ModuleHashes recordedHashes; @@ -65,6 +68,7 @@ URI location, Supplier readerSupplier, ModulePatcher patcher, + ModuleTarget target, ModuleHashes recordedHashes, ModuleHashes.HashSupplier hasher, ModuleResolution moduleResolution) @@ -72,6 +76,7 @@ super(descriptor, Objects.requireNonNull(location)); this.readerSupplier = readerSupplier; this.patcher = patcher; + this.target = target; this.recordedHashes = recordedHashes; this.hasher = hasher; this.moduleResolution = moduleResolution; @@ -94,6 +99,13 @@ } /** + * Returns the ModuleTarget or {@code null} if the no target platform. + */ + public ModuleTarget moduleTarget() { + return target; + } + + /** * Returns the hashes recorded in this module or {@code null} if there * are no hashes recorded. */ --- old/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java 2017-03-21 13:44:11.628011873 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleReferences.java 2017-03-21 13:44:11.428998217 +0000 @@ -25,7 +25,6 @@ package jdk.internal.module; -import java.io.File; import java.io.IOError; import java.io.IOException; import java.io.InputStream; @@ -50,7 +49,6 @@ import java.util.zip.ZipFile; import jdk.internal.jmod.JmodFile; -import jdk.internal.loader.ResourceHelper; import jdk.internal.misc.SharedSecrets; import jdk.internal.module.ModuleHashes.HashSupplier; import jdk.internal.util.jar.VersionedStream; @@ -78,6 +76,7 @@ uri, supplier, null, + attrs.target(), attrs.recordedHashes(), hasher, attrs.moduleResolution()); @@ -242,8 +241,7 @@ } private JarEntry getEntry(String name) { - JarEntry entry = jf.getJarEntry(Objects.requireNonNull(name)); - return (entry == null || entry.isDirectory()) ? null : entry; + return jf.getJarEntry(Objects.requireNonNull(name)); } @Override @@ -252,6 +250,8 @@ if (je != null) { if (jf.isMultiRelease()) name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je); + if (je.isDirectory() && !name.endsWith("/")) + name += "/"; String encodedPath = ParseUtil.encodePath(name, false); String uris = "jar:" + uri + "!/" + encodedPath; return Optional.of(URI.create(uris)); @@ -274,7 +274,6 @@ Stream implList() throws IOException { // take snapshot to avoid async close List names = VersionedStream.stream(jf) - .filter(e -> !e.isDirectory()) .map(JarEntry::getName) .collect(Collectors.toList()); return names.stream(); @@ -316,6 +315,8 @@ Optional implFind(String name) { JmodFile.Entry je = getEntry(name); if (je != null) { + if (je.isDirectory() && !name.endsWith("/")) + name += "/"; String encodedPath = ParseUtil.encodePath(name, false); String uris = "jmod:" + uri + "!/" + encodedPath; return Optional.of(URI.create(uris)); @@ -376,26 +377,10 @@ if (closed) throw new IOException("ModuleReader is closed"); } - /** - * Returns a Path to access the given resource. Returns null if the - * resource name does not convert to a file path that locates a regular - * file in the module. - */ - private Path toFilePath(String name) { - Path path = ResourceHelper.toFilePath(name); - if (path != null) { - Path file = dir.resolve(path); - if (Files.isRegularFile(file)) { - return file; - } - } - return null; - } - @Override public Optional find(String name) throws IOException { ensureOpen(); - Path path = toFilePath(name); + Path path = Resources.toFilePath(dir, name); if (path != null) { try { return Optional.of(path.toUri()); @@ -410,7 +395,7 @@ @Override public Optional open(String name) throws IOException { ensureOpen(); - Path path = toFilePath(name); + Path path = Resources.toFilePath(dir, name); if (path != null) { return Optional.of(Files.newInputStream(path)); } else { @@ -421,7 +406,7 @@ @Override public Optional read(String name) throws IOException { ensureOpen(); - Path path = toFilePath(name); + Path path = Resources.toFilePath(dir, name); if (path != null) { return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); } else { @@ -432,12 +417,9 @@ @Override public Stream list() throws IOException { ensureOpen(); - // sym links not followed - return Files.find(dir, Integer.MAX_VALUE, - (path, attrs) -> attrs.isRegularFile()) - .map(f -> dir.relativize(f) - .toString() - .replace(File.separatorChar, '/')); + return Files.walk(dir, Integer.MAX_VALUE) + .map(f -> Resources.toResourceName(dir, f)) + .filter(s -> s.length() > 0); } @Override --- old/src/java.base/share/classes/jdk/internal/module/Modules.java 2017-03-21 13:44:12.144047282 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/Modules.java 2017-03-21 13:44:11.946033695 +0000 @@ -31,7 +31,6 @@ import java.net.URI; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.Set; import jdk.internal.loader.BootLoader; import jdk.internal.loader.ClassLoaders; @@ -39,10 +38,10 @@ import jdk.internal.misc.SharedSecrets; /** - * A helper class to allow JDK classes create dynamic modules and to update - * modules, exports and the readability graph. It is also invoked by the VM - * to add read edges when agents are instrumenting code that need to link - * to supporting classes. + * A helper class for creating and updating modules. This class is intended to + * support command-line options, tests, and the instrumentation API. It is also + * used by the VM to add read edges when agents are instrumenting code that + * need to link to supporting classes. * * The parameters that are package names in this API are the fully-qualified * names of the packages as defined in section 6.5.3 of The Java™ @@ -72,25 +71,7 @@ } /** - * Define a new module to the VM. The module has the given set of - * packages and is defined to the given class loader. - * - * The resulting Module is in a larval state in that it does not not read - * any other module and does not have any exports. - */ - public static Module defineModule(ClassLoader loader, - String name, - Set packages) - { - ModuleDescriptor descriptor = ModuleDescriptor.newModule(name) - .packages(packages) - .build(); - - return JLRMA.defineModule(loader, descriptor, null); - } - - /** - * Adds a read-edge so that module {@code m1} reads module {@code m1}. + * Updates m1 to read m2. * Same as m1.addReads(m2) but without a caller check. */ public static void addReads(Module m1, Module m2) { @@ -98,47 +79,51 @@ } /** - * Update module {@code m} to read all unnamed modules. + * Update module m to read all unnamed modules. */ public static void addReadsAllUnnamed(Module m) { JLRMA.addReadsAllUnnamed(m); } /** - * Updates module m1 to export a package to module m2. - * Same as m1.addExports(pn, m2) but without a caller check. + * Update module m to export a package to all modules. + * + * This method is for intended for use by tests only. */ - public static void addExports(Module m1, String pn, Module m2) { - JLRMA.addExports(m1, pn, m2); + public static void addExports(Module m, String pn) { + JLRMA.addExports(m, pn); } /** - * Updates module m1 to open a package to module m2. - * Same as m1.addOpens(pn, m2) but without a caller check. + * Updates module m1 to export a package to module m2. + * Same as m1.addExports(pn, m2) but without a caller check */ - public static void addOpens(Module m1, String pn, Module m2) { - JLRMA.addOpens(m1, pn, m2); + public static void addExports(Module m1, String pn, Module m2) { + JLRMA.addExports(m1, pn, m2); } /** - * Updates a module m to export a package to all modules. + * Updates module m to export a package to all unnamed modules. */ - public static void addExportsToAll(Module m, String pn) { - JLRMA.addExportsToAll(m, pn); + public static void addExportsToAllUnnamed(Module m, String pn) { + JLRMA.addExportsToAllUnnamed(m, pn); } /** - * Updates a module m to open a package to all modules. + * Update module m to open a package to all modules. + * + * This method is for intended for use by tests only. */ - public static void addOpensToAll(Module m, String pn) { - JLRMA.addOpensToAll(m, pn); + public static void addOpens(Module m, String pn) { + JLRMA.addOpens(m, pn); } /** - * Updates module m to export a package to all unnamed modules. + * Updates module m1 to open a package to module m2. + * Same as m1.addOpens(pn, m2) but without a caller check. */ - public static void addExportsToAllUnnamed(Module m, String pn) { - JLRMA.addExportsToAllUnnamed(m, pn); + public static void addOpens(Module m1, String pn, Module m2) { + JLRMA.addOpens(m1, pn, m2); } /** @@ -149,7 +134,8 @@ } /** - * Updates module m to use a service + * Updates module m to use a service. + * Same as m2.addUses(service) but without a caller check. */ public static void addUses(Module m, Class service) { JLRMA.addUses(m, service); @@ -183,16 +169,6 @@ } /** - * Adds a package to a module's content. - * - * This method is a no-op if the module already contains the package or the - * module is an unnamed module. - */ - public static void addPackage(Module m, String pn) { - JLRMA.addPackage(m, pn); - } - - /** * Called by the VM when code in the given Module has been transformed by * an agent and so may have been instrumented to call into supporting * classes on the boot class path or application class path. --- old/src/java.base/share/classes/jdk/internal/module/SystemModuleFinder.java 2017-03-21 13:44:12.645081662 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/SystemModuleFinder.java 2017-03-21 13:44:12.450068281 +0000 @@ -150,18 +150,21 @@ System.getProperty("jdk.system.module.finder.disabledFastPath") != null; ModuleDescriptor[] descriptors; + ModuleTarget[] targets; ModuleHashes[] recordedHashes; ModuleResolution[] moduleResolutions; // fast loading of ModuleDescriptor of system modules if (isFastPathSupported() && !disabled) { descriptors = SystemModules.descriptors(); + targets = SystemModules.targets(); recordedHashes = SystemModules.hashes(); moduleResolutions = SystemModules.moduleResolutions(); } else { // if fast loading of ModuleDescriptors is disabled // fallback to read module-info.class descriptors = new ModuleDescriptor[n]; + targets = new ModuleTarget[n]; recordedHashes = new ModuleHashes[n]; moduleResolutions = new ModuleResolution[n]; ImageReader imageReader = SystemImage.reader(); @@ -171,6 +174,7 @@ ModuleInfo.Attributes attrs = ModuleInfo.read(imageReader.getResourceBuffer(loc), null); descriptors[i] = attrs.descriptor(); + targets[i] = attrs.target(); recordedHashes[i] = attrs.recordedHashes(); moduleResolutions[i] = attrs.moduleResolution(); } @@ -206,6 +210,7 @@ // create the ModuleReference ModuleReference mref = toModuleReference(md, + targets[i], recordedHashes[i], hashSupplier(names[i]), moduleResolutions[i]); @@ -233,6 +238,7 @@ } private ModuleReference toModuleReference(ModuleDescriptor md, + ModuleTarget target, ModuleHashes recordedHashes, HashSupplier hasher, ModuleResolution mres) { @@ -246,9 +252,14 @@ } }; - ModuleReference mref = - new ModuleReferenceImpl(md, uri, readerSupplier, null, - recordedHashes, hasher, mres); + ModuleReference mref = new ModuleReferenceImpl(md, + uri, + readerSupplier, + null, + target, + recordedHashes, + hasher, + mres); // may need a reference to a patched module if --patch-module specified mref = ModuleBootstrap.patcher().patchIfNeeded(mref); --- old/src/java.base/share/classes/jdk/internal/module/SystemModules.java 2017-03-21 13:44:13.153116522 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/SystemModules.java 2017-03-21 13:44:12.955102935 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ import java.lang.module.ModuleDescriptor; -/* +/** * SystemModules class will be generated at link time to create * ModuleDescriptor for the system modules directly to improve * the module descriptor reconstitution time. @@ -65,7 +65,7 @@ } /** - * Returns a non-empty array of ModuleDescriptors in the run-time image. + * Returns a non-empty array of ModuleDescriptor objects in the run-time image. * * When running an exploded image it returns an empty array. */ @@ -74,6 +74,15 @@ } /** + * Returns a non-empty array of ModuleTarget objects in the run-time image. + * + * When running an exploded image it returns an empty array. + */ + public static ModuleTarget[] targets() { + throw new InternalError("expected to be overridden at link time"); + } + + /** * Returns a non-empty array of ModuleHashes recorded in each module * in the run-time image. * --- old/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java 2017-03-21 13:44:13.766158588 +0000 +++ new/src/java.base/share/classes/jdk/internal/org/objectweb/asm/ClassReader.java 2017-03-21 13:44:13.569145069 +0000 @@ -2508,7 +2508,7 @@ } /** - * Reads a CONSTANT_Pakcage_info item in {@code b}. This method is + * Reads a CONSTANT_Package_info item in {@code b}. This method is * intended for {@link Attribute} sub slasses, and is normally not needed * by class generators or adapters. * --- old/src/java.base/share/classes/jdk/internal/reflect/Reflection.java 2017-03-21 13:44:14.433204359 +0000 +++ new/src/java.base/share/classes/jdk/internal/reflect/Reflection.java 2017-03-21 13:44:14.233190635 +0000 @@ -31,9 +31,7 @@ import java.util.Map; import java.util.Objects; import jdk.internal.HotSpotIntrinsicCandidate; -import jdk.internal.misc.SharedSecrets; import jdk.internal.misc.VM; -import sun.security.action.GetPropertyAction; /** Common utility routines used by both java.lang and java.lang.reflect */ @@ -104,39 +102,40 @@ int modifiers) throws IllegalAccessException { - if (currentClass == null || memberClass == null) { - throw new InternalError(); - } - if (!verifyMemberAccess(currentClass, memberClass, targetClass, modifiers)) { - throwIllegalAccessException(currentClass, memberClass, targetClass, modifiers); + throw newIllegalAccessException(currentClass, memberClass, targetClass, modifiers); } } /** - * Verify access to a member, returning {@code false} if no access + * Verify access to a member and return {@code true} if it is granted. + * + * @param currentClass the class performing the access + * @param memberClass the declaring class of the member being accessed + * @param targetClass the class of target object if accessing instance + * field or method; + * or the declaring class if accessing constructor; + * or null if accessing static field or method + * @param modifiers the member's access modifiers + * @return {@code true} if access to member is granted */ public static boolean verifyMemberAccess(Class currentClass, Class memberClass, Class targetClass, int modifiers) { - // Verify that currentClass can access a field, method, or - // constructor of memberClass, where that member's access bits are - // "modifiers". - - boolean gotIsSameClassPackage = false; - boolean isSameClassPackage = false; - if (currentClass == memberClass) { // Always succeeds return true; } - if (!verifyModuleAccess(currentClass, memberClass)) { + if (!verifyModuleAccess(currentClass.getModule(), memberClass)) { return false; } + boolean gotIsSameClassPackage = false; + boolean isSameClassPackage = false; + if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true; @@ -196,31 +195,20 @@ } /** - * Returns {@code true} if memberClass's's module exports memberClass's - * package to currentClass's module. + * Returns {@code true} if memberClass's module exports memberClass's + * package to currentModule. */ - public static boolean verifyModuleAccess(Class currentClass, - Class memberClass) { - return verifyModuleAccess(currentClass.getModule(), memberClass); - } - public static boolean verifyModuleAccess(Module currentModule, Class memberClass) { Module memberModule = memberClass.getModule(); - - // module may be null during startup (initLevel 0) - if (currentModule == memberModule) - return true; // same module (named or unnamed) - - String pkg = memberClass.getPackageName(); - boolean allowed = memberModule.isExported(pkg, currentModule); - if (allowed && memberModule.isNamed() && printStackTraceWhenAccessSucceeds()) { - if (!SharedSecrets.getJavaLangReflectModuleAccess() - .isStaticallyExported(memberModule, pkg, currentModule)) { - String msg = currentModule + " allowed access to member of " + memberClass; - new Exception(msg).printStackTrace(System.err); - } + if (currentModule == memberModule) { + // same module (named or unnamed) or both null if called + // before module system is initialized, which means we are + // dealing with java.base only. + return true; + } else { + String pkg = memberClass.getPackageName(); + return memberModule.isExported(pkg, currentModule); } - return allowed; } /** @@ -344,46 +332,14 @@ return false; } - - // true to print a stack trace when access fails - private static volatile boolean printStackWhenAccessFails; - - // true to print a stack trace when access succeeds - private static volatile boolean printStackWhenAccessSucceeds; - - // true if printStack* values are initialized - private static volatile boolean printStackPropertiesSet; - - private static void ensurePrintStackPropertiesSet() { - if (!printStackPropertiesSet && VM.initLevel() >= 1) { - String s = GetPropertyAction.privilegedGetProperty( - "sun.reflect.debugModuleAccessChecks"); - if (s != null) { - printStackWhenAccessFails = !s.equalsIgnoreCase("false"); - printStackWhenAccessSucceeds = s.equalsIgnoreCase("access"); - } - printStackPropertiesSet = true; - } - } - - public static boolean printStackTraceWhenAccessFails() { - ensurePrintStackPropertiesSet(); - return printStackWhenAccessFails; - } - - public static boolean printStackTraceWhenAccessSucceeds() { - ensurePrintStackPropertiesSet(); - return printStackWhenAccessSucceeds; - } - /** - * Throws IllegalAccessException with the an exception message based on + * Returns an IllegalAccessException with an exception message based on * the access that is denied. */ - private static void throwIllegalAccessException(Class currentClass, - Class memberClass, - Object target, - int modifiers) + public static IllegalAccessException newIllegalAccessException(Class currentClass, + Class memberClass, + Class targetClass, + int modifiers) throws IllegalAccessException { String currentSuffix = ""; @@ -411,20 +367,6 @@ if (m2.isNamed()) msg += " to " + m1; } - throwIllegalAccessException(msg); - } - - /** - * Throws IllegalAccessException with the given exception message. - */ - public static void throwIllegalAccessException(String msg) - throws IllegalAccessException - { - IllegalAccessException e = new IllegalAccessException(msg); - ensurePrintStackPropertiesSet(); - if (printStackWhenAccessFails) { - e.printStackTrace(System.err); - } - throw e; + return new IllegalAccessException(msg); } } --- old/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java 2017-03-21 13:44:14.944239425 +0000 +++ new/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java 2017-03-21 13:44:14.746225838 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ import java.util.Objects; import java.util.Properties; +import jdk.internal.misc.VM; import sun.reflect.misc.ReflectUtil; import sun.security.action.GetPropertyAction; @@ -585,17 +586,10 @@ private static void checkInitted() { if (initted) return; - // Tests to ensure the system properties table is fully - // initialized. This is needed because reflection code is - // called very early in the initialization process (before - // command-line arguments have been parsed and therefore - // these user-settable properties installed.) We assume that - // if System.out is non-null then the System class has been - // fully initialized and that the bulk of the startup code - // has been run. - - if (System.out == null) { - // java.lang.System not yet fully initialized + // Defer initialization until module system is initialized so as + // to avoid inflation and spinning bytecode in unnamed modules + // during early startup. + if (!VM.isModuleSystemInited()) { return; } --- old/src/java.base/share/classes/module-info.java 2017-03-21 13:44:15.449274079 +0000 +++ new/src/java.base/share/classes/module-info.java 2017-03-21 13:44:15.253260629 +0000 @@ -125,10 +125,9 @@ jdk.jlink; exports jdk.internal.loader to java.instrument, - java.logging, - jdk.jlink; + java.logging; exports jdk.internal.jmod to - jdk.compiler, + jdk.compiler, // reflective dependency jdk.jlink; exports jdk.internal.logger to java.logging; @@ -140,10 +139,7 @@ exports jdk.internal.org.objectweb.asm.tree to jdk.jlink; exports jdk.internal.org.objectweb.asm.util to - jdk.jlink, jdk.scripting.nashorn; - exports jdk.internal.org.objectweb.asm.tree.analysis to - jdk.jlink; exports jdk.internal.org.objectweb.asm.commons to jdk.scripting.nashorn; exports jdk.internal.org.objectweb.asm.signature to @@ -157,7 +153,6 @@ jdk.jlink; exports jdk.internal.misc to java.desktop, - jdk.incubator.httpclient, java.logging, java.management, java.naming, @@ -166,8 +161,8 @@ java.sql, java.xml, jdk.charsets, - jdk.compiler, - jdk.jartool, + jdk.compiler, // reflective dependency + jdk.incubator.httpclient, jdk.jdeps, jdk.jlink, jdk.jshell, @@ -210,11 +205,10 @@ jdk.naming.dns; exports sun.net.util to java.desktop, - jdk.jconsole, - jdk.naming.dns; + jdk.jconsole; exports sun.net.www to - jdk.incubator.httpclient, java.desktop, + jdk.incubator.httpclient, jdk.jartool; exports sun.net.www.protocol.http to java.security.jgss; --- old/src/java.base/share/classes/sun/launcher/LauncherHelper.java 2017-03-21 13:44:15.956308871 +0000 +++ new/src/java.base/share/classes/sun/launcher/LauncherHelper.java 2017-03-21 13:44:15.752294872 +0000 @@ -85,6 +85,7 @@ import java.util.stream.Stream; import jdk.internal.misc.VM; +import jdk.internal.module.IllegalAccessLogger; import jdk.internal.module.Modules; @@ -428,14 +429,20 @@ abort(null, "java.launcher.jar.error3", jarname); } - // Add-Exports and Add-Opens to break encapsulation + // Add-Exports and Add-Opens to allow illegal access String exports = mainAttrs.getValue(ADD_EXPORTS); if (exports != null) { - addExportsOrOpens(exports, false); + String warn = getLocalizedMessage("java.launcher.permitaccess.warning", + jarname, ADD_EXPORTS); + System.err.println(warn); + addExportsOrOpens(exports, false, ADD_EXPORTS); } String opens = mainAttrs.getValue(ADD_OPENS); if (opens != null) { - addExportsOrOpens(opens, true); + String warn = getLocalizedMessage("java.launcher.permitaccess.warning", + jarname, ADD_OPENS); + System.err.println(warn); + addExportsOrOpens(opens, true, ADD_OPENS); } /* @@ -460,23 +467,36 @@ * Process the Add-Exports or Add-Opens value. The value is * {@code / ( /)*}. */ - static void addExportsOrOpens(String value, boolean open) { + static void addExportsOrOpens(String value, boolean open, String how) { + IllegalAccessLogger.Builder builder; + IllegalAccessLogger logger = IllegalAccessLogger.illegalAccessLogger(); + if (logger == null) { + builder = new IllegalAccessLogger.Builder(); + } else { + builder = logger.toBuilder(); + } + for (String moduleAndPackage : value.split(" ")) { String[] s = moduleAndPackage.trim().split("/"); if (s.length == 2) { String mn = s[0]; String pn = s[1]; + Layer.boot().findModule(mn).ifPresent(m -> { if (m.getDescriptor().packages().contains(pn)) { if (open) { + builder.logAccessToOpenPackage(m, pn, how); Modules.addOpensToAllUnnamed(m, pn); } else { + builder.logAccessToExportedPackage(m, pn, how); Modules.addExportsToAllUnnamed(m, pn); } } }); } } + + IllegalAccessLogger.setIllegalAccessLogger(builder.build()); } // From src/share/bin/java.c: --- old/src/java.base/share/classes/sun/launcher/resources/launcher.properties 2017-03-21 13:44:16.470344142 +0000 +++ new/src/java.base/share/classes/sun/launcher/resources/launcher.properties 2017-03-21 13:44:16.274330692 +0000 @@ -24,12 +24,15 @@ # # Translators please note do not translate the options themselves -java.launcher.opt.header = Usage: {0} [options] class [args...]\n\ -\ (to execute a class)\n or {0} [options] -jar jarfile [args...]\n\ +java.launcher.opt.header = Usage: {0} [options] [args...]\n\ +\ (to execute a class)\n or {0} [options] -jar [args...]\n\ \ (to execute a jar file)\n\ -\ or {0} [options] -p -m [/] [args...]\n\ +\ or {0} [options] -m [/] [args...]\n\ +\ {0} [options] --module [/] [args...]\n\ \ (to execute the main class in a module)\n\n\ -where options include:\n\n +\ Arguments following the main class, -jar , -m or --module\n\ +\ / are passed as the arguments to main class.\n\n\ +\ where options include:\n\n java.launcher.opt.datamodel =\ -d{0}\t Deprecated, will be removed in a future release\n java.launcher.opt.vmselect =\ {0}\t to select the "{1}" VM\n @@ -49,10 +52,6 @@ \ A {0} separated list of directories, each directory\n\ \ is a directory of modules that replace upgradeable\n\ \ modules in the runtime image\n\ -\ -m [/]\n\ -\ --module [/]\n\ -\ the initial module to resolve, and the name of the main class\n\ -\ to execute if not specified by the module\n\ \ --add-modules [,...]\n\ \ root modules to resolve in addition to the initial module.\n\ \ can also be ALL-DEFAULT, ALL-SYSTEM,\n\ @@ -157,6 +156,10 @@ \ --add-opens /=(,)*\n\ \ updates to open to\n\ \ , regardless of module declaration.\n\ +\ --permit-illegal-access\n\ +\ permit illegal access to members of types in named modules\n\ +\ by code in unnamed modules. This compatibility option will\n\ +\ be removed in the next release.\n\ \ --disable-@files disable further argument file expansion\n\ \ --patch-module =({0})*\n\ \ Override or augment a module with classes and resources\n\ @@ -208,4 +211,6 @@ java.launcher.module.error3=\ Error: Unable to load main class {0} from module {1}\n\ \t{2} +java.launcher.permitaccess.warning=\ + WARNING: Main manifest of {0} contains {1} attribute to permit illegal access --- old/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java 2017-03-21 13:44:16.969378385 +0000 +++ new/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java 2017-03-21 13:44:16.771364797 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,8 +26,6 @@ package sun.net.www.protocol.jrt; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -44,7 +42,6 @@ import jdk.internal.loader.Resource; import sun.net.www.ParseUtil; import sun.net.www.URLConnection; -import sun.security.action.GetPropertyAction; /** * URLConnection implementation that can be used to connect to resources @@ -66,9 +63,6 @@ // the Resource when connected private volatile Resource resource; - // the permission to access resources in the runtime image, created lazily - private static volatile Permission permission; - JavaRuntimeURLConnection(URL url) throws IOException { super(url); String path = url.getPath(); @@ -164,14 +158,8 @@ } @Override - public Permission getPermission() throws IOException { - Permission p = permission; - if (p == null) { - String home = GetPropertyAction.privilegedGetProperty("java.home"); - p = new FilePermission(home + File.separator + "-", "read"); - permission = p; - } - return p; + public Permission getPermission() { + return new RuntimePermission("accessSystemModules"); } /** --- old/src/java.base/share/native/libjava/ClassLoader.c 2017-03-21 13:44:17.474413039 +0000 +++ new/src/java.base/share/native/libjava/ClassLoader.c 2017-03-21 13:44:17.274399314 +0000 @@ -72,23 +72,9 @@ return utfStr; } -// The existence or signature of this method is not guaranteed since it -// supports a private method. This method will be changed in 1.7. -JNIEXPORT jclass JNICALL -Java_java_lang_ClassLoader_defineClass0(JNIEnv *env, - jobject loader, - jstring name, - jbyteArray data, - jint offset, - jint length, - jobject pd) -{ - return Java_java_lang_ClassLoader_defineClass1(env, loader, name, data, offset, - length, pd, NULL); -} - JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_defineClass1(JNIEnv *env, + jclass cls, jobject loader, jstring name, jbyteArray data, @@ -163,6 +149,7 @@ JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_defineClass2(JNIEnv *env, + jclass cls, jobject loader, jstring name, jobject data, --- old/src/java.base/share/native/libjava/Module.c 2017-03-21 13:44:17.983447968 +0000 +++ new/src/java.base/share/native/libjava/Module.c 2017-03-21 13:44:17.786434449 +0000 @@ -73,30 +73,32 @@ jstring location, jobjectArray packages) { char** pkgs = NULL; - jsize idx; jsize num_packages = (*env)->GetArrayLength(env, packages); if (num_packages != 0 && (pkgs = calloc(num_packages, sizeof(char*))) == NULL) { JNU_ThrowOutOfMemoryError(env, NULL); return; - } else { - int valid = 1; + } else if ((*env)->EnsureLocalCapacity(env, (jint)num_packages) == 0) { + jboolean failed = JNI_FALSE; + int idx; for (idx = 0; idx < num_packages; idx++) { jstring pkg = (*env)->GetObjectArrayElement(env, packages, idx); - pkgs[idx] = GetInternalPackageName(env, pkg, NULL, 0); - if (pkgs[idx] == NULL) { - valid = 0; + char* name = GetInternalPackageName(env, pkg, NULL, 0); + if (name != NULL) { + pkgs[idx] = name; + } else { + failed = JNI_TRUE; break; } } - - if (valid != 0) { + if (!failed) { JVM_DefineModule(env, module, is_open, version, location, - (const char* const*)pkgs, num_packages); + (const char* const*)pkgs, num_packages); } } if (num_packages > 0) { + int idx; for (idx = 0; idx < num_packages; idx++) { if (pkgs[idx] != NULL) { free(pkgs[idx]); --- old/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java 2017-03-21 13:44:18.486482484 +0000 +++ new/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java 2017-03-21 13:44:18.287468828 +0000 @@ -191,6 +191,9 @@ * if the input does not represent a well-formed class file * @return a well-formed class file buffer (the result of the transform), * or {@code null} if no transform is performed + * + * @revised 9 + * @spec JPMS */ default byte[] transform( ClassLoader loader, --- old/src/jdk.jartool/share/classes/sun/tools/jar/Main.java 2017-03-21 13:44:18.993517276 +0000 +++ new/src/jdk.jartool/share/classes/sun/tools/jar/Main.java 2017-03-21 13:44:18.795503688 +0000 @@ -63,6 +63,7 @@ import jdk.internal.module.ModuleInfo; import jdk.internal.module.ModuleInfoExtender; import jdk.internal.module.ModuleResolution; +import jdk.internal.module.ModuleTarget; import jdk.internal.util.jar.JarIndex; import static jdk.internal.util.jar.JarIndex.INDEX_NAME; @@ -1780,6 +1781,7 @@ { ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null); ModuleDescriptor md = attrs.descriptor(); + ModuleTarget target = attrs.target(); ModuleHashes hashes = attrs.recordedHashes(); StringBuilder sb = new StringBuilder(); @@ -1824,11 +1826,14 @@ md.mainClass().ifPresent(v -> sb.append("\n main-class " + v)); - md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v)); - - md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v)); - - md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v)); + if (target != null) { + String osName = target.osName(); + if (osName != null) + sb.append("\n operating-system-name " + osName); + String osArch = target.osArch(); + if (osArch != null) + sb.append("\n operating-system-architecture " + osArch); + } if (hashes != null) { hashes.names().stream().sorted().forEach( --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java 2017-03-21 13:44:19.532554263 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java 2017-03-21 13:44:19.332540538 +0000 @@ -151,16 +151,8 @@ @Override public void storeFiles(ResourcePool files) { try { - // populate targetOsName field up-front because it's used elsewhere. - Optional javaBase = files.moduleView().findModule("java.base"); - javaBase.ifPresent(mod -> { - // fill release information available from transformed "java.base" module! - ModuleDescriptor desc = mod.descriptor(); - desc.osName().ifPresent(s -> { - this.targetOsName = s; - }); - }); - + this.targetOsName = files.moduleView(). + findModule("java.base").get().osName(); if (this.targetOsName == null) { throw new PluginException("ModuleTarget attribute is missing for java.base module"); } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java 2017-03-21 13:44:20.045589466 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImagePluginStack.java 2017-03-21 13:44:19.846575810 +0000 @@ -43,6 +43,8 @@ import java.util.stream.Stream; import jdk.internal.jimage.decompressor.Decompressor; +import jdk.internal.module.ModuleInfo.Attributes; +import jdk.internal.module.ModuleTarget; import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.plugin.PluginException; @@ -298,6 +300,7 @@ final ResourcePoolModule module; // lazily initialized ModuleDescriptor descriptor; + ModuleTarget target; LastModule(ResourcePoolModule module) { this.module = module; @@ -316,13 +319,31 @@ @Override public ModuleDescriptor descriptor() { - if (descriptor == null) { - descriptor = ResourcePoolManager.readModuleDescriptor(this); - } + initModuleAttributes(); return descriptor; } @Override + public String osName() { + initModuleAttributes(); + return target != null? target.osName() : null; + } + + @Override + public String osArch() { + initModuleAttributes(); + return target != null? target.osArch() : null; + } + + private void initModuleAttributes() { + if (this.descriptor == null) { + Attributes attr = ResourcePoolManager.readModuleAttributes(this); + this.descriptor = attr.descriptor(); + this.target = attr.target(); + } + } + + @Override public Set packages() { return module.packages(); } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2017-03-21 13:44:20.555624463 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2017-03-21 13:44:20.356610807 +0000 @@ -54,7 +54,6 @@ import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.plugin.Plugin; -import jdk.internal.module.Checks; import jdk.internal.module.ModulePath; import jdk.internal.module.ModuleResolution; @@ -423,13 +422,6 @@ ModuleFinder.of(), roots); - // emit warning for modules that end with a digit - cf.modules().stream() - .map(ResolvedModule::name) - .filter(mn -> !Checks.hasLegalModuleNameLastCharacter(mn)) - .forEach(mn -> System.err.println("WARNING: Module name \"" - + mn + "\" may soon be illegal")); - // emit a warning for any incubating modules in the configuration if (log != null) { String im = cf.modules() --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolConfiguration.java 2017-03-21 13:44:21.058658980 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolConfiguration.java 2017-03-21 13:44:20.863645599 +0000 @@ -66,9 +66,6 @@ md.version().ifPresent(builder::version); md.mainClass().ifPresent(builder::mainClass); - md.osName().ifPresent(builder::osName); - md.osArch().ifPresent(builder::osArch); - md.osVersion().ifPresent(builder::osVersion); return builder.build(); } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java 2017-03-21 13:44:21.555693085 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java 2017-03-21 13:44:21.356679429 +0000 @@ -35,7 +35,10 @@ import java.util.Set; import java.util.stream.Stream; import jdk.internal.jimage.decompressor.CompressedResourceHeader; -import jdk.internal.loader.ResourceHelper; +import jdk.internal.module.Resources; +import jdk.internal.module.ModuleInfo; +import jdk.internal.module.ModuleInfo.Attributes; +import jdk.internal.module.ModuleTarget; import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePoolBuilder; import jdk.tools.jlink.plugin.ResourcePoolEntry; @@ -47,8 +50,8 @@ * A manager for pool of resources. */ public class ResourcePoolManager { - // utility to read ModuleDescriptor of the given ResourcePoolModule - static ModuleDescriptor readModuleDescriptor(ResourcePoolModule mod) { + // utility to read Module Attributes of the given ResourcePoolModule + static Attributes readModuleAttributes(ResourcePoolModule mod) { String p = "/" + mod.name() + "/module-info.class"; Optional content = mod.findEntry(p); if (!content.isPresent()) { @@ -57,9 +60,9 @@ } ByteBuffer bb = ByteBuffer.wrap(content.get().contentBytes()); try { - return ModuleDescriptor.read(bb); + return ModuleInfo.read(bb, null); } catch (RuntimeException re) { - throw new RuntimeException("module descriptor cannot be read for " + mod.name(), re); + throw new RuntimeException("module info cannot be read for " + mod.name(), re); } } @@ -68,7 +71,7 @@ */ public static boolean isNamedPackageResource(String path) { return (path.endsWith(".class") && !path.endsWith("module-info.class")) || - !ResourceHelper.isSimpleResource(path); + Resources.canEncapsulate(path); } class ResourcePoolModuleImpl implements ResourcePoolModule { @@ -76,6 +79,8 @@ final Map moduleContent = new LinkedHashMap<>(); // lazily initialized private ModuleDescriptor descriptor; + private ModuleTarget target; + final String name; private ResourcePoolModuleImpl(String name) { @@ -100,13 +105,31 @@ @Override public ModuleDescriptor descriptor() { - if (descriptor == null) { - descriptor = readModuleDescriptor(this); - } + initModuleAttributes(); return descriptor; } @Override + public String osName() { + initModuleAttributes(); + return target != null? target.osName() : null; + } + + @Override + public String osArch() { + initModuleAttributes(); + return target != null? target.osArch() : null; + } + + private void initModuleAttributes() { + if (this.descriptor == null) { + Attributes attr = readModuleAttributes(this); + this.descriptor = attr.descriptor(); + this.target = attr.target(); + } + } + + @Override public Set packages() { Set pkgs = new HashSet<>(); moduleContent.values().stream() --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ExcludeVMPlugin.java 2017-03-21 13:44:22.060727739 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ExcludeVMPlugin.java 2017-03-21 13:44:21.866714426 +0000 @@ -115,7 +115,7 @@ @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { ResourcePoolModule javaBase = in.moduleView().findModule("java.base").get(); - String[] jvmlibs = jvmlibs(javaBase.descriptor().osName().get()); + String[] jvmlibs = jvmlibs(javaBase.osName()); TreeSet existing = new TreeSet<>(new JvmComparator()); TreeSet removed = new TreeSet<>(new JvmComparator()); if (!keepAll) { --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java 2017-03-21 13:44:22.558761913 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/ReleaseInfoPlugin.java 2017-03-21 13:44:22.361748394 +0000 @@ -137,15 +137,13 @@ javaBase.ifPresent(mod -> { // fill release information available from transformed "java.base" module! ModuleDescriptor desc = mod.descriptor(); - desc.osName().ifPresent(s -> { - release.put("OS_NAME", quote(s)); - }); - desc.osVersion().ifPresent(s -> release.put("OS_VERSION", quote(s))); - desc.osArch().ifPresent(s -> release.put("OS_ARCH", quote(s))); desc.version().ifPresent(s -> release.put("JAVA_VERSION", quote(parseVersion(s.toString())))); desc.version().ifPresent(s -> release.put("JAVA_FULL_VERSION", quote(s.toString()))); + + release.put("OS_NAME", quote(mod.osName())); + release.put("OS_ARCH", quote(mod.osArch())); }); // put topological sorted module names separated by space --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java 2017-03-21 13:44:23.050795674 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java 2017-03-21 13:44:22.854782224 +0000 @@ -52,6 +52,7 @@ import jdk.internal.module.ModuleInfo.Attributes; import jdk.internal.module.ModuleInfoExtender; import jdk.internal.module.ModuleResolution; +import jdk.internal.module.ModuleTarget; import jdk.internal.module.SystemModules; import jdk.internal.org.objectweb.asm.Attribute; import jdk.internal.org.objectweb.asm.ClassReader; @@ -206,12 +207,12 @@ // add ModulePackages attribute if this module contains some packages // and ModulePackages is not present this.addModulePackages = packages.size() > 0 && !hasModulePackages(); + // drop target attribute only if any OS property is present - if (dropModuleTarget) { - this.dropModuleTarget = - descriptor.osName().isPresent() || - descriptor.osArch().isPresent() || - descriptor.osVersion().isPresent(); + ModuleTarget target = attrs.target(); + if (dropModuleTarget && target != null) { + this.dropModuleTarget = (target.osName() != null) + || (target.osArch() != null); } else { this.dropModuleTarget = false; } @@ -230,6 +231,10 @@ return packages; } + ModuleTarget target() { + return attrs.target(); + } + ModuleHashes recordedHashes() { return attrs.recordedHashes(); } @@ -372,7 +377,7 @@ } void dropModuleTarget() { - extender.targetPlatform("", "", ""); + extender.targetPlatform("", ""); } byte[] getBytes() throws IOException { @@ -399,6 +404,10 @@ "java/lang/module/ModuleDescriptor$Exports$Modifier"; private static final String OPENS_MODIFIER_CLASSNAME = "java/lang/module/ModuleDescriptor$Opens$Modifier"; + private static final String MODULE_TARGET_CLASSNAME = + "jdk/internal/module/ModuleTarget"; + private static final String MODULE_TARGET_ARRAY_SIGNATURE = + "[Ljdk/internal/module/ModuleTarget;"; private static final String MODULE_HASHES_ARRAY_SIGNATURE = "[Ljdk/internal/module/ModuleHashes;"; private static final String MODULE_RESOLUTION_CLASSNAME = @@ -414,6 +423,7 @@ private final int BUILDER_VAR = 0; private final int MD_VAR = 1; // variable for ModuleDescriptor + private final int MT_VAR = 1; // variable for ModuleTarget private final int MH_VAR = 1; // variable for ModuleHashes private int nextLocalVar = 2; // index to next local variable @@ -515,11 +525,10 @@ if (entry.moduleName().equals("java.base")) { moduleInfo = new ModuleInfo(entry.contentBytes(), packages, false); ModuleDescriptor md = moduleInfo.descriptor; - // drop Moduletarget attribute only if java.base has all OS properties - // otherwise, retain it - if (dropModuleTarget && - md.osName().isPresent() && md.osArch().isPresent() && - md.osVersion().isPresent()) { + // drop ModuleTarget attribute if java.base has all OS properties + ModuleTarget target = moduleInfo.target(); + if (dropModuleTarget + && (target.osName() != null) && (target.osArch() != null)) { dropModuleTarget = true; } else { dropModuleTarget = false; @@ -584,15 +593,20 @@ // generate SystemModules::descriptors genDescriptorsMethod(); + + // generate SystemModules::targets + genTargetsMethod(); + // generate SystemModules::hashes genHashesMethod(); + // generate SystemModules::moduleResolutions genModuleResolutionsMethod(); return cw; } - /* + /** * Generate bytecode for SystemModules::descriptors method */ private void genDescriptorsMethod() { @@ -616,10 +630,47 @@ mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); + } + + /** + * Generate bytecode for SystemModules::targets method + */ + private void genTargetsMethod() { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC+ACC_STATIC, + "targets", + "()" + MODULE_TARGET_ARRAY_SIGNATURE, + "()" + MODULE_TARGET_ARRAY_SIGNATURE, + null); + mv.visitCode(); + pushInt(mv, moduleInfos.size()); + mv.visitTypeInsn(ANEWARRAY, MODULE_TARGET_CLASSNAME); + mv.visitVarInsn(ASTORE, MT_VAR); + + for (int index=0; index < moduleInfos.size(); index++) { + ModuleInfo minfo = moduleInfos.get(index); + if (minfo.target() != null && !minfo.dropModuleTarget) { + mv.visitVarInsn(ALOAD, MT_VAR); + pushInt(mv, index); + + // new ModuleTarget(String, String) + mv.visitTypeInsn(NEW, MODULE_TARGET_CLASSNAME); + mv.visitInsn(DUP); + mv.visitLdcInsn(minfo.target().osName()); + mv.visitLdcInsn(minfo.target().osArch()); + mv.visitMethodInsn(INVOKESPECIAL, MODULE_TARGET_CLASSNAME, + "", "(Ljava/lang/String;Ljava/lang/String;)V", false); + + mv.visitInsn(AASTORE); + } + } + mv.visitVarInsn(ALOAD, MT_VAR); + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); } - /* + /** * Generate bytecode for SystemModules::hashes method */ private void genHashesMethod() { @@ -647,10 +698,9 @@ hmv.visitInsn(ARETURN); hmv.visitMaxs(0, 0); hmv.visitEnd(); - } - /* + /** * Generate bytecode for SystemModules::methodResoultions method */ private void genModuleResolutionsMethod() { @@ -749,6 +799,7 @@ final ModuleDescriptor md; final Set packages; final int index; + ModuleDescriptorBuilder(ModuleDescriptor md, Set packages, int index) { if (md.isAutomatic()) { throw new InternalError("linking automatic module is not supported"); @@ -786,11 +837,6 @@ // main class md.mainClass().ifPresent(this::mainClass); - // os name, arch, version - targetPlatform(md.osName().orElse(null), - md.osArch().orElse(null), - md.osVersion().orElse(null)); - putModuleDescriptor(); } @@ -1088,25 +1134,6 @@ mv.visitInsn(POP); } - /* - * Invoke Builder.osName(String name) - * Builder.osArch(String arch) - * Builder.osVersion(String version) - */ - void targetPlatform(String osName, String osArch, String osVersion) { - if (osName != null) { - invokeBuilderMethod("osName", osName); - } - - if (osArch != null) { - invokeBuilderMethod("osArch", osArch); - } - - if (osVersion != null) { - invokeBuilderMethod("osVersion", osVersion); - } - } - void invokeBuilderMethod(String methodName, String value) { mv.visitVarInsn(ALOAD, BUILDER_VAR); mv.visitLdcInsn(value); --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolModule.java 2017-03-21 13:44:23.558830534 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolModule.java 2017-03-21 13:44:23.361817016 +0000 @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import jdk.internal.module.ModuleTarget; /** * Link-time representation of a module. @@ -57,6 +58,20 @@ public ModuleDescriptor descriptor(); /** + * The module target OS name for this module. + * + * @return The module target OS name + */ + public String osName(); + + /** + * The module target OS arch for this module. + * + * @return The module target OS arch + */ + public String osArch(); + + /** * Retrieves all the packages located in this module. * * @return The set of packages. --- old/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java 2017-03-21 13:44:24.060864982 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java 2017-03-21 13:44:23.864851532 +0000 @@ -96,13 +96,14 @@ import jdk.internal.joptsimple.OptionSet; import jdk.internal.joptsimple.OptionSpec; import jdk.internal.joptsimple.ValueConverter; -import jdk.internal.loader.ResourceHelper; import jdk.internal.module.ModuleHashes; import jdk.internal.module.ModuleHashesBuilder; import jdk.internal.module.ModuleInfo; import jdk.internal.module.ModuleInfoExtender; import jdk.internal.module.ModulePath; import jdk.internal.module.ModuleResolution; +import jdk.internal.module.ModuleTarget; +import jdk.internal.module.Resources; import jdk.tools.jlink.internal.Utils; import static java.util.stream.Collectors.joining; @@ -178,7 +179,6 @@ String mainClass; String osName; String osArch; - String osVersion; Pattern modulesToHash; ModuleResolution moduleResolution; boolean dryrun; @@ -311,7 +311,9 @@ try (JmodFile jf = new JmodFile(options.jmodFile)) { try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) { ModuleInfo.Attributes attrs = ModuleInfo.read(in, null); - printModuleDescriptor(attrs.descriptor(), attrs.recordedHashes()); + printModuleDescriptor(attrs.descriptor(), + attrs.target(), + attrs.recordedHashes()); return true; } catch (IOException e) { throw new CommandException("err.module.descriptor.not.found"); @@ -325,7 +327,9 @@ .collect(joining(" ")); } - private void printModuleDescriptor(ModuleDescriptor md, ModuleHashes hashes) + private void printModuleDescriptor(ModuleDescriptor md, + ModuleTarget target, + ModuleHashes hashes) throws IOException { StringBuilder sb = new StringBuilder(); @@ -365,11 +369,14 @@ md.mainClass().ifPresent(v -> sb.append("\n main-class " + v)); - md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v)); - - md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v)); - - md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v)); + if (target != null) { + String osName = target.osName(); + if (osName != null) + sb.append("\n operating-system-name " + osName); + String osArch = target.osArch(); + if (osArch != null) + sb.append("\n operating-system-architecture " + osArch); + } if (hashes != null) { hashes.names().stream().sorted().forEach( @@ -432,7 +439,6 @@ final String mainClass = options.mainClass; final String osName = options.osName; final String osArch = options.osArch; - final String osVersion = options.osVersion; final List excludes = options.excludes; final ModuleResolution moduleResolution = options.moduleResolution; @@ -528,9 +534,9 @@ if (mainClass != null) extender.mainClass(mainClass); - // --os-name, --os-arch, --os-version - if (osName != null || osArch != null || osVersion != null) - extender.targetPlatform(osName, osArch, osVersion); + // --os-name, --os-arch + if (osName != null || osArch != null) + extender.targetPlatform(osName, osArch); // --module-version if (moduleVersion != null) @@ -675,7 +681,7 @@ */ boolean isResource(String name) { name = name.replace(File.separatorChar, '/'); - return name.endsWith(".class") || !ResourceHelper.isSimpleResource(name); + return name.endsWith(".class") || Resources.canEncapsulate(name); } @@ -1331,11 +1337,6 @@ .withRequiredArg() .describedAs(getMessage("main.opt.os-arch.arg")); - OptionSpec osVersion - = parser.accepts("os-version", getMessage("main.opt.os-version")) - .withRequiredArg() - .describedAs(getMessage("main.opt.os-version.arg")); - OptionSpec doNotResolveByDefault = parser.accepts("do-not-resolve-by-default", getMessage("main.opt.do-not-resolve-by-default")); @@ -1403,8 +1404,6 @@ options.osName = getLastElement(opts.valuesOf(osName)); if (opts.has(osArch)) options.osArch = getLastElement(opts.valuesOf(osArch)); - if (opts.has(osVersion)) - options.osVersion = getLastElement(opts.valuesOf(osVersion)); if (opts.has(warnIfResolved)) options.moduleResolution = getLastElement(opts.valuesOf(warnIfResolved)); if (opts.has(doNotResolveByDefault)) { --- old/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties 2017-03-21 13:44:24.594901626 +0000 +++ new/src/jdk.jlink/share/classes/jdk/tools/jmod/resources/jmod.properties 2017-03-21 13:44:24.396888039 +0000 @@ -68,8 +68,6 @@ main.opt.os-name.arg=os-name main.opt.os-arch=Operating system architecture main.opt.os-arch.arg=os-arch -main.opt.os-version=Operating system version -main.opt.os-version.arg=os-version main.opt.module-path=Module path main.opt.hash-modules=Compute and record hashes to tie a packaged module\ \ with modules matching the given and depending upon it directly\ --- old/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java 2017-03-21 13:44:25.088935525 +0000 +++ new/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java 2017-03-21 13:44:24.892922075 +0000 @@ -813,6 +813,7 @@ /** * Tells the VM to define a class, without security checks. By default, the * class loader and protection domain come from the caller's class. + * @see java.lang.invoke.MethodHandles.Lookup#defineClass(byte[]) */ @ForceInline public Class defineClass(String name, byte[] b, int off, int len, --- old/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java 2017-03-21 13:44:25.614971620 +0000 +++ new/test/java/lang/Class/getDeclaredField/FieldSetAccessibleTest.java 2017-03-21 13:44:25.413957827 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -380,6 +380,7 @@ permissions.add(new RuntimePermission("closeClassLoader")); permissions.add(new RuntimePermission("getClassLoader")); permissions.add(new RuntimePermission("accessDeclaredMembers")); + permissions.add(new RuntimePermission("accessSystemModules")); permissions.add(new ReflectPermission("suppressAccessChecks")); permissions.add(new PropertyPermission("*", "read")); permissions.add(new FilePermission("<>", "read")); --- old/test/java/lang/module/AutomaticModulesTest.java 2017-03-21 13:44:26.124006548 +0000 +++ new/test/java/lang/module/AutomaticModulesTest.java 2017-03-21 13:44:25.924992893 +0000 @@ -773,9 +773,6 @@ // test miscellaneous methods assertTrue(m.isAutomatic()); assertFalse(m.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC)); - assertFalse(m.osName().isPresent()); - assertFalse(m.osArch().isPresent()); - assertFalse(m.osVersion().isPresent()); } --- old/test/java/lang/module/ConfigurationTest.java 2017-03-21 13:44:26.636041683 +0000 +++ new/test/java/lang/module/ConfigurationTest.java 2017-03-21 13:44:26.437028027 +0000 @@ -25,25 +25,31 @@ * @test * @library /lib/testlibrary * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.module * @build ConfigurationTest ModuleUtils * @run testng ConfigurationTest * @summary Basic tests for java.lang.module.Configuration */ +import java.io.IOException; +import java.io.OutputStream; import java.lang.module.Configuration; import java.lang.module.FindException; import java.lang.module.ModuleDescriptor; -import java.lang.module.ModuleDescriptor.Builder; import java.lang.module.ModuleDescriptor.Requires; import java.lang.module.ModuleFinder; import java.lang.module.ResolutionException; import java.lang.module.ResolvedModule; import java.lang.reflect.Layer; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.Set; import jdk.internal.misc.SharedSecrets; +import jdk.internal.module.ModuleInfoWriter; +import jdk.internal.module.ModuleTarget; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -438,70 +444,6 @@ /** - * Basic test of "requires transitive" with configurations. - * - * The test consists of three configurations: - * - Configuration cf1: m1, m2 requires transitive m1 - * - Configuration cf2: m1, m3 requires transitive m1 - * - Configuration cf3(cf1,cf2): m4 requires m2, m3 - */ - public void testRequiresTransitive6() { - ModuleDescriptor descriptor1 = newBuilder("m1") - .build(); - - ModuleDescriptor descriptor2 = newBuilder("m2") - .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") - .build(); - - ModuleDescriptor descriptor3 = newBuilder("m3") - .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") - .build(); - - ModuleDescriptor descriptor4 = newBuilder("m4") - .requires("m2") - .requires("m3") - .build(); - - ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2); - Configuration cf1 = resolve(finder1, "m2"); - assertTrue(cf1.modules().size() == 2); - assertTrue(cf1.findModule("m1").isPresent()); - assertTrue(cf1.findModule("m2").isPresent()); - assertTrue(cf1.parents().size() == 1); - assertTrue(cf1.parents().get(0) == Configuration.empty()); - - ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor3); - Configuration cf2 = resolve(finder2, "m3"); - assertTrue(cf2.modules().size() == 2); - assertTrue(cf2.findModule("m3").isPresent()); - assertTrue(cf2.findModule("m1").isPresent()); - assertTrue(cf2.parents().size() == 1); - assertTrue(cf2.parents().get(0) == Configuration.empty()); - - ModuleFinder finder3 = ModuleUtils.finderOf(descriptor4); - Configuration cf3 = Configuration.resolve(finder3, - List.of(cf1, cf2), - ModuleFinder.of(), - Set.of("m4")); - assertTrue(cf3.modules().size() == 1); - assertTrue(cf3.findModule("m4").isPresent()); - - ResolvedModule m1_l = cf1.findModule("m1").get(); - ResolvedModule m1_r = cf2.findModule("m1").get(); - ResolvedModule m2 = cf1.findModule("m2").get(); - ResolvedModule m3 = cf2.findModule("m3").get(); - ResolvedModule m4 = cf3.findModule("m4").get(); - assertTrue(m4.configuration() == cf3); - - assertTrue(m4.reads().size() == 4); - assertTrue(m4.reads().contains(m1_l)); - assertTrue(m4.reads().contains(m1_r)); - assertTrue(m4.reads().contains(m2)); - assertTrue(m4.reads().contains(m3)); - } - - - /** * Basic test of "requires static": * m1 requires static m2 * m2 is not observable @@ -1603,6 +1545,76 @@ /** + * Basic test to detect reading a module with the same name as itself + * + * The test consists of three configurations: + * - Configuration cf1: m1, m2 requires transitive m1 + * - Configuration cf2: m1 requires m2 + */ + @Test(expectedExceptions = { ResolutionException.class }) + public void testReadModuleWithSameNameAsSelf() { + ModuleDescriptor descriptor1_v1 = newBuilder("m1") + .build(); + + ModuleDescriptor descriptor2 = newBuilder("m2") + .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") + .build(); + + ModuleDescriptor descriptor1_v2 = newBuilder("m1") + .requires("m2") + .build(); + + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1_v1, descriptor2); + Configuration cf1 = resolve(finder1, "m2"); + assertTrue(cf1.modules().size() == 2); + + // resolve should throw ResolutionException + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1_v2); + resolve(cf1, finder2, "m1"); + } + + + /** + * Basic test to detect reading two modules with the same name + * + * The test consists of three configurations: + * - Configuration cf1: m1, m2 requires transitive m1 + * - Configuration cf2: m1, m3 requires transitive m1 + * - Configuration cf3(cf1,cf2): m4 requires m2, m3 + */ + @Test(expectedExceptions = { ResolutionException.class }) + public void testReadTwoModuleWithSameName() { + ModuleDescriptor descriptor1 = newBuilder("m1") + .build(); + + ModuleDescriptor descriptor2 = newBuilder("m2") + .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") + .build(); + + ModuleDescriptor descriptor3 = newBuilder("m3") + .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") + .build(); + + ModuleDescriptor descriptor4 = newBuilder("m4") + .requires("m2") + .requires("m3") + .build(); + + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2); + Configuration cf1 = resolve(finder1, "m2"); + assertTrue(cf1.modules().size() == 2); + + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor3); + Configuration cf2 = resolve(finder2, "m3"); + assertTrue(cf2.modules().size() == 2); + + // should throw ResolutionException as m4 will read modules named "m1". + ModuleFinder finder3 = ModuleUtils.finderOf(descriptor4); + Configuration.resolve(finder3, List.of(cf1, cf2), ModuleFinder.of(), Set.of("m4")); + } + + + /** * Test two modules exporting package p to a module that reads both. */ @Test(expectedExceptions = { ResolutionException.class }) @@ -1832,26 +1844,17 @@ public Object[][] createPlatformMatches() { return new Object[][]{ - { "linux-*-*", "*-*-*" }, - { "*-arm-*", "*-*-*" }, - { "*-*-2.6", "*-*-*" }, - - { "linux-arm-*", "*-*-*" }, - { "linux-*-2.6", "*-*-*" }, - { "*-arm-2.6", "*-*-*" }, - - { "linux-arm-2.6", "*-*-*" }, - - { "linux-*-*", "linux-*-*" }, - { "*-arm-*", "*-arm-*" }, - { "*-*-2.6", "*-*-2.6" }, - - { "linux-arm-*", "linux-arm-*" }, - { "linux-arm-*", "linux-*-*" }, - { "linux-*-2.6", "linux-*-2.6" }, - { "linux-*-2.6", "linux-arm-*" }, + { "linux-arm", "*-*" }, + { "linux-*", "*-*" }, + { "*-arm", "*-*" }, - { "linux-arm-2.6", "linux-arm-2.6" }, + { "linux-*", "linux-*" }, + { "linux-arm", "linux-*" }, + + { "*-arm", "*-arm" }, + { "linux-arm", "*-arm" }, + + { "linux-arm", "linux-arm" }, }; @@ -1861,9 +1864,10 @@ public Object[][] createBad() { return new Object[][] { - { "linux-*-*", "solaris-*-*" }, - { "linux-x86-*", "linux-arm-*" }, - { "linux-*-2.4", "linux-x86-2.6" }, + { "linux-*", "solaris-*" }, + { "*-arm", "*-sparc" }, + { "linux-x86", "solaris-sparc" }, + }; } @@ -1871,21 +1875,25 @@ * Test creating a configuration containing platform specific modules. */ @Test(dataProvider = "platformmatch") - public void testPlatformMatch(String s1, String s2) { + public void testPlatformMatch(String s1, String s2) throws IOException { - Builder builder = newBuilder("m1").requires("m2"); - addPlatformConstraints(builder, s1); - ModuleDescriptor descriptor1 = builder.build(); - - builder = newBuilder("m2"); - addPlatformConstraints(builder, s2); - ModuleDescriptor descriptor2 = builder.build(); + ModuleDescriptor base = ModuleDescriptor.newModule("java.base").build(); + Path system = writeModule(base, "*-*"); - ModuleFinder finder = ModuleUtils.finderOf(descriptor1, descriptor2); + ModuleDescriptor descriptor1 = ModuleDescriptor.newModule("m1") + .requires("m2") + .build(); + Path dir1 = writeModule(descriptor1, s1); + + ModuleDescriptor descriptor2 = ModuleDescriptor.newModule("m2").build(); + Path dir2 = writeModule(descriptor2, s2); + + ModuleFinder finder = ModuleFinder.of(system, dir1, dir2); Configuration cf = resolve(finder, "m1"); - assertTrue(cf.modules().size() == 2); + assertTrue(cf.modules().size() == 3); + assertTrue(cf.findModule("java.base").isPresent()); assertTrue(cf.findModule("m1").isPresent()); assertTrue(cf.findModule("m2").isPresent()); } @@ -1896,11 +1904,10 @@ */ @Test(dataProvider = "platformmismatch", expectedExceptions = FindException.class ) - public void testPlatformMisMatch(String s1, String s2) { + public void testPlatformMisMatch(String s1, String s2) throws IOException { testPlatformMatch(s1, s2); } - // no parents @Test(expectedExceptions = { IllegalArgumentException.class }) @@ -1917,21 +1924,23 @@ // parents with modules for specific platforms - @Test(dataProvider = "platformmatch") - public void testResolveRequiresWithCompatibleParents(String s1, String s2) { - Builder builder = newBuilder("m1"); - addPlatformConstraints(builder, s1); - ModuleDescriptor descriptor1 = builder.build(); - - builder = newBuilder("m2"); - addPlatformConstraints(builder, s2); - ModuleDescriptor descriptor2 = builder.build(); + public void testResolveRequiresWithCompatibleParents(String s1, String s2) + throws IOException + { + ModuleDescriptor base = ModuleDescriptor.newModule("java.base").build(); + Path system = writeModule(base, "*-*"); - ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); + ModuleDescriptor descriptor1 = ModuleDescriptor.newModule("m1").build(); + Path dir1 = writeModule(descriptor1, s1); + + ModuleDescriptor descriptor2 = ModuleDescriptor.newModule("m2").build(); + Path dir2 = writeModule(descriptor2, s2); + + ModuleFinder finder1 = ModuleFinder.of(system, dir1); Configuration cf1 = resolve(finder1, "m1"); - ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2); + ModuleFinder finder2 = ModuleFinder.of(system, dir2); Configuration cf2 = resolve(finder2, "m2"); Configuration cf3 = Configuration.resolve(ModuleFinder.of(), @@ -1941,32 +1950,16 @@ assertTrue(cf3.parents().size() == 2); } + @Test(dataProvider = "platformmismatch", expectedExceptions = IllegalArgumentException.class ) - public void testResolveRequiresWithConflictingParents(String s1, String s2) { - Builder builder = newBuilder("m1"); - addPlatformConstraints(builder, s1); - ModuleDescriptor descriptor1 = builder.build(); - - builder = newBuilder("m2"); - addPlatformConstraints(builder, s2); - ModuleDescriptor descriptor2 = builder.build(); - - ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); - Configuration cf1 = resolve(finder1, "m1"); - - ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2); - Configuration cf2 = resolve(finder2, "m2"); - - // should throw IAE - Configuration.resolve(ModuleFinder.of(), - List.of(cf1, cf2), - ModuleFinder.of(), - Set.of()); + public void testResolveRequiresWithConflictingParents(String s1, String s2) + throws IOException + { + testResolveRequiresWithCompatibleParents(s1, s2); } - // null handling // finder1, finder2, roots @@ -2121,30 +2114,23 @@ /** - * Returns {@code true} if the configuration contains module mn1 - * that reads module mn2. - */ - static boolean reads(Configuration cf, String mn1, String mn2) { - Optional om1 = cf.findModule(mn1); - if (!om1.isPresent()) - return false; - - return om1.get().reads().stream() - .map(ResolvedModule::name) - .anyMatch(mn2::equals); - } - - /** * Decodes the platform string and calls the builder osName/osArch/osVersion * methods to set the platform constraints. */ - static void addPlatformConstraints(Builder builder, String platformString) { + static Path writeModule(ModuleDescriptor descriptor, String platformString) + throws IOException + { String[] s = platformString.split("-"); - if (!s[0].equals("*")) - builder.osName(s[0]); - if (!s[1].equals("*")) - builder.osArch(s[1]); - if (!s[2].equals("*")) - builder.osVersion(s[2]); + String osName = !s[0].equals("*") ? s[0] : null; + String osArch = !s[1].equals("*") ? s[1] : null; + ModuleTarget target = new ModuleTarget(osName, osArch); + + String name = descriptor.name(); + Path dir = Files.createTempDirectory(name); + Path mi = dir.resolve("module-info.class"); + try (OutputStream out = Files.newOutputStream(mi)) { + ModuleInfoWriter.write(descriptor, target, out); + } + return dir; } } --- old/test/java/lang/module/ModuleDescriptorTest.java 2017-03-21 13:44:27.189079630 +0000 +++ new/test/java/lang/module/ModuleDescriptorTest.java 2017-03-21 13:44:26.990065975 +0000 @@ -50,11 +50,13 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static java.lang.module.ModuleDescriptor.Requires.Modifier.*; +import jdk.internal.misc.JavaLangModuleAccess; import jdk.internal.misc.SharedSecrets; import jdk.internal.module.ModuleInfoWriter; import org.testng.annotations.DataProvider; @@ -988,6 +990,107 @@ } + @DataProvider(name = "unparseableVersions") + public Object[][] unparseableVersions() { + return new Object[][]{ + + { null, "A1" }, // no version < unparseable + { "A1", "A2" }, // unparseable < unparseable + { "A1", "1.0" }, // unparseable < parseable + + }; + } + + /** + * Basic test for unparseable module versions + */ + @Test(dataProvider = "unparseableVersions") + public void testUnparseableModuleVersion(String vs1, String vs2) { + ModuleDescriptor descriptor1 = newModule("m", vs1); + ModuleDescriptor descriptor2 = newModule("m", vs2); + + if (vs1 != null && !isParsableVersion(vs1)) { + assertFalse(descriptor1.version().isPresent()); + assertTrue(descriptor1.rawVersion().isPresent()); + assertEquals(descriptor1.rawVersion().get(), vs1); + } + + if (vs2 != null && !isParsableVersion(vs2)) { + assertFalse(descriptor2.version().isPresent()); + assertTrue(descriptor2.rawVersion().isPresent()); + assertEquals(descriptor2.rawVersion().get(), vs2); + } + + assertFalse(descriptor1.equals(descriptor2)); + assertFalse(descriptor2.equals(descriptor1)); + assertTrue(descriptor1.compareTo(descriptor2) == -1); + assertTrue(descriptor2.compareTo(descriptor1) == 1); + } + + /** + * Basic test for requiring a module with an unparseable version recorded + * at compile version. + */ + @Test(dataProvider = "unparseableVersions") + public void testUnparseableCompiledVersion(String vs1, String vs2) { + Requires r1 = newRequires("m", vs1); + Requires r2 = newRequires("m", vs2); + + if (vs1 != null && !isParsableVersion(vs1)) { + assertFalse(r1.compiledVersion().isPresent()); + assertTrue(r1.rawCompiledVersion().isPresent()); + assertEquals(r1.rawCompiledVersion().get(), vs1); + } + + if (vs2 != null && !isParsableVersion(vs2)) { + assertFalse(r2.compiledVersion().isPresent()); + assertTrue(r2.rawCompiledVersion().isPresent()); + assertEquals(r2.rawCompiledVersion().get(), vs2); + } + + assertFalse(r1.equals(r2)); + assertFalse(r2.equals(r1)); + assertTrue(r1.compareTo(r2) == -1); + assertTrue(r2.compareTo(r1) == 1); + } + + private ModuleDescriptor newModule(String name, String vs) { + JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); + Builder builder = JLMA.newModuleBuilder(name, false, Set.of()); + if (vs != null) + builder.version(vs); + builder.requires("java.base"); + ByteBuffer bb = ModuleInfoWriter.toByteBuffer(builder.build()); + return ModuleDescriptor.read(bb); + } + + private Requires newRequires(String name, String vs) { + JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); + Builder builder = JLMA.newModuleBuilder("foo", false, Set.of()); + if (vs == null) { + builder.requires(name); + } else { + JLMA.requires(builder, Set.of(), name, vs); + } + Set requires = builder.build().requires(); + Iterator iterator = requires.iterator(); + ModuleDescriptor.Requires r = iterator.next(); + if (r.name().equals("java.base")) { + r = iterator.next(); + } + return r; + } + + private boolean isParsableVersion(String vs) { + try { + Version.parse(vs); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + // toNameAndVersion public void testToNameAndVersion() { @@ -1170,59 +1273,6 @@ } - // osName - - public void testOsName() { - String osName = ModuleDescriptor.newModule("foo").osName("Linux").build().osName().get(); - assertEquals(osName, "Linux"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testNullOsName() { - ModuleDescriptor.newModule("foo").osName(null); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testEmptyOsName() { - ModuleDescriptor.newModule("foo").osName(""); - } - - - // osArch - - public void testOsArch() { - String osArch = ModuleDescriptor.newModule("foo").osName("arm").build().osName().get(); - assertEquals(osArch, "arm"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testNullOsArch() { - ModuleDescriptor.newModule("foo").osArch(null); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testEmptyOsArch() { - ModuleDescriptor.newModule("foo").osArch(""); - } - - - // osVersion - - public void testOsVersion() { - String osVersion = ModuleDescriptor.newModule("foo").osName("11.2").build().osName().get(); - assertEquals(osVersion, "11.2"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testNullOsVersion() { - ModuleDescriptor.newModule("foo").osVersion(null); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testEmptyOsVersion() { - ModuleDescriptor.newModule("foo").osVersion(""); - } - // reads private static InputStream EMPTY_INPUT_STREAM = new InputStream() { @@ -1239,7 +1289,9 @@ } }; - // basic test reading module-info.class + /** + * Basic test reading module-info.class + */ public void testRead() throws Exception { Module base = Object.class.getModule(); @@ -1256,6 +1308,7 @@ assertEquals(descriptor.name(), "java.base"); } } + /** * Test ModuleDescriptor with a packager finder */ --- old/test/java/lang/module/ModuleFinderTest.java 2017-03-21 13:44:27.723116274 +0000 +++ new/test/java/lang/module/ModuleFinderTest.java 2017-03-21 13:44:27.522102481 +0000 @@ -373,7 +373,7 @@ /** - * Test ModuleModule with a JAR file containing a .class file in the top + * Test ModuleFinder with a JAR file containing a .class file in the top * level directory. */ public void testOfOneJarFileWithTopLevelClass() throws Exception { @@ -386,6 +386,7 @@ assertTrue(false); } catch (FindException e) { assertTrue(e.getCause() instanceof InvalidModuleDescriptorException); + assertTrue(e.getCause().getMessage().contains("Mojo.class")); } finder = ModuleFinder.of(jar); @@ -394,11 +395,12 @@ assertTrue(false); } catch (FindException e) { assertTrue(e.getCause() instanceof InvalidModuleDescriptorException); + assertTrue(e.getCause().getMessage().contains("Mojo.class")); } } /** - * Test ModuleModule with a JAR file containing a .class file in the top + * Test ModuleFinder with a JAR file containing a .class file in the top * level directory. */ public void testOfOneExplodedModuleWithTopLevelClass() throws Exception { @@ -411,6 +413,7 @@ assertTrue(false); } catch (FindException e) { assertTrue(e.getCause() instanceof InvalidModuleDescriptorException); + assertTrue(e.getCause().getMessage().contains("Mojo.class")); } finder = ModuleFinder.of(m_dir); @@ -419,6 +422,7 @@ assertTrue(false); } catch (FindException e) { assertTrue(e.getCause() instanceof InvalidModuleDescriptorException); + assertTrue(e.getCause().getMessage().contains("Mojo.class")); } } --- old/test/java/lang/module/ModuleReader/ModuleReaderTest.java 2017-03-21 13:44:28.230151065 +0000 +++ new/test/java/lang/module/ModuleReader/ModuleReaderTest.java 2017-03-21 13:44:28.034137615 +0000 @@ -79,17 +79,21 @@ "java/lang/Object.class" }; + // (directory) resources that may be in the base module + private static final String[] MAYBE_BASE_RESOURCES = { + "java", + "java/", + "java/lang", + "java/lang/", + }; + // resource names that should not be found in the base module - private static final String[] BAD_BASE_RESOURCES = { + private static final String[] NOT_BASE_RESOURCES = { "NotFound", - "java", "/java", "//java", - "java/", - "java/lang", "/java/lang", "//java/lang", - "java/lang/", "java//lang", "/java/lang/Object.class", "//java/lang/Object.class", @@ -109,13 +113,17 @@ "p/Main.class" }; + // (directory) resources that may be in the test module + private static final String[] MAYBE_TEST_RESOURCES = { + "p", + "p/" + }; + // resource names that should not be found in the test module - private static final String[] BAD_TEST_RESOURCES = { + private static final String[] NOT_TEST_RESOURCES = { "NotFound", - "p", "/p", "//p", - "p/", "/p/Main.class", "//p/Main.class", "p/Main.class/", @@ -160,11 +168,19 @@ testOpen(reader, name, expectedBytes); testRead(reader, name, expectedBytes); testList(reader, name); + } + // test resources that may be in the base module + for (String name : MAYBE_BASE_RESOURCES) { + Optional ouri = reader.find(name); + ouri.ifPresent(uri -> { + if (name.endsWith("/")) + assertTrue(uri.toString().endsWith("/")); + }); } - // test "not found" - for (String name : BAD_BASE_RESOURCES) { + // test "not found" in java.base module + for (String name : NOT_BASE_RESOURCES) { assertFalse(reader.find(name).isPresent()); assertFalse(reader.open(name).isPresent()); assertFalse(reader.read(name).isPresent()); @@ -261,7 +277,7 @@ try (reader) { - // test each of the known resources in the module + // test resources in test module for (String name : TEST_RESOURCES) { byte[] expectedBytes = Files.readAllBytes(MODS_DIR @@ -274,8 +290,18 @@ testList(reader, name); } - // test "not found" - for (String name : BAD_TEST_RESOURCES) { + // test resources that may be in the test module + for (String name : MAYBE_TEST_RESOURCES) { + System.out.println(name); + Optional ouri = reader.find(name); + ouri.ifPresent(uri -> { + if (name.endsWith("/")) + assertTrue(uri.toString().endsWith("/")); + }); + } + + // test "not found" in test module + for (String name : NOT_TEST_RESOURCES) { assertFalse(reader.find(name).isPresent()); assertFalse(reader.open(name).isPresent()); assertFalse(reader.read(name).isPresent()); @@ -394,9 +420,6 @@ for (String e : names) { assertTrue(reader.find(e).isPresent()); } - - // should not contain directories - names.forEach(e -> assertFalse(e.endsWith("/"))); } } --- old/test/java/lang/reflect/Layer/BasicLayerTest.java 2017-03-21 13:44:28.740186062 +0000 +++ new/test/java/lang/reflect/Layer/BasicLayerTest.java 2017-03-21 13:44:28.541172406 +0000 @@ -45,6 +45,8 @@ import java.util.stream.Collectors; import jdk.internal.misc.SharedSecrets; + +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -670,6 +672,258 @@ /** + * Test layers with a qualified export. The module exporting the package + * does not read the target module. + * + * m1 { exports p to m2 } + * m2 { } + */ + public void testQualifiedExports1() { + ModuleDescriptor descriptor1 = newBuilder("m1"). + exports("p", Set.of("m2")) + .build(); + + ModuleDescriptor descriptor2 = newBuilder("m2") + .build(); + + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2); + + Configuration cf = resolve(finder1, "m1", "m2"); + + ClassLoader cl = new ClassLoader() { }; + Layer layer = Layer.empty().defineModules(cf, mn -> cl); + assertTrue(layer.modules().size() == 2); + + Module m1 = layer.findModule("m1").get(); + Module m2 = layer.findModule("m2").get(); + + // check m1 exports p to m2 + assertFalse(m1.isExported("p")); + assertTrue(m1.isExported("p", m2)); + assertFalse(m1.isOpen("p", m2)); + } + + + /** + * Test layers with a qualified export. The module exporting the package + * reads the target module. + * + * m1 { exports p to m2; } + * m2 { requires m1; } + */ + public void testQualifiedExports2() { + ModuleDescriptor descriptor1 = newBuilder("m1") + .exports("p", Set.of("m2")) + .build(); + + ModuleDescriptor descriptor2 = newBuilder("m2") + .requires("m1") + .build(); + + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2); + + Configuration cf = resolve(finder1, "m2"); + ClassLoader cl = new ClassLoader() { }; + Layer layer = Layer.empty().defineModules(cf, mn -> cl); + assertTrue(layer.modules().size() == 2); + + Module m1 = layer.findModule("m1").get(); + Module m2 = layer.findModule("m2").get(); + + // check m1 exports p to m2 + assertFalse(m1.isExported("p")); + assertTrue(m1.isExported("p", m2)); + assertFalse(m1.isOpen("p", m2)); + } + + + /** + * Test layers with a qualified export. The module exporting the package + * does not read the target module in the parent layer. + * + * - Configuration/layer1: m1 { } + * - Configuration/layer2: m2 { exports p to m1; } + */ + public void testQualifiedExports3() { + // create layer1 with m1 + ModuleDescriptor descriptor1 = newBuilder("m1").build(); + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); + Configuration cf1 = resolve(finder1, "m1"); + ClassLoader cl1 = new ClassLoader() { }; + Layer layer1 = Layer.empty().defineModules(cf1, mn -> cl1); + assertTrue(layer1.modules().size() == 1); + + // create layer2 with m2 + ModuleDescriptor descriptor2 = newBuilder("m2") + .exports("p", Set.of("m1")) + .build(); + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2); + Configuration cf2 = resolve(cf1, finder2, "m2"); + ClassLoader cl2 = new ClassLoader() { }; + Layer layer2 = layer1.defineModules(cf2, mn -> cl2); + assertTrue(layer2.modules().size() == 1); + + Module m1 = layer1.findModule("m1").get(); + Module m2 = layer2.findModule("m2").get(); + + // check m2 exports p to layer1/m1 + assertFalse(m2.isExported("p")); + assertTrue(m2.isExported("p", m1)); + assertFalse(m2.isOpen("p", m1)); + } + + + /** + * Test layers with a qualified export. The module exporting the package + * reads the target module in the parent layer. + * + * - Configuration/layer1: m1 { } + * - Configuration/layer2: m2 { requires m1; exports p to m1; } + */ + public void testQualifiedExports4() { + // create layer1 with m1 + ModuleDescriptor descriptor1 = newBuilder("m1").build(); + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); + Configuration cf1 = resolve(finder1, "m1"); + ClassLoader cl1 = new ClassLoader() { }; + Layer layer1 = Layer.empty().defineModules(cf1, mn -> cl1); + assertTrue(layer1.modules().size() == 1); + + // create layer2 with m2 + ModuleDescriptor descriptor2 = newBuilder("m2") + .requires("m1") + .exports("p", Set.of("m1")) + .build(); + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2); + Configuration cf2 = resolve(cf1, finder2, "m2"); + ClassLoader cl2 = new ClassLoader() { }; + Layer layer2 = layer1.defineModules(cf2, mn -> cl2); + assertTrue(layer2.modules().size() == 1); + + Module m1 = layer1.findModule("m1").get(); + Module m2 = layer2.findModule("m2").get(); + + // check m2 exports p to layer1/m1 + assertFalse(m2.isExported("p")); + assertTrue(m2.isExported("p", m1)); + assertFalse(m2.isOpen("p", m1)); + } + + /** + * Test layers with a qualified export. The module exporting the package + * does not read the target module. + * + * - Configuration/layer1: m1 + * - Configuration/layer2: m1, m2 { exports p to m1; } + */ + public void testQualifiedExports5() { + // create layer1 with m1 + ModuleDescriptor descriptor1 = newBuilder("m1").build(); + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); + Configuration cf1 = resolve(finder1, "m1"); + ClassLoader cl1 = new ClassLoader() { }; + Layer layer1 = Layer.empty().defineModules(cf1, mn -> cl1); + assertTrue(layer1.modules().size() == 1); + + // create layer2 with m1 and m2 + ModuleDescriptor descriptor2 = newBuilder("m2").exports("p", Set.of("m1")).build(); + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor2); + Configuration cf2 = resolve(cf1, finder2, "m1", "m2"); + ClassLoader cl2 = new ClassLoader() { }; + Layer layer2 = layer1.defineModules(cf2, mn -> cl2); + assertTrue(layer2.modules().size() == 2); + + Module m1_v1 = layer1.findModule("m1").get(); + Module m1_v2 = layer2.findModule("m1").get(); + Module m2 = layer2.findModule("m2").get(); + + // check m2 exports p to layer2/m2 + assertFalse(m2.isExported("p")); + assertTrue(m2.isExported("p", m1_v2)); + assertFalse(m2.isExported("p", m1_v1)); + } + + + /** + * Test layers with a qualified export. The module exporting the package + * reads the target module in the parent layer (due to requires transitive). + * + * - Configuration/layer1: m1, m2 { requires transitive m1; } + * - Configuration/layer2: m1, m3 { requires m2; exports p to m1; } + */ + public void testQualifiedExports6() { + // create layer1 with m1 and m2 + ModuleDescriptor descriptor1 = newBuilder("m1").build(); + ModuleDescriptor descriptor2 = newBuilder("m2") + .requires(Set.of(Requires.Modifier.TRANSITIVE), "m1") + .build(); + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2); + Configuration cf1 = resolve(finder1, "m2"); + ClassLoader loader1 = new ClassLoader() { }; + Layer layer1 = Layer.empty().defineModules(cf1, mn -> loader1); + assertTrue(layer1.modules().size() == 2); + + // create layer2 with m1 and m3 + ModuleDescriptor descriptor3 = newBuilder("m3") + .requires("m2") + .exports("p", Set.of("m1")) + .build(); + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor1, descriptor3); + Configuration cf2 = resolve(cf1, finder2, "m1", "m3"); + ClassLoader loader2 = new ClassLoader() { }; + Layer layer2 = layer1.defineModules(cf2, mn -> loader2); + assertTrue(layer2.modules().size() == 2); + + Module m1_v1 = layer1.findModule("m1").get(); + Module m2 = layer1.findModule("m2").get(); + + Module m1_v2 = layer2.findModule("m1").get(); + Module m3 = layer2.findModule("m3").get(); + + assertTrue(m3.canRead(m1_v1)); + assertFalse(m3.canRead(m1_v2)); + + assertFalse(m3.isExported("p")); + assertTrue(m3.isExported("p", m1_v1)); + assertFalse(m3.isExported("p", m1_v2)); + assertFalse(m3.isExported("p", m2)); + } + + + /** + * Test layers with a qualified export. The target module is not in any layer. + * + * - Configuration/layer1: m1 { } + * - Configuration/layer2: m2 { exports p to m3; } + */ + public void testQualifiedExports7() { + // create layer1 with m1 + ModuleDescriptor descriptor1 = newBuilder("m1").build(); + ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1); + Configuration cf1 = resolve(finder1, "m1"); + ClassLoader cl1 = new ClassLoader() { }; + Layer layer1 = Layer.empty().defineModules(cf1, mn -> cl1); + assertTrue(layer1.modules().size() == 1); + + // create layer2 with m2 + ModuleDescriptor descriptor2 = newBuilder("m2") + .exports("p", Set.of("m3")) + .build(); + ModuleFinder finder2 = ModuleUtils.finderOf(descriptor2); + Configuration cf2 = resolve(cf1, finder2, "m2"); + ClassLoader cl2 = new ClassLoader() { }; + Layer layer2 = layer1.defineModules(cf2, mn -> cl2); + assertTrue(layer2.modules().size() == 1); + + Module m1 = layer1.findModule("m1").get(); + Module m2 = layer2.findModule("m2").get(); + + // check m2 does not export p to anyone + assertFalse(m2.isExported("p")); + assertFalse(m2.isExported("p", m1)); + } + + /** * Attempt to use Layer defineModules to create a layer with a module * defined to a class loader that already has a module of the same name * defined to the class loader. @@ -796,29 +1050,31 @@ } + @DataProvider(name = "javaPackages") + public Object[][] javaPackages() { + return new Object[][] { { "m1", "java" }, { "m2", "java.x" } }; + } + /** - * Attempt to create a Layer with a module containing a "java." package. + * Attempt to create a Layer with a module containing a "java" package. * This should only be allowed when the module is defined to the platform * class loader. */ - @Test(enabled = false) - public void testLayerWithJavaPackage() { - ModuleDescriptor descriptor = newBuilder("foo") - .packages(Set.of("java.foo")) - .build(); - + @Test(dataProvider = "javaPackages") + public void testLayerWithJavaPackage(String mn, String pn) { + ModuleDescriptor descriptor = newBuilder(mn).packages(Set.of(pn)).build(); ModuleFinder finder = ModuleUtils.finderOf(descriptor); Configuration cf = Layer.boot() .configuration() - .resolve(finder, ModuleFinder.of(), Set.of("foo")); + .resolve(finder, ModuleFinder.of(), Set.of(mn)); assertTrue(cf.modules().size() == 1); ClassLoader pcl = ClassLoader.getPlatformClassLoader(); ClassLoader scl = ClassLoader.getSystemClassLoader(); try { - Layer.boot().defineModules(cf, mn -> new ClassLoader() { }); + Layer.boot().defineModules(cf, _mn -> new ClassLoader() { }); assertTrue(false); } catch (LayerInstantiationException e) { } @@ -833,13 +1089,13 @@ } catch (LayerInstantiationException e) { } // create layer with module defined to platform class loader - Layer layer = Layer.boot().defineModules(cf, mn -> pcl); - Optional om = layer.findModule("foo"); + Layer layer = Layer.boot().defineModules(cf, _mn -> pcl); + Optional om = layer.findModule(mn); assertTrue(om.isPresent()); Module foo = om.get(); assertTrue(foo.getClassLoader() == pcl); assertTrue(foo.getPackages().length == 1); - assertTrue(foo.getPackages()[0].equals("java.foo")); + assertTrue(foo.getPackages()[0].equals(pn)); } --- old/test/java/lang/reflect/Module/allow.policy 2017-03-21 13:44:29.272222568 +0000 +++ new/test/java/lang/reflect/Module/allow.policy 2017-03-21 13:44:29.074208981 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,5 +23,6 @@ grant { permission java.lang.RuntimePermission "getClassLoader"; - permission java.io.FilePermission "${java.home}/-", "read"; + permission java.lang.RuntimePermission "accessSystemModules"; + permission java.io.FilePermission "${java.home}/modules/-", "read"; // exploded build }; --- old/test/java/util/ServiceLoader/basic/basic.sh 2017-03-21 13:44:29.773256948 +0000 +++ new/test/java/util/ServiceLoader/basic/basic.sh 2017-03-21 13:44:29.575243360 +0000 @@ -76,6 +76,7 @@ (cd $JARD; "$JAR" ${TESTTOOLVMOPTS} -cf ../p$n.jar *) done + cp p2.jar p2dup.jar mv p3.jar $EXTD cp $TESTCLASSES/Load.class $TESTD @@ -117,6 +118,8 @@ go ".${SEP}p2.jar" "" FooProvider2 +go ".${SEP}p2.jar${SEP}p2dup.jar" "" FooProvider2 + go "${P3JAR}${SEP}p2.jar" "" FooProvider3 FooProvider2 go "$TESTD${SEP}p2.jar" "" FooProvider1 FooProvider2 --- old/test/jdk/internal/jrtfs/java.policy 2017-03-21 13:44:30.274291327 +0000 +++ new/test/jdk/internal/jrtfs/java.policy 2017-03-21 13:44:30.075277671 +0000 @@ -1,3 +1,3 @@ grant { - permission java.io.FilePermission "${java.home}/-", "read"; + permission java.lang.RuntimePermission "accessSystemModules"; }; --- old/test/sun/net/www/protocol/jrt/java.policy 2017-03-21 13:44:30.774325637 +0000 +++ new/test/sun/net/www/protocol/jrt/java.policy 2017-03-21 13:44:30.576312050 +0000 @@ -1,3 +1,3 @@ grant { - permission java.io.FilePermission "${java.home}/-", "read"; + permission java.lang.RuntimePermission "accessSystemModules"; }; --- old/test/sun/security/krb5/auto/HttpNegotiateServer.java 2017-03-21 13:44:31.274359948 +0000 +++ new/test/sun/security/krb5/auto/HttpNegotiateServer.java 2017-03-21 13:44:31.075346292 +0000 @@ -28,6 +28,7 @@ * java.security.jgss/sun.security.krb5.internal:+open * java.security.jgss/sun.security.jgss * java.security.jgss/sun.security.krb5:+open + * java.security.jgss/sun.security.krb5.internal.ccache * java.security.jgss/sun.security.krb5.internal.crypto * java.security.jgss/sun.security.krb5.internal.ktab * jdk.security.auth --- old/test/tools/jlink/IntegrationTest.java 2017-03-21 13:44:31.786395082 +0000 +++ new/test/tools/jlink/IntegrationTest.java 2017-03-21 13:44:31.586381358 +0000 @@ -214,7 +214,8 @@ checkReleaseProperty(props, "JAVA_FULL_VERSION"); checkReleaseProperty(props, "OS_NAME"); checkReleaseProperty(props, "OS_ARCH"); - checkReleaseProperty(props, "OS_VERSION"); + // OS_VERSION is added from makefile. We're testing API-way to create image here! + // checkReleaseProperty(props, "OS_VERSION"); if (!Files.exists(output.resolve("toto.txt"))) { throw new AssertionError("Post processing not called"); --- old/test/tools/jlink/JLinkNegativeTest.java 2017-03-21 13:44:32.402437353 +0000 +++ new/test/tools/jlink/JLinkNegativeTest.java 2017-03-21 13:44:32.202423628 +0000 @@ -193,7 +193,7 @@ .output(imageFile) .addMods("not_zip") .modulePath(helper.defaultModulePath()) - .call().assertFailure("Error: java.io.IOException: Invalid jmod file"); + .call().assertFailure("Error: java.io.IOException: Invalid JMOD file"); } finally { deleteDirectory(jmod); } --- old/test/tools/jlink/plugins/SystemModuleDescriptors/CompiledVersionTest.java 2017-03-21 13:44:33.024480035 +0000 +++ new/test/tools/jlink/plugins/SystemModuleDescriptors/CompiledVersionTest.java 2017-03-21 13:44:32.825466379 +0000 @@ -81,9 +81,13 @@ Path msrc = SRC_DIR.resolve(mn); if (version.equals("0")) { assertTrue(CompilerUtils.compile(msrc, MODS_DIR, + "--add-exports", "java.base/jdk.internal.module=m1", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1", "--module-source-path", SRC_DIR.toString())); } else { assertTrue(CompilerUtils.compile(msrc, MODS_DIR, + "--add-exports", "java.base/jdk.internal.module=m1", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1", "--module-source-path", SRC_DIR.toString(), "--module-version", version)); } --- old/test/tools/jlink/plugins/SystemModuleDescriptors/SystemModulesTest.java 2017-03-21 13:44:33.524514345 +0000 +++ new/test/tools/jlink/plugins/SystemModuleDescriptors/SystemModulesTest.java 2017-03-21 13:44:33.326500758 +0000 @@ -25,6 +25,8 @@ import java.lang.module.ModuleDescriptor.*; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; +import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -40,7 +42,11 @@ /** * @test * @bug 8142968 8173381 + * @library /lib/testlibrary * @modules java.base/jdk.internal.misc + * @modules java.base/jdk.internal.module + * @modules java.base/jdk.internal.org.objectweb.asm + * @build ModuleTargetHelper * @run testng SystemModulesTest * @summary Verify the properties of ModuleDescriptor created * by SystemModules @@ -62,7 +68,6 @@ return; ModuleFinder.ofSystem().findAll().stream() - .map(ModuleReference::descriptor) .forEach(this::checkAttributes); } @@ -72,9 +77,7 @@ if (name.equals(OS_NAME)) return true; - if (OS_NAME.equals("Mac OS X")) { - return name.equals("Darwin"); - } else if (OS_NAME.startsWith("Windows")) { + if (OS_NAME.startsWith("Windows")) { return name.startsWith("Windows"); } else { System.err.println("ERROR: " + name + " but expected: " + OS_NAME); @@ -89,28 +92,28 @@ switch (OS_ARCH) { case "i386": case "x86": - return name.equals("i586"); + return name.equals("x86"); + case "amd64": + return name.equals("x86_64"); default: System.err.println("ERROR: " + name + " but expected: " + OS_ARCH); return false; } } - private void checkAttributes(ModuleDescriptor md) { - System.out.format("%s %s %s %s%n", md.name(), - md.osName(), md.osArch(), md.osVersion()); - - if (md.name().equals("java.base")) { - assertTrue(checkOSName(md.osName().get())); - assertTrue(checkOSArch(md.osArch().get())); - assertTrue(md.osVersion().isPresent()); - } else { - // target platform attribute is dropped by jlink plugin - assertFalse(md.osName().isPresent()); - assertFalse(md.osArch().isPresent()); - assertFalse(md.osVersion().isPresent()); - assertTrue(md.packages().size() > 0 - || EMPTY_MODULES.contains(md.name()), md.name()); + private void checkAttributes(ModuleReference modRef) { + try { + if (modRef.descriptor().name().equals("java.base")) { + ModuleTargetHelper.ModuleTarget mt = ModuleTargetHelper.read(modRef); + assertTrue(checkOSName(mt.osName())); + assertTrue(checkOSArch(mt.osArch())); + } else { + // target platform attribute is dropped by jlink plugin for other modules + ModuleTargetHelper.ModuleTarget mt = ModuleTargetHelper.read(modRef); + assertTrue(mt == null || (mt.osName() == null && mt.osArch() == null)); + } + } catch (IOException exp) { + throw new UncheckedIOException(exp); } } --- old/test/tools/jlink/plugins/SystemModuleDescriptors/UserModuleTest.java 2017-03-21 13:44:34.026548793 +0000 +++ new/test/tools/jlink/plugins/SystemModuleDescriptors/UserModuleTest.java 2017-03-21 13:44:33.829535275 +0000 @@ -38,7 +38,6 @@ import static jdk.testlibrary.ProcessTools.*; - import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import static org.testng.Assert.*; @@ -48,7 +47,9 @@ * @bug 8142968 8173381 8174740 * @library /lib/testlibrary * @modules jdk.compiler jdk.jlink - * @build UserModuleTest CompilerUtils jdk.testlibrary.FileUtils jdk.testlibrary.ProcessTools + * @modules java.base/jdk.internal.module + * @modules java.base/jdk.internal.org.objectweb.asm + * @build ModuleTargetHelper UserModuleTest CompilerUtils jdk.testlibrary.FileUtils jdk.testlibrary.ProcessTools * @run testng UserModuleTest */ @@ -85,7 +86,9 @@ for (String mn : modules) { Path msrc = SRC_DIR.resolve(mn); assertTrue(CompilerUtils.compile(msrc, MODS_DIR, - "--module-source-path", SRC_DIR.toString())); + "--module-source-path", SRC_DIR.toString(), + "--add-exports", "java.base/jdk.internal.module=" + mn, + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=" + mn)); } if (Files.exists(IMAGE)) { @@ -106,7 +109,10 @@ if (!hasJmods()) return; Path java = IMAGE.resolve("bin").resolve("java"); - assertTrue(executeProcess(java.toString(), "-m", MAIN_MID) + assertTrue(executeProcess(java.toString(), + "--add-exports", "java.base/jdk.internal.module=m1,m4", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1,m4", + "-m", MAIN_MID) .outputTo(System.out) .errorTo(System.out) .getExitValue() == 0); @@ -136,6 +142,8 @@ Path java = IMAGE.resolve("bin").resolve("java"); assertTrue(executeProcess(java.toString(), + "--add-exports", "java.base/jdk.internal.module=m1,m4", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1,m4", "-Djdk.system.module.finder.disabledFastPath", "-m", MAIN_MID) .outputTo(System.out) @@ -154,7 +162,10 @@ Path dir = Paths.get("dedupSetTest"); createImage(dir, "m1", "m2", "m3", "m4"); Path java = dir.resolve("bin").resolve("java"); - assertTrue(executeProcess(java.toString(), "-m", MAIN_MID) + assertTrue(executeProcess(java.toString(), + "--add-exports", "java.base/jdk.internal.module=m1,m4", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1,m4", + "-m", MAIN_MID) .outputTo(System.out) .errorTo(System.out) .getExitValue() == 0); @@ -205,11 +216,13 @@ } private void createJmods(String... modules) throws IOException { - // use the same target platform as in java.base - ModuleDescriptor md = Layer.boot().findModule("java.base").get() - .getDescriptor(); - String osName = md.osName().get(); - String osArch = md.osArch().get(); + ModuleTargetHelper.ModuleTarget mt = ModuleTargetHelper.getJavaBaseTarget(); + if (mt == null) { + throw new RuntimeException("ModuleTarget is missing for java.base"); + } + + String osName = mt.osName(); + String osArch = mt.osArch(); // create JMOD files Files.createDirectories(JMODS_DIR); @@ -246,6 +259,8 @@ // verify ModuleDescriptor Path java = dir.resolve("bin").resolve("java"); assertTrue(executeProcess(java.toString(), + "--add-exports", "java.base/jdk.internal.module=m1,m4", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1,m4", "--add-modules=m1", "-m", "m4") .outputTo(System.out) .errorTo(System.out) @@ -275,6 +290,8 @@ // verify ModuleDescriptor Path java = dir.resolve("bin").resolve("java"); assertTrue(executeProcess(java.toString(), + "--add-exports", "java.base/jdk.internal.module=m1,m4", + "--add-exports", "java.base/jdk.internal.org.objectweb.asm=m1,m4", "--add-modules=m1", "-m", "m4", "retainModuleTarget") .outputTo(System.out) .errorTo(System.out) --- old/test/tools/jlink/plugins/SystemModuleDescriptors/src/m1/p1/Main.java 2017-03-21 13:44:34.533583584 +0000 +++ new/test/tools/jlink/plugins/SystemModuleDescriptors/src/m1/p1/Main.java 2017-03-21 13:44:34.335569997 +0000 @@ -23,6 +23,7 @@ package p1; +import java.io.InputStream; import java.io.IOException; import java.lang.module.ModuleDescriptor; import java.lang.reflect.Layer; @@ -35,7 +36,38 @@ import java.util.Collections; import java.util.Set; +import jdk.internal.module.ClassFileAttributes; +import jdk.internal.module.ClassFileAttributes.ModuleTargetAttribute; +import jdk.internal.module.ClassFileConstants; +import jdk.internal.org.objectweb.asm.Attribute; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; + public class Main { + private static boolean hasModuleTarget(InputStream in) throws IOException { + ModuleTargetAttribute[] modTargets = new ModuleTargetAttribute[1]; + ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) { + @Override + public void visitAttribute(Attribute attr) { + if (attr instanceof ModuleTargetAttribute) { + modTargets[0] = (ModuleTargetAttribute)attr; + } + } + }; + + // prototype of attributes that should be parsed + Attribute[] attrs = new Attribute[] { + new ModuleTargetAttribute() + }; + + // parse module-info.class + ClassReader cr = new ClassReader(in); + cr.accept(cv, attrs, 0); + return modTargets[0] != null && + (modTargets[0].osName() != null || modTargets[0].osArch() != null); + } + public static void main(String... args) throws Exception { // load another package p2.T.test(); @@ -44,12 +76,13 @@ validate(Main.class.getModule()); // validate the Moduletarget attribute for java.base - ModuleDescriptor md = Layer.boot().findModule("java.base").get() - .getDescriptor(); - if (!md.osName().isPresent() || !md.osArch().isPresent() || - !md.osVersion().isPresent()) { - throw new RuntimeException("java.base: " + md.osName() + " " + - md.osArch() + " " + md.osVersion()); + FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), + Collections.emptyMap()); + Path path = fs.getPath("/", "modules", "java.base", "module-info.class"); + try (InputStream in = Files.newInputStream(path)) { + if (! hasModuleTarget(in)) { + throw new RuntimeException("Missing ModuleTarget for java.base"); + } } } @@ -67,9 +100,9 @@ checkPackages(md.packages(), "p1", "p2"); checkPackages(md1.packages(), "p1", "p2"); - // check ModuleTarget attribute - checkModuleTargetAttribute(md); - checkModuleTargetAttribute(md1); + try (InputStream in = Files.newInputStream(path)) { + checkModuleTargetAttribute(in, "p1"); + } } static void checkPackages(Set pkgs, String... expected) { @@ -78,10 +111,9 @@ } } - static void checkModuleTargetAttribute(ModuleDescriptor md) { - if (md.osName().isPresent() || md.osArch().isPresent() || - md.osVersion().isPresent()) { - throw new RuntimeException(md.osName() + " " + md.osArch() + " " + md.osVersion()); + static void checkModuleTargetAttribute(InputStream in, String modName) throws IOException { + if (hasModuleTarget(in)) { + throw new RuntimeException("ModuleTarget present for " + modName); } } } --- old/test/tools/jlink/plugins/SystemModuleDescriptors/src/m4/p4/Main.java 2017-03-21 13:44:35.033617894 +0000 +++ new/test/tools/jlink/plugins/SystemModuleDescriptors/src/m4/p4/Main.java 2017-03-21 13:44:34.836604376 +0000 @@ -36,7 +36,47 @@ import java.util.Collections; import java.util.Set; +import jdk.internal.module.ClassFileAttributes; +import jdk.internal.module.ClassFileAttributes.ModuleTargetAttribute; +import jdk.internal.module.ClassFileConstants; +import jdk.internal.org.objectweb.asm.Attribute; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; + public class Main { + private static boolean hasModuleTarget(InputStream in) throws IOException { + ModuleTargetAttribute[] modTargets = new ModuleTargetAttribute[1]; + ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) { + @Override + public void visitAttribute(Attribute attr) { + if (attr instanceof ModuleTargetAttribute) { + modTargets[0] = (ModuleTargetAttribute)attr; + } + } + }; + + // prototype of attributes that should be parsed + Attribute[] attrs = new Attribute[] { + new ModuleTargetAttribute() + }; + + // parse module-info.class + ClassReader cr = new ClassReader(in); + cr.accept(cv, attrs, 0); + return modTargets[0] != null && + (modTargets[0].osName() != null || modTargets[0].osArch() != null); + } + + private static boolean hasModuleTarget(String modName) throws IOException { + FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), + Collections.emptyMap()); + Path path = fs.getPath("/", "modules", modName, "module-info.class"); + try (InputStream in = Files.newInputStream(path)) { + return hasModuleTarget(in); + } + } + // the system module plugin by default drops ModuleTarget attribute private static boolean expectModuleTarget = false; public static void main(String... args) throws IOException { @@ -49,13 +89,8 @@ } // java.base is packaged with osName/osArch/osVersion - ModuleDescriptor md = Layer.boot().findModule("java.base").get() - .getDescriptor(); - if (!md.osName().isPresent() || - !md.osArch().isPresent() || - !md.osVersion().isPresent()) { - throw new RuntimeException("osName/osArch/osVersion is missing: " + - md.osName() + " " + md.osArch() + " " + md.osVersion()); + if (! hasModuleTarget("java.base")) { + throw new RuntimeException("ModuleTarget absent for java.base"); } // verify module-info.class for m1 and m4 @@ -82,7 +117,7 @@ checkModuleDescriptor(ModuleDescriptor.read(Files.newInputStream(path)), packages); } - static void checkModuleDescriptor(ModuleDescriptor md, String... packages) { + static void checkModuleDescriptor(ModuleDescriptor md, String... packages) throws IOException { String mainClass = md.name().replace('m', 'p') + ".Main"; if (!md.mainClass().get().equals(mainClass)) { throw new RuntimeException(md.mainClass().toString()); @@ -90,22 +125,16 @@ if (expectModuleTarget) { // ModuleTarget attribute is retained - if (!md.osName().isPresent() || !md.osArch().isPresent()) { - throw new RuntimeException("osName or osArch is missing: " + - md.osName() + " " + md.osArch()); + if (! hasModuleTarget(md.name())) { + throw new RuntimeException("ModuleTarget missing for " + md.name()); } } else { // by default ModuleTarget attribute is dropped - if (md.osName().isPresent() || md.osArch().isPresent()) { - throw new RuntimeException("osName and osArch should not be set: " + - md.osName() + " " + md.osArch()); + if (hasModuleTarget(md.name())) { + throw new RuntimeException("ModuleTarget present for " + md.name()); } } - if (md.osVersion().isPresent()) { - throw new RuntimeException("Expected no osVersion set: " + md.osVersion()); - } - Set pkgs = md.packages(); if (!pkgs.equals(Set.of(packages))) { throw new RuntimeException(pkgs + " expected: " + Set.of(packages)); --- old/test/tools/launcher/modules/addexports/AddExportsTestWarningError.java 2017-03-21 13:44:35.653660439 +0000 +++ new/test/tools/launcher/modules/addexports/AddExportsTestWarningError.java 2017-03-21 13:44:35.453646715 +0000 @@ -134,16 +134,16 @@ return new Object[][]{ // source not found - {"DoesNotExist/p=m1", "WARNING: Unknown module: DoesNotExist specified in --add-exports"}, + {"DoesNotExist/p=m1", "WARNING: Unknown module: DoesNotExist specified to --add-exports"}, {"m1/DoesNotExist=m2", "WARNING: package DoesNotExist not in m1"}, // target not found - {"m1/p1=DoesNotExist", "WARNING: Unknown module: DoesNotExist specified in --add-exports"}, + {"m1/p1=DoesNotExist", "WARNING: Unknown module: DoesNotExist specified to --add-exports"}, // bad names - {"m*/p1=m2", "WARNING: Unknown module: m* specified in --add-exports"}, + {"m*/p1=m2", "WARNING: Unknown module: m* specified to --add-exports"}, {"m1/p!=m2", "WARNING: package p! not in m1"}, - {"m1/p1=m!", "WARNING: Unknown module: m! specified in --add-exports"}, + {"m1/p1=m!", "WARNING: Unknown module: m! specified to --add-exports"}, }; } --- old/test/tools/launcher/modules/addreads/AddReadsTestWarningError.java 2017-03-21 13:44:36.392711150 +0000 +++ new/test/tools/launcher/modules/addreads/AddReadsTestWarningError.java 2017-03-21 13:44:36.190697288 +0000 @@ -168,14 +168,14 @@ return new Object[][]{ // source not found - {"DoesNotExist=m2", "WARNING: Unknown module: DoesNotExist specified in --add-reads"}, + {"DoesNotExist=m2", "WARNING: Unknown module: DoesNotExist specified to --add-reads"}, // target not found - {"m2=DoesNotExist", "WARNING: Unknown module: DoesNotExist specified in --add-reads"}, + {"m2=DoesNotExist", "WARNING: Unknown module: DoesNotExist specified to --add-reads"}, // bad names - {"m*=m2", "WARNING: Unknown module: m* specified in --add-reads"}, - {"m2=m!", "WARNING: Unknown module: m! specified in --add-reads"}, + {"m*=m2", "WARNING: Unknown module: m* specified to --add-reads"}, + {"m2=m!", "WARNING: Unknown module: m! specified to --add-reads"}, }; } --- old/test/tools/launcher/modules/patch/basic/PatchTestWarningError.java 2017-03-21 13:44:37.129761723 +0000 +++ new/test/tools/launcher/modules/patch/basic/PatchTestWarningError.java 2017-03-21 13:44:36.928747930 +0000 @@ -179,7 +179,7 @@ "-m", "test/jdk.test.Main", arg) .outputTo(System.out) .errorTo(System.out) - .shouldContain("WARNING: Unknown module: DoesNotExist specified in --patch-module") + .shouldContain("WARNING: Unknown module: DoesNotExist specified to --patch-module") .getExitValue(); assertTrue(exitValue == 0); --- old/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java 2017-03-21 13:44:37.784806670 +0000 +++ new/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java 2017-03-21 13:44:37.583792877 +0000 @@ -1544,7 +1544,6 @@ Element e = new Element(x.getCpString(attr.attribute_name_index)); e.add(x.getCpString(attr.os_name_index)); e.add(x.getCpString(attr.os_arch_index)); - e.add(x.getCpString(attr.os_version_index)); e.trimToSize(); p.add(e); return null; --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/IllegalAccessLogger.java 2017-03-21 13:44:38.122829864 +0000 @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Module; +import java.net.URL; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Supports logging of access to members of API packages that are exported or + * opened via backdoor mechanisms to code in unnamed modules. + */ + +public final class IllegalAccessLogger { + + // true to print stack trace + private static final boolean PRINT_STACK_TRACE; + static { + String s = System.getProperty("sun.reflect.debugModuleAccessChecks"); + PRINT_STACK_TRACE = "access".equals(s); + } + + private static final StackWalker STACK_WALKER + = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + + // the maximum number of frames to capture + private static final int MAX_STACK_FRAMES = 32; + + // lock to avoid interference when printing stack traces + private static final Object OUTPUT_LOCK = new Object(); + + // caller -> usages + private final Map, Set> callerToUsages = new WeakHashMap<>(); + + // module -> (package name -> CLI option) + private final Map> exported; + private final Map> opened; + + private IllegalAccessLogger(Map> exported, + Map> opened) { + this.exported = deepCopy(exported); + this.opened = deepCopy(opened); + } + + /** + * Returns that a Builder that is seeded with the packages known to this logger. + */ + public Builder toBuilder() { + return new Builder(exported, opened); + } + + /** + * Logs access to the member of a target class by a caller class if the class + * is in a package that is exported via a backdoor mechanism. + * + * The {@code whatSupplier} supplies the message that describes the member. + */ + public void logIfExportedByBackdoor(Class caller, + Class target, + Supplier whatSupplier) { + Map packages = exported.get(target.getModule()); + if (packages != null) { + String how = packages.get(target.getPackageName()); + if (how != null) { + log(caller, whatSupplier.get(), how); + } + } + } + + /** + * Logs access to the member of a target class by a caller class if the class + * is in a package that is opened via a backdoor mechanism. + * + * The {@code what} parameter supplies the message that describes the member. + */ + public void logIfOpenedByBackdoor(Class caller, + Class target, + Supplier whatSupplier) { + Map packages = opened.get(target.getModule()); + if (packages != null) { + String how = packages.get(target.getPackageName()); + if (how != null) { + log(caller, whatSupplier.get(), how); + } + } + } + + /** + * Logs access by a caller class. The {@code what} parameter describes + * the member is accessed, the {@code how} parameter is the means by which + * access is allocated (CLI option for example). + */ + private void log(Class caller, String what, String how) { + log(caller, what, () -> { + PrivilegedAction pa = caller::getProtectionDomain; + CodeSource cs = AccessController.doPrivileged(pa).getCodeSource(); + URL url = (cs != null) ? cs.getLocation() : null; + String source = caller.getName(); + if (url != null) + source += " (" + url + ")"; + return "WARNING: Illegal access by " + source + " to " + what + + " (permitted by " + how + ")"; + }); + } + + + /** + * Logs access to caller class if the class is in a package that is opened via + * a backdoor mechanism. + */ + public void logIfOpenedByBackdoor(MethodHandles.Lookup caller, Class target) { + Map packages = opened.get(target.getModule()); + if (packages != null) { + String how = packages.get(target.getPackageName()); + if (how != null) { + log(caller.lookupClass(), target.getName(), () -> + "WARNING: Illegal access using Lookup on " + caller.lookupClass() + + " to " + target + " (permitted by " + how + ")"); + } + } + } + + /** + * Log access by a caller. The {@code what} parameter describes the class or + * member that is being accessed. The {@code msgSupplier} supplies the log + * message. + * + * To reduce output, this method only logs the access if it hasn't been seen + * previously. "Seen previously" is implemented as a map of caller class -> Usage, + * where a Usage is the "what" and a hash of the stack trace. The map has weak + * keys so it can be expunged when the caller is GC'ed/unloaded. + */ + private void log(Class caller, String what, Supplier msgSupplier) { + // stack trace without the top-most frames in java.base + List stack = STACK_WALKER.walk(s -> + s.dropWhile(this::isJavaBase) + .limit(MAX_STACK_FRAMES) + .collect(Collectors.toList()) + ); + + // check if the access has already been recorded + Usage u = new Usage(what, hash(stack)); + boolean firstUsage; + synchronized (this) { + firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u); + } + + // log message if first usage + if (firstUsage) { + String msg = msgSupplier.get(); + if (PRINT_STACK_TRACE) { + synchronized (OUTPUT_LOCK) { + System.err.println(msg); + stack.forEach(f -> System.err.println("\tat " + f)); + } + } else { + System.err.println(msg); + } + } + } + + private static class Usage { + private final String what; + private final int stack; + Usage(String what, int stack) { + this.what = what; + this.stack = stack; + } + @Override + public int hashCode() { + return what.hashCode() ^ stack; + } + @Override + public boolean equals(Object ob) { + if (ob instanceof Usage) { + Usage that = (Usage)ob; + return what.equals(that.what) && stack == (that.stack); + } else { + return false; + } + } + } + + /** + * Returns true if the stack frame is for a class in java.base. + */ + private boolean isJavaBase(StackWalker.StackFrame frame) { + Module caller = frame.getDeclaringClass().getModule(); + return "java.base".equals(caller.getName()); + } + + /** + * Computes a hash code for the give stack frames. The hash code is based + * on the class, method name, and BCI. + */ + private int hash(List stack) { + int hash = 0; + for (StackWalker.StackFrame frame : stack) { + hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(), + frame.getMethodName(), + frame.getByteCodeIndex()); + } + return hash; + } + + // system-wide IllegalAccessLogger + private static volatile IllegalAccessLogger logger; + + /** + * Sets the system-wide IllegalAccessLogger + */ + public static void setIllegalAccessLogger(IllegalAccessLogger l) { + if (l.exported.isEmpty() && l.opened.isEmpty()) { + logger = null; + } else { + logger = l; + } + } + + /** + * Returns the system-wide IllegalAccessLogger or {@code null} if there is + * no logger. + */ + public static IllegalAccessLogger illegalAccessLogger() { + return logger; + } + + /** + * A builder for IllegalAccessLogger objects. + */ + public static class Builder { + private Map> exported; + private Map> opened; + + public Builder() { } + + public Builder(Map> exported, + Map> opened) { + this.exported = deepCopy(exported); + this.opened = deepCopy(opened); + } + + public void logAccessToExportedPackage(Module m, String pn, String how) { + if (!m.isExported(pn)) { + if (exported == null) + exported = new HashMap<>(); + exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); + } + } + + public void logAccessToOpenPackage(Module m, String pn, String how) { + // opens implies exported at run-time. + logAccessToExportedPackage(m, pn, how); + + if (!m.isOpen(pn)) { + if (opened == null) + opened = new HashMap<>(); + opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); + } + } + + /** + * Builds the logger. + */ + public IllegalAccessLogger build() { + return new IllegalAccessLogger(exported, opened); + } + } + + + static Map> deepCopy(Map> map) { + if (map == null || map.isEmpty()) { + return new HashMap<>(); + } else { + Map> newMap = new HashMap<>(); + for (Map.Entry> e : map.entrySet()) { + newMap.put(e.getKey(), new HashMap<>(e.getValue())); + } + return newMap; + } + } +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleTarget.java 2017-03-21 13:44:38.632864860 +0000 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +public final class ModuleTarget { + + private final String osName; + private final String osArch; + + public ModuleTarget(String osName, String osArch) { + this.osName = osName; + this.osArch = osArch; + } + + public String osName() { + return osName; + } + + public String osArch() { + return osArch; + } + +} --- old/src/java.base/share/classes/jdk/internal/loader/ResourceHelper.java 2017-03-21 13:44:39.403917767 +0000 +++ /dev/null 2017-03-09 18:14:19.024999985 +0000 @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package jdk.internal.loader; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; - -import jdk.internal.module.Checks; - -/** - * Helper class for Class#getResource, Module#getResourceAsStream, and other - * methods that locate a resource in a module. - */ -public final class ResourceHelper { - private ResourceHelper() { } - - /** - * Returns the package name for a resource or the empty package if - * the resource name does not contain a slash. - */ - public static String getPackageName(String name) { - int index = name.lastIndexOf('/'); - if (index != -1) { - return name.substring(0, index).replace("/", "."); - } else { - return ""; - } - } - - /** - * Returns true if the resource is a simple resource. Simple - * resources can never be encapsulated. Resources ending in "{@code .class}" - * or where the package name is not a legal package name can not be - * encapsulated. - */ - public static boolean isSimpleResource(String name) { - int len = name.length(); - if (len > 6 && name.endsWith(".class")) { - return true; - } - if (!Checks.isPackageName(getPackageName(name))) { - return true; - } - return false; - } - - /** - * Converts a resource name to a file path. Returns {@code null} if the - * resource name cannot be converted into a file path. Resource names - * with empty elements, or elements that are "." or ".." are rejected, - * as is a resource name that translates to a file path with a root - * component. - */ - public static Path toFilePath(String name) { - // scan the resource name to eagerly reject obviously invalid names - int next; - int off = 0; - while ((next = name.indexOf('/', off)) != -1) { - int len = next - off; - if (!mayTranslate(name, off, len)) { - return null; - } - off = next + 1; - } - int rem = name.length() - off; - if (!mayTranslate(name, off, rem)) { - return null; - } - - // convert to file path - Path path; - if (File.separatorChar == '/') { - path = Paths.get(name); - } else { - // not allowed to embed file separators - if (name.contains(File.separator)) - return null; - path = Paths.get(name.replace('/', File.separatorChar)); - } - - // file path not allowed to have root component - return (path.getRoot() == null) ? path : null; - } - - /** - * Returns {@code true} if the element in a resource name is a candidate - * to translate to the element of a file path. - */ - private static boolean mayTranslate(String name, int off, int len) { - if (len <= 2) { - if (len == 0) - return false; - boolean starsWithDot = (name.charAt(off) == '.'); - if (len == 1 && starsWithDot) - return false; - if (len == 2 && starsWithDot && (name.charAt(off+1) == '.')) - return false; - } - return true; - } - -} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/src/java.base/share/classes/jdk/internal/module/Resources.java 2017-03-21 13:44:39.134899308 +0000 @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * A helper class to support working with resources in modules. Also provides + * support for translating resource names to file paths. + */ +public final class Resources { + private Resources() { } + + /** + * Return true if a resource can be encapsulated. Resource with names + * ending in ".class" or "/" cannot be encapsulated. Resource names + * that map to a legal package name can be encapsulated. + */ + public static boolean canEncapsulate(String name) { + int len = name.length(); + if (len > 6 && name.endsWith(".class")) { + return false; + } else { + return Checks.isPackageName(toPackageName(name)); + } + } + + /** + * Derive a package name for a resource. The package name + * returned by this method may not be a legal package name. This method + * returns null if the the resource name ends with a "/" (a directory) + * or the resource name does not contain a "/". + */ + public static String toPackageName(String name) { + int index = name.lastIndexOf('/'); + if (index == -1 || index == name.length()-1) { + return ""; + } else { + return name.substring(0, index).replace("/", "."); + } + } + + /** + * Returns a resource name corresponding to the relative file path + * between {@code dir} and {@code file}. If the file is a directory + * then the name will end with a "/", except the top-level directory + * where the empty string is returned. + */ + public static String toResourceName(Path dir, Path file) { + String s = dir.relativize(file) + .toString() + .replace(File.separatorChar, '/'); + if (s.length() > 0 && Files.isDirectory(file)) + s += "/"; + return s; + } + + /** + * Returns a file path to a resource in a file tree. If the resource + * name has a trailing "/" then the file path will locate a directory. + * Returns {@code null} if the resource does not map to a file in the + * tree file. + */ + public static Path toFilePath(Path dir, String name) throws IOException { + boolean expectDirectory = name.endsWith("/"); + if (expectDirectory) { + name = name.substring(0, name.length() - 1); // drop trailing "/" + } + Path path = toSafeFilePath(name); + if (path != null) { + Path file = dir.resolve(path); + try { + BasicFileAttributes attrs; + attrs = Files.readAttributes(file, BasicFileAttributes.class); + if (attrs.isDirectory() + || (!attrs.isDirectory() && !expectDirectory)) + return file; + } catch (NoSuchFileException ignore) { } + } + return null; + } + + /** + * Map a resource name to a "safe" file path. Returns {@code null} if + * the resource name cannot be converted into a "safe" file path. + * + * Resource names with empty elements, or elements that are "." or ".." + * are rejected, as are resource names that translates to a file path + * with a root component. + */ + private static Path toSafeFilePath(String name) { + // scan elements of resource name + int next; + int off = 0; + while ((next = name.indexOf('/', off)) != -1) { + int len = next - off; + if (!mayTranslate(name, off, len)) { + return null; + } + off = next + 1; + } + int rem = name.length() - off; + if (!mayTranslate(name, off, rem)) { + return null; + } + + // convert to file path + Path path; + if (File.separatorChar == '/') { + path = Paths.get(name); + } else { + // not allowed to embed file separators + if (name.contains(File.separator)) + return null; + path = Paths.get(name.replace('/', File.separatorChar)); + } + + // file path not allowed to have root component + return (path.getRoot() == null) ? path : null; + } + + /** + * Returns {@code true} if the element in a resource name is a candidate + * to translate to the element of a file path. + */ + private static boolean mayTranslate(String name, int off, int len) { + if (len <= 2) { + if (len == 0) + return false; + boolean starsWithDot = (name.charAt(off) == '.'); + if (len == 1 && starsWithDot) + return false; + if (len == 2 && starsWithDot && (name.charAt(off+1) == '.')) + return false; + } + return true; + } + +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/ClassLoader/getResource/automaticmodules/Driver.java 2017-03-21 13:44:39.713939039 +0000 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /lib/testlibrary + * @build Driver Main JarUtils jdk.testlibrary.ProcessTools + * @run main Driver + * @summary Test ClassLoader.getResourceXXX to locate resources in an automatic + * module + */ + +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import jdk.testlibrary.ProcessTools; + +/** + * The driver creates a JAR file containing p/Foo.class, p/foo.properties, + * and p/resources/bar.properties. This ensures there are is a resource in + * a module package and a resource that is not in the module package. The + * test is then launched to locate every resource in the JAR file. + */ + +public class Driver { + + private static final String TEST_CLASSES = System.getProperty("test.classes"); + + public static void main(String[] args) throws Exception { + // create content for JAR file + Path dir = Files.createTempDirectory("classes"); + Path p = Files.createDirectory(dir.resolve("p")); + Files.createFile(p.resolve("Foo.class")); + Files.createFile(p.resolve("foo.properties")); + Path resources = Files.createDirectory(p.resolve("resources")); + Files.createFile(resources.resolve("bar.properties")); + + // create the JAR file, including a manifest + Path jarFile = Paths.get("library-1.0.jar"); + Manifest man = new Manifest(); + Attributes attrs = man.getMainAttributes(); + attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0"); + JarUtils.createJarFile(jarFile, man, dir, p); + + // get the module name + ModuleFinder finder = ModuleFinder.of(jarFile); + ModuleReference mref = finder.findAll().stream().findAny().orElse(null); + if (mref == null) + throw new RuntimeException("Module not found!!!"); + String name = mref.descriptor().name(); + + // launch the test with the JAR file on the module path + if (ProcessTools.executeTestJava("-p", jarFile.toString(), + "--add-modules", name, + "-cp", TEST_CLASSES, + "Main", name) + .outputTo(System.out) + .errorTo(System.out) + .getExitValue() != 0) + throw new RuntimeException("Test failed - see output"); + } + +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/ClassLoader/getResource/automaticmodules/Main.java 2017-03-21 13:44:40.214973418 +0000 @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.lang.reflect.Layer; +import java.net.URL; +import java.util.Enumeration; + +/** + * Usage: Main $MODULE + * + * Finds $MODULE in the boot layer and then tests that it can locate every + * resource in the module content (JAR file). + */ + +public class Main { + + static void testFind(String name) throws Exception { + // getResource + URL url = Main.class.getClassLoader().getResource(name); + if (url == null) + throw new RuntimeException("Unable to locate: " + name); + System.out.println(name + " => " + url); + + // getResources + Enumeration urls = Main.class.getClassLoader().getResources(name); + if (!urls.hasMoreElements()) + throw new RuntimeException("Unable to locate: " + name); + URL first = urls.nextElement(); + if (!first.toURI().equals(url.toURI())) + throw new RuntimeException("found " + first + " ???"); + + // getResourceAsStream + if (!url.toString().endsWith("/")) { + InputStream in = Main.class.getClassLoader().getResourceAsStream(name); + if (in == null) + throw new RuntimeException("Unable to locate: " + name); + in.close(); + } + } + + static void testFindUnchecked(String name) { + try { + testFind(name); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) throws Exception { + String mn = args[0]; + + ModuleReference mref = Layer.boot() + .configuration() + .findModule(mn) + .map(ResolvedModule::reference) + .orElseThrow(() -> new RuntimeException(mn + " not resolved!!")); + + try (ModuleReader reader = mref.open()) { + reader.list().forEach(name -> { + testFindUnchecked(name); + + // if the resource is a directory then find without trailing slash + if (name.endsWith("/")) { + testFindUnchecked(name.substring(0, name.length() - 1)); + } + }); + } + } +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/invoke/DefineClassTest.java 2017-03-21 13:44:40.709007316 +0000 @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @modules java.base/java.lang:open + * java.base/jdk.internal.org.objectweb.asm + * @run testng/othervm test.DefineClassTest + * @summary Basic test for java.lang.invoke.MethodHandles.Lookup.defineClass + */ + +package test; + +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodHandles.Lookup.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class DefineClassTest { + private static final String THIS_PACKAGE = DefineClassTest.class.getPackageName(); + + /** + * Test that a class has the same class loader, and is in the same package and + * protection domain, as a lookup class. + */ + void testSameAbode(Class clazz, Class lc) { + assertTrue(clazz.getClassLoader() == lc.getClassLoader()); + assertEquals(clazz.getPackageName(), lc.getPackageName()); + assertTrue(clazz.getProtectionDomain() == lc.getProtectionDomain()); + } + + /** + * Tests that a class is discoverable by name using Class.forName and + * lookup.findClass + */ + void testDiscoverable(Class clazz, Lookup lookup) throws Exception { + String cn = clazz.getName(); + ClassLoader loader = clazz.getClassLoader(); + assertTrue(Class.forName(cn, false, loader) == clazz); + assertTrue(lookup.findClass(cn) == clazz); + } + + /** + * Basic test of defineClass to define a class in the same package as test. + */ + @Test + public void testDefineClass() throws Exception { + final String CLASS_NAME = THIS_PACKAGE + ".Foo"; + Lookup lookup = lookup().dropLookupMode(PRIVATE); + Class clazz = lookup.defineClass(generateClass(CLASS_NAME)); + + // test name + assertEquals(clazz.getName(), CLASS_NAME); + + // test loader/package/protection-domain + testSameAbode(clazz, lookup.lookupClass()); + + // test discoverable + testDiscoverable(clazz, lookup); + + // attempt defineClass again + try { + lookup.defineClass(generateClass(CLASS_NAME)); + assertTrue(false); + } catch (LinkageError expected) { } + } + + /** + * Test public/package/protected/private access from class defined with defineClass. + */ + @Test + public void testAccess() throws Exception { + final String THIS_CLASS = this.getClass().getName(); + final String CLASS_NAME = THIS_PACKAGE + ".Runner"; + Lookup lookup = lookup().dropLookupMode(PRIVATE); + + // public + byte[] classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method1"); + testInvoke(lookup.defineClass(classBytes)); + + // package + classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method2"); + testInvoke(lookup.defineClass(classBytes)); + + // protected (same package) + classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method3"); + testInvoke(lookup.defineClass(classBytes)); + + // private + classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method4"); + Class clazz = lookup.defineClass(classBytes); + Runnable r = (Runnable) clazz.newInstance(); + try { + r.run(); + assertTrue(false); + } catch (IllegalAccessError expected) { } + } + + public static void method1() { } + static void method2() { } + protected static void method3() { } + private static void method4() { } + + void testInvoke(Class clazz) throws Exception { + Object obj = clazz.newInstance(); + ((Runnable) obj).run(); + } + + /** + * Test that defineClass does not run the class initializer + */ + @Test + public void testInitializerNotRun() throws Exception { + final String THIS_CLASS = this.getClass().getName(); + final String CLASS_NAME = THIS_PACKAGE + ".ClassWithClinit"; + + byte[] classBytes = generateClassWithInitializer(CLASS_NAME, THIS_CLASS, "fail"); + Lookup lookup = lookup().dropLookupMode(PRIVATE); + + Class clazz = lookup.defineClass(classBytes); + // trigger initializer to run + try { + clazz.newInstance(); + assertTrue(false); + } catch (ExceptionInInitializerError e) { + assertTrue(e.getCause() instanceof IllegalCallerException); + } + } + + static void fail() { throw new IllegalCallerException(); } + + + /** + * Test defineClass to define classes in a package containing classes with + * different protection domains. + */ + @Test + public void testTwoProtectionDomains() throws Exception { + // p.C1 in one exploded directory + Path dir1 = Files.createTempDirectory("classes"); + Path p = Files.createDirectory(dir1.resolve("p")); + Files.write(p.resolve("C1.class"), generateClass("p.C1")); + URL url1 = dir1.toUri().toURL(); + + // p.C2 in another exploded directory + Path dir2 = Files.createTempDirectory("classes"); + p = Files.createDirectory(dir2.resolve("p")); + Files.write(p.resolve("C2.class"), generateClass("p.C2")); + URL url2 = dir2.toUri().toURL(); + + // load p.C1 and p.C2 + ClassLoader loader = new URLClassLoader(new URL[] { url1, url2 }); + Class target1 = Class.forName("p.C1", false, loader); + Class target2 = Class.forName("p.C2", false, loader); + assertTrue(target1.getClassLoader() == loader); + assertTrue(target1.getClassLoader() == loader); + assertNotEquals(target1.getProtectionDomain(), target2.getProtectionDomain()); + + // protection domain 1 + Lookup lookup1 = privateLookupIn(target1, lookup()).dropLookupMode(PRIVATE); + + Class clazz = lookup1.defineClass(generateClass("p.Foo")); + testSameAbode(clazz, lookup1.lookupClass()); + testDiscoverable(clazz, lookup1); + + // protection domain 2 + Lookup lookup2 = privateLookupIn(target2, lookup()).dropLookupMode(PRIVATE); + + clazz = lookup2.defineClass(generateClass("p.Bar")); + testSameAbode(clazz, lookup2.lookupClass()); + testDiscoverable(clazz, lookup2); + } + + /** + * Test defineClass defining a class to the boot loader + */ + @Test + public void testBootLoader() throws Exception { + Lookup lookup = privateLookupIn(Thread.class, lookup()).dropLookupMode(PRIVATE); + assertTrue(lookup.getClass().getClassLoader() == null); + + Class clazz = lookup.defineClass(generateClass("java.lang.Foo")); + assertEquals(clazz.getName(), "java.lang.Foo"); + testSameAbode(clazz, Thread.class); + testDiscoverable(clazz, lookup); + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void testWrongPackage() throws Exception { + Lookup lookup = lookup().dropLookupMode(PRIVATE); + lookup.defineClass(generateClass("other.C")); + } + + @Test(expectedExceptions = { IllegalAccessException.class }) + public void testNoPackageAccess() throws Exception { + Lookup lookup = lookup().dropLookupMode(PACKAGE); + lookup.defineClass(generateClass(THIS_PACKAGE + ".C")); + } + + @Test(expectedExceptions = { UnsupportedOperationException.class }) + public void testHasPrivateAccess() throws Exception { + Lookup lookup = lookup(); + assertTrue(lookup.hasPrivateAccess()); + lookup.defineClass(generateClass(THIS_PACKAGE + ".C")); + } + + @Test(expectedExceptions = { ClassFormatError.class }) + public void testTruncatedClassFile() throws Exception { + Lookup lookup = lookup().dropLookupMode(PRIVATE); + lookup.defineClass(new byte[0]); + } + + @Test(expectedExceptions = { NullPointerException.class }) + public void testNull() throws Exception { + Lookup lookup = lookup().dropLookupMode(PRIVATE); + lookup.defineClass(null); + } + + /** + * Generates a class file with the given class name + */ + byte[] generateClass(String className) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + null); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * Generate a class file with the given class name. The class implements Runnable + * with a run method to invokestatic the given targetClass/targetMethod. + */ + byte[] generateRunner(String className, + String targetClass, + String targetMethod) throws Exception { + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + new String[] { "java/lang/Runnable" }); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // run() + String tc = targetClass.replace(".", "/"); + mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); + mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * Generate a class file with the given class name. The class will initializer + * to invokestatic the given targetClass/targetMethod. + */ + byte[] generateClassWithInitializer(String className, + String targetClass, + String targetMethod) throws Exception { + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + null); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // + String tc = targetClass.replace(".", "/"); + mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); + mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + private int nextNumber() { + return ++nextNumber; + } + + private int nextNumber; +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/module/ModuleFinderWithSecurityManager.java 2017-03-21 13:44:41.209041627 +0000 @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @run main/othervm ModuleFinderWithSecurityManager allow + * @run main/othervm ModuleFinderWithSecurityManager deny + * @summary Basic test for ModuleFinder.ofSystem() with security manager + */ + +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; + +public class ModuleFinderWithSecurityManager { + public static void main(String[] args) throws Exception { + boolean allow = args[0].equals("allow"); + + // set security policy to allow access + if (allow) { + String testSrc = System.getProperty("test.src"); + if (testSrc == null) + testSrc = "."; + Path policyFile = Paths.get(testSrc, "java.policy"); + System.setProperty("java.security.policy", policyFile.toString()); + } + + System.setSecurityManager(new SecurityManager()); + + ModuleFinder finder = null; + try { + finder = ModuleFinder.ofSystem(); + if (!allow) throw new RuntimeException("SecurityException expected"); + } catch (SecurityException e) { + if (allow) throw new RuntimeException("SecurityException not expected"); + } + + // no additional permissions should be required to locate modules + if (finder != null) { + ModuleReference base = finder.find("java.base").orElse(null); + if (base == null) + throw new RuntimeException("java.base not found"); + Set allModules = finder.findAll(); + if (!allModules.contains(base)) + throw new RuntimeException("java.base not in all modules"); + } + } +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/module/java.policy 2017-03-21 13:44:41.712076143 +0000 @@ -0,0 +1,4 @@ +grant { + // ModuleFinder.ofSystem() needs this + permission java.lang.RuntimePermission "accessSystemModules"; +}; --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/lib/testlibrary/ModuleTargetHelper.java 2017-03-21 13:44:42.214110590 +0000 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.InputStream; +import java.io.IOException; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import jdk.internal.module.ClassFileConstants; +import jdk.internal.module.ClassFileAttributes; +import jdk.internal.module.ClassFileAttributes.ModuleTargetAttribute; +import jdk.internal.org.objectweb.asm.Attribute; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; + +public class ModuleTargetHelper { + private ModuleTargetHelper() {} + + public static final class ModuleTarget { + private String osName, osArch; + + public ModuleTarget(String osName, String osArch) { + this.osName = osName; + this.osArch = osArch; + } + + public String osName() { + return osName; + } + + public String osArch() { + return osArch; + } + } + + public static ModuleTarget getJavaBaseTarget() throws IOException { + Path p = Paths.get(URI.create("jrt:/modules/java.base/module-info.class")); + try (InputStream in = Files.newInputStream(p)) { + return read(in); + } + } + + public static ModuleTarget read(InputStream in) throws IOException { + ModuleTargetAttribute[] modTargets = new ModuleTargetAttribute[1]; + ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) { + @Override + public void visitAttribute(Attribute attr) { + if (attr instanceof ModuleTargetAttribute) { + modTargets[0] = (ModuleTargetAttribute)attr; + } + } + }; + + // prototype of attributes that should be parsed + Attribute[] attrs = new Attribute[] { + new ModuleTargetAttribute() + }; + + // parse module-info.class + ClassReader cr = new ClassReader(in); + cr.accept(cv, attrs, 0); + if (modTargets[0] != null) { + return new ModuleTarget(modTargets[0].osName(), modTargets[0].osArch()); + } + + return null; + } + + public static ModuleTarget read(ModuleReference modRef) throws IOException { + ModuleReader reader = modRef.open(); + try (InputStream in = reader.open("module-info.class").get()) { + return read(in); + } finally { + reader.close(); + } + } +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/tools/launcher/modules/basic/InitErrors.java 2017-03-21 13:44:42.708144489 +0000 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /lib/testlibrary + * @build InitErrors jdk.testlibrary.* + * @run testng InitErrors + * @summary Basic test to ensure that module system initialization errors + * go the right stream and with the right level of verbosity + */ + + +import java.util.Arrays; +import jdk.testlibrary.ProcessTools; +import jdk.testlibrary.OutputAnalyzer; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class InitErrors { + + // the option to cause module initialization to fail + private static final String ADD_UNKNOWN_MODULE = "--add-modules=XXX"; + + // the expected error message + private static final String UNKNOWN_MODULE_NOT_FOUND= "Module XXX not found"; + + // output expected in the stack trace when using -Xlog:init=debug + private static final String STACK_FRAME = "java.base/java.lang.System.initPhase2"; + + + /** + * Default behavior, send error message to stdout + */ + @Test + public void testDefaultOutput() throws Exception { + expectFail(showVersion(ADD_UNKNOWN_MODULE) + .stdoutShouldContain(UNKNOWN_MODULE_NOT_FOUND) + .stdoutShouldNotContain(STACK_FRAME) + .stderrShouldNotContain(UNKNOWN_MODULE_NOT_FOUND) + .stderrShouldNotContain(STACK_FRAME)); + } + + /** + * -XX:+DisplayVMOutputToStderr should send error message to stderr + */ + @Test + public void testOutputToStderr() throws Exception { + expectFail(showVersion(ADD_UNKNOWN_MODULE, "-XX:+DisplayVMOutputToStderr") + .stdoutShouldNotContain(UNKNOWN_MODULE_NOT_FOUND) + .stdoutShouldNotContain(STACK_FRAME) + .stderrShouldContain(UNKNOWN_MODULE_NOT_FOUND) + .stderrShouldNotContain(STACK_FRAME)); + } + + /** + * -Xlog:init=debug should print stack trace to stdout + */ + @Test + public void testStackTrace() throws Exception { + expectFail(showVersion(ADD_UNKNOWN_MODULE, "-Xlog:init=debug") + .stdoutShouldContain(UNKNOWN_MODULE_NOT_FOUND) + .stdoutShouldContain(STACK_FRAME) + .stderrShouldNotContain(UNKNOWN_MODULE_NOT_FOUND) + .stderrShouldNotContain(STACK_FRAME)); + } + + /** + * -Xlog:init=debug -XX:+DisplayVMOutputToStderr should print stack trace + * to stderr + */ + @Test + public void testStackTraceToStderr() throws Exception { + expectFail(showVersion(ADD_UNKNOWN_MODULE, + "-Xlog:init=debug", + "-XX:+DisplayVMOutputToStderr") + .stdoutShouldNotContain(UNKNOWN_MODULE_NOT_FOUND) + .stdoutShouldNotContain(STACK_FRAME) + .stderrShouldContain(UNKNOWN_MODULE_NOT_FOUND) + .stderrShouldContain(STACK_FRAME)); + } + + private OutputAnalyzer showVersion(String... args) throws Exception { + int len = args.length; + args = Arrays.copyOf(args, len+1); + args[len] = "-version"; + return ProcessTools.executeTestJava(args) + .outputTo(System.out) + .errorTo(System.out); + } + + private void expectFail(OutputAnalyzer output) { + assertFalse(output.getExitValue() == 0); + } + +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/tools/launcher/modules/permit/AttemptAccess.java 2017-03-21 13:44:43.206178661 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Launched by PermitIllegalAccess to attempt illegal access. + */ + +public class AttemptAccess { + + public static void main(String[] args) throws Exception { + String action = args[0]; + int count = Integer.parseInt(args[1]); + + for (int i=0; i clazz = Class.forName("sun.security.x509.X500Name"); + Constructor ctor = clazz.getConstructor(String.class); + Object name = ctor.newInstance("CN=user"); + } + + static void trySetAccessible() throws Exception { + Method find = ClassLoader.class.getDeclaredMethod("findClass", String.class); + find.setAccessible(true); + } + + static void tryTrySetAccessible() throws Exception { + Method find = ClassLoader.class.getDeclaredMethod("findClass", String.class); + find.trySetAccessible(); + } + +} --- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/tools/launcher/modules/permit/PermitIllegalAccess.java 2017-03-21 13:44:43.702212697 +0000 @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /lib/testlibrary + * @build PermitIllegalAccess AttemptAccess jdk.testlibrary.* + * @run testng PermitIllegalAccess + * @summary Basic test for java --permit-illegal-access + */ + +import java.util.List; + +import jdk.testlibrary.ProcessTools; +import jdk.testlibrary.OutputAnalyzer; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * Basic test of --permit-illegal-access to ensure that it permits access + * via core reflection and setAccessible/trySetAccessible. + */ + +@Test +public class PermitIllegalAccess { + + static final String TEST_CLASSES = System.getProperty("test.classes"); + static final String TEST_MAIN = "AttemptAccess"; + + static final String WARNING = "WARNING"; + static final String STARTUP_WARNING = + "WARNING: --permit-illegal-access will be removed in the next major release"; + static final String ILLEGAL_ACCESS_WARNING = + "WARNING: Illegal access by " + TEST_MAIN; + + /** + * Launches AttemptAccess to execute an action, returning the OutputAnalyzer + * to analyze the output/exitCode. + */ + private OutputAnalyzer tryAction(String action, int count) throws Exception { + String arg = "" + count; + return ProcessTools + .executeTestJava("-cp", TEST_CLASSES, TEST_MAIN, action, arg) + .outputTo(System.out) + .errorTo(System.out); + } + + /** + * Launches AttemptAccess with --permit-illegal-access to execute an action, + * returning the OutputAnalyzer to analyze the output/exitCode. + */ + private OutputAnalyzer tryActionPermittingIllegalAccess(String action, + int count) + throws Exception + { + String arg = "" + count; + return ProcessTools + .executeTestJava("-cp", TEST_CLASSES, "--permit-illegal-access", + TEST_MAIN, action, arg) + .outputTo(System.out) + .errorTo(System.out); + } + + /** + * Sanity check to ensure that IllegalAccessException is thrown. + */ + public void testAccessFail() throws Exception { + int exitValue = tryAction("access", 1) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("IllegalAccessException") + .stderrShouldNotContain(WARNING) + .stderrShouldContain("IllegalAccessException") + .getExitValue(); + assertTrue(exitValue != 0); + } + + /** + * Sanity check to ensure that InaccessibleObjectException is thrown. + */ + public void testSetAccessibleFail() throws Exception { + int exitValue = tryAction("setAccessible", 1) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("InaccessibleObjectException") + .stderrShouldNotContain(WARNING) + .stderrShouldContain("InaccessibleObjectException") + .getExitValue(); + assertTrue(exitValue != 0); + } + + /** + * Permit illegal access to succeed + */ + public void testAccessPermitted() throws Exception { + tryActionPermittingIllegalAccess("access", 1) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("IllegalAccessException") + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldNotContain("IllegalAccessException") + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0); + } + + /** + * Permit repeated illegal access to succeed + */ + public void testRepeatedAccessPermitted() throws Exception { + OutputAnalyzer outputAnalyzer = tryActionPermittingIllegalAccess("access", 10) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("IllegalAccessException") + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldNotContain("IllegalAccessException") + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0);; + + // should only have one illegal access warning + assertTrue(containsCount(outputAnalyzer.asLines(), ILLEGAL_ACCESS_WARNING) == 1); + } + + /** + * Permit setAccessible to succeed + */ + public void testSetAccessiblePermitted() throws Exception { + tryActionPermittingIllegalAccess("setAccessible", 1) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0); + } + + /** + * Permit repeated calls to setAccessible to succeed + */ + public void testRepeatedSetAccessiblePermitted() throws Exception { + OutputAnalyzer outputAnalyzer = tryActionPermittingIllegalAccess("setAccessible", 10) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0); + + // should only have one illegal access warning + assertTrue(containsCount(outputAnalyzer.asLines(), ILLEGAL_ACCESS_WARNING) == 1); + } + + /** + * Permit trySetAccessible to succeed + */ + public void testTrySetAccessiblePermitted() throws Exception { + tryActionPermittingIllegalAccess("trySetAccessible", 1) + .stdoutShouldNotContain(WARNING) + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0); + } + + /** + * Permit repeated calls to trySetAccessible to succeed + */ + public void testRepeatedTrySetAccessiblePermitted() throws Exception { + OutputAnalyzer outputAnalyzer = tryActionPermittingIllegalAccess("trySetAccessible", 10) + .stdoutShouldNotContain(WARNING) + .stdoutShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(STARTUP_WARNING) + .stderrShouldNotContain("InaccessibleObjectException") + .stderrShouldContain(ILLEGAL_ACCESS_WARNING) + .shouldHaveExitValue(0); + + // should only have one illegal access warning + assertTrue(containsCount(outputAnalyzer.asLines(), ILLEGAL_ACCESS_WARNING) == 1); + + } + + /** + * Returns the number of lines in the given input that contain the + * given char sequence. + */ + private int containsCount(List lines, CharSequence cs) { + int count = 0; + for (String line : lines) { + if (line.contains(cs)) count++; + } + return count; + } +}