mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8294972: Convert jdk.jlink internal plugins to use the Classfile API
Reviewed-by: mchung, alanb
This commit is contained in:
parent
c74507eeb3
commit
358c61b58d
6 changed files with 744 additions and 803 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue