Skip to content

Commit

Permalink
feat: Support record features.
Browse files Browse the repository at this point in the history
  • Loading branch information
teletha committed Mar 13, 2023
1 parent 9ea48b0 commit 82ddb15
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 76 deletions.
155 changes: 93 additions & 62 deletions src/main/java/reincarnation/JavaMethodDecompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
package reincarnation;

import static org.objectweb.asm.Opcodes.*;
import static reincarnation.Node.*;
import static reincarnation.Node.Termination;
import static reincarnation.OperandCondition.*;
import static reincarnation.OperandUtil.*;
import static reincarnation.OperandUtil.load;

import java.lang.reflect.Executable;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -46,10 +46,30 @@
import reincarnation.operator.BinaryOperator;
import reincarnation.operator.UnaryOperator;
import reincarnation.structure.Structure;
import reincarnation.util.GeneratedRecordCodes;
import reincarnation.util.MultiMap;

class JavaMethodDecompiler extends MethodVisitor implements Code, Naming {

/** The reusable reference to record method. */
private static final Method RecordToString;

/** The reusable reference to record method. */
private static final Method RecordEquals;

/** The reusable reference to record method. */
private static final Method RecordHashCode;

static {
try {
RecordToString = GeneratedRecordCodes.class.getMethod("recordToString", Object.class);
RecordEquals = GeneratedRecordCodes.class.getMethod("recordEquals", Object.class, Object.class);
RecordHashCode = GeneratedRecordCodes.class.getMethod("recordHashCode", Object.class);
} catch (Exception e) {
throw I.quiet(e);
}
}

/** The description of {@link Debugger}. */
private static final String DEBUGGER = Type.getType(Debuggable.class).getDescriptor();

Expand Down Expand Up @@ -1290,75 +1310,86 @@ public void visitIntInsn(int opcode, int operand) {
* {@inheritDoc} bootstrapMethodHandle
*/
@Override
public void visitInvokeDynamicInsn(String name, String description, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
public void visitInvokeDynamicInsn(String name, String description, Handle bootstrap, Object... bootstrapMethodArguments) {
// recode current instruction
record(INVOKEDYNAMIC);

Handle handle = (Handle) bootstrapMethodArguments[1];
Type functionalInterfaceType = (Type) bootstrapMethodArguments[2];
Type lambdaType = Type.getMethodType(handle.getDesc());
Type callerType = Type.getMethodType(description);

// detect functional interface
Class interfaceClass = load(callerType.getReturnType());

// detect lambda method
try {
Class lambdaClass = load(handle.getOwner());
// Class lambdaReturnType = load(Type.getReturnType(handle.getDesc()));
Class[] lambdaParameterTypes = load(Type.getArgumentTypes(handle.getDesc()));
Method lambdaMethod = lambdaClass.getDeclaredMethod(handle.getName(), lambdaParameterTypes);

if (lambdaMethod.isSynthetic()) {
// ==================================
// Lambda
// ==================================
// build parameter from local environment
int consumableStackSize = callerType.getArgumentTypes().length;
List<Operand> params = new ArrayList();

for (int i = consumableStackSize - 1; 0 <= i; i--) {
Operand removed = current.remove(i);
if (removed instanceof OperandLocalVariable local && local.index == 0) {
// ignore "this" variable
} else {
params.add(removed);
if (bootstrap.getOwner().equals("java/lang/runtime/ObjectMethods") && bootstrap.getName().equals("bootstrap")) {
// Bootstrap methods for state-driven implementations of core methods, including
// Object.equals(Object), Object.hashCode(), and Object.toString(). These methods may be
// used, for example, by Java compiler implementations to implement the bodies of Object
// methods for record classes.
switch (name) {
case "toString" -> current.addOperand(OperandUtil.convertMethod(RecordToString, current.remove(0)));
case "hashCode" -> current.addOperand(OperandUtil.convertMethod(RecordHashCode, current.remove(0)));
case "equals" -> current.addOperand(OperandUtil.convertMethod(RecordEquals, current.remove(0), current.remove(0)));
default -> throw new IllegalArgumentException("Unexpected value: " + name);
}
} else {
Handle handle = (Handle) bootstrapMethodArguments[1];
Type callerType = Type.getMethodType(description);

// detect functional interface
Class interfaceClass = load(callerType.getReturnType());

// detect lambda method
try {
Class lambdaClass = load(handle.getOwner());
Class[] lambdaParameterTypes = load(Type.getArgumentTypes(handle.getDesc()));
Method lambdaMethod = lambdaClass.getDeclaredMethod(handle.getName(), lambdaParameterTypes);

if (lambdaMethod.isSynthetic()) {
// ==================================
// Lambda
// ==================================
// build parameter from local environment
int consumableStackSize = callerType.getArgumentTypes().length;
List<Operand> params = new ArrayList();

for (int i = consumableStackSize - 1; 0 <= i; i--) {
Operand removed = current.remove(i);
if (removed instanceof OperandLocalVariable local && local.index == 0) {
// ignore "this" variable
} else {
params.add(removed);
}
}
}

current.addOperand(new OperandLambda(interfaceClass, lambdaMethod, params, source));
} else {
// ==================================
// Method Reference
// ==================================
source.require(lambdaClass);

boolean needInstance = callerType.getArgumentTypes().length != 0;

switch (handle.getTag()) {
case H_INVOKESTATIC:
case H_INVOKEVIRTUAL:
case H_INVOKEINTERFACE:
SpecializedType specialized = new SpecializedType(interfaceClass)
.specializeByReturnAndParamTypes(functionalInterfaceType)
.specializeBySAM(lambdaMethod);

current.addOperand(new OperandMethodReference(lambdaMethod, needInstance ? current.remove(0) : null).fix(specialized));
break;
current.addOperand(new OperandLambda(interfaceClass, lambdaMethod, params, source));
} else {
// ==================================
// Method Reference
// ==================================
source.require(lambdaClass);

boolean needInstance = callerType.getArgumentTypes().length != 0;

switch (handle.getTag()) {
case H_INVOKESTATIC:
case H_INVOKEVIRTUAL:
case H_INVOKEINTERFACE:
SpecializedType specialized = new SpecializedType(interfaceClass)
.specializeByReturnAndParamTypes((Type) bootstrapMethodArguments[2])
.specializeBySAM(lambdaMethod);

current.addOperand(new OperandMethodReference(lambdaMethod, needInstance ? current.remove(0) : null)
.fix(specialized));
break;

case H_INVOKESPECIAL:
case H_NEWINVOKESPECIAL:
break;
case H_INVOKESPECIAL:
case H_NEWINVOKESPECIAL:
break;

default:
// If this exception will be thrown, it is bug of this program. So we must
// rethrow
// the wrapped error in here.
throw new Error();
default:
// If this exception will be thrown, it is bug of this program. So we must
// rethrow
// the wrapped error in here.
throw new Error();
}
}
} catch (Exception e) {
throw I.quiet(e);
}
} catch (Exception e) {
throw I.quiet(e);
}
}

Expand Down
56 changes: 56 additions & 0 deletions src/main/java/reincarnation/OperandUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import kiss.I;
import reincarnation.operator.AccessMode;

public class OperandUtil {

Expand Down Expand Up @@ -243,6 +246,59 @@ static Operand defaultValueFor(Class type) {
return Operand.Null;
}

/**
* Convert from java value to the suitable operand.
*
* @param value
* @return
*/
static Operand convert(Object value) {
if (value instanceof Operand op) {
return op;
} else if (value instanceof String text) {
return new OperandString(text);
} else if (value instanceof Class clazz) {
return new OperandType(clazz);
} else if (value instanceof Method method) {
return convertMethod(method, new Object[0]);
} else if (value.getClass().isArray()) {
int length = Array.getLength(value);
List<Operand> ops = new ArrayList();
for (int i = 0; i < length; i++) {
ops.add(convert(Array.get(value, i)));
}
return new OperandArray(ops, value.getClass().getComponentType());
} else {
throw new Error();
}
}

/**
* Convert from java value to the suitable operand.
*
* @param values
* @return
*/
static Operand[] convert(Object... values) {
Operand[] ops = new Operand[values.length];
for (int i = 0; i < ops.length; i++) {
ops[i] = convert(values[i]);
}
return ops;
}

/**
* Convert method to method call operand.
*
* @param method
* @param parameters
* @return
*/
static OperandMethodCall convertMethod(Method method, Object... parameters) {
return new OperandMethodCall(AccessMode.THIS, method.getDeclaringClass(), method.getName(), method
.getParameterTypes(), convert(method.getDeclaringClass()), I.list(convert(parameters)));
}

/**
* Check whether the specified method is unwrapper for primitive or not.
*
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/reincarnation/coder/Coder.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,14 @@ public String toString() {
*
* @param constructor
*/
public abstract void writeConstructor(Constructor constructor, Code code);
public abstract void writeConstructor(Constructor constructor, Code<Code> code);

/**
* Method declaration.
*
* @param method
*/
public abstract void writeMethod(Method method, Code code);
public abstract void writeMethod(Method method, Code<Code> code);

/**
* Lambda.
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/reincarnation/coder/DelegatableCoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,15 @@ public void writeStaticInitializer(Code code) {
* {@inheritDoc}
*/
@Override
public void writeConstructor(Constructor constructor, Code code) {
public void writeConstructor(Constructor constructor, Code<Code> code) {
coder.writeConstructor(constructor, code);
}

/**
* {@inheritDoc}
*/
@Override
public void writeMethod(Method method, Code code) {
public void writeMethod(Method method, Code<Code> code) {
coder.writeMethod(method, code);
}

Expand Down
32 changes: 27 additions & 5 deletions src/main/java/reincarnation/coder/java/JavaCoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import reincarnation.operator.AssignOperator;
import reincarnation.operator.BinaryOperator;
import reincarnation.operator.UnaryOperator;
import reincarnation.util.GeneratedRecordCodes;
import reincarnation.util.MultiMap;

public class JavaCoder extends Coder<JavaCodingOption> {
Expand Down Expand Up @@ -165,7 +166,7 @@ public void writeType(Class type, Runnable inner) {
? Join.of(type.getPermittedSubclasses()).ignoreEmpty().prefix(" permits ").separator("," + space).converter(this::name)
: null;
Join accessor = modifier(type);
Join<TypeVariable> variable = Join.of(type.getTypeParameters())
Join variable = Join.of(type.getTypeParameters())
.ignoreEmpty()
.prefix("<")
.separator("," + space)
Expand All @@ -175,6 +176,17 @@ public void writeType(Class type, Runnable inner) {
if (type.isRecord()) {
kind = "record";
implement = Join.of(type.getGenericInterfaces()).ignoreEmpty().prefix(" implements ").converter(this::name);
accessor.remove("static", "final");

boolean vararg = type.getDeclaredConstructors()[0].isVarArgs();
int max = type.getRecordComponents().length - 1;
variable = Join.of(type.getRecordComponents()).prefix("(").separator("," + space).suffix(")").converter((index, x) -> {
String paramType = name(x.getGenericType());
if (vararg && index == max) {
paramType = paramType.replaceAll("\\[\\]$", "...");
}
return paramType + " " + x.getName();
});
} else if (type.isInterface()) {
kind = "interface";
implement = Join.of(type.getGenericInterfaces()).ignoreEmpty().prefix(" extends ").converter(this::name);
Expand Down Expand Up @@ -207,6 +219,11 @@ public void writeField(Field field) {
*/
@Override
public void writeStaticField(Field field) {
// ignore compiler generated code
if (GeneratedRecordCodes.isGenerated(field)) {
return;
}

if (current.is(Class::isInterface)) {
// ignore, write fields in static initializer
} else {
Expand Down Expand Up @@ -244,7 +261,12 @@ public void writeStaticInitializer(Code code) {
* {@inheritDoc}
*/
@Override
public void writeConstructor(Constructor constructor, Code code) {
public void writeConstructor(Constructor constructor, Code<Code> code) {
// ignore compiler generated code
if (GeneratedRecordCodes.isGenerated(constructor, code)) {
return;
}

vars.start();

line();
Expand All @@ -259,9 +281,9 @@ public void writeConstructor(Constructor constructor, Code code) {
* {@inheritDoc}
*/
@Override
public void writeMethod(Method method, Code code) {
// ignore compiler generated method
if (method.isSynthetic()) {
public void writeMethod(Method method, Code<Code> code) {
// ignore compiler generated code
if (method.isSynthetic() || GeneratedRecordCodes.isGenerated(method, code)) {
return;
}

Expand Down
Loading

0 comments on commit 82ddb15

Please sign in to comment.