8160829: Remove ASMPool support from jlink

Reviewed-by: sundar, psandoz, forax
This commit is contained in:
Jim Laskey 2016-07-07 09:38:33 -03:00
parent 74a59850a5
commit 748aad61f6
23 changed files with 193 additions and 5144 deletions

View File

@ -0,0 +1,188 @@
/*
* Copyright (c) 2016, 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.tools.jlink.internal.plugins;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import jdk.tools.jlink.plugin.ModulePool;
import jdk.tools.jlink.plugin.Plugin.Category;
import jdk.internal.org.objectweb.asm.ClassReader;
import static jdk.internal.org.objectweb.asm.ClassReader.*;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.InsnList;
import jdk.internal.org.objectweb.asm.tree.LabelNode;
import jdk.internal.org.objectweb.asm.tree.LdcInsnNode;
import jdk.internal.org.objectweb.asm.tree.LineNumberNode;
import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.Plugin;
public final class ClassForNamePlugin implements Plugin {
public static final String NAME = "class-for-name";
private static String binaryClassName(String path) {
return path.substring(path.indexOf('/', 1) + 1,
path.length() - ".class".length());
}
private static int getAccess(ModuleEntry resource) {
ClassReader cr = new ClassReader(resource.getBytes());
return cr.getAccess();
}
private static String getPackage(String binaryName) {
int index = binaryName.lastIndexOf("/");
return index == -1 ? "" : binaryName.substring(0, index);
}
private ModuleEntry transform(ModuleEntry resource, Map<String, ModuleEntry> classes) {
byte[] inBytes = resource.getBytes();
ClassReader cr = new ClassReader(inBytes);
ClassNode cn = new ClassNode();
cr.accept(cn, EXPAND_FRAMES);
List<MethodNode> ms = cn.methods;
boolean modified = false;
LdcInsnNode ldc = null;
String thisPackage = getPackage(binaryClassName(resource.getPath()));
for (MethodNode mn : ms) {
InsnList il = mn.instructions;
Iterator<AbstractInsnNode> it = il.iterator();
while (it.hasNext()) {
AbstractInsnNode insn = it.next();
if (insn instanceof LdcInsnNode) {
ldc = (LdcInsnNode)insn;
} else if (insn instanceof MethodInsnNode && ldc != null) {
MethodInsnNode min = (MethodInsnNode)insn;
if (min.getOpcode() == Opcodes.INVOKESTATIC &&
min.name.equals("forName") &&
min.owner.equals("java/lang/Class") &&
min.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
String ldcClassName = ldc.cst.toString();
String thatClassName = ldcClassName.replaceAll("\\.", "/");
ModuleEntry thatClass = classes.get(thatClassName);
if (thatClass != null) {
int thatAccess = getAccess(thatClass);
String thatPackage = getPackage(thatClassName);
if ((thatAccess & Opcodes.ACC_PRIVATE) != Opcodes.ACC_PRIVATE &&
((thatAccess & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC ||
thisPackage.equals(thatPackage))) {
Type type = Type.getObjectType(thatClassName);
il.remove(ldc);
il.set(min, new LdcInsnNode(type));
modified = true;
}
}
}
ldc = null;
} else if (!(insn instanceof LabelNode) &&
!(insn instanceof LineNumberNode)) {
ldc = null;
}
}
}
if (modified) {
ClassWriter cw = new ClassWriter(cr, 0);
cn.accept(cw);
byte[] outBytes = cw.toByteArray();
return resource.create(outBytes);
}
return resource;
}
@Override
public String getName() {
return NAME;
}
@Override
public void visit(ModulePool in, ModulePool out) {
Objects.requireNonNull(in);
Objects.requireNonNull(out);
Map<String, ModuleEntry> classes = in.entries()
.filter(resource -> resource != null &&
resource.getPath().endsWith(".class") &&
!resource.getPath().endsWith("/module-info.class"))
.collect(Collectors.toMap(resource -> binaryClassName(resource.getPath()),
resource -> resource));
in.entries()
.filter(resource -> resource != null)
.forEach(resource -> {
String path = resource.getPath();
if (path.endsWith(".class") && !path.endsWith("/module-info.class")) {
out.add(transform(resource, classes));
} else {
out.add(resource);
}
});
}
@Override
public Category getType() {
return Category.TRANSFORMER;
}
@Override
public boolean hasArguments() {
return false;
}
@Override
public String getDescription() {
return PluginsResourceBundle.getDescription(NAME);
}
@Override
public String getArgumentsDescription() {
return PluginsResourceBundle.getArgument(NAME);
}
@Override
public void configure(Map<String, String> config) {
}
}

View File

@ -1,285 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.optim.ForNameFolding;
import jdk.tools.jlink.internal.plugins.optim.ReflectionOptimizer.TypeResolver;
import jdk.tools.jlink.plugin.PluginException;
/**
*
* Optimize Classes following various strategies. Strategies are implementation
* of <code>ClassOptimizer</code> and <code>MethodOptimizer</code>.
*/
public final class OptimizationPlugin extends AsmPlugin {
public static final String NAME = "class-optim";
public static final String LOG = "log";
public static final String ALL = "all";
public static final String FORNAME_REMOVAL = "forName-folding";
/**
* Default resolver. A resolver that retrieve types that are in an
* accessible package, are public or are located in the same package as the
* caller.
*/
private static final class DefaultTypeResolver implements TypeResolver {
private final Set<String> packages;
private final AsmPools pools;
DefaultTypeResolver(AsmPools pools, AsmModulePool modulePool) {
Objects.requireNonNull(pools);
Objects.requireNonNull(modulePool);
this.pools = pools;
packages = pools.getGlobalPool().getAccessiblePackages(modulePool.getModuleName());
}
@Override
public ClassReader resolve(ClassNode cn, MethodNode mn, String type) {
int classIndex = cn.name.lastIndexOf("/");
String callerPkg = classIndex == -1 ? ""
: cn.name.substring(0, classIndex);
int typeClassIndex = type.lastIndexOf("/");
String pkg = typeClassIndex == - 1 ? ""
: type.substring(0, typeClassIndex);
ClassReader reader = null;
if (packages.contains(pkg) || pkg.equals(callerPkg)) {
ClassReader r = pools.getGlobalPool().getClassReader(type);
if (r != null) {
// if not private
if ((r.getAccess() & Opcodes.ACC_PRIVATE)
!= Opcodes.ACC_PRIVATE) {
// public
if (((r.getAccess() & Opcodes.ACC_PUBLIC)
== Opcodes.ACC_PUBLIC)) {
reader = r;
} else if (pkg.equals(callerPkg)) {
reader = r;
}
}
}
}
return reader;
}
}
public interface Optimizer {
void close() throws IOException;
}
public interface ClassOptimizer extends Optimizer {
boolean optimize(Consumer<String> logger, AsmPools pools,
AsmModulePool modulePool,
ClassNode cn) throws Exception;
}
public interface MethodOptimizer extends Optimizer {
boolean optimize(Consumer<String> logger, AsmPools pools,
AsmModulePool modulePool,
ClassNode cn, MethodNode m, TypeResolver resolver) throws Exception;
}
private List<Optimizer> optimizers = new ArrayList<>();
private OutputStream stream;
private int numMethods;
private void log(String content) {
if (stream != null) {
try {
content = content + "\n";
stream.write(content.getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
System.err.println(ex);
}
}
}
private void close() throws IOException {
log("Num analyzed methods " + numMethods);
for (Optimizer optimizer : optimizers) {
try {
optimizer.close();
} catch (IOException ex) {
System.err.println("Error closing optimizer " + ex);
}
}
if (stream != null) {
stream.close();
}
}
@Override
public String getName() {
return NAME;
}
@Override
public void visit(AsmPools pools) {
try {
for (AsmModulePool p : pools.getModulePools()) {
DefaultTypeResolver resolver = new DefaultTypeResolver(pools, p);
p.visitClassReaders((reader) -> {
ClassWriter w = null;
try {
w = optimize(pools, p, reader, resolver);
} catch (IOException ex) {
throw new PluginException("Problem optimizing "
+ reader.getClassName(), ex);
}
return w;
});
}
} finally {
try {
close();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
private ClassWriter optimize(AsmPools pools, AsmModulePool modulePool,
ClassReader reader, TypeResolver resolver)
throws IOException {
ClassNode cn = new ClassNode();
ClassWriter writer = null;
if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
reader.accept(cn, ClassReader.EXPAND_FRAMES);
boolean optimized = false;
for (Optimizer optimizer : optimizers) {
if (optimizer instanceof ClassOptimizer) {
try {
boolean optim = ((ClassOptimizer) optimizer).
optimize(this::log, pools, modulePool, cn);
if (optim) {
optimized = true;
}
} catch (Throwable ex) {
throw new PluginException("Exception optimizing "
+ reader.getClassName(), ex);
}
} else {
MethodOptimizer moptimizer = (MethodOptimizer) optimizer;
for (MethodNode m : cn.methods) {
if ((m.access & Opcodes.ACC_ABSTRACT) == 0
&& (m.access & Opcodes.ACC_NATIVE) == 0) {
numMethods += 1;
try {
boolean optim = moptimizer.
optimize(this::log, pools, modulePool, cn,
m, resolver);
if (optim) {
optimized = true;
}
} catch (Throwable ex) {
throw new PluginException("Exception optimizing "
+ reader.getClassName() + "." + m.name, ex);
}
}
}
}
}
if (optimized) {
writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
try {
// add a validation layer in between to check for class vallidity
CheckClassAdapter ca = new CheckClassAdapter(writer);
cn.accept(ca);
} catch (Exception ex) {
throw new PluginException("Exception optimizing class " + cn.name, ex);
}
}
}
return writer;
}
@Override
public String getDescription() {
return PluginsResourceBundle.getDescription(NAME);
}
@Override
public boolean hasArguments() {
return true;
}
@Override
public String getArgumentsDescription() {
return PluginsResourceBundle.getArgument(NAME);
}
@Override
public void configure(Map<String, String> config) {
String strategies = config.get(NAME);
String[] arr = strategies.split(",");
for (String s : arr) {
if (s.equals(ALL)) {
optimizers.clear();
optimizers.add(new ForNameFolding());
break;
} else if (s.equals(FORNAME_REMOVAL)) {
optimizers.add(new ForNameFolding());
} else {
throw new IllegalArgumentException("Unknown optimization: " + s);
}
}
String f = config.get(LOG);
if (f != null) {
try {
stream = new FileOutputStream(f);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.util.Set;
/**
* A pool containing all class and resource files.
*/
public interface AsmGlobalPool extends AsmPool {
/**
* Associate a package to a module, useful when adding new classes in new
* packages. WARNING: In order to properly handle new package and/or new
* module, module-info class must be added and/or updated.
*
* @param pkg The new package, following java binary syntax (/-separated
* path name).
* @param module An existing or new module.
* @throws jdk.tools.jlink.plugins.PluginException If a mapping already
* exist for this package.
*/
public void addPackageModuleMapping(String pkg, String module);
/**
* Return the set of accessible packages for a given module.
*
* @param module The module from which packages are accessible.
* @return Set of packages or null if the module is not found.
*/
public Set<String> getAccessiblePackages(String module);
}

View File

@ -1,90 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.lang.module.ModuleDescriptor;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
/**
* A pool for a given module
*/
public interface AsmModulePool extends AsmPool {
/**
* Associate a package to this module, useful when adding new classes in new
* packages. WARNING: In order to properly handle new package and/or new
* module, module-info class must be added and/or updated.
*
* @param pkg The new package, following java binary syntax (/-separated
* path name).
* @throws jdk.tools.jlink.plugins.PluginException If a mapping already
* exist for this package.
*/
public void addPackage(String pkg);
/**
* The module name of this pool.
* @return The module name;
*/
public String getModuleName();
/**
* Lookup the class in this pool and the required pools. NB: static module
* readability can be different at execution time.
*
* @param binaryName The class to lookup.
* @return The reader or null if not found
* @throws jdk.tools.jlink.plugins.PluginException
*/
public ClassReader getClassReaderInDependencies(String binaryName);
/**
* Lookup the class in the exported packages of this module. "public
* requires" modules are looked up. NB: static module readability can be
* different at execution time.
*
* @param callerModule Name of calling module.
* @param binaryName The class to lookup.
* @return The reader or null if not found
* @throws jdk.tools.jlink.plugins.PluginException
*/
public ClassReader getExportedClassReader(String callerModule,
String binaryName);
/**
* The module descriptor.
*
* @return The module descriptor;
*/
public ModuleDescriptor getDescriptor();
/**
* Retrieve the internal and exported packages.
*
* @return
*/
public Set<String> getAllPackages();
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.util.Objects;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
import jdk.tools.jlink.internal.ModulePoolImpl;
/**
* Extend this class to develop your own plugin in order to transform jimage
* resources.
*
*/
public abstract class AsmPlugin implements Plugin {
public AsmPlugin() {
}
@Override
public void visit(ModulePool allContent, ModulePool outResources) {
Objects.requireNonNull(allContent);
Objects.requireNonNull(outResources);
ModulePoolImpl resources = new ModulePoolImpl(allContent.getByteOrder());
allContent.entries().forEach(md -> {
if(md.getType().equals(ModuleEntry.Type.CLASS_OR_RESOURCE)) {
resources.add(md);
} else {
outResources.add(md);
}
});
AsmPools pools = new AsmPools(resources);
visit(pools);
pools.fillOutputResources(outResources);
}
/**
* This is the method to implement in order to
* apply Asm transformation to jimage contained classes.
* @param pools The pool of Asm classes and other resource files.
* @param strings To add a string to the jimage strings table.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public abstract void visit(AsmPools pools);
}

View File

@ -1,314 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.util.Collection;
import java.util.List;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
/**
* A pool of ClassReader and other resource files.
* This class allows to transform and sort classes and resource files.
* <p>
* Classes in the class pool are named following java binary name specification.
* For example, java.lang.Object class is named java/lang/Object
* <p>
* Module information has been stripped out from class and other resource files
* (.properties, binary files, ...).</p>
*/
public interface AsmPool {
/**
* A resource that is not a class file.
* <p>
* The path of a resource is a /-separated path name that identifies the
* resource. For example com.foo.bar.Bundle.properties resource name is
* com/foo/bar/Bundle.properties </p>
* <p>
*/
public class ResourceFile {
private final String path;
private final byte[] content;
public ResourceFile(String path, byte[] content) {
this.path = path;
this.content = content;
}
public String getPath() {
return path;
}
public byte[] getContent() {
return content;
}
}
/**
* To visit each Class contained in the pool
*/
public interface ClassReaderVisitor {
/**
* Called for each ClassReader located in the pool.
*
* @param reader A class reader.
* @return A writer or null if the class has not been transformed.
*/
public ClassWriter visit(ClassReader reader);
}
/**
* To visit each Resource contained in the pool
*/
public interface ResourceFileVisitor {
/**
* Called for each Resource file located in the pool.
*
* @param reader A resource file.
* @return A resource file or null if the resource has not been
* transformed.
*/
public ResourceFile visit(ResourceFile reader);
}
/**
* Contains the transformed classes. When the jimage file is generated,
* transformed classes take precedence on unmodified ones.
*/
public interface WritableClassPool {
/**
* Add a class to the pool, if a class already exists, it is replaced.
*
* @param writer The class writer.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void addClass(ClassWriter writer);
/**
* The class will be not added to the jimage file.
*
* @param className The class name to forget.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void forgetClass(String className);
/**
* Get a transformed class.
*
* @param binaryName The java class binary name
* @return The ClassReader or null if the class is not found.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public ClassReader getClassReader(String binaryName);
/**
* Get a transformed class.
*
* @param res A class resource.
* @return The ClassReader or null if the class is not found.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public ClassReader getClassReader(ModuleEntry res);
/**
* Returns all the classes contained in the writable pool.
*
* @return The collection of classes.
*/
public Collection<ModuleEntry> getClasses();
}
/**
* Contains the transformed resources. When the jimage file is generated,
* transformed resources take precedence on unmodified ones.
*/
public interface WritableResourcePool {
/**
* Add a resource, if the resource exists, it is replaced.
*
* @param resFile The resource file to add.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void addResourceFile(ResourceFile resFile);
/**
* The resource will be not added to the jimage file.
*
* @param resourceName
* @throws jdk.tools.jlink.plugin.PluginException If the resource to
* forget doesn't exist or is null.
*/
public void forgetResourceFile(String resourceName);
/**
* Get a transformed resource.
*
* @param name The java resource name
* @return The Resource or null if the resource is not found.
*/
public ResourceFile getResourceFile(String name);
/**
* Get a transformed resource.
*
* @param res The java resource
* @return The Resource or null if the resource is not found.
*/
public ResourceFile getResourceFile(ModuleEntry res);
/**
* Returns all the resources contained in the writable pool.
*
* @return The array of resources.
*/
public Collection<ModuleEntry> getResourceFiles();
}
/**
* To order the classes and resources within a jimage file.
*/
public interface Sorter {
/**
* @param resources The resources will be added to the jimage following
* the order of this ResourcePool.
* @return The resource paths ordered in the way to use for storage in the jimage.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public List<String> sort(ModulePool resources);
}
/**
* The writable pool used to store transformed resources.
*
* @return The writable pool.
*/
public WritableClassPool getTransformedClasses();
/**
* The writable pool used to store transformed resource files.
*
* @return The writable pool.
*/
public WritableResourcePool getTransformedResourceFiles();
/**
* Set a sorter instance to sort all files. If no sorter is set, then input
* Resources will be added in the order they have been received followed by
* newly added resources.
*
* @param sorter
*/
public void setSorter(Sorter sorter);
/**
* Returns the classes contained in the pool.
*
* @return The classes.
*/
public Collection<ModuleEntry> getClasses();
/**
* Returns the resources contained in the pool. Resources are all the file
* that are not classes (eg: properties file, binary files, ...)
*
* @return The array of resource files.
*/
public Collection<ModuleEntry> getResourceFiles();
/**
* Retrieves a resource based on the binary name. This name doesn't contain
* the module name.
* <b>NB:</b> When dealing with resources that have the same name in various
* modules (eg: META-INFO/*), you should use the <code>ResourcePool</code>
* referenced from this <code>AsmClassPool</code>.
*
* @param binaryName Name of a Java resource or null if the resource doesn't
* exist.
* @return
*/
public ResourceFile getResourceFile(String binaryName);
/**
* Retrieves a resource for the passed resource.
*
* @param res The resource
* @return The resource file or null if it doesn't exist.
*/
public ResourceFile getResourceFile(ModuleEntry res);
/**
* Retrieve a ClassReader from the pool.
*
* @param binaryName Class binary name
* @return A reader or null if the class is unknown
* @throws jdk.tools.jlink.plugin.PluginException
*/
public ClassReader getClassReader(String binaryName);
/**
* Retrieve a ClassReader from the pool.
*
* @param res A resource.
* @return A reader or null if the class is unknown
* @throws jdk.tools.jlink.plugin.PluginException
*/
public ClassReader getClassReader(ModuleEntry res);
/**
* To visit the set of ClassReaders.
*
* @param visitor The visitor.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void visitClassReaders(ClassReaderVisitor visitor);
/**
* To visit the set of ClassReaders.
*
* @param visitor The visitor.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void visitResourceFiles(ResourceFileVisitor visitor);
/**
* Returns the pool of all the resources (transformed and unmodified).
* The input resources are replaced by the transformed ones.
* If a sorter has been set, it is used to sort the returned resources.
*
* @param output The pool used to fill the jimage.
* @throws jdk.tools.jlink.plugin.PluginException
*/
public void fillOutputResources(ModulePool output);
}

View File

@ -1,698 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleDescriptor.Requires.Modifier;
import java.lang.module.ModuleDescriptor.Exports;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.tools.jlink.internal.ImageFileCreator;
import jdk.tools.jlink.internal.ModulePoolImpl;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModulePool;
/**
* A pool of ClassReader and other resource files. This class allows to
* transform and sort classes and resource files.
* <p>
* Classes in the class pool are named following java binary name specification.
* For example, java.lang.Object class is named java/lang/Object
* <p>
* Module information has been stripped out from class and other resource files
* (.properties, binary files, ...).</p>
*/
final class AsmPoolImpl implements AsmModulePool {
/**
* Contains the transformed classes. When the jimage file is generated,
* transformed classes take precedence on unmodified ones.
*/
public final class WritableClassPoolImpl implements WritableClassPool {
private WritableClassPoolImpl() {
}
/**
* Add a class to the pool, if a class already exists, it is replaced.
*
* @param writer The class writer.
* @throws java.io.IOException
*/
@Override
public void addClass(ClassWriter writer) {
Objects.requireNonNull(writer);
// Retrieve the className
ClassReader reader = newClassReader(writer.toByteArray());
String className = reader.getClassName();
String path;
if (className.endsWith("module-info")) {
// remove the module name contained in the class name
className = className.substring(className.indexOf("/") + 1);
path = "/" + moduleName + "/" + className;
} else {
path = toClassNamePath(className);
}
byte[] content = writer.toByteArray();
ModuleEntry res = ModuleEntry.create(path, content);
transformedClasses.put(className, res);
}
/**
* The class will be not added to the jimage file.
*
* @param className The class name to forget.
*/
@Override
public void forgetClass(String className) {
Objects.requireNonNull(className);
// do we have a resource?
ModuleEntry res = transformedClasses.get(className);
if (res == null) {
res = inputClasses.get(className);
if (res == null) {
throw new PluginException("Unknown class " + className);
}
}
String path = toClassNamePath(className);
forgetResources.add(path);
// Just in case it has been added.
transformedClasses.remove(className);
}
/**
* Get a transformed class.
*
* @param binaryName The java class binary name
* @return The ClassReader or null if the class is not found.
*/
@Override
public ClassReader getClassReader(String binaryName) {
Objects.requireNonNull(binaryName);
ModuleEntry res = transformedClasses.get(binaryName);
ClassReader reader = null;
if (res != null) {
reader = getClassReader(res);
}
return reader;
}
/**
* Returns all the classes contained in the writable pool.
*
* @return The array of transformed classes.
*/
@Override
public Collection<ModuleEntry> getClasses() {
List<ModuleEntry> classes = new ArrayList<>();
for (Entry<String, ModuleEntry> entry : transformedClasses.entrySet()) {
classes.add(entry.getValue());
}
return classes;
}
@Override
public ClassReader getClassReader(ModuleEntry res) {
return newClassReader(res.getBytes());
}
}
/**
* Contains the transformed resources. When the jimage file is generated,
* transformed resources take precedence on unmodified ones.
*/
public final class WritableResourcePoolImpl implements WritableResourcePool {
private WritableResourcePoolImpl() {
}
/**
* Add a resource, if the resource exists, it is replaced.
*
* @param resFile The resource file to add.
*/
@Override
public void addResourceFile(ResourceFile resFile) {
Objects.requireNonNull(resFile);
String path = toResourceNamePath(resFile.getPath());
ModuleEntry res = ModuleEntry.create(path, resFile.getContent());
transformedResources.put(resFile.getPath(), res);
}
/**
* The resource will be not added to the jimage file.
*
* @param resourceName
* @throws java.io.IOException
*/
@Override
public void forgetResourceFile(String resourceName) {
Objects.requireNonNull(resourceName);
String path = toResourceNamePath(resourceName);
// do we have a resource?
ModuleEntry res = transformedResources.get(resourceName);
if (res == null) {
res = inputResources.get(resourceName);
if (res == null) {
throw new PluginException("Unknown resource " + resourceName);
}
}
forgetResources.add(path);
// Just in case it has been added.
transformedResources.remove(resourceName);
}
/**
* Get a transformed resource.
*
* @param name The java resource name
* @return The Resource or null if the resource is not found.
*/
@Override
public ResourceFile getResourceFile(String name) {
Objects.requireNonNull(name);
ModuleEntry res = transformedResources.get(name);
ResourceFile resFile = null;
if (res != null) {
resFile = getResourceFile(res);
}
return resFile;
}
/**
* Returns all the resources contained in the writable pool.
*
* @return The array of transformed classes.
*/
@Override
public Collection<ModuleEntry> getResourceFiles() {
List<ModuleEntry> resources = new ArrayList<>();
for (Entry<String, ModuleEntry> entry : transformedResources.entrySet()) {
resources.add(entry.getValue());
}
return resources;
}
@Override
public ResourceFile getResourceFile(ModuleEntry res) {
return new ResourceFile(toJavaBinaryResourceName(res.getPath()),
res.getBytes());
}
}
private final ModulePool jimageResources;
private final Map<String, ModuleEntry> inputClasses;
private final Map<String, ModuleEntry> inputResources;
private final Map<String, String> inputClassPackageMapping;
private final Map<String, String> inputOtherPackageMapping;
private final WritableClassPool transClassesPool
= new WritableClassPoolImpl();
private final WritableResourcePool transResourcesPool
= new WritableResourcePoolImpl();
private Sorter sorter;
private final Map<String, ModuleEntry> transformedClasses
= new LinkedHashMap<>();
private final Map<String, ModuleEntry> transformedResources
= new LinkedHashMap<>();
private final List<String> forgetResources = new ArrayList<>();
private final Map<String, String> newPackageMapping = new HashMap<>();
private final String moduleName;
private final ModuleDescriptor descriptor;
private final AsmPools pools;
/**
* A new Asm pool.
*
* @param inputResources The raw resources to build the pool from.
* @param moduleName The name of a module.
* @param pools The resource pools.
* @param descriptor The module descriptor.
*/
AsmPoolImpl(ModulePool inputResources, String moduleName,
AsmPools pools,
ModuleDescriptor descriptor) {
Objects.requireNonNull(inputResources);
Objects.requireNonNull(moduleName);
Objects.requireNonNull(pools);
Objects.requireNonNull(descriptor);
this.jimageResources = inputResources;
this.moduleName = moduleName;
this.pools = pools;
this.descriptor = descriptor;
Map<String, ModuleEntry> classes = new LinkedHashMap<>();
Map<String, ModuleEntry> resources = new LinkedHashMap<>();
Map<String, String> packageClassToModule = new HashMap<>();
Map<String, String> packageOtherToModule = new HashMap<>();
inputResources.entries().forEach(res -> {
if (res.getPath().endsWith(".class")) {
classes.put(toJavaBinaryClassName(res.getPath()), res);
} else {
resources.put(toJavaBinaryResourceName(res.getPath()), res);
}
String[] split = ImageFileCreator.splitPath(res.getPath());
if (ImageFileCreator.isClassPackage(res.getPath())) {
packageClassToModule.put(split[1], res.getModule());
} else {
// Keep a map of other resources
// Same resource names such as META-INF/* should be handled with full path name.
if (!split[1].isEmpty()) {
packageOtherToModule.put(split[1], res.getModule());
}
}
});
this.inputClasses = Collections.unmodifiableMap(classes);
this.inputResources = Collections.unmodifiableMap(resources);
this.inputClassPackageMapping = Collections.unmodifiableMap(packageClassToModule);
this.inputOtherPackageMapping = Collections.unmodifiableMap(packageOtherToModule);
}
@Override
public String getModuleName() {
return moduleName;
}
/**
* The writable pool used to store transformed resources.
*
* @return The writable pool.
*/
@Override
public WritableClassPool getTransformedClasses() {
return transClassesPool;
}
/**
* The writable pool used to store transformed resource files.
*
* @return The writable pool.
*/
@Override
public WritableResourcePool getTransformedResourceFiles() {
return transResourcesPool;
}
/**
* Set a sorter instance to sort all files. If no sorter is set, then input
* Resources will be added in the order they have been received followed by
* newly added resources.
*
* @param sorter
*/
@Override
public void setSorter(Sorter sorter) {
this.sorter = sorter;
}
/**
* Returns the classes contained in the pool.
*
* @return The array of classes.
*/
@Override
public Collection<ModuleEntry> getClasses() {
return inputClasses.values();
}
/**
* Returns the resources contained in the pool. Resources are all the file
* that are not classes (eg: properties file, binary files, ...)
*
* @return The array of classes.
*/
@Override
public Collection<ModuleEntry> getResourceFiles() {
return inputResources.values();
}
/**
* Retrieves a resource based on the binary name. This name doesn't contain
* the module name.
* <b>NB:</b> When dealing with resources that have the same name in various
* modules (eg: META-INFO/*), you should use the <code>ResourcePool</code>
* referenced from this <code>AsmClassPool</code>.
*
* @param binaryName Name of a Java resource or null if the resource doesn't
* exist.
* @return
*/
@Override
public ResourceFile getResourceFile(String binaryName) {
Objects.requireNonNull(binaryName);
ModuleEntry res = inputResources.get(binaryName);
ResourceFile resFile = null;
if (res != null) {
resFile = getResourceFile(res);
}
return resFile;
}
/**
* Retrieve a ClassReader from the pool.
*
* @param binaryName Class binary name
* @return A reader or null if the class is unknown
*/
@Override
public ClassReader getClassReader(String binaryName) {
Objects.requireNonNull(binaryName);
ModuleEntry res = inputClasses.get(binaryName);
ClassReader reader = null;
if (res != null) {
reader = getClassReader(res);
}
return reader;
}
@Override
public ResourceFile getResourceFile(ModuleEntry res) {
return new ResourceFile(toJavaBinaryResourceName(res.getPath()),
res.getBytes());
}
@Override
public ClassReader getClassReader(ModuleEntry res) {
return newClassReader(res.getBytes());
}
/**
* Lookup the class in this pool and the required pools. NB: static module
* readability can be different at execution time.
*
* @param binaryName The class to lookup.
* @return The reader or null if not found
*/
@Override
public ClassReader getClassReaderInDependencies(String binaryName) {
Objects.requireNonNull(binaryName);
ClassReader reader = getClassReader(binaryName);
if (reader == null) {
for (Requires requires : descriptor.requires()) {
AsmModulePool pool = pools.getModulePool(requires.name());
reader = pool.getExportedClassReader(moduleName, binaryName);
if (reader != null) {
break;
}
}
}
return reader;
}
/**
* Lookup the class in the exported packages of this module. "public
* requires" modules are looked up. NB: static module readability can be
* different at execution time.
*
* @param callerModule Name of calling module.
* @param binaryName The class to lookup.
* @return The reader or null if not found
*/
@Override
public ClassReader getExportedClassReader(String callerModule, String binaryName) {
Objects.requireNonNull(callerModule);
Objects.requireNonNull(binaryName);
boolean exported = false;
ClassReader clazz = null;
for (Exports e : descriptor.exports()) {
String pkg = e.source();
Set<String> targets = e.targets();
System.out.println("PKG " + pkg);
if (targets.isEmpty() || targets.contains(callerModule)) {
if (binaryName.startsWith(pkg)) {
String className = binaryName.substring(pkg.length());
System.out.println("CLASS " + className);
exported = !className.contains(".");
}
if (exported) {
break;
}
}
}
// public requires (re-export)
if (!exported) {
for (Requires requires : descriptor.requires()) {
if (requires.modifiers().contains(Modifier.PUBLIC)) {
AsmModulePool pool = pools.getModulePool(requires.name());
clazz = pool.getExportedClassReader(moduleName, binaryName);
if (clazz != null) {
break;
}
}
}
} else {
clazz = getClassReader(binaryName);
}
return clazz;
}
@Override
public ModuleDescriptor getDescriptor() {
return descriptor;
}
/**
* To visit the set of ClassReaders.
*
* @param visitor The visitor.
*/
@Override
public void visitClassReaders(ClassReaderVisitor visitor) {
Objects.requireNonNull(visitor);
for (ModuleEntry res : getClasses()) {
ClassReader reader = newClassReader(res.getBytes());
ClassWriter writer = visitor.visit(reader);
if (writer != null) {
getTransformedClasses().addClass(writer);
}
}
}
/**
* To visit the set of ClassReaders.
*
* @param visitor The visitor.
*/
@Override
public void visitResourceFiles(ResourceFileVisitor visitor) {
Objects.requireNonNull(visitor);
for (ModuleEntry resource : getResourceFiles()) {
ResourceFile resFile
= new ResourceFile(toJavaBinaryResourceName(resource.getPath()),
resource.getBytes());
ResourceFile res = visitor.visit(resFile);
if (res != null) {
getTransformedResourceFiles().addResourceFile(res);
}
}
}
/**
* Returns the pool of all the resources (transformed and unmodified). The
* input resources are replaced by the transformed ones. If a sorter has
* been set, it is used to sort the returned resources. *
*/
@Override
public void fillOutputResources(ModulePool outputResources) {
List<String> added = new ArrayList<>();
// If the sorter is null, use the input order.
// New resources are added at the end
// First input classes that have not been removed
ModulePool output = new ModulePoolImpl(outputResources.getByteOrder(),
((ModulePoolImpl)outputResources).getStringTable());
jimageResources.entries().forEach(inResource -> {
if (!forgetResources.contains(inResource.getPath())) {
ModuleEntry resource = inResource;
// Do we have a transformed class with the same name?
ModuleEntry res = transformedResources.
get(toJavaBinaryResourceName(inResource.getPath()));
if (res != null) {
resource = res;
} else {
res = transformedClasses.
get(toJavaBinaryClassName(inResource.getPath()));
if (res != null) {
resource = res;
}
}
output.add(resource);
added.add(resource.getPath());
}
});
// Then new resources
for (Map.Entry<String, ModuleEntry> entry : transformedResources.entrySet()) {
ModuleEntry resource = entry.getValue();
if (!forgetResources.contains(resource.getPath())) {
if (!added.contains(resource.getPath())) {
output.add(resource);
}
}
}
// And new classes
for (Map.Entry<String, ModuleEntry> entry : transformedClasses.entrySet()) {
ModuleEntry resource = entry.getValue();
if (!forgetResources.contains(resource.getPath())) {
if (!added.contains(resource.getPath())) {
output.add(resource);
}
}
}
AsmPools.sort(outputResources, output, sorter);
}
/**
* Associate a package to this module, useful when adding new classes in new
* packages. WARNING: In order to properly handle new package and/or new
* module, module-info class must be added and/or updated.
*
* @param pkg The new package, following java binary syntax (/-separated
* path name).
* @throws PluginException If a mapping already exist for this package.
*/
@Override
public void addPackage(String pkg) {
Objects.requireNonNull(pkg);
Objects.requireNonNull(moduleName);
pkg = pkg.replaceAll("/", ".");
String mod = newPackageMapping.get(pkg);
if (mod != null) {
throw new PluginException(mod + " module already contains package " + pkg);
}
newPackageMapping.put(pkg, moduleName);
}
@Override
public Set<String> getAllPackages() {
ModuleDescriptor desc = getDescriptor();
Set<String> packages = new HashSet<>();
for (String p : desc.conceals()) {
packages.add(p.replaceAll("\\.", "/"));
}
for (String p : newPackageMapping.keySet()) {
packages.add(p.replaceAll("\\.", "/"));
}
for (Exports ex : desc.exports()) {
packages.add(ex.source().replaceAll("\\.", "/"));
}
return packages;
}
private static ClassReader newClassReader(byte[] bytes) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
ClassReader reader = new ClassReader(stream);
return reader;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private static String toJavaBinaryClassName(String path) {
if (path.endsWith("module-info.class")) {
path = removeClassExtension(path);
} else {
path = removeModuleName(path);
path = removeClassExtension(path);
}
return path;
}
private static String toJavaBinaryResourceName(String path) {
if (!path.endsWith("module-info.class")) {
path = removeModuleName(path);
}
return path;
}
private static String removeClassExtension(String path) {
return path.substring(0, path.length() - ".class".length());
}
private static String removeModuleName(String path) {
path = path.substring(1);
return path.substring(path.indexOf("/") + 1, path.length());
}
private String toClassNamePath(String className) {
return toResourceNamePath(className) + ".class";
}
/**
* Entry point to manage resource<->module association.
*/
private String toResourceNamePath(String resourceName) {
if (!resourceName.startsWith("/")) {
resourceName = "/" + resourceName;
}
String pkg = toPackage(resourceName);
String module = inputClassPackageMapping.get(pkg);
if (module == null) {
module = newPackageMapping.get(pkg);
if (module == null) {
module = inputOtherPackageMapping.get(pkg);
if (module == null) {
throw new PluginException("No module for package" + pkg);
}
}
}
return "/" + module + resourceName;
}
private static String toPackage(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
int i = path.lastIndexOf("/");
if (i == -1) {
// Default package...
return "";
}
return path.substring(0, i).replaceAll("/", ".");
}
}

View File

@ -1,497 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.asm;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Requires;
import static java.lang.module.ModuleDescriptor.Requires.Modifier.PUBLIC;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.tools.jlink.internal.ModulePoolImpl;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.Sorter;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModulePool;
/**
* A container for pools of ClassReader and other resource files. A pool of all
* the resources or a pool for a given module can be retrieved
*/
public final class AsmPools {
/**
* Sort the order in which the modules will be stored in the jimage file.
*/
public interface ModuleSorter {
/**
* Sort the list of modules.
*
* @param modules The list of module names. The module will be stored in
* the jimage following this order.
* @return A list of module names that expresses the order in which the
* modules are stored in the jimage.
*/
public List<String> sort(List<String> modules);
}
private class AsmGlobalPoolImpl implements AsmGlobalPool {
private Sorter sorter = null;
private class GlobalWritableClassPool implements WritableClassPool {
@Override
public void addClass(ClassWriter writer) {
visitFirstNonFailingPool((AsmModulePool pool) -> {
pool.getTransformedClasses().addClass(writer);
});
}
@Override
public void forgetClass(String className) {
visitFirstNonFailingPool((AsmModulePool pool) -> {
pool.getTransformedClasses().forgetClass(className);
});
}
@Override
public ClassReader getClassReader(String binaryName) {
return visitPools((AsmModulePool pool) -> {
return pool.getTransformedClasses().getClassReader(binaryName);
});
}
@Override
public Collection<ModuleEntry> getClasses() {
List<ModuleEntry> all = new ArrayList<>();
visitAllPools((AsmModulePool pool) -> {
for (ModuleEntry rf : pool.getTransformedClasses().getClasses()) {
all.add(rf);
}
});
return all;
}
@Override
public ClassReader getClassReader(ModuleEntry res) {
return visitPools((AsmModulePool pool) -> {
return pool.getTransformedClasses().getClassReader(res);
});
}
}
private class GlobalWritableResourcePool implements WritableResourcePool {
@Override
public void addResourceFile(ResourceFile resFile) {
visitFirstNonFailingPool((AsmModulePool pool) -> {
pool.getTransformedResourceFiles().addResourceFile(resFile);
});
}
@Override
public void forgetResourceFile(String resourceName) {
visitFirstNonFailingPool((AsmModulePool pool) -> {
pool.getTransformedResourceFiles().forgetResourceFile(resourceName);
});
}
@Override
public ResourceFile getResourceFile(String name) {
return visitPools((AsmModulePool pool) -> {
return pool.getTransformedResourceFiles().getResourceFile(name);
});
}
@Override
public Collection<ModuleEntry> getResourceFiles() {
List<ModuleEntry> all = new ArrayList<>();
visitAllPools((AsmModulePool pool) -> {
for (ModuleEntry rf : pool.getTransformedResourceFiles().getResourceFiles()) {
all.add(rf);
}
});
return all;
}
@Override
public ResourceFile getResourceFile(ModuleEntry res) {
return visitPools((AsmModulePool pool) -> {
return pool.getTransformedResourceFiles().getResourceFile(res);
});
}
}
@Override
public AsmPool.WritableClassPool getTransformedClasses() {
return new GlobalWritableClassPool();
}
@Override
public AsmPool.WritableResourcePool getTransformedResourceFiles() {
return new GlobalWritableResourcePool();
}
@Override
public void setSorter(AsmPool.Sorter sorter) {
this.sorter = sorter;
}
@Override
public Collection<ModuleEntry> getClasses() {
List<ModuleEntry> all = new ArrayList<>();
visitAllPools((AsmModulePool pool) -> {
for (ModuleEntry rf : pool.getClasses()) {
all.add(rf);
}
});
return all;
}
@Override
public Collection<ModuleEntry> getResourceFiles() {
List<ModuleEntry> all = new ArrayList<>();
visitAllPools((AsmModulePool pool) -> {
for (ModuleEntry rf : pool.getResourceFiles()) {
all.add(rf);
}
});
return all;
}
@Override
public AsmPool.ResourceFile getResourceFile(String binaryName) {
return visitPools((AsmModulePool pool) -> {
return pool.getResourceFile(binaryName);
});
}
@Override
public ClassReader getClassReader(String binaryName) {
return visitPoolsEx((AsmModulePool pool) -> {
return pool.getClassReader(binaryName);
});
}
@Override
public ResourceFile getResourceFile(ModuleEntry res) {
return visitPools((AsmModulePool pool) -> {
return pool.getResourceFile(res);
});
}
@Override
public ClassReader getClassReader(ModuleEntry res) {
return visitPoolsEx((AsmModulePool pool) -> {
return pool.getClassReader(res);
});
}
@Override
public void visitClassReaders(AsmPool.ClassReaderVisitor visitor) {
visitAllPoolsEx((AsmModulePool pool) -> {
pool.visitClassReaders(visitor);
});
}
@Override
public void visitResourceFiles(AsmPool.ResourceFileVisitor visitor) {
visitAllPoolsEx((AsmModulePool pool) -> {
pool.visitResourceFiles(visitor);
});
}
@Override
public void fillOutputResources(ModulePool outputResources) {
AsmPools.this.fillOutputResources(outputResources);
}
@Override
public void addPackageModuleMapping(String pkg, String module) {
AsmModulePool p = pools.get(module);
if (p == null) {
throw new PluginException("Unknown module " + module);
}
p.addPackage(pkg);
}
@Override
public Set<String> getAccessiblePackages(String module) {
AsmModulePool p = pools.get(module);
if (p == null) {
return null;
}
ModuleDescriptor desc = p.getDescriptor();
Set<String> packages = new HashSet<>();
packages.addAll(p.getAllPackages());
// Retrieve direct dependencies and indirect ones (public)
Set<String> modules = new HashSet<>();
for (Requires req : desc.requires()) {
modules.add(req.name());
addAllRequirePublicModules(req.name(), modules);
}
// Add exported packages of readable modules
for (String readable : modules) {
AsmModulePool mp = pools.get(readable);
if (mp != null) {
for (Exports e : mp.getDescriptor().exports()) {
// exported to all or to the targeted module
if (e.targets().isEmpty() || e.targets().contains(module)) {
packages.add(e.source().replaceAll("\\.", "/"));
}
}
}
}
return packages;
}
private void addAllRequirePublicModules(String module, Set<String> modules) {
AsmModulePool p = pools.get(module);
if (p != null) {
for (Requires req : p.getDescriptor().requires()) {
if (req.modifiers().contains(PUBLIC)) {
modules.add(req.name());
addAllRequirePublicModules(req.name(), modules);
}
}
}
}
}
private interface VoidPoolVisitor {
void visit(AsmModulePool pool);
}
private interface VoidPoolVisitorEx {
void visit(AsmModulePool pool);
}
private interface RetPoolVisitor<P> {
P visit(AsmModulePool pool);
}
private final Map<String, AsmModulePool> pools = new LinkedHashMap<>();
private final AsmModulePool[] poolsArray;
private final AsmGlobalPoolImpl global;
private ModuleSorter moduleSorter;
/**
* A new Asm pools.
*
* @param inputResources The raw resources to build the pool from.
*/
public AsmPools(ModulePool inputResources) {
Objects.requireNonNull(inputResources);
Map<String, ModulePool> resPools = new LinkedHashMap<>();
Map<String, ModuleDescriptor> descriptors = new HashMap<>();
inputResources.entries().forEach(res -> {
ModulePool p = resPools.get(res.getModule());
if (p == null) {
p = new ModulePoolImpl(inputResources.getByteOrder(),
((ModulePoolImpl)inputResources).getStringTable());
resPools.put(res.getModule(), p);
}
if (res.getPath().endsWith("module-info.class")) {
ByteBuffer bb = ByteBuffer.wrap(res.getBytes());
ModuleDescriptor descriptor = ModuleDescriptor.read(bb);
descriptors.put(res.getModule(), descriptor);
}
p.add(res);
});
poolsArray = new AsmModulePool[resPools.size()];
int i = 0;
for (Entry<String, ModulePool> entry : resPools.entrySet()) {
ModuleDescriptor descriptor = descriptors.get(entry.getKey());
if (descriptor == null) {
throw new PluginException("module-info.class not found for " + entry.getKey() + " module");
}
AsmModulePool p = new AsmPoolImpl(entry.getValue(),
entry.getKey(), this, descriptor);
pools.put(entry.getKey(), p);
poolsArray[i] = p;
i += 1;
}
global = new AsmGlobalPoolImpl();
}
/**
* The pool containing all classes and other resources.
*
* @return The global pool
*/
public AsmGlobalPool getGlobalPool() {
return global;
}
/**
* A pool for a given module
*
* @param name The module name
* @return The pool that contains content of the passed module or null if
* the module doesn't exist.
*/
public AsmModulePool getModulePool(String name) {
Objects.requireNonNull(name);
return pools.get(name);
}
/**
* The array of module pools.
* @return The module pool array.
*/
public AsmModulePool[] getModulePools() {
return poolsArray.clone();
}
/**
* Set a module sorter. Sorter is used when computing the output resources.
*
* @param moduleSorter The module sorter
*/
public void setModuleSorter(ModuleSorter moduleSorter) {
Objects.requireNonNull(moduleSorter);
this.moduleSorter = moduleSorter;
}
/**
* Returns the pool of all the resources (transformed and unmodified). The
* input resources are replaced by the transformed ones. If a sorter has
* been set, it is used to sort in modules.
*
* @param outputResources The pool used to fill the jimage.
*/
public void fillOutputResources(ModulePool outputResources) {
// First sort modules
List<String> modules = new ArrayList<>();
for (String k : pools.keySet()) {
modules.add(k);
}
if (moduleSorter != null) {
modules = moduleSorter.sort(modules);
}
ModulePool output = new ModulePoolImpl(outputResources.getByteOrder(),
((ModulePoolImpl)outputResources).getStringTable());
for (String mn : modules) {
AsmPool pool = pools.get(mn);
pool.fillOutputResources(output);
}
sort(outputResources, output, global.sorter);
}
static void sort(ModulePool outputResources,
ModulePool transientOutput, Sorter sorter) {
if (sorter != null) {
List<String> order = sorter.sort(transientOutput);
for (String s : order) {
outputResources.add(transientOutput.findEntry(s).get());
}
} else {
transientOutput.entries().forEach(res-> {
outputResources.add(res);
});
}
}
private void visitFirstNonFailingPool(VoidPoolVisitorEx pv) {
boolean found = false;
for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
try {
pv.visit(entry.getValue());
found = true;
break;
} catch (Exception ex) {
// XXX OK, try another one.
}
}
if (!found) {
throw new PluginException("No module found");
}
}
private void visitAllPools(VoidPoolVisitor pv) {
for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
pv.visit(entry.getValue());
}
}
private void visitAllPoolsEx(VoidPoolVisitorEx pv) {
for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
pv.visit(entry.getValue());
}
}
private <P> P visitPoolsEx(RetPoolVisitor<P> pv) {
P p = null;
for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
try {
p = pv.visit(entry.getValue());
if (p != null) {
break;
}
} catch (Exception ex) {
// XXX OK, try another one.
}
}
return p;
}
private <P> P visitPools(RetPoolVisitor<P> pv) {
P p = null;
for (Entry<String, AsmModulePool> entry : pools.entrySet()) {
try {
p = pv.visit(entry.getValue());
if (p != null) {
break;
}
} catch (Exception ex) {
// XXX OK, try another one.
}
}
return p;
}
}

View File

@ -1,516 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.optim;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.tree.analysis.Analyzer;
import jdk.internal.org.objectweb.asm.tree.analysis.AnalyzerException;
import jdk.internal.org.objectweb.asm.tree.analysis.BasicInterpreter;
import jdk.internal.org.objectweb.asm.tree.analysis.BasicValue;
/**
* Split Java method onto a control flow.
*
*/
public final class ControlFlow {
/**
* A block of control
*/
public static final class Block implements Comparable<Block> {
private final InstructionNode firstInstruction;
private final List<InstructionNode> instr = new ArrayList<>();
private final List<Block> reachable = new ArrayList<>();
private final List<Block> exceptionHandlers = new ArrayList<>();
private boolean isExceptionHandler;
private Block(InstructionNode firstInstruction) {
this.firstInstruction = firstInstruction;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Block)) {
return false;
}
Block b = (Block) other;
return firstInstruction.equals(b.firstInstruction);
}
@Override
public int hashCode() {
return Objects.hashCode(this.firstInstruction);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (InstructionNode in : instr) {
builder.append(in).append(" ");
}
builder.append(" reachables: ");
for (Block r : reachable) {
builder.append(r.getFirstInstruction()).append(" ");
}
builder.append(" exception handlers: ");
for (Block r : exceptionHandlers) {
builder.append(r.getFirstInstruction()).append(" ");
}
return "block[" + getFirstInstruction() + "],ex:"
+ isExceptionHandler + ", " + builder.toString();
}
/**
* @return the firstInstruction
*/
public InstructionNode getFirstInstruction() {
return firstInstruction;
}
/**
* @return the instr
*/
public List<InstructionNode> getInstructions() {
return Collections.unmodifiableList(instr);
}
/**
* @return the reachable
*/
public List<Block> getReachableBlocks() {
return Collections.unmodifiableList(reachable);
}
/**
* @return the exceptionHandlers
*/
public List<Block> getExceptionHandlerBlocks() {
return Collections.unmodifiableList(exceptionHandlers);
}
@Override
public int compareTo(Block t) {
return this.firstInstruction.index - t.firstInstruction.index;
}
public boolean isExceptionHandler() {
return isExceptionHandler;
}
}
private class ClosureBuilder {
private final Block root;
private ClosureBuilder(Block root) {
Objects.requireNonNull(root);
this.root = root;
}
private Set<Block> build() {
Set<Block> allReachable = new TreeSet<>();
addAll(root, allReachable);
// filter out the reachable from outside this graph
Iterator<Block> it = allReachable.iterator();
Set<Block> toExclude = new HashSet<>();
while (it.hasNext()) {
Block b = it.next();
for (Block ref : blocks) {
if (!allReachable.contains(ref) && ref.reachable.contains(b)) {
addAll(b, toExclude);
break;
}
}
}
//System.err.println("TO EXCLUDE:\n " + toExclude);
allReachable.removeAll(toExclude);
//System.err.println("CLOSURE:\n " + allReachable);
return Collections.unmodifiableSet(allReachable);
}
// Compute the set of blocks reachable from the current block
private void addAll(Block current, Set<Block> closure) {
Objects.requireNonNull(current);
closure.add(current);
for (Block ex : current.exceptionHandlers) {
Objects.requireNonNull(ex);
if (!closure.contains(ex)) {
addAll(ex, closure);
}
}
for (Block r : current.reachable) {
Objects.requireNonNull(r);
if (!closure.contains(r)) {
addAll(r, closure);
}
}
}
}
/**
* An instruction
*/
public static final class InstructionNode {
private final int index;
private final List<InstructionNode> next = new ArrayList<>();
private final AbstractInsnNode instr;
private InstructionNode(int index, AbstractInsnNode instr) {
this.index = index;
this.instr = instr;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof InstructionNode)) {
return false;
}
final InstructionNode other = (InstructionNode) obj;
return this.getIndex() == other.getIndex();
}
@Override
public int hashCode() {
return this.getIndex();
}
@Override
public String toString() {
return getIndex() + "(" + (getInstr().getOpcode() == - 1 ? -1
: Integer.toHexString(getInstr().getOpcode())) + ")";
}
/**
* @return the index
*/
public int getIndex() {
return index;
}
/**
* @return the instr
*/
public AbstractInsnNode getInstr() {
return instr;
}
}
private final Map<Integer, Block> allBlocks;
private final List<Block> blocks = new ArrayList<>();
private ControlFlow(Map<Integer, Block> allBlocks) {
this.allBlocks = allBlocks;
for (Block b : allBlocks.values()) {
blocks.add(b);
}
Collections.sort(blocks);
}
public List<Block> getBlocks() {
return Collections.unmodifiableList(blocks);
}
public Block getBlock(int firstInstr) {
return allBlocks.get(firstInstr);
}
public static ControlFlow createControlFlow(String owner,
MethodNode method) throws Exception {
BlockBuilder bb = new BlockBuilder(owner, method);
return bb.build();
}
/**
* Return the set of blocks that are only reachable from this block For
* example, if b is an Exception handler, returns all the blocks reachable
* only from this handler
*
* @param b
* @return
*/
public Set<Block> getClosure(Block b) {
return new ClosureBuilder(b).build();
}
private static final class BlockBuilder {
private InstructionNode root;
private final Map<Integer, InstructionNode> instructions = new HashMap<>();
private final Map<Integer, List<Integer>> handlers = new HashMap<>();
private final Map<Integer, Block> allBlocks = new HashMap<>();
private final String owner;
private final MethodNode method;
private BlockBuilder(String owner, MethodNode method) {
this.owner = owner;
this.method = method;
}
private void analyze() throws AnalyzerException {
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(new BasicInterpreter()) {
@Override
protected boolean newControlFlowExceptionEdge(int insn,
int successor) {
List<Integer> lst = handlers.get(successor);
if (lst == null) {
lst = new ArrayList<>();
handlers.put(successor, lst);
}
lst.add(insn);
return true;
}
@Override
protected void newControlFlowEdge(int from,
int to) {
if (root == null) {
root = new InstructionNode(from, method.instructions.get(from));
instructions.put(from, root);
}
InstructionNode fromNode = instructions.get(from);
if (fromNode == null) {
fromNode = new InstructionNode(from, method.instructions.get(from));
instructions.put(from, fromNode);
}
InstructionNode toNode = instructions.get(to);
if (toNode == null) {
toNode = new InstructionNode(to, method.instructions.get(to));
instructions.put(to, toNode);
}
if (!fromNode.next.contains(toNode)) {
fromNode.next.add(toNode);
}
}
};
analyzer.analyze(owner, method);
}
private Block newBlock(InstructionNode firstInstruction) {
Objects.requireNonNull(firstInstruction);
Block b = new Block(firstInstruction);
allBlocks.put(firstInstruction.getIndex(), b);
return b;
}
private ControlFlow build() throws AnalyzerException {
analyze();
buildBlocks();
return new ControlFlow(allBlocks);
}
private void buildBlocks() {
List<Block> reachableBlocks = new ArrayList<>();
createBlocks(root, reachableBlocks);
List<Block> handlersBlocks = new ArrayList<>();
for (Entry<Integer, List<Integer>> entry : handlers.entrySet()) {
InstructionNode node = instructions.get(entry.getKey());
createBlocks(node, handlersBlocks);
}
// attach handler to try blocks
for (Entry<Integer, List<Integer>> entry : handlers.entrySet()) {
Block handlerBlock = allBlocks.get(entry.getKey());
handlerBlock.isExceptionHandler = true;
int startTry = entry.getValue().get(0);
Block tryBlock = allBlocks.get(startTry);
if (tryBlock == null) {
// Need to find the block that contains the instruction and
// make a new block
Block split = null;
for (Block b : allBlocks.values()) {
Iterator<InstructionNode> it = b.instr.iterator();
while (it.hasNext()) {
InstructionNode in = it.next();
if (split == null) {
if (in.index == startTry) {
split = newBlock(in);
split.instr.add(in);
it.remove();
}
} else {
split.instr.add(in);
it.remove();
}
}
if (split != null) {
Iterator<Block> reachables = b.reachable.iterator();
while (reachables.hasNext()) {
Block r = reachables.next();
split.reachable.add(r);
reachables.remove();
}
b.reachable.add(split);
break;
}
}
if (split == null) {
throw new RuntimeException("No try block for handler " + handlerBlock);
}
split.exceptionHandlers.add(handlerBlock);
} else {
tryBlock.exceptionHandlers.add(handlerBlock);
}
}
// System.err.println("ALL BLOCKS FOUND");
// Iterator<Entry<Integer, Block>> blockIt0 = allBlocks.entrySet().iterator();
// while (blockIt0.hasNext()) {
// Block b = blockIt0.next().getValue();
// System.err.println(b);
// }
//compute real exception blocks, if an instruction is in another block, stop.
Iterator<Entry<Integer, Block>> blockIt = allBlocks.entrySet().iterator();
while (blockIt.hasNext()) {
Block b = blockIt.next().getValue();
Iterator<InstructionNode> in = b.instr.iterator();
boolean found = false;
while (in.hasNext()) {
int i = in.next().getIndex();
if (found) {
in.remove();
} else {
if (startsWith(b, i, allBlocks.values())) {
// Move it to reachable
Block r = allBlocks.get(i);
b.reachable.add(r);
found = true;
in.remove();
} else {
}
}
}
}
// System.err.println("Reduced blocks");
// Iterator<Entry<Integer, Block>> blockIt1 = allBlocks.entrySet().iterator();
// while (blockIt1.hasNext()) {
// Block b = blockIt1.next().getValue();
// System.err.println(b);
// }
}
private boolean startsWith(Block block, int index, Collection<Block> reachableBlocks) {
for (Block b : reachableBlocks) {
if (b != block && !b.instr.isEmpty() && b.instr.get(0).getIndex() == index) {
return true;
}
}
return false;
}
private static final class StackItem {
private final InstructionNode instr;
private final Block currentBlock;
private StackItem(InstructionNode instr, Block currentBlock) {
Objects.requireNonNull(instr);
Objects.requireNonNull(currentBlock);
this.instr = instr;
this.currentBlock = currentBlock;
}
}
/**
* This algorithm can't be recursive, possibly too much instructions in
* methods.
*/
private void createBlocks(InstructionNode root, List<Block> blocks) {
final Stack<StackItem> stack = new Stack<>();
stack.push(new StackItem(root, newBlock(root)));
while (!stack.isEmpty()) {
final StackItem item = stack.pop();
final Block currentBlock = item.currentBlock;
final InstructionNode current = item.instr;
// loop
if (currentBlock.instr.contains(current)) {
currentBlock.reachable.add(currentBlock);
continue;
}
Block existing = allBlocks.get(current.index);
if (existing != null && existing != currentBlock) {
currentBlock.reachable.add(existing);
continue;
}
int previous = currentBlock.instr.size() > 0
? currentBlock.instr.get(currentBlock.instr.size() - 1).getIndex() : -1;
if (previous == -1 || current.getIndex() == previous + 1) {
currentBlock.instr.add(current);
if (current.next.isEmpty()) {
blocks.add(currentBlock);
} else {
if (current.next.size() > 1) {
blocks.add(currentBlock);
for (InstructionNode n : current.next) {
Block loop = allBlocks.get(n.index);
if (loop == null) {
Block newBlock = newBlock(n);
currentBlock.reachable.add(newBlock);
stack.push(new StackItem(n, newBlock));
} else { // loop
currentBlock.reachable.add(loop);
}
}
} else {
stack.push(new StackItem(current.next.get(0),
currentBlock));
}
}
} else { // to a new block...
// Do nothing...
blocks.add(currentBlock);
Block newBlock = newBlock(current);
currentBlock.reachable.add(newBlock);
stack.push(new StackItem(current, newBlock));
}
}
}
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.optim;
import java.io.IOException;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.tools.jlink.internal.plugins.OptimizationPlugin.MethodOptimizer;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.optim.ControlFlow.Block;
import jdk.tools.jlink.internal.plugins.optim.ReflectionOptimizer.Data;
import jdk.tools.jlink.internal.plugins.optim.ReflectionOptimizer.TypeResolver;
/**
* MethodOptimizer that removes Class.forName when possible.
* WARNING: This code is experimental.
* TODO: Need to check that the type is accessible prior to replace with a constant.
*/
public class ForNameFolding implements MethodOptimizer {
private int numNotReplaced;
private int numReplacement;
private int numRemovedHandlers;
private int instructionsRemoved;
private Consumer<String> logger;
@Override
public boolean optimize(Consumer<String> logger, AsmPools pools,
AsmModulePool modulePool,
ClassNode cn, MethodNode m, TypeResolver resolver) throws Exception {
this.logger = logger;
Data data = ReflectionOptimizer.replaceWithClassConstant(cn, m, createResolver(resolver));
instructionsRemoved += data.removedInstructions();
numRemovedHandlers += data.removedHandlers().size();
for (Entry<String, Set<Block>> entry : data.removedHandlers().entrySet()) {
logRemoval(cn.name + "." + m.name + "removed block for " + entry.getKey()
+ " : " + entry.getValue());
}
return data.removedInstructions() > 0;
}
public TypeResolver createResolver(TypeResolver resolver) {
return (ClassNode cn, MethodNode mn, String type) -> {
ClassReader reader = resolver.resolve(cn, mn, type);
if (reader == null) {
logNotReplaced(type);
} else {
logReplaced(type);
}
return reader;
};
}
private void logReplaced(String type) {
numReplacement += 1;
}
private void logNotReplaced(String type) {
numNotReplaced += 1;
if (logger != null) {
logger.accept(type + " not resolved");
}
}
private void logRemoval(String content) {
numRemovedHandlers += 1;
if (logger != null) {
logger.accept(content);
}
}
@Override
public void close() throws IOException {
if (logger != null) {
logger.accept("Class.forName Folding results:\n " + numReplacement
+ " removed reflection. " + numRemovedHandlers
+ " removed exception handlers."
+ numNotReplaced + " types unknown. "
+ instructionsRemoved + " instructions removed\n");
}
}
}

View File

@ -1,226 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.optim;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.LabelNode;
import jdk.internal.org.objectweb.asm.tree.LdcInsnNode;
import jdk.internal.org.objectweb.asm.tree.LineNumberNode;
import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
import jdk.tools.jlink.internal.plugins.optim.ControlFlow.Block;
/**
* Implement the reflection optimization.
*/
public class ReflectionOptimizer {
public static class Data {
private int removedInstructions;
private final Map<String, Set<Block>> removedHandlers = new HashMap<>();
private Data() {
}
public int removedInstructions() {
return removedInstructions;
}
public Map<String, Set<Block>> removedHandlers() {
return Collections.unmodifiableMap(removedHandlers);
}
}
public interface TypeResolver {
public ClassReader resolve(ClassNode cn, MethodNode m, String type);
}
public static Data replaceWithClassConstant(ClassNode cn, MethodNode m,
TypeResolver cch)
throws Exception {
Iterator<AbstractInsnNode> it = m.instructions.iterator();
LdcInsnNode insNode = null;
Map<LdcInsnNode, LdcInsnNode> replacement = new IdentityHashMap<>();
Data data = new Data();
while (it.hasNext()) {
AbstractInsnNode n = it.next();
if (n instanceof LdcInsnNode) {
LdcInsnNode ldc = (LdcInsnNode) n;
if (ldc.cst instanceof String) {
insNode = ldc;
}
} else {
if (n instanceof MethodInsnNode && insNode != null) {
MethodInsnNode met = (MethodInsnNode) n;
if (met.name.equals("forName")
&& met.owner.equals("java/lang/Class")
&& met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
// Can we load the type?
Type type = null;
String binaryName = insNode.cst.toString().replaceAll("\\.", "/");
String unaryClassName = binaryName;
int arrayIndex = binaryName.lastIndexOf("[");
if (arrayIndex >= 0) {
int objIndex = unaryClassName.indexOf("L");
if (objIndex >= 0) {
unaryClassName = unaryClassName.substring(objIndex + 1);
unaryClassName = unaryClassName.substring(0,
unaryClassName.length() - 1);
} else {
//primitive, this is just fine.
type = Type.getObjectType(binaryName);
}
}
if (type == null) {
if (cch.resolve(cn, m, unaryClassName) != null) {
type = Type.getObjectType(binaryName);
}
}
if (type != null) {
replacement.put(insNode, new LdcInsnNode(type));
it.remove();
data.removedInstructions += 1;
}
} else {
insNode = null;
}
// Virtual node, not taken into account
} else if (!(n instanceof LabelNode) && !(n instanceof LineNumberNode)) {
insNode = null;
}
}
}
for (Map.Entry<LdcInsnNode, LdcInsnNode> entry : replacement.entrySet()) {
m.instructions.set(entry.getKey(), entry.getValue());
}
if (!replacement.isEmpty()) {
String[] types = {"java/lang/ClassNotFoundException"};
data.removedInstructions += deleteExceptionHandlers(cch, data, cn, m, types);
}
return data;
}
private static int deleteExceptionHandlers(TypeResolver cch, Data data,
ClassNode cn, MethodNode m, String[] exTypes)
throws Exception {
int instructionsRemoved = 0;
for (String ex : exTypes) {
ControlFlow f = ControlFlow.createControlFlow(cn.name, m);
List<Integer> removed = new ArrayList<>();
Set<ControlFlow.Block> blocksToRemove = new TreeSet<>();
Iterator<TryCatchBlockNode> it = m.tryCatchBlocks.iterator();
List<TryCatchBlockNode> tcbToRemove = new ArrayList<>();
while (it.hasNext()) {
TryCatchBlockNode bn = it.next();
if (bn.type == null
|| !bn.type.equals(ex) // An empty block
|| tcbToRemove.contains(bn)) {
continue;
}
// Check that the handler is still required
if (!Utils.canThrowCheckedException(cch, cn, m, bn)) {
// try to suppress it.
int block = m.instructions.indexOf(bn.handler);
ControlFlow.Block blockHandler = f.getBlock(block);
if (blockHandler == null) {
if (removed.contains(block)) {
continue;
} else {
throw new Exception(cn.name
+ ", no block for handler " + block);
}
}
tcbToRemove.add(bn);
// Don't delete block if shared (eg: ClassNotFoundException | NoSuchMethodException |
Iterator<TryCatchBlockNode> it2 = m.tryCatchBlocks.iterator();
boolean cont = false;
while (it2.hasNext()) {
TryCatchBlockNode bn2 = it2.next();
if (bn2 != bn) {
if (bn2.start.equals(bn.start)) {
cont = true;
}
}
}
if (cont) {
continue;
}
// An handler is a root, blocks that are only reachable by it
// can be removed.
Set<ControlFlow.Block> blocks = f.getClosure(blockHandler);
StringBuilder sb = new StringBuilder();
for (ControlFlow.Block b : blocks) {
sb.append(b).append("\n");
removed.add(b.getFirstInstruction().getIndex());
// Remove Exception handler if the associated block has been removed
for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
if (tcb != bn) {
// An exception handler removed as a side effect.
if (b.isExceptionHandler()
&& b.getFirstInstruction().getInstr() == tcb.handler) {
tcbToRemove.add(tcb);
}
}
}
}
blocksToRemove.addAll(blocks);
data.removedHandlers.put(ex, blocks);
}
}
m.tryCatchBlocks.removeAll(tcbToRemove);
if (!blocksToRemove.isEmpty()) {
for (ControlFlow.Block b : blocksToRemove) {
for (ControlFlow.InstructionNode ins : b.getInstructions()) {
if (ins.getInstr().getOpcode() > 0) {
instructionsRemoved += 1;
}
}
}
Utils.suppressBlocks(m, blocksToRemove);
}
}
return instructionsRemoved;
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) 2015, 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.tools.jlink.internal.plugins.optim;
import java.util.Iterator;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
/**
* Optimization utility methods
*/
public class Utils {
public static boolean canThrowCheckedException(ReflectionOptimizer.TypeResolver cch,
ClassNode classNode, MethodNode m, TryCatchBlockNode bn) throws Exception {
int istart = m.instructions.indexOf(bn.start);
int iend = m.instructions.indexOf(bn.end);
for (int i = istart; i < iend - 1; i++) {
AbstractInsnNode instr = m.instructions.get(i);
if (instr instanceof MethodInsnNode) {
MethodInsnNode meth = (MethodInsnNode) instr;
ClassReader reader = cch.resolve(classNode, m, meth.owner);
if (reader != null) {
ClassNode cn = new ClassNode();
reader.accept(cn, ClassReader.EXPAND_FRAMES);
for (MethodNode method : cn.methods) {
if (method.name.equals(meth.name)) {
for (String e : method.exceptions) {
if (e.equals(bn.type)) {
return true;
}
}
}
}
} else {
return true;
}
}
}
return false;
}
public static void suppressBlocks(MethodNode m, Set<ControlFlow.Block> toRemove) throws Exception {
m.instructions.resetLabels();
Iterator<AbstractInsnNode> it = m.instructions.iterator();
while (it.hasNext()) {
AbstractInsnNode n = it.next();
Iterator<TryCatchBlockNode> handlers = m.tryCatchBlocks.iterator();
boolean cont = false;
// Do not delete instructions that are end of other try block.
while (handlers.hasNext()) {
TryCatchBlockNode handler = handlers.next();
if (handler.end == n) {
cont = true;
}
}
if (cont) {
continue;
}
for (ControlFlow.Block b : toRemove) {
for (ControlFlow.InstructionNode ins : b.getInstructions()) {
if (ins.getInstr() == n) {
it.remove();
}
}
}
}
}
}

View File

@ -31,11 +31,10 @@ add: is to add properties to the 'release' file.\n\
Any number of <key>=<value> pairs can be passed.\n\
del: is to delete the list of keys in release file.
class-optim.argument=<all|forName-folding>[:log=<log file>]
class-for-name.argument=
class-optim.description=\
Class optimization. Warning: This plugin is experimental.\n\
An optional <log file> can be specified to log applied optimizations.
class-for-name.description=\
Class optimization: convert Class.forName calls to constant loads.
compress.argument=<0|1|2>[:filter=<pattern-list>]
@ -47,7 +46,6 @@ Level 2: both.\n\
An optional <pattern-list> filter can be specified to list the pattern of\n\
files to be included.
compact-cp.argument=<resource paths>
compact-cp.description=Constant Pool strings sharing.\n\

View File

@ -39,9 +39,9 @@ module jdk.jlink {
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.StripNativeCommandsPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.OrderResourcesPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.DefaultCompressPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.OptimizationPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ExcludeVMPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.IncludeLocalesPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin;
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ReleaseInfoPlugin;
}
provides jdk.tools.jlink.plugin.Plugin with jdk.tools.jlink.internal.plugins.ClassForNamePlugin;
}

View File

@ -1,354 +0,0 @@
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.tree.AbstractInsnNode;
import jdk.internal.org.objectweb.asm.tree.ClassNode;
import jdk.internal.org.objectweb.asm.tree.MethodInsnNode;
import jdk.internal.org.objectweb.asm.tree.MethodNode;
import jdk.internal.org.objectweb.asm.tree.TryCatchBlockNode;
import jdk.tools.jlink.internal.PluginRepository;
import jdk.tools.jlink.internal.ModulePoolImpl;
import jdk.tools.jlink.internal.plugins.OptimizationPlugin;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.internal.plugins.optim.ControlFlow;
import jdk.tools.jlink.internal.plugins.optim.ControlFlow.Block;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
import tests.Helper;
import tests.JImageGenerator;
/*
* Copyright (c) 2015, 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
* @summary Test image creation with class optimization
* @author Jean-Francois Denise
* @library ../lib
* @modules java.base/jdk.internal.jimage
* jdk.jdeps/com.sun.tools.classfile
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jmod
* jdk.jlink/jdk.tools.jimage
* jdk.jlink/jdk.tools.jlink.internal.plugins
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* jdk.jlink/jdk.tools.jlink.internal.plugins.optim
* java.base/jdk.internal.org.objectweb.asm
* java.base/jdk.internal.org.objectweb.asm.tree
* java.base/jdk.internal.org.objectweb.asm.util
* jdk.compiler
* @build tests.*
* @run main JLinkOptimTest
*/
public class JLinkOptimTest {
private static final String EXPECTED = "expected";
private static Helper helper;
public static class ControlFlowPlugin extends AsmPlugin {
private boolean called;
private int numMethods;
private int numBlocks;
private static final String NAME = "test-optim";
private ControlFlowPlugin() {
}
@Override
public void visit(AsmPools pools) {
called = true;
for (AsmModulePool p : pools.getModulePools()) {
p.visitClassReaders((reader) -> {
ClassNode cn = new ClassNode();
if ((reader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
reader.accept(cn, ClassReader.EXPAND_FRAMES);
for (MethodNode m : cn.methods) {
if ((m.access & Opcodes.ACC_ABSTRACT) == 0
&& (m.access & Opcodes.ACC_NATIVE) == 0) {
numMethods += 1;
try {
ControlFlow f
= ControlFlow.createControlFlow(cn.name, m);
for (Block b : f.getBlocks()) {
numBlocks += 1;
f.getClosure(b);
}
} catch (Throwable ex) {
//ex.printStackTrace();
throw new RuntimeException("Exception in "
+ cn.name + "." + m.name, ex);
}
}
}
}
return null;
});
}
}
@Override
public String getName() {
return NAME;
}
}
private static void testForName() throws Exception {
String moduleName = "optimplugin";
Path src = Paths.get(System.getProperty("test.src")).resolve(moduleName);
Path classes = helper.getJmodClassesDir().resolve(moduleName);
JImageGenerator.compile(src, classes);
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path root = fs.getPath("/modules/java.base");
// Access module-info.class to be reused as fake module-info.class
List<ModuleEntry> javabaseResources = new ArrayList<>();
try (Stream<Path> stream = Files.walk(root)) {
for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext();) {
Path p = iterator.next();
if (Files.isRegularFile(p)) {
try {
javabaseResources.add(ModuleEntry.create(p.toString().
substring("/modules".length()), Files.readAllBytes(p)));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
}
//forName folding
ModulePoolImpl pool = new ModulePoolImpl();
byte[] content = Files.readAllBytes(classes.
resolve("optim").resolve("ForNameTestCase.class"));
byte[] content2 = Files.readAllBytes(classes.
resolve("optim").resolve("AType.class"));
byte[] mcontent = Files.readAllBytes(classes.resolve("module-info.class"));
pool.add(ModuleEntry.create("/optimplugin/optim/ForNameTestCase.class", content));
pool.add(ModuleEntry.create("/optimplugin/optim/AType.class", content2));
pool.add(ModuleEntry.create("/optimplugin/module-info.class", mcontent));
for (ModuleEntry r : javabaseResources) {
pool.add(r);
}
OptimizationPlugin plugin = new OptimizationPlugin();
Map<String, String> optional = new HashMap<>();
optional.put(OptimizationPlugin.NAME, OptimizationPlugin.FORNAME_REMOVAL);
optional.put(OptimizationPlugin.LOG, "forName.log");
plugin.configure(optional);
ModulePool out = new ModulePoolImpl();
plugin.visit(pool, out);
ModuleEntry result = out.entries().iterator().next();
ClassReader optimReader = new ClassReader(result.getBytes());
ClassNode optimClass = new ClassNode();
optimReader.accept(optimClass, ClassReader.EXPAND_FRAMES);
if (!optimClass.name.equals("optim/ForNameTestCase")) {
throw new Exception("Invalid class " + optimClass.name);
}
if (optimClass.methods.size() < 2) {
throw new Exception("Not enough methods in new class");
}
for (MethodNode mn : optimClass.methods) {
if (!mn.name.contains("forName") && !mn.name.contains("<clinit>")) {
continue;
}
if (mn.name.startsWith("negative")) {
checkForName(mn);
} else {
checkNoForName(mn);
}
}
Map<String, byte[]> newClasses = new HashMap<>();
newClasses.put("optim.ForNameTestCase", result.getBytes());
newClasses.put("optim.AType", content2);
MemClassLoader loader = new MemClassLoader(newClasses);
Class<?> loaded = loader.loadClass("optim.ForNameTestCase");
if (loaded.getDeclaredMethods().length < 2) {
throw new Exception("Not enough methods in new class");
}
for (Method m : loaded.getDeclaredMethods()) {
if (m.getName().contains("Exception")) {
try {
m.invoke(null);
} catch (Exception ex) {
//ex.getCause().printStackTrace();
if (!ex.getCause().getMessage().equals(EXPECTED)) {
throw new Exception("Unexpected exception " + ex);
}
}
} else if (!m.getName().startsWith("negative")) {
Class<?> clazz = (Class<?>) m.invoke(null);
if (clazz != String.class && clazz != loader.findClass("optim.AType")) {
throw new Exception("Invalid class " + clazz);
}
}
}
}
private static void checkNoForName(MethodNode m) throws Exception {
Iterator<AbstractInsnNode> it = m.instructions.iterator();
while (it.hasNext()) {
AbstractInsnNode n = it.next();
if (n instanceof MethodInsnNode) {
MethodInsnNode met = (MethodInsnNode) n;
if (met.name.equals("forName")
&& met.owner.equals("java/lang/Class")
&& met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
throw new Exception("forName not removed in " + m.name);
}
}
}
for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
throw new Exception("ClassNotFoundException Block not removed for " + m.name);
}
}
}
private static void checkForName(MethodNode m) throws Exception {
Iterator<AbstractInsnNode> it = m.instructions.iterator();
boolean found = false;
while (it.hasNext()) {
AbstractInsnNode n = it.next();
if (n instanceof MethodInsnNode) {
MethodInsnNode met = (MethodInsnNode) n;
if (met.name.equals("forName")
&& met.owner.equals("java/lang/Class")
&& met.desc.equals("(Ljava/lang/String;)Ljava/lang/Class;")) {
found = true;
break;
}
}
}
if (!found) {
throw new Exception("forName removed but shouldn't have");
}
found = false;
for (TryCatchBlockNode tcb : m.tryCatchBlocks) {
if (tcb.type.equals(ClassNotFoundException.class.getName().replaceAll("\\.", "/"))) {
found = true;
break;
}
}
if (!found) {
throw new Exception("tryCatchBlocks removed but shouldn't have");
}
}
static class MemClassLoader extends ClassLoader {
private final Map<String, byte[]> classes;
private final Map<String, Class<?>> cache = new HashMap<>();
MemClassLoader(Map<String, byte[]> classes) {
super(null);
this.classes = classes;
}
@Override
public Class findClass(String name) throws ClassNotFoundException {
Class<?> clazz = cache.get(name);
if (clazz == null) {
byte[] b = classes.get(name);
if (b == null) {
return super.findClass(name);
} else {
clazz = defineClass(name, b, 0, b.length);
cache.put(name, clazz);
}
}
return clazz;
}
}
public static void main(String[] args) throws Exception {
helper = Helper.newHelper();
if (helper == null) {
System.err.println("Test not run");
return;
}
testForName();
helper.generateDefaultModules();
helper.generateDefaultJModule("optim1", "java.se");
{
String[] userOptions = {"--class-optim=all:log=./class-optim-log.txt"};
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
helper.checkImage(imageDir, "optim1", null, null);
}
{
String[] userOptions = {"--class-optim=forName-folding:log=./class-optim-log.txt"};
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
helper.checkImage(imageDir, "optim1", null, null);
}
{
ControlFlowPlugin plugin = new ControlFlowPlugin();
PluginRepository.registerPlugin(plugin);
String[] userOptions = {"--test-optim"};
Path imageDir = helper.generateDefaultImage(userOptions, "optim1").assertSuccess();
helper.checkImage(imageDir, "optim1", null, null);
//System.out.println("Num methods analyzed " + provider.numMethods
// + "num blocks " + provider.numBlocks);
if (!plugin.called) {
throw new Exception("Plugin not called");
}
if (plugin.numMethods < 1000) {
throw new Exception("Not enough method called, should be "
+ "around 10000 but is " + plugin.numMethods);
}
if (plugin.numBlocks < 100000) {
throw new Exception("Not enough blocks, should be "
+ "around 640000 but is " + plugin.numMethods);
}
}
}
}

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test resource transformation.
* @author Andrei Eremeev
* @modules java.base/jdk.internal.org.objectweb.asm
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* jdk.jdeps/com.sun.tools.classfile
* @build AsmPluginTestBase
* @run main AddForgetResourcesTest
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.Method;
import java.io.UncheckedIOException;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.internal.plugins.asm.AsmGlobalPool;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFile;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableClassPool;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableResourcePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class AddForgetResourcesTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new AddForgetResourcesTest().test();
}
@Override
public void test() throws Exception {
TestPlugin[] plugins = new TestPlugin[] {
new AddClassesPlugin(),
new AddResourcesPlugin(),
new ReplaceClassesPlugin(),
new ReplaceResourcesPlugin(),
new ForgetClassesPlugin(),
new ForgetResourcesPlugin(),
new AddForgetClassesPlugin(),
new AddForgetResourcesPlugin(),
new ComboPlugin()
};
for (TestPlugin p : plugins) {
ModulePool out = p.visit(getPool());
p.test(getPool(), out);
}
}
private static final String SUFFIX = "HELLOWORLD";
private static class RenameClassVisitor extends ClassVisitor {
public RenameClassVisitor(ClassWriter cv) {
super(Opcodes.ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name + SUFFIX, signature, superName, interfaces);
}
}
private static class AddMethodClassVisitor extends ClassVisitor {
public AddMethodClassVisitor(ClassWriter cv) {
super(Opcodes.ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.visitMethod(0, SUFFIX, "()V", null, null);
super.visit(version, access, name, signature, superName, interfaces);
}
}
private class AddClassesPlugin extends TestPlugin {
private int expected = 0;
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
WritableClassPool transformedClasses = globalPool.getTransformedClasses();
expected = globalPool.getClasses().size();
for (ModuleEntry res : globalPool.getClasses()) {
ClassReader reader = globalPool.getClassReader(res);
String className = reader.getClassName();
if (!className.endsWith("module-info")) {
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
reader.accept(new RenameClassVisitor(writer), ClassReader.EXPAND_FRAMES);
transformedClasses.addClass(writer);
++expected;
}
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) {
Collection<ModuleEntry> inClasses = extractClasses(inResources);
Collection<ModuleEntry> outClasses = extractClasses(outResources);
if (expected != outClasses.size()) {
throw new AssertionError("Classes were not added. Expected: " + expected
+ ", got: " + outClasses.size());
}
for (ModuleEntry in : inClasses) {
String path = in.getPath();
if (!outClasses.contains(in)) {
throw new AssertionError("Class not found: " + path);
}
if (path.endsWith("module-info.class")) {
continue;
}
String modifiedPath = path.replace(".class", SUFFIX + ".class");
if (!outClasses.contains(ModuleEntry.create(modifiedPath, new byte[0]))) {
throw new AssertionError("Class not found: " + modifiedPath);
}
}
}
}
private class AddResourcesPlugin extends TestPlugin {
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
for (ModuleEntry res : globalPool.getResourceFiles()) {
String path = res.getPath();
String moduleName = getModule(path);
AsmModulePool modulePool = pools.getModulePool(moduleName);
WritableResourcePool resourcePool = modulePool.getTransformedResourceFiles();
resourcePool.addResourceFile(new ResourceFile(removeModule(res.getPath()) + SUFFIX,
res.getBytes()));
}
}
@Override
public void test(ModulePool in, ModulePool out) throws Exception {
Collection<ModuleEntry> inResources = extractResources(in);
Collection<ModuleEntry> outResources = extractResources(out);
if (2 * inResources.size() != outResources.size()) {
throw new AssertionError("Classes were not added. Expected: " + (2 * inResources.size())
+ ", got: " + outResources.size());
}
for (ModuleEntry r : inResources) {
String path = r.getPath();
if (!outResources.contains(r)) {
throw new AssertionError("Class not found: " + path);
}
String modifiedPath = path + SUFFIX;
if (!outResources.contains(ModuleEntry.create(modifiedPath, new byte[0]))) {
throw new AssertionError("Class not found: " + modifiedPath);
}
}
}
}
private class ReplaceClassesPlugin extends TestPlugin {
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
WritableClassPool transformedClasses = globalPool.getTransformedClasses();
for (ModuleEntry res : globalPool.getClasses()) {
ClassReader reader = globalPool.getClassReader(res);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
reader.accept(new AddMethodClassVisitor(writer), ClassReader.EXPAND_FRAMES);
transformedClasses.addClass(writer);
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
Collection<ModuleEntry> inClasses = extractClasses(inResources);
Collection<ModuleEntry> outClasses = extractClasses(outResources);
if (inClasses.size() != outClasses.size()) {
throw new AssertionError("Number of classes. Expected: " + (inClasses.size())
+ ", got: " + outClasses.size());
}
for (ModuleEntry out : outClasses) {
String path = out.getPath();
if (!inClasses.contains(out)) {
throw new AssertionError("Class not found: " + path);
}
ClassFile cf = ClassFile.read(new ByteArrayInputStream(out.getBytes()));
if (path.endsWith("module-info.class")) {
continue;
}
boolean failed = true;
for (Method m : cf.methods) {
if (m.getName(cf.constant_pool).equals(SUFFIX)) {
failed = false;
}
}
if (failed) {
throw new AssertionError("Not found method with name " + SUFFIX + " in class " + path);
}
}
}
}
private class ReplaceResourcesPlugin extends TestPlugin {
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
for (ModuleEntry res : globalPool.getResourceFiles()) {
String path = res.getPath();
AsmModulePool modulePool = pools.getModulePool(getModule(path));
modulePool.getTransformedResourceFiles().addResourceFile(new ResourceFile(removeModule(path),
"HUI".getBytes()));
}
}
@Override
public void test(ModulePool in, ModulePool out) throws Exception {
Collection<ModuleEntry> inResources = extractResources(in);
Collection<ModuleEntry> outResources = extractResources(out);
if (inResources.size() != outResources.size()) {
throw new AssertionError("Number of resources. Expected: " + inResources.size()
+ ", got: " + outResources.size());
}
for (ModuleEntry r : outResources) {
String path = r.getPath();
if (!inResources.contains(r)) {
throw new AssertionError("Resource not found: " + path);
}
String content = new String(r.getBytes());
if (!"HUI".equals(content)) {
throw new AssertionError("Content expected: 'HUI', got: " + content);
}
}
}
}
private class ForgetClassesPlugin extends TestPlugin {
private int expected = 0;
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
WritableClassPool transformedClasses = globalPool.getTransformedClasses();
int i = 0;
for (ModuleEntry res : globalPool.getClasses()) {
String path = removeModule(res.getPath());
String className = path.replace(".class", "");
if ((i & 1) == 0 && !className.endsWith("module-info")) {
transformedClasses.forgetClass(className);
} else {
++expected;
}
i ^= 1;
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
Collection<ModuleEntry> outClasses = extractClasses(outResources);
if (expected != outClasses.size()) {
throw new AssertionError("Number of classes. Expected: " + expected +
", got: " + outClasses.size());
}
}
}
private class ForgetResourcesPlugin extends TestPlugin {
private int expectedAmount = 0;
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
int i = 0;
for (ModuleEntry res : globalPool.getResourceFiles()) {
String path = res.getPath();
if (!path.contains("META-INF/services")) {
if ((i & 1) == 0) {
AsmModulePool modulePool = pools.getModulePool(getModule(path));
modulePool.getTransformedResourceFiles().forgetResourceFile(removeModule(res.getPath()));
} else {
++expectedAmount;
}
i ^= 1;
} else {
++expectedAmount;
}
}
}
@Override
public void test(ModulePool in, ModulePool out) throws Exception {
Collection<ModuleEntry> outResources = extractResources(out);
if (expectedAmount != outResources.size()) {
throw new AssertionError("Number of classes. Expected: " + expectedAmount
+ ", got: " + outResources.size());
}
}
}
private class AddForgetClassesPlugin extends TestPlugin {
private int expected = 0;
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
WritableClassPool transformedClasses = globalPool.getTransformedClasses();
int i = 0;
for (ModuleEntry res : globalPool.getClasses()) {
ClassReader reader = globalPool.getClassReader(res);
String className = reader.getClassName();
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
if (!className.endsWith("module-info")) {
reader.accept(new RenameClassVisitor(writer), ClassReader.EXPAND_FRAMES);
transformedClasses.addClass(writer);
++expected;
}
if ((i & 1) == 0 && !className.endsWith("module-info")) {
transformedClasses.forgetClass(className);
} else {
++expected;
}
i ^= 1;
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
Collection<ModuleEntry> outClasses = extractClasses(outResources);
if (expected != outClasses.size()) {
throw new AssertionError("Number of classes. Expected: " + expected
+ ", got: " + outClasses.size());
}
}
}
private class AddForgetResourcesPlugin extends TestPlugin {
private int expectedAmount = 0;
@Override
public void visit() {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
int i = 0;
for (ModuleEntry res : globalPool.getResourceFiles()) {
String path = res.getPath();
String moduleName = getModule(path);
if (!path.contains("META-INF")) {
AsmModulePool modulePool = pools.getModulePool(moduleName);
WritableResourcePool transformedResourceFiles = modulePool.getTransformedResourceFiles();
String newPath = removeModule(path) + SUFFIX;
transformedResourceFiles.addResourceFile(new ResourceFile(newPath, res.getBytes()));
if ((i & 1) == 0) {
transformedResourceFiles.forgetResourceFile(newPath);
} else {
++expectedAmount;
}
i ^= 1;
}
++expectedAmount;
}
}
@Override
public void test(ModulePool inResources, ModulePool out) throws Exception {
Collection<ModuleEntry> outResources = extractResources(out);
if (expectedAmount != outResources.size()) {
throw new AssertionError("Number of classes. Expected: " + expectedAmount
+ ", got: " + outResources.size());
}
}
}
private class ComboPlugin extends TestPlugin {
private class RenameClassVisitor extends ClassVisitor {
public RenameClassVisitor(ClassWriter cv) {
super(Opcodes.ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name + SUFFIX, signature, superName, interfaces);
}
}
@Override
public void visit() {
try {
renameClasses();
renameResources();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
if (!isVisitCalled()) {
throw new AssertionError("Resources not visited");
}
AsmGlobalPool globalPool = getPools().getGlobalPool();
if (globalPool.getTransformedClasses().getClasses().size() != getClasses().size()) {
throw new AssertionError("Number of transformed classes not equal to expected");
}
// Check that only renamed classes and resource files are in the result.
outResources.entries().forEach(r -> {
String resourceName = r.getPath();
if (resourceName.endsWith(".class") && !resourceName.endsWith("module-info.class")) {
if (!resourceName.endsWith(SUFFIX + ".class")) {
throw new AssertionError("Class not renamed " + resourceName);
}
} else if (resourceName.contains("META-INF/services/") && MODULES.containsKey(r.getModule())) {
String newClassName = new String(r.getBytes());
if(!newClassName.endsWith(SUFFIX)) {
throw new AssertionError("Resource file not renamed " + resourceName);
}
}
});
}
private void renameResources() throws IOException {
AsmPools pools = getPools();
// Rename the resource Files
for (Map.Entry<String, List<String>> mod : MODULES.entrySet()) {
String moduleName = mod.getKey();
AsmModulePool modulePool = pools.getModulePool(moduleName);
for (ModuleEntry res : modulePool.getResourceFiles()) {
ResourceFile resFile = modulePool.getResourceFile(res);
if (resFile.getPath().startsWith("META-INF/services/")) {
String newContent = new String(resFile.getContent()) + SUFFIX;
ResourceFile newResourceFile = new ResourceFile(resFile.getPath(),
newContent.getBytes());
modulePool.getTransformedResourceFiles().addResourceFile(newResourceFile);
}
}
}
}
private void renameClasses() throws IOException {
AsmPools pools = getPools();
AsmGlobalPool globalPool = pools.getGlobalPool();
WritableClassPool transformedClasses = globalPool.getTransformedClasses();
for (ModuleEntry res : globalPool.getClasses()) {
if (res.getPath().endsWith("module-info.class")) {
continue;
}
ClassReader reader = globalPool.getClassReader(res);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
RenameClassVisitor visitor = new RenameClassVisitor(writer);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
transformedClasses.forgetClass(reader.getClassName());
transformedClasses.addClass(writer);
}
}
}
}

View File

@ -1,249 +0,0 @@
/*
* Copyright (c) 2015, 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.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.tools.jlink.internal.ModulePoolImpl;
import jdk.tools.jlink.internal.StringTable;
import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public abstract class AsmPluginTestBase {
protected static final String TEST_MODULE = "jlink.test";
protected static final Map<String, List<String>> MODULES;
private static final Predicate<ModuleEntry> isClass = r -> r.getPath().endsWith(".class");
private final List<String> classes;
private final List<String> resources;
private final ModulePool pool;
static {
Map<String, List<String>> map = new HashMap<>();
map.put("jdk.localedata", new ArrayList<>());
map.put("java.base", new ArrayList<>());
map.put(TEST_MODULE, new ArrayList<>());
MODULES = Collections.unmodifiableMap(map);
}
public static boolean isImageBuild() {
Path javaHome = Paths.get(System.getProperty("test.jdk"));
Path jmods = javaHome.resolve("jmods");
return Files.exists(jmods);
}
public AsmPluginTestBase() {
try {
List<String> classes = new ArrayList<>();
List<String> resources = new ArrayList<>();
pool = new ModulePoolImpl();
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path root = fs.getPath("/modules");
List<byte[]> moduleInfos = new ArrayList<>();
try (Stream<Path> stream = Files.walk(root)) {
for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext(); ) {
Path p = iterator.next();
if (Files.isRegularFile(p)) {
String module = p.toString().substring("/modules/".length());
module = module.substring(0, module.indexOf("/"));
if (MODULES.keySet().contains(module)) {
try {
boolean isModuleInfo = p.endsWith("module-info.class");
if (isModuleInfo) {
moduleInfos.add(Files.readAllBytes(p));
}
byte[] content = Files.readAllBytes(p);
if (p.toString().endsWith(".class") && !isModuleInfo) {
classes.add(toClassName(p));
} else if (!isModuleInfo) {
MODULES.get(module).add(toResourceFile(p));
}
resources.add(toPath(p.toString()));
ModuleEntry res = ModuleEntry.create(toPath(p.toString()), content);
pool.add(res);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
}
}
// There is more than 10 classes in java.base...
if (classes.size() < 10 || pool.getEntryCount() < 10) {
throw new AssertionError("Not expected resource or class number");
}
//Add a fake resource file
String content = "java.lang.Object";
String path = "META-INF/services/com.foo.BarProvider";
ModuleEntry resFile = ModuleEntry.create("/" + TEST_MODULE + "/" +
path, content.getBytes());
pool.add(resFile);
ModuleEntry fakeInfoFile = ModuleEntry.create("/" + TEST_MODULE
+ "/module-info.class", moduleInfos.get(0));
pool.add(fakeInfoFile);
MODULES.get(TEST_MODULE).add(path);
for(Map.Entry<String, List<String>> entry : MODULES.entrySet()) {
if (entry.getValue().isEmpty()) {
throw new AssertionError("No resource file for " + entry.getKey());
}
}
this.classes = Collections.unmodifiableList(classes);
this.resources = Collections.unmodifiableList(resources);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public List<String> getClasses() {
return classes;
}
public List<String> getResources() {
return resources;
}
public ModulePool getPool() {
return pool;
}
public abstract void test() throws Exception;
public Collection<ModuleEntry> extractClasses(ModulePool pool) {
return pool.entries()
.filter(isClass)
.collect(Collectors.toSet());
}
public Collection<ModuleEntry> extractResources(ModulePool pool) {
return pool.entries()
.filter(isClass.negate())
.collect(Collectors.toSet());
}
public String getModule(String path) {
int index = path.indexOf("/", 1);
return path.substring(1, index);
}
public String removeModule(String path) {
int index = path.indexOf("/", 1);
return path.substring(index + 1);
}
private String toPath(String p) {
return p.substring("/modules".length());
}
private String toClassName(Path p) {
String path = p.toString();
path = path.substring("/modules/".length());
// remove module
if (!path.endsWith("module-info.class")) {
path = path.substring(path.indexOf("/") + 1);
}
path = path.substring(0, path.length() - ".class".length());
return path;
}
private String toResourceFile(Path p) {
String path = p.toString();
path = path.substring("/modules/".length());
// remove module
path = path.substring(path.indexOf("/") + 1);
return path;
}
public abstract class TestPlugin extends AsmPlugin {
private AsmPools pools;
public AsmPools getPools() {
return pools;
}
public boolean isVisitCalled() {
return pools != null;
}
public ModulePool visit(ModulePool inResources) throws IOException {
try {
ModulePool outResources = new ModulePoolImpl(inResources.getByteOrder(), new StringTable() {
@Override
public int addString(String str) {
return -1;
}
@Override
public String getString(int id) {
return null;
}
});
visit(inResources, outResources);
return outResources;
} catch (Exception e) {
throw new IOException(e);
}
}
@Override
public void visit(AsmPools pools) {
if (isVisitCalled()) {
throw new AssertionError("Visit was called twice");
}
this.pools = pools;
visit();
}
public abstract void visit();
public abstract void test(ModulePool inResources, ModulePool outResources) throws Exception;
@Override
public String getName() {
return "test-plugin";
}
}
}

View File

@ -1,177 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test basic functionality.
* @author Jean-Francois Denise
* @modules java.base/jdk.internal.org.objectweb.asm
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @build AsmPluginTestBase
* @run main BasicTest
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPool;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class BasicTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new BasicTest().test();
}
@Override
public void test() throws Exception {
BasicPlugin basicPlugin = new BasicPlugin(getClasses());
ModulePool res = basicPlugin.visit(getPool());
basicPlugin.test(getPool(), res);
}
private class BasicPlugin extends TestPlugin {
private final List<String> classes;
public BasicPlugin(List<String> classes) {
this.classes = classes;
}
@Override
public void visit() {
for (String m : MODULES.keySet()) {
AsmModulePool pool = getPools().getModulePool(m);
if (pool == null) {
throw new AssertionError(m + " pool not found");
}
if(!pool.getModuleName().equals(m)) {
throw new AssertionError("Invalid module name " +
pool.getModuleName() + " should be "+ m);
}
if (pool.getClasses().size() == 0 && !m.equals(TEST_MODULE)) {
throw new AssertionError("Empty pool " + m);
}
pool.addPackage("toto");
if (!pool.getTransformedClasses().getClasses().isEmpty()) {
throw new AssertionError("Should be empty");
}
for(String res : MODULES.get(m)) {
AsmPool.ResourceFile resFile = pool.getResourceFile(res);
if(resFile == null) {
throw new AssertionError("No resource file for " + res);
}
}
}
try {
testPools();
testVisitor();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
if (!isVisitCalled()) {
throw new AssertionError("Resources not visited");
}
if (inResources.getEntryCount() != outResources.getEntryCount()) {
throw new AssertionError("Input size " + inResources.getEntryCount() +
" != to " + outResources.getEntryCount());
}
}
private void testVisitor() throws IOException {
List<String> seen = new ArrayList<>();
getPools().getGlobalPool().visitClassReaders((reader) -> {
String className = reader.getClassName();
// Wrong naming of module-info.class in ASM
if (className.endsWith("module-info")) {
return null;
}
if (!classes.contains(className)) {
throw new AssertionError("Class is not expected " + className);
}
if (getPools().getGlobalPool().getClassReader(className) == null) {
throw new AssertionError("Class not found in pool " + className);
}
seen.add(className);
return null;
});
if (!seen.equals(classes)) {
throw new AssertionError("Expected and seen are not equal");
}
}
private void testPools() throws IOException {
Set<String> remain = new HashSet<>(classes);
for (ModuleEntry res : getPools().getGlobalPool().getClasses()) {
ClassReader reader = getPools().getGlobalPool().getClassReader(res);
String className = reader.getClassName();
// Wrong naming of module-info.class in ASM
if (className.endsWith("module-info")) {
continue;
}
if (!classes.contains(className)) {
throw new AssertionError("Class is not expected " + className);
}
if (getPools().getGlobalPool().getClassReader(className) == null) {
throw new AssertionError("Class " + className + " not found in pool ");
}
// Check the module pool
boolean found = false;
for(AsmModulePool mp : getPools().getModulePools()) {
if(mp.getClassReader(className) != null) {
found = true;
break;
}
}
if(!found) {
throw new AssertionError("No modular pool for " +
className);
}
remain.remove(className);
}
if (!remain.isEmpty()) {
throw new AssertionError("Remaining classes " + remain);
}
}
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test basic functionality.
* @author Jean-Francois Denise
* @modules java.base/jdk.internal.org.objectweb.asm
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @build AsmPluginTestBase
* @run main IdentityPluginTest
*/
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableClassPool;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class IdentityPluginTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new IdentityPluginTest().test();
}
public void test() throws Exception {
IdentityPlugin asm = new IdentityPlugin();
ModulePool resourcePool = asm.visit(getPool());
asm.test(getPool(), resourcePool);
}
private class IdentityPlugin extends TestPlugin {
@Override
public void visit() {
for (ModuleEntry res : getPools().getGlobalPool().getClasses()) {
if (res.getPath().endsWith("module-info.class")) {
continue;
}
ClassReader reader = getPools().getGlobalPool().getClassReader(res);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
IdentityClassVisitor visitor = new IdentityClassVisitor(writer);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
getPools().getGlobalPool().getTransformedClasses().addClass(writer);
}
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws IOException {
if (outResources.isEmpty()) {
throw new AssertionError("Empty result");
}
if (!isVisitCalled()) {
throw new AssertionError("Resources not visited");
}
WritableClassPool transformedClasses = getPools().getGlobalPool().getTransformedClasses();
if (transformedClasses.getClasses().size() != getClasses().size()) {
throw new AssertionError("Number of transformed classes not equal to expected");
}
for (String className : getClasses()) {
if (transformedClasses.getClassReader(className) == null) {
throw new AssertionError("Class not transformed " + className);
}
}
outResources.entries().forEach(r -> {
if (r.getPath().endsWith(".class") && !r.getPath().endsWith("module-info.class")) {
try {
ClassReader reader = new ClassReader(new ByteArrayInputStream(r.getBytes()));
ClassWriter w = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
reader.accept(w, ClassReader.EXPAND_FRAMES);
} catch (IOException exp) {
throw new UncheckedIOException(exp);
}
}
});
}
@Override
public String getName() {
return "identity-plugin";
}
}
private static class IdentityClassVisitor extends ClassVisitor {
public IdentityClassVisitor(ClassWriter cv) {
super(Opcodes.ASM5, cv);
}
}
}

