8294972: Convert jdk.jlink internal plugins to use the Classfile API

Reviewed-by: mchung, alanb
This commit is contained in:
Adam Sotona 2023-03-22 12:12:14 +00:00
parent c74507eeb3
commit 358c61b58d
6 changed files with 744 additions and 803 deletions

View file

@ -196,17 +196,18 @@ module java.base {
jdk.jlink, jdk.jlink,
jdk.jshell; jdk.jshell;
exports jdk.internal.classfile.attribute to exports jdk.internal.classfile.attribute to
jdk.jartool; jdk.jartool,
jdk.jlink;
exports jdk.internal.classfile.constantpool to exports jdk.internal.classfile.constantpool to
jdk.jartool; jdk.jartool,
jdk.jlink;
exports jdk.internal.classfile.instruction to exports jdk.internal.classfile.instruction to
jdk.jlink,
jdk.jshell; jdk.jshell;
exports jdk.internal.org.objectweb.asm to exports jdk.internal.org.objectweb.asm to
jdk.jfr, jdk.jfr;
jdk.jlink;
exports jdk.internal.org.objectweb.asm.tree to exports jdk.internal.org.objectweb.asm.tree to
jdk.jfr, jdk.jfr;
jdk.jlink;
exports jdk.internal.org.objectweb.asm.util to exports jdk.internal.org.objectweb.asm.util to
jdk.jfr; jdk.jfr;
exports jdk.internal.org.objectweb.asm.commons to exports jdk.internal.org.objectweb.asm.commons to

View file

@ -35,7 +35,8 @@ import java.nio.file.Paths;
import java.util.Locale; import java.util.Locale;
import java.util.MissingResourceException; import java.util.MissingResourceException;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.classfile.ClassModel;
import jdk.internal.classfile.Classfile;
import jdk.tools.jlink.plugin.ResourcePoolEntry; import jdk.tools.jlink.plugin.ResourcePoolEntry;
public abstract class AbstractPlugin implements Plugin { public abstract class AbstractPlugin implements Plugin {
@ -84,10 +85,10 @@ public abstract class AbstractPlugin implements Plugin {
} }
} }
ClassReader newClassReader(String path, ResourcePoolEntry resource) { ClassModel newClassReader(String path, ResourcePoolEntry resource, Classfile.Option... options) {
byte[] content = resource.contentBytes(); byte[] content = resource.contentBytes();
try { try {
return new ClassReader(content); return Classfile.parse(content, options);
} catch (Exception e) { } catch (Exception e) {
if (JlinkTask.DEBUG) { if (JlinkTask.DEBUG) {
System.err.printf("Failed to parse class file: %s from resource of type %s\n", path, System.err.printf("Failed to parse class file: %s from resource of type %s\n", path,
@ -99,9 +100,9 @@ public abstract class AbstractPlugin implements Plugin {
} }
} }
protected ClassReader newClassReader(String path, byte[] buf) { protected ClassModel newClassReader(String path, byte[] buf, Classfile.Option... options) {
try { try {
return new ClassReader(buf); return Classfile.parse(buf, options);
} catch (Exception e) { } catch (Exception e) {
if (JlinkTask.DEBUG) { if (JlinkTask.DEBUG) {
System.err.printf("Failed to parse class file: %s\n", path); System.err.printf("Failed to parse class file: %s\n", path);

View file

@ -35,12 +35,12 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static java.util.ResourceBundle.Control; import static java.util.ResourceBundle.Control;
import java.util.Set; import java.util.Set;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.org.objectweb.asm.ClassReader; import static jdk.internal.classfile.Classfile.*;
import jdk.tools.jlink.internal.ResourcePrevisitor; import jdk.tools.jlink.internal.ResourcePrevisitor;
import jdk.tools.jlink.internal.StringTable; import jdk.tools.jlink.internal.StringTable;
import jdk.tools.jlink.plugin.ResourcePoolModule; import jdk.tools.jlink.plugin.ResourcePoolModule;
@ -159,10 +159,9 @@ public final class IncludeLocalesPlugin extends AbstractPlugin implements Resour
resource.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE) && resource.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE) &&
path.endsWith(".class")) { path.endsWith(".class")) {
byte[] bytes = resource.contentBytes(); byte[] bytes = resource.contentBytes();
ClassReader cr = newClassReader(path, bytes); if (newClassReader(path, bytes).interfaces().stream()
if (Arrays.stream(cr.getInterfaces()) .anyMatch(i -> i.asInternalName().contains(METAINFONAME)) &&
.anyMatch(i -> i.contains(METAINFONAME)) && stripUnsupportedLocales(bytes)) {
stripUnsupportedLocales(bytes, cr)) {
resource = resource.copyWithContent(bytes); resource = resource.copyWithContent(bytes);
} }
} }
@ -270,26 +269,49 @@ public final class IncludeLocalesPlugin extends AbstractPlugin implements Resour
.toList(); .toList();
} }
private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) { private boolean stripUnsupportedLocales(byte[] bytes) {
boolean[] modified = new boolean[1]; boolean modified = false;
// scan CP entries directly to read the bytes of UTF8 entries and
IntStream.range(1, cr.getItemCount()) // patch in place with unsupported locale tags stripped
.map(item -> cr.getItem(item)) IntUnaryOperator readU2 = p -> ((bytes[p] & 0xff) << 8) + (bytes[p + 1] & 0xff);
.forEach(itemIndex -> { int cpLength = readU2.applyAsInt(8);
if (bytes[itemIndex - 1] == 1 && // UTF-8 int offset = 10;
bytes[itemIndex + 2] == (byte)' ') { // fast check for leading space for (int cpSlot=1; cpSlot<cpLength; cpSlot++) {
int length = cr.readUnsignedShort(itemIndex); switch (bytes[offset]) { //entry tag
byte[] b = new byte[length]; case TAG_UTF8 -> {
System.arraycopy(bytes, itemIndex + 2, b, 0, length); int length = readU2.applyAsInt(offset + 1);
if (filterOutUnsupportedTags(b)) { if (bytes[offset + 3] == (byte)' ') { // fast check for leading space
// copy back byte[] b = new byte[length];
System.arraycopy(b, 0, bytes, itemIndex + 2, length); System.arraycopy(bytes, offset + 3, b, 0, length);
modified[0] = true; if (filterOutUnsupportedTags(b)) {
// copy back
System.arraycopy(b, 0, bytes, offset + 3, length);
modified = true;
}
} }
offset += 3 + length;
} }
}); case TAG_CLASS,
TAG_STRING,
return modified[0]; TAG_METHODTYPE,
TAG_MODULE,
TAG_PACKAGE -> offset += 3;
case TAG_METHODHANDLE -> offset += 4;
case TAG_INTEGER,
TAG_FLOAT,
TAG_FIELDREF,
TAG_METHODREF,
TAG_INTERFACEMETHODREF,
TAG_NAMEANDTYPE,
TAG_CONSTANTDYNAMIC,
TAG_INVOKEDYNAMIC -> offset += 5;
case TAG_LONG,
TAG_DOUBLE -> {offset += 9; cpSlot++;} //additional slot for double and long entries
default -> throw new IllegalArgumentException("Unknown constant pool entry: 0x"
+ Integer.toHexString(Byte.toUnsignedInt(bytes[offset])).toUpperCase(Locale.ROOT));
}
}
return modified;
} }
private boolean filterOutUnsupportedTags(byte[] b) { private boolean filterOutUnsupportedTags(byte[] b) {

View file

@ -25,9 +25,14 @@
package jdk.tools.jlink.internal.plugins; package jdk.tools.jlink.internal.plugins;
import java.util.function.Predicate; import java.util.function.Predicate;
import jdk.internal.classfile.Classfile;
import jdk.internal.classfile.ClassTransform;
import jdk.internal.classfile.CodeTransform;
import jdk.internal.classfile.MethodTransform;
import jdk.internal.classfile.attribute.MethodParametersAttribute;
import jdk.internal.classfile.attribute.SourceFileAttribute;
import jdk.internal.classfile.attribute.SourceDebugExtensionAttribute;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder; import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry; import jdk.tools.jlink.plugin.ResourcePoolEntry;
@ -57,12 +62,17 @@ public final class StripJavaDebugAttributesPlugin extends AbstractPlugin {
String path = resource.path(); String path = resource.path();
if (path.endsWith(".class")) { if (path.endsWith(".class")) {
if (path.endsWith("module-info.class")) { if (path.endsWith("module-info.class")) {
// XXX. Do we have debug info? Is Asm ready for module-info? // XXX. Do we have debug info?
} else { } else {
ClassReader reader = newClassReader(path, resource); byte[] content = newClassReader(path, resource,
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); Classfile.Option.processDebug(false),
reader.accept(writer, ClassReader.SKIP_DEBUG); Classfile.Option.processLineNumbers(false)).transform(ClassTransform
byte[] content = writer.toByteArray(); .dropping(cle -> cle instanceof SourceFileAttribute
|| cle instanceof SourceDebugExtensionAttribute)
.andThen(ClassTransform.transformingMethods(MethodTransform
.dropping(me -> me instanceof MethodParametersAttribute)
.andThen(MethodTransform
.transformingCode(CodeTransform.ACCEPT_ALL)))));
res = resource.copyWithContent(content); res = resource.copyWithContent(content);
} }
} }

View file

@ -26,12 +26,13 @@
package jdk.tools.jlink.internal.plugins; package jdk.tools.jlink.internal.plugins;
import java.util.Map; import java.util.Map;
import jdk.internal.classfile.ClassTransform;
import jdk.internal.classfile.CodeBuilder;
import jdk.internal.classfile.CodeElement;
import jdk.internal.classfile.Instruction;
import jdk.internal.classfile.instruction.FieldInstruction;
import jdk.internal.classfile.CodeTransform;
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.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder; import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry; import jdk.tools.jlink.plugin.ResourcePoolEntry;
@ -96,63 +97,38 @@ abstract class VersionPropsPlugin extends AbstractPlugin {
private boolean redefined = false; private boolean redefined = false;
@SuppressWarnings("deprecation")
private byte[] redefine(String path, byte[] classFile) { private byte[] redefine(String path, byte[] classFile) {
return newClassReader(path, classFile).transform(ClassTransform.transformingMethodBodies(
mm -> mm.methodName().equalsString("<clinit>"),
new CodeTransform() {
private CodeElement pendingLDC = null;
var cr = newClassReader(path, classFile); private void flushPendingLDC(CodeBuilder cob) {
var cw = new ClassWriter(0); if (pendingLDC != null) {
cob.accept(pendingLDC);
pendingLDC = null;
}
}
cr.accept(new ClassVisitor(Opcodes.ASM7, cw) { @Override
public void accept(CodeBuilder cob, CodeElement coe) {
@Override if (coe instanceof Instruction ins) {
public MethodVisitor visitMethod(int access, switch (ins.opcode()) {
String name, case LDC, LDC_W, LDC2_W -> {
String desc, flushPendingLDC(cob);
String sig, pendingLDC = coe;
String[] xs)
{
if (name.equals("<clinit>"))
return new MethodVisitor(Opcodes.ASM7,
super.visitMethod(access,
name,
desc,
sig,
xs))
{
private Object pendingLDC = null;
private void flushPendingLDC() {
if (pendingLDC != null) {
super.visitLdcInsn(pendingLDC);
pendingLDC = null;
}
} }
case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE -> {
@Override flushPendingLDC(cob);
public void visitLdcInsn(Object value) { cob.accept(coe);
flushPendingLDC();
pendingLDC = value;
} }
case GETSTATIC, GETFIELD, PUTFIELD -> {
@Override flushPendingLDC(cob);
public void visitMethodInsn(int opcode, cob.accept(coe);
String owner,
String name,
String descriptor,
boolean isInterface) {
flushPendingLDC();
super.visitMethodInsn(opcode, owner, name,
descriptor, isInterface);
} }
case PUTSTATIC -> {
@Override if (((FieldInstruction)coe).name().equalsString(field)) {
public void visitFieldInsn(int opcode,
String owner,
String name,
String desc)
{
if (opcode == Opcodes.PUTSTATIC
&& name.equals(field))
{
// assert that there is a pending ldc // assert that there is a pending ldc
// for the old value // for the old value
if (pendingLDC == null) { if (pendingLDC == null) {
@ -164,24 +140,20 @@ abstract class VersionPropsPlugin extends AbstractPlugin {
// forget about it // forget about it
pendingLDC = null; pendingLDC = null;
// and add an ldc for the new value // and add an ldc for the new value
super.visitLdcInsn(value); cob.constantInstruction(value);
redefined = true; redefined = true;
} else { } else {
flushPendingLDC(); flushPendingLDC(cob);
} }
super.visitFieldInsn(opcode, owner, cob.accept(coe);
name, desc);
} }
default -> cob.accept(coe);
}; }
else } else {
return super.visitMethod(access, name, desc, sig, xs); cob.accept(coe);
} }
}
}, 0); }));
return cw.toByteArray();
} }
@Override @Override
@ -198,5 +170,4 @@ abstract class VersionPropsPlugin extends AbstractPlugin {
throw new AssertionError(field); throw new AssertionError(field);
return out.build(); return out.build();
} }
} }