Skip to content

Commit

Permalink
feat: (javac) support constructor reference
Browse files Browse the repository at this point in the history
  • Loading branch information
teletha committed Apr 24, 2024
1 parent efed2a0 commit 69e4088
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 418 deletions.
390 changes: 126 additions & 264 deletions bytecode.txt

Large diffs are not rendered by default.

104 changes: 60 additions & 44 deletions src/main/java/reincarnation/JavaMethodDecompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static reincarnation.OperandCondition.*;
import static reincarnation.OperandUtil.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
Expand Down Expand Up @@ -936,18 +937,23 @@ public void visitInsn(int opcode) {
break;

case POP:
// When the JDK compiler compiles the code including "instance method reference", it
// When the compiler compiles the code including "instance method reference", it
// generates the byte code expressed in following ASM codes.
//
// In Javac
// visitInsn(DUP);
// visitMethodInsn(INVOKESTATIC, "java/util/Objects","requireNonNull",SIGNATURE, false);
// visitInsn(POP);
//
// In ECJ
// visitInsn(DUP);
// visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object","getClass","()Ljava/lang/Class;");
// visitInsn(POP);
//
// Although i guess that it is the initialization code for the class to
// which the lambda method belongs, ECJ doesn't generated such code.
// In Javascript runtime, it is a completely unnecessary code,
// so we should delete them unconditionally.
if (match(DUP, INVOKEVIRTUAL, POP)) {
// I guess that it is NULL checker and initialize code for the class to which the lambda
// method belongs. It is a completely unnecessary code, so we should remove them
// unconditionally.
if (match(DUP, INVOKESTATIC, POP) || match(DUP, INVOKEVIRTUAL, POP)) {
current.remove(0);
break;
}
Expand Down Expand Up @@ -1389,60 +1395,70 @@ public void visitInvokeDynamicInsn(String name, String description, Handle boots
// detect functional interface
Class interfaceClass = load(callerType.getReturnType());

// detect lambda method
try {
// detect lambda method
Class lambdaClass = load(handle.getOwner());
String lambdaName = handle.getName();
Class[] lambdaParameterTypes = load(Type.getArgumentTypes(handle.getDesc()));
Method lambdaMethod = lambdaClass.getDeclaredMethod(handle.getName(), lambdaParameterTypes);
boolean needInstance = callerType.getArgumentTypes().length != 0;

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);
}
}
source.require(lambdaClass);

current.addOperand(new OperandLambda(interfaceClass, lambdaMethod, params, source));
} else {
switch (handle.getTag()) {
case H_INVOKESTATIC:
case H_INVOKEVIRTUAL:
case H_INVOKEINTERFACE:
// ==================================
// Method Reference
// ==================================
source.require(lambdaClass);

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

switch (handle.getTag()) {
case H_INVOKESTATIC:
case H_INVOKEVIRTUAL:
case H_INVOKEINTERFACE:
Method lambdaMethod = lambdaClass.getDeclaredMethod(lambdaName, 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 {
SpecializedType specialized = new SpecializedType(interfaceClass)
.specializeByReturnAndParamTypes((Type) bootstrapMethodArguments[2])
.specializeBySAM(lambdaMethod);

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

case H_INVOKESPECIAL:
case H_NEWINVOKESPECIAL:
break;
case H_INVOKESPECIAL:
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();
}
case H_NEWINVOKESPECIAL:
// ==================================
// Constructor Reference in Javac
// ==================================
Constructor lambdaConstructor = lambdaClass.getDeclaredConstructor(lambdaParameterTypes);
SpecializedType specialized = new SpecializedType(interfaceClass).specializeByReturnType(lambdaClass)
.specializeByParamTypes(lambdaParameterTypes);

current.addOperand(new OperandConstructorReference(lambdaConstructor).fix(specialized));
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();
}
} catch (Exception e) {
throw I.quiet(e);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/reincarnation/OperandCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,6 @@ protected boolean isValue() {
*/
@Override
protected String view() {
return "if (" + left.view() + " " + right.view() + ") then " + then.id + " else " + elze.id;
return "if (" + left.view() + " " + right.view() + ") then " + then.id + " else " + (elze == null ? "SAME" : elze.id);
}
}
53 changes: 53 additions & 0 deletions src/main/java/reincarnation/OperandConstructorReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 The REINCARNATION Development Team
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/MIT
*/
package reincarnation;

import java.lang.reflect.Constructor;

import reincarnation.coder.Coder;

class OperandConstructorReference extends Operand {

/** The lambda body. */
private final Constructor reference;

/**
* Create the operand for constructor reference.
*
* @param reference
*/
OperandConstructorReference(Constructor reference) {
this.reference = reference;
}

/**
* {@inheritDoc}
*/
@Override
protected void writeCode(Coder coder) {
coder.writeConstructorReference(reference);
}

/**
* {@inheritDoc}
*/
@Override
protected boolean isValue() {
return true;
}

/**
* {@inheritDoc}
*/
@Override
protected String view() {
return reference.getName() + "::new";
}
}
2 changes: 1 addition & 1 deletion src/main/java/reincarnation/OperandLocalVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected boolean isNegatable() {
* {@inheritDoc}
*/
@Override
public boolean isValue() {
protected boolean isValue() {
return true;
}

Expand Down
16 changes: 13 additions & 3 deletions src/main/java/reincarnation/OperandMethodCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import kiss.I;
import kiss.Model;
Expand Down Expand Up @@ -80,7 +81,14 @@ class OperandMethodCall extends Operand {
* @return
*/
private Method find(Class owner, String name, Class[] types) {
for (Class type : Model.collectTypes(owner)) {
Set<Class> collections = Model.collectTypes(owner);

// In Javac, when the interface method reference invokes the Object class method,
// it is compiled as a call to the interface, not to the object. However, this would fail to
// find methods of the Object class, so the Object class is added unconditionally.
collections.add(Object.class);

for (Class type : collections) {
try {
Method method = type.getDeclaredMethod(name, types);
int mod = method.getModifiers();
Expand All @@ -98,7 +106,9 @@ private Method find(Class owner, String name, Class[] types) {
// ignore
}
}
throw new NoSuchMethodError(owner + "#" + name + Arrays.toString(types));
throw new NoSuchMethodError(owner + "#" + name + Stream.of(types)
.map(Class::getSimpleName)
.collect(Collectors.joining(", ", "(", ")")));
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/reincarnation/OperandMethodReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ protected boolean isValue() {
*/
@Override
protected String view() {
return context.view() + "::" + reference.getName();
if (context == null) {
return reference.getDeclaringClass().getSimpleName() + "::" + reference.getName();
} else {
return context.view() + "::" + reference.getName();
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/reincarnation/coder/Coder.java
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,13 @@ public final void writeEnclose(Code code) {
*/
public abstract void writeThisConstructorCall(Constructor constructor, List<Code> params);

/**
* Constructor reference.
*
* @param constructor A constructor info.
*/
public abstract void writeConstructorReference(Constructor constructor);

/**
* Method call expression.
*
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/reincarnation/coder/DelegatableCoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ public void writeConstructorCall(Constructor constructor, List<Code> params) {
coder.writeConstructorCall(constructor, params);
}

/**
* {@inheritDoc}
*/
@Override
public void writeConstructorReference(Constructor constructor) {
coder.writeConstructorReference(constructor);
}

/**
* {@inheritDoc}
*/
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/reincarnation/coder/java/JavaCoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,14 @@ public void writeThisConstructorCall(Constructor constructor, List<Code> params)
}
}

/**
* {@inheritDoc}
*/
@Override
public void writeConstructorReference(Constructor constructor) {
write(name(constructor.getDeclaringClass()), "::new");
}

/**
* {@inheritDoc}
*/
Expand Down
77 changes: 0 additions & 77 deletions src/test/java/reincarnation/JavacEnvironment.java

This file was deleted.

Loading

0 comments on commit 69e4088

Please sign in to comment.