View File

@ -1,172 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test basic functionality.
* @author Andrei Eremeev
* @modules java.base/jdk.internal.org.objectweb.asm
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @build AsmPluginTestBase
* @run main NegativeTest
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteOrder;
import java.util.Map;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.internal.ModulePoolImpl;
import jdk.tools.jlink.internal.StringTable;
import jdk.tools.jlink.internal.plugins.asm.AsmGlobalPool;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPlugin;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFile;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModulePool;
public class NegativeTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new NegativeTest().test();
}
@Override
public void test() throws Exception {
testNull();
testUnknownPackage();
}
private void testUnknownPackage() throws Exception {
AsmPlugin t = new AsmPlugin() {
@Override
public void visit(AsmPools pools) {
try {
AsmGlobalPool globalPool = pools.getGlobalPool();
AsmModulePool javabase = pools.getModulePool("java.base");
ClassReader cr = new ClassReader(NegativeTest.class.getResourceAsStream("NegativeTest.class"));
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(new RenameClassVisitor(cw), ClassReader.EXPAND_FRAMES);
action(() -> globalPool.getTransformedClasses().addClass(cw),
"Unknown package", PluginException.class);
action(() -> javabase.getTransformedClasses().addClass(cw),
"Unknown package", PluginException.class);
ResourceFile newResFile = new ResourceFile("java/aaa/file", new byte[0]);
action(() -> globalPool.getTransformedResourceFiles().addResourceFile(newResFile),
"Unknown package", PluginException.class);
action(() -> javabase.getTransformedResourceFiles().addResourceFile(newResFile),
"Unknown package", PluginException.class);
action(() -> globalPool.getTransformedClasses().forgetClass("java/aaa/file"),
"Unknown package", PluginException.class);
action(() -> javabase.getTransformedClasses().forgetClass("java/aaa/file"),
"Unknown package", PluginException.class);
action(() -> globalPool.getTransformedResourceFiles().forgetResourceFile("java/aaa/file"),
"Unknown package", PluginException.class);
action(() -> javabase.getTransformedResourceFiles().forgetResourceFile("java/aaa/file"),
"Unknown package", PluginException.class);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
};
ModulePool resources = new ModulePoolImpl(ByteOrder.BIG_ENDIAN, new StringTable() {
@Override
public int addString(String str) {
return -1;
}
@Override
public String getString(int id) {
throw new UnsupportedOperationException("Not supported yet.");
}
});
t.visit(getPool(), resources);
}
private static class RenameClassVisitor extends ClassVisitor {
public RenameClassVisitor(ClassWriter cv) {
super(Opcodes.ASM5, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, "RENAMED", signature, superName, interfaces);
}
}
private void testNull() throws Exception {
AsmPlugin t = new AsmPlugin() {
@Override
public void visit(AsmPools pools) {
action(() -> pools.getModulePool(null), "Module name is null", NullPointerException.class);
action(() -> pools.fillOutputResources(null), "Output resource is null", NullPointerException.class);
}
};
ModulePool resources = new ModulePoolImpl(ByteOrder.BIG_ENDIAN, new StringTable() {
@Override
public int addString(String str) {
return -1;
}
@Override
public String getString(int id) {
throw new UnsupportedOperationException("Not supported yet.");
}
});
action(() -> t.visit(null, resources), "Input resource is null", NullPointerException.class);
action(() -> t.visit(resources, null), "Output resource is null", NullPointerException.class);
t.visit(resources, resources);
}
private void action(Action action, String message, Class<? extends Exception> expected) {
try {
System.err.println("Testing: " + message);
action.call();
throw new AssertionError(message + ": should have failed");
} catch (Exception e) {
if (!expected.isInstance(e)) {
throw new RuntimeException(e);
} else {
System.err.println("Got exception as expected: " + e);
}
}
}
private interface Action {
void call() throws Exception;
}
}

View File

@ -1,197 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test plugins
* @author Andrei Eremeev
* @modules jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @run main PackageMappingTest
*/
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
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.function.Function;
import java.util.stream.Collectors;
import jdk.tools.jlink.internal.plugins.asm.AsmGlobalPool;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFile;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.WritableResourcePool;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class PackageMappingTest extends AsmPluginTestBase {
private final List<String> newFiles = Arrays.asList(
"/java.base/a1/bbb/c",
"/" + TEST_MODULE + "/a2/bbb/d"
);
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new PackageMappingTest().test();
}
public void test() throws Exception {
TestPlugin[] plugins = new TestPlugin[]{
new PackageMappingPlugin(newFiles, false),
new PackageMappingPlugin(newFiles, true)
};
for (TestPlugin p : plugins) {
ModulePool pool = p.visit(getPool());
p.test(getPool(), pool);
}
}
public class PackageMappingPlugin extends TestPlugin {
private final Map<String, List<ResourceFile>> newFiles;
private final boolean testGlobal;
private String getModuleName(String res) {
return res.substring(1, res.indexOf("/", 1));
}
private PackageMappingPlugin(List<String> files, boolean testGlobal) {
this.newFiles = new HashMap<>();
this.testGlobal = testGlobal;
for (String file : files) {
String moduleName = getModuleName(file);
String path = file.substring(1 + moduleName.length() + 1);
newFiles.computeIfAbsent(moduleName, $ -> new ArrayList<>()).add(
new ResourceFile(path, new byte[0]));
}
}
@Override
public void visit() {
testMapToUnknownModule();
testMapPackageTwice();
testPackageMapping();
}
@Override
public void test(ModulePool inResources, ModulePool outResources) {
Set<String> in = getPools().getGlobalPool().getResourceFiles().stream()
.map(ModuleEntry::getPath)
.collect(Collectors.toSet());
Set<String> out = extractResources(outResources).stream()
.map(ModuleEntry::getPath)
.collect(Collectors.toSet());
in.addAll(PackageMappingTest.this.newFiles);
if (!Objects.equals(in, out)) {
throw new AssertionError("Expected: " + in + ", got: " + outResources);
}
}
private void testPackageMapping() {
AsmGlobalPool globalPool = getPools().getGlobalPool();
try {
Map<String, Set<String>> mappedPackages = new HashMap<>();
Function<String, Set<String>> produceSet = $ -> new HashSet<>();
for (Map.Entry<String, List<ResourceFile>> entry : newFiles.entrySet()) {
String moduleName = entry.getKey();
Set<String> module = mappedPackages.computeIfAbsent(moduleName, produceSet);
AsmModulePool modulePool = getPools().getModulePool(moduleName);
for (ResourceFile r : entry.getValue()) {
String name = r.getPath();
String packageName = name.substring(0, name.lastIndexOf('/'));
if (module.add(packageName)) {
globalPool.addPackageModuleMapping(packageName, moduleName);
}
WritableResourcePool transformedResourceFiles = testGlobal
? globalPool.getTransformedResourceFiles()
: modulePool.getTransformedResourceFiles();
transformedResourceFiles.addResourceFile(r);
}
try {
modulePool.getTransformedResourceFiles().addResourceFile(
new ResourceFile("a3/bbb", new byte[0]));
throw new AssertionError("Exception expected");
} catch (Exception ex) {
// expected
}
}
try {
globalPool.getTransformedResourceFiles().addResourceFile(
new ResourceFile("a3/bbb", new byte[0]));
throw new AssertionError("Exception expected");
} catch (Exception ex) {
// expected
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void testMapPackageTwice() {
try {
AsmGlobalPool globalPool = getPools().getGlobalPool();
globalPool.addPackageModuleMapping("a/p1", TEST_MODULE);
globalPool.addPackageModuleMapping("a/p1", TEST_MODULE);
throw new AssertionError("Exception expected after mapping a package twice to the same module");
} catch (Exception e) {
if (e instanceof PluginException) {
// expected
String message = e.getMessage();
if (!(TEST_MODULE + " module already contains package a.p1").equals(message)) {
throw new AssertionError(e);
}
} else {
throw new AssertionError(e);
}
}
}
private void testMapToUnknownModule() {
AsmModulePool unknownModule = getPools().getModulePool("UNKNOWN");
if (unknownModule != null) {
throw new AssertionError("getModulePool returned not null value: " + unknownModule.getModuleName());
}
try {
AsmGlobalPool globalPool = getPools().getGlobalPool();
globalPool.addPackageModuleMapping("a/b", "UNKNOWN");
throw new AssertionError("Exception expected after mapping a package to unknown module");
} catch (Exception e) {
String message = e.getMessage();
if (message == null || !message.startsWith("Unknown module UNKNOWN")) {
throw new AssertionError(e);
}
}
}
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test resource sorting.
* @author Jean-Francois Denise
* @modules jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @build AsmPluginTestBase
* @run main SortingTest
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import jdk.tools.jlink.internal.plugins.asm.AsmModulePool;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class SortingTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new SortingTest().test();
}
@Override
public void test() {
try {
classSorting();
moduleSorting();
} catch (Exception ex) {
throw new PluginException(ex);
}
}
private void classSorting() throws Exception {
List<String> sorted = new ArrayList<>(getResources());
sorted.sort(null);
ClassSorterPlugin sorterPlugin = new ClassSorterPlugin(sorted);
ModulePool resourcePool = sorterPlugin.visit(getPool());
sorterPlugin.test(getPool(), resourcePool);
}
private String getModuleName(String p) {
return p.substring(1, p.indexOf('/', 1));
}
private void moduleSorting() throws Exception {
List<String> sorted = new ArrayList<>(getResources());
sorted.sort((s1, s2) -> -getModuleName(s1).compareTo(getModuleName(s2)));
ModuleSorterPlugin sorterPlugin = new ModuleSorterPlugin();
ModulePool resourcePool = sorterPlugin.visit(getPool());
sorterPlugin.test(getPool(), resourcePool);
}
private class ModuleSorterPlugin extends TestPlugin {
@Override
public void visit() {
for (AsmModulePool modulePool : getPools().getModulePools()) {
modulePool.setSorter(resources -> {
List<String> sort = resources.entries()
.map(ModuleEntry::getPath)
.collect(Collectors.toList());
sort.sort(null);
return sort;
});
}
getPools().setModuleSorter(modules -> {
modules.sort((s1, s2) -> -s1.compareTo(s2));
return modules;
});
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
if (!isVisitCalled()) {
throw new AssertionError("Resources not visited");
}
List<String> sortedResourcePaths = outResources.entries()
.map(ModuleEntry::getPath)
.collect(Collectors.toList());
List<String> defaultResourceOrder = new ArrayList<>();
inResources.entries().forEach(r -> {
if (!inResources.contains(r)) {
throw new AssertionError("Resource " + r.getPath() + " not in result pool");
}
defaultResourceOrder.add(r.getPath());
});
// Check that default sorting is not equal to sorted one
if (defaultResourceOrder.equals(sortedResourcePaths)) {
throw new AssertionError("Sorting not applied, default ordering");
}
// Check module order.
for (int i = 0; i < sortedResourcePaths.size() - 1; ++i) {
String first = sortedResourcePaths.get(i);
String p1 = getModuleName(first);
String second = sortedResourcePaths.get(i + 1);
String p2 = getModuleName(second);
if (p1.compareTo(p2) < 0 || p1.compareTo(p2) == 0 &&
removeModule(first).compareTo(removeModule(second)) >= 0) {
throw new AssertionError("Modules are not sorted properly: resources: " + first + " " + second);
}
}
}
}
private class ClassSorterPlugin extends TestPlugin {
private final List<String> expectedClassesOrder;
private ClassSorterPlugin(List<String> expectedClassesOrder) {
this.expectedClassesOrder = expectedClassesOrder;
}
@Override
public void visit() {
getPools().getGlobalPool().setSorter(
(resources) -> expectedClassesOrder.stream()
.map(resources::findEntry)
.map(Optional::get)
.map(ModuleEntry::getPath)
.collect(Collectors.toList()));
}
@Override
public void test(ModulePool inResources, ModulePool outResources) throws Exception {
if (!isVisitCalled()) {
throw new AssertionError("Resources not visited");
}
List<String> sortedResourcePaths = outResources.entries()
.map(ModuleEntry::getPath)
.collect(Collectors.toList());
List<String> defaultResourceOrder = new ArrayList<>();
getPool().entries().forEach(r -> {
if (!getPool().contains(r)) {
throw new AssertionError("Resource " + r.getPath() + " not in result pool");
}
defaultResourceOrder.add(r.getPath());
});
// Check that default sorting is not equal to sorted one
if (defaultResourceOrder.equals(sortedResourcePaths)) {
throw new AssertionError("Sorting not applied, default ordering");
}
// Check that sorted is equal to result.
if (!expectedClassesOrder.equals(sortedResourcePaths)) {
throw new AssertionError("Sorting not properly applied");
}
}
}
}

View File

@ -1,219 +0,0 @@
/*
* Copyright (c) 2015, 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.
*/
/*
* Asm plugin testing.
* @test
* @summary Test visitors.
* @author Andrei Eremeev
* @modules java.base/jdk.internal.org.objectweb.asm
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.internal.plugins.asm
* @build AsmPluginTestBase
* @run main VisitorTest
*/
import java.io.IOException;
import java.util.Collection;
import java.util.function.Function;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.internal.plugins.asm.AsmPool;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ClassReaderVisitor;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFile;
import jdk.tools.jlink.internal.plugins.asm.AsmPool.ResourceFileVisitor;
import jdk.tools.jlink.internal.plugins.asm.AsmPools;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
public class VisitorTest extends AsmPluginTestBase {
public static void main(String[] args) throws Exception {
if (!isImageBuild()) {
System.err.println("Test not run. Not image build.");
return;
}
new VisitorTest().test();
}
@Override
public void test() throws Exception {
TestPlugin[] plugins = new TestPlugin[] {
new ClassVisitorPlugin("Class-global-pool", AsmPools::getGlobalPool),
new ClassVisitorPlugin("Class-module-pool", pools -> pools.getModulePool("java.base")),
new ResourceVisitorPlugin("Resource-global-pool", AsmPools::getGlobalPool),
new ResourceVisitorPlugin("Resource-module-pool", pools -> pools.getModulePool("java.base"))
};
for (TestPlugin p : plugins) {
System.err.println("Testing: " + p.getName());
ModulePool out = p.visit(getPool());
p.test(getPool(), out);
}
}
private static class CustomClassReaderVisitor implements ClassReaderVisitor {
private int amount = 0;
private int changed = 0;
@Override
public ClassWriter visit(ClassReader reader) {
if ((amount++ % 2) == 0) {
String className = reader.getClassName();
if (className.endsWith("module-info")) {
return null;
}
ClassWriter cw = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
reader.accept(new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public void visit(int i, int i1, String s, String s1, String s2, String[] strings) {
super.visit(i, i1, s + "Changed", s1, s2, strings);
}
}, ClassReader.EXPAND_FRAMES);
++changed;
return cw;
} else {
return null;
}
}
public int getAmount() {
return amount;
}
public int getNumberOfChanged() {
return changed;
}
}
private static class CustomResourceFileVisitor implements ResourceFileVisitor {
private int amount = 0;
private int changed = 0;
@Override
public ResourceFile visit(ResourceFile resourceFile) {
if ((amount++ % 2) == 0) {
++changed;
return new ResourceFile(resourceFile.getPath() + "Changed", resourceFile.getContent());
} else {
return null;
}
}
public int getAmount() {
return amount;
}
public int getNumberOfChanged() {
return changed;
}
}
public class ClassVisitorPlugin extends TestPlugin {
private final String name;
private final Function<AsmPools, AsmPool> getPool;
private final CustomClassReaderVisitor classReaderVisitor = new CustomClassReaderVisitor();
public ClassVisitorPlugin(String name, Function<AsmPools, AsmPool> getPool) {
this.name = name;
this.getPool = getPool;
}
@Override
public void visit() {
AsmPool pool = getPool.apply(getPools());
pool.visitClassReaders(classReaderVisitor);
}
@Override
public void test(ModulePool in, ModulePool out) throws Exception {
Collection<ModuleEntry> inClasses = getPool.apply(getPools()).getClasses();
if (inClasses.size() != classReaderVisitor.getAmount()) {
throw new AssertionError("Testing " + name + ". Number of visited classes. Expected: " +
inClasses.size() + ", got: " + classReaderVisitor.getAmount());
}
Collection<ModuleEntry> outClasses = extractClasses(out);
int changedClasses = 0;
for (ModuleEntry r : outClasses) {
if (r.getPath().endsWith("Changed.class")) {
++changedClasses;
}
}
if (changedClasses != classReaderVisitor.getNumberOfChanged()) {
throw new AssertionError("Testing " + name + ". Changed classes. Expected: " + changedClasses +
", got: " + classReaderVisitor.getNumberOfChanged());
}
}
@Override
public String getName() {
return name;
}
}
public class ResourceVisitorPlugin extends TestPlugin {
private final String name;
private final Function<AsmPools, AsmPool> getPool;
private final CustomResourceFileVisitor resourceFileVisitor = new CustomResourceFileVisitor();
public ResourceVisitorPlugin(String name, Function<AsmPools, AsmPool> getPool) {
this.name = name;
this.getPool = getPool;
}
@Override
public void visit() {
AsmPool pool = getPool.apply(getPools());
pool.visitResourceFiles(resourceFileVisitor);
}
@Override
public void test(ModulePool in, ModulePool out) throws Exception {
Collection<ModuleEntry> inResources = getPool.apply(getPools()).getResourceFiles();
if (inResources.size() != resourceFileVisitor.getAmount()) {
throw new AssertionError("Testing " + name + ". Number of visited resources. Expected: " +
inResources.size() + ", got: " + resourceFileVisitor.getAmount());
}
Collection<ModuleEntry> outResources = extractResources(out);
int changedClasses = 0;
for (ModuleEntry r : outResources) {
if (r.getPath().endsWith("Changed")) {
++changedClasses;
}
}
if (changedClasses != resourceFileVisitor.getNumberOfChanged()) {
throw new AssertionError("Testing " + name + ". Changed classes. Expected: " + changedClasses +
", got: " + resourceFileVisitor.getNumberOfChanged());
}
}
@Override
public String getName() {
return name;
}
}
}