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

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

@ -35,7 +35,8 @@ import java.nio.file.Paths;
import java.util.Locale;
import java.util.MissingResourceException;
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;
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();
try {
return new ClassReader(content);
return Classfile.parse(content, options);
} catch (Exception e) {
if (JlinkTask.DEBUG) {
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 {
return new ClassReader(buf);
return Classfile.parse(buf, options);
} catch (Exception e) {
if (JlinkTask.DEBUG) {
System.err.printf("Failed to parse class file: %s\n", path);

@ -35,12 +35,12 @@ import java.util.Objects;
import java.util.Optional;
import static java.util.ResourceBundle.Control;
import java.util.Set;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
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.StringTable;
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) &&
path.endsWith(".class")) {
byte[] bytes = resource.contentBytes();
ClassReader cr = newClassReader(path, bytes);
if (Arrays.stream(cr.getInterfaces())
.anyMatch(i -> i.contains(METAINFONAME)) &&
stripUnsupportedLocales(bytes, cr)) {
if (newClassReader(path, bytes).interfaces().stream()
.anyMatch(i -> i.asInternalName().contains(METAINFONAME)) &&
stripUnsupportedLocales(bytes)) {
resource = resource.copyWithContent(bytes);
}
}
@ -270,26 +269,49 @@ public final class IncludeLocalesPlugin extends AbstractPlugin implements Resour
.toList();
}
private boolean stripUnsupportedLocales(byte[] bytes, ClassReader cr) {
boolean[] modified = new boolean[1];
IntStream.range(1, cr.getItemCount())
.map(item -> cr.getItem(item))
.forEach(itemIndex -> {
if (bytes[itemIndex - 1] == 1 && // UTF-8
bytes[itemIndex + 2] == (byte)' ') { // fast check for leading space
int length = cr.readUnsignedShort(itemIndex);
byte[] b = new byte[length];
System.arraycopy(bytes, itemIndex + 2, b, 0, length);
if (filterOutUnsupportedTags(b)) {
// copy back
System.arraycopy(b, 0, bytes, itemIndex + 2, length);
modified[0] = true;
private boolean stripUnsupportedLocales(byte[] bytes) {
boolean modified = false;
// scan CP entries directly to read the bytes of UTF8 entries and
// patch in place with unsupported locale tags stripped
IntUnaryOperator readU2 = p -> ((bytes[p] & 0xff) << 8) + (bytes[p + 1] & 0xff);
int cpLength = readU2.applyAsInt(8);
int offset = 10;
for (int cpSlot=1; cpSlot<cpLength; cpSlot++) {
switch (bytes[offset]) { //entry tag
case TAG_UTF8 -> {
int length = readU2.applyAsInt(offset + 1);
if (bytes[offset + 3] == (byte)' ') { // fast check for leading space
byte[] b = new byte[length];
System.arraycopy(bytes, offset + 3, b, 0, length);
if (filterOutUnsupportedTags(b)) {
// copy back
System.arraycopy(b, 0, bytes, offset + 3, length);
modified = true;
}
}
offset += 3 + length;
}
});
return modified[0];
case TAG_CLASS,
TAG_STRING,
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) {

@ -25,9 +25,14 @@
package jdk.tools.jlink.internal.plugins;
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.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
@ -57,12 +62,17 @@ public final class StripJavaDebugAttributesPlugin extends AbstractPlugin {
String path = resource.path();
if (path.endsWith(".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 {
ClassReader reader = newClassReader(path, resource);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
reader.accept(writer, ClassReader.SKIP_DEBUG);
byte[] content = writer.toByteArray();
byte[] content = newClassReader(path, resource,
Classfile.Option.processDebug(false),
Classfile.Option.processLineNumbers(false)).transform(ClassTransform
.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);
}
}

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