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\
|
Any number of <key>=<value> pairs can be passed.\n\
|
||||||
del: is to delete the list of keys in release file.
|
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-for-name.description=\
|
||||||
Class optimization. Warning: This plugin is experimental.\n\
|
Class optimization: convert Class.forName calls to constant loads.
|
||||||
An optional <log file> can be specified to log applied optimizations.
|
|
||||||
|
|
||||||
compress.argument=<0|1|2>[:filter=<pattern-list>]
|
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\
|
An optional <pattern-list> filter can be specified to list the pattern of\n\
|
||||||
files to be included.
|
files to be included.
|
||||||
|
|
||||||
|
|
||||||
compact-cp.argument=<resource paths>
|
compact-cp.argument=<resource paths>
|
||||||
|
|
||||||
compact-cp.description=Constant Pool strings sharing.\n\
|
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.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.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.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.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.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.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.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