Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support bootstrap method coping when using code coping #480

Merged
merged 1 commit into from May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 81 additions & 22 deletions src/main/javassist/bytecode/BootstrapMethodsAttribute.java
Expand Up @@ -2,6 +2,7 @@

import java.io.DataInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

public class BootstrapMethodsAttribute extends AttributeInfo {
Expand Down Expand Up @@ -35,6 +36,26 @@ public BootstrapMethod(int method, int[] args) {
* <code>bootstrap_arguments</code>.
*/
public int[] arguments;

/**
* Makes a copy. Class names are replaced according to the
* * given <code>Map</code> object.
*
* @param srcCp the constant pool table from the source
* @param destCp the constant pool table used bt new copy
* @param classnames pairs of replaced and substituted class names.
*
* @return new BootstrapMethod
*/
protected BootstrapMethod copy(ConstPool srcCp, ConstPool destCp, Map<String,String> classnames) {
int newMethodRef = srcCp.copy(methodRef, destCp, classnames);
int[] newArguments = new int[arguments.length];

for (int i = 0; i < arguments.length; i++)
newArguments[i] = srcCp.copy(arguments[i], destCp, classnames);

return new BootstrapMethod(newMethodRef, newArguments);
}
}

BootstrapMethodsAttribute(ConstPool cp, int n, DataInputStream in)
Expand All @@ -51,25 +72,8 @@ public BootstrapMethod(int method, int[] args) {
*/
public BootstrapMethodsAttribute(ConstPool cp, BootstrapMethod[] methods) {
super(cp, tag);
int size = 2;
for (int i = 0; i < methods.length; i++)
size += 4 + methods[i].arguments.length * 2;

byte[] data = new byte[size];
ByteArray.write16bit(methods.length, data, 0); // num_bootstrap_methods
int pos = 2;
for (int i = 0; i < methods.length; i++) {
ByteArray.write16bit(methods[i].methodRef, data, pos);
ByteArray.write16bit(methods[i].arguments.length, data, pos + 2);
int[] args = methods[i].arguments;
pos += 4;
for (int k = 0; k < args.length; k++) {
ByteArray.write16bit(args[k], data, pos);
pos += 2;
}
}

set(data);
set(convertMethodsToBytes(methods));
}

/**
Expand Down Expand Up @@ -113,12 +117,67 @@ public AttributeInfo copy(ConstPool newCp, Map<String,String> classnames) {
BootstrapMethod[] methods = getMethods();
ConstPool thisCp = getConstPool();
for (int i = 0; i < methods.length; i++) {
BootstrapMethod m = methods[i];
m.methodRef = thisCp.copy(m.methodRef, newCp, classnames);
for (int k = 0; k < m.arguments.length; k++)
m.arguments[k] = thisCp.copy(m.arguments[k], newCp, classnames);
methods[i] = methods[i].copy(thisCp, newCp, classnames);
}

return new BootstrapMethodsAttribute(newCp, methods);
}

/**
* add bootstrap method from given <code>ConstPool</code> and <code>BootstrapMethod</code>,
* and add it to the specified index. Class names are replaced according to the
* given <code>Map</code> object.
*
* <p>
* if the index less than 0 or large than the origin method length, then throw <code>RuntimeException</code>;<br>
* if the index large or equals to 0 and less or equals to the origin method length,
* then replace the origin method with the new <code>BootstrapMethod srcBm</code> ;<br>
* if the index equals to the origin method length, then append the new <code>BootstrapMethod srcBm</code> at
* the origin methods tail.
* </p>
*
* @param srcCp the constant pool table of source.
* @param srcBm the bootstrap method of source
* @param index the new method index on bootstrap methods
* @param classnames pairs of replaced and substituted
* class names.
*/
public void addMethod(ConstPool srcCp, BootstrapMethod srcBm, int index, Map<String,String> classnames) {
BootstrapMethod[] methods = getMethods();

if (index < 0 || index > methods.length) {
throw new RuntimeException("index out of range");
}

if (index == methods.length) {
BootstrapMethod[] newBmArray = new BootstrapMethod[methods.length + 1];
System.arraycopy(methods, 0, newBmArray, 0, methods.length);
methods = newBmArray;
}

methods[index] = srcBm.copy(srcCp, getConstPool(), classnames);
set(convertMethodsToBytes(methods));
}

private static byte[] convertMethodsToBytes(BootstrapMethod[] methods) {
int size = 2;
for (int i = 0; i < methods.length; i++)
size += 4 + methods[i].arguments.length * 2;

byte[] data = new byte[size];
ByteArray.write16bit(methods.length, data, 0); // num_bootstrap_methods
int pos = 2;
for (int i = 0; i < methods.length; i++) {
ByteArray.write16bit(methods[i].methodRef, data, pos);
ByteArray.write16bit(methods[i].arguments.length, data, pos + 2);
int[] args = methods[i].arguments;
pos += 4;
for (int k = 0; k < args.length; k++) {
ByteArray.write16bit(args[k], data, pos);
pos += 2;
}
}

return data;
}
}
149 changes: 141 additions & 8 deletions src/main/javassist/bytecode/CodeAttribute.java
Expand Up @@ -16,6 +16,8 @@

package javassist.bytecode;

import javassist.*;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -76,8 +78,7 @@ public CodeAttribute(ConstPool cp, int stack, int locals, byte[] code,
* class names.
*/
private CodeAttribute(ConstPool cp, CodeAttribute src, Map<String,String> classnames)
throws BadBytecode
{
throws BadBytecode, NotFoundException, CannotCompileException {
super(cp, tag);

maxStack = src.getMaxStack();
Expand Down Expand Up @@ -139,6 +140,10 @@ public AttributeInfo copy(ConstPool newCp, Map<String,String> classnames)
}
catch (BadBytecode e) {
throw new RuntimeCopyException("bad bytecode. fatal?");
} catch (NotFoundException e) {
throw new RuntimeException(e);
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}

Expand Down Expand Up @@ -324,7 +329,7 @@ public AttributeInfo getAttribute(String name) {
*
* @param smt the stack map table added to this code attribute.
* If it is null, a new stack map is not added.
* Only the old stack map is removed.
* Only the old stack map is removed.
*/
public void setAttribute(StackMapTable smt) {
AttributeInfo.remove(attributes, StackMapTable.tag);
Expand Down Expand Up @@ -352,21 +357,20 @@ public void setAttribute(StackMap sm) {
*/
private byte[] copyCode(ConstPool destCp, Map<String,String> classnames,
ExceptionTable etable, CodeAttribute destCa)
throws BadBytecode
{
throws BadBytecode, NotFoundException, CannotCompileException {
int len = getCodeLength();
byte[] newCode = new byte[len];
destCa.info = newCode;

LdcEntry ldc = copyCode(this.info, 0, len, this.getConstPool(),
newCode, destCp, classnames);
return LdcEntry.doit(newCode, ldc, etable, destCa);
}

private static LdcEntry copyCode(byte[] code, int beginPos, int endPos,
ConstPool srcCp, byte[] newcode,
ConstPool destCp, Map<String,String> classnameMap)
throws BadBytecode
{
ConstPool destCp, Map<String,String> classnameMap)
throws BadBytecode, NotFoundException, CannotCompileException {
int i2, index;
LdcEntry ldcEntry = null;

Expand Down Expand Up @@ -415,6 +419,7 @@ private static LdcEntry copyCode(byte[] code, int beginPos, int endPos,
case INVOKEDYNAMIC :
copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
classnameMap);
copyBootstrapMethod(srcCp, destCp, i + 1, code, newcode, classnameMap);
newcode[i + 3] = 0;
newcode[i + 4] = 0;
break;
Expand All @@ -434,6 +439,134 @@ private static LdcEntry copyCode(byte[] code, int beginPos, int endPos,
return ldcEntry;
}

/**
* Copy the Bootstrap method of the specified index referenced in the source <code>InvokeDynamic</code> directive
* to the specified index in the destination Boostrap Attribute.<br>
* if the Bootstrap Attribute does not exist in the destination class, create a new Bootstrap Attribute; <br>
* if the destination Bootstrap Method already exists at the specified index method,
* the method at that position will be overwritten, otherwise it will be added
* at the end of the destination Bootstrap method.
*
* @param srcCp the constant pool table of source
* @param destCp the constant pool table of destination
* @param codeIndex the index of the invoke dynamic first parameter in code array
* @param srcCode the code array of source
* @param newCode the code array of destination
* @param classnameMap pairs of replaced and substituted class names.
*
* @throws NotFoundException this exception thrown when the class
* cannot be found in the default <code>ClassPool</code>
* @throws CannotCompileException this exception thrown from the method
* {@link #copyInvokeStaticMethod(CtClass, ConstPool,
* BootstrapMethodsAttribute.BootstrapMethod, CtClass, Map)}
*/
private static void copyBootstrapMethod(ConstPool srcCp, ConstPool destCp, int codeIndex, byte[] srcCode,
byte[] newCode, Map<String,String> classnameMap)
throws NotFoundException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass srcCc = classPool.get(srcCp.getClassName());
CtClass destCc = classPool.get(destCp.getClassName());
ClassFile srcCf = srcCc.getClassFile();
ClassFile destCf = destCc.getClassFile();
BootstrapMethodsAttribute srcBma = (BootstrapMethodsAttribute)
srcCf.getAttribute(BootstrapMethodsAttribute.tag);

// if source class does not have bootstrap attribute then stop copy
if (srcBma == null) {
return;
}

BootstrapMethodsAttribute destBma = (BootstrapMethodsAttribute)
destCf.getAttribute(BootstrapMethodsAttribute.tag);

int srcCpIndex = ((srcCode[codeIndex] & 0xff) << 8) | (srcCode[codeIndex + 1] & 0xff);
int destCpIndex = ((newCode[codeIndex] & 0xff) << 8) | (newCode[codeIndex + 1] & 0xff);
int srcBmIndex = srcCp.getInvokeDynamicBootstrap(srcCpIndex);
int destBmIndex = destCp.getInvokeDynamicBootstrap(destCpIndex);

// if source class does not have bootstrap attribute, then create bootstrap attribute
if (destBma == null) {
destBma = new BootstrapMethodsAttribute(destCp,
new BootstrapMethodsAttribute.BootstrapMethod[0]);
destCf.addAttribute(destBma);
}

BootstrapMethodsAttribute.BootstrapMethod srcBm = srcBma.getMethods()[srcBmIndex];
destBma.addMethod(srcCp, srcBm, destBmIndex, classnameMap);

copyInvokeStaticMethod(srcCc, srcCp, srcBm, destCc, classnameMap);
}

/**
* Copy the static methods referenced by the bootstrap method in this class (such as some lambda methods).<br>
* If the source method exists in the destination class, it will be ignored.
*
* @param srcCc source class
* @param srcCp constant pool table of source class
* @param srcBm source method to be copied
* @param destCc destination class
* @param classnameMap irs of replaced and substituted class names.
*
* @throws CannotCompileException thrown by {@link CtNewMethod#copy(CtMethod, CtClass, ClassMap)}
* or{@link CtClass#addMethod(CtMethod)}
*/
private static void copyInvokeStaticMethod(CtClass srcCc, ConstPool srcCp,
BootstrapMethodsAttribute.BootstrapMethod srcBm, CtClass destCc,
Map<String, String> classnameMap) throws CannotCompileException {
for (int argument : srcBm.arguments) {
ConstInfo constInfo = srcCp.getItem(argument);

if (!(constInfo instanceof MethodHandleInfo)) continue;

MethodHandleInfo methodHandleInfo = (MethodHandleInfo) constInfo;
if (ConstPool.REF_invokeStatic != methodHandleInfo.refKind) continue;

String methodRefClassName = srcCp.getMethodrefClassName(methodHandleInfo.refIndex);
if (methodRefClassName == null || !methodRefClassName.equals(srcCc.getName())) continue;

String staticMethodName = srcCp.getMethodrefName(methodHandleInfo.refIndex);
String staticMethodSignature = srcCp.getMethodrefType(methodHandleInfo.refIndex);
CtMethod srcMethod = getStaticCtMethod(srcCc, staticMethodName, staticMethodSignature);

if (!checkStaticMethodExisted(destCc, staticMethodName, staticMethodSignature)) {
ClassMap classMap = new ClassMap();
classMap.putAll(classnameMap);

CtMethod ctMethod = CtNewMethod.copy(srcMethod, destCc, classMap);
destCc.addMethod(ctMethod);
}
}
}

private static CtMethod getStaticCtMethod(CtClass ctClass, String staticMethodName, String staticMethodSignature) {
CtMethod srcMethod = null;
for (CtMethod declaredMethod : ctClass.getDeclaredMethods()) {
if (Modifier.isStatic(declaredMethod.getModifiers())
&& declaredMethod.getName().equals(staticMethodName)
&& declaredMethod.getSignature().equals(staticMethodSignature)) {
srcMethod = declaredMethod;
break;
}
}

if (srcMethod == null) {
throw new RuntimeException("Can not found static method:" + staticMethodName);
}
return srcMethod;
}

private static boolean checkStaticMethodExisted(CtClass ctClass, String staticMethodName, String staticMethodSignature) {
for (CtMethod declaredMethod : ctClass.getDeclaredMethods()) {
if (Modifier.isStatic(declaredMethod.getModifiers())
&& declaredMethod.getName().equals(staticMethodName)
&& declaredMethod.getSignature().equals(staticMethodSignature)) {
return true;
}
}

return false;
}

private static void copyConstPoolInfo(int i, byte[] code, ConstPool srcCp,
byte[] newcode, ConstPool destCp,
Map<String,String> classnameMap) {
Expand Down