8160829: Remove ASMPool support from jlink
Reviewed-by: sundar, psandoz, forax
This commit is contained in:
parent
74a59850a5
commit
748aad61f6
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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("/", ".");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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\
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user