Skip to content

Commit

Permalink
Composing .class files with byte arrays, removed ASM dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
gudzpoz committed Jul 29, 2022
1 parent 52702a4 commit d4af528
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 68 deletions.
7 changes: 0 additions & 7 deletions luajava/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@ repositories {
group = rootProject.group
version = rootProject.version

java {
registerFeature('proxy') {
usingSourceSet(sourceSets.main)
}
}

dependencies {
implementation 'com.badlogicgames.gdx:gdx-jnigen-loader:2.3.1'
implementation 'com.google.errorprone:error_prone_annotations:2.14.0'
implementation 'org.jetbrains:annotations:23.0.0'
proxyImplementation 'org.ow2.asm:asm:9.3'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.mockito:mockito-core:4.6.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package party.iroiro.luajava.util;

import org.objectweb.asm.*;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
Expand All @@ -10,45 +10,35 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class AsmLookupProvider implements LookupProvider {
private final AtomicInteger counter = new AtomicInteger(0);
private final ConcurrentMap<String, Class<?>> extenders = new ConcurrentHashMap<>();
private final LookupLoader loader = new LookupLoader(LookupLoader.class.getClassLoader());

private Class<?> lookupExtender(Class<?> iClass) {
private final NastyLookupProvider fallback = new NastyLookupProvider();

private Class<?> lookupExtender(Class<?> iClass) throws ClassNotFoundException {
try {
String internalName = Type.getInternalName(iClass);
Class<?> extender = extenders.get(internalName);
String iName = iClass.getName();
Class<?> extender = extenders.get(iName);
if (extender != null) {
return extender;
}

ClassReader reader = new ClassReader("party.iroiro.luajava.util.SampleExtender");
ClassWriter writer = new ClassWriter(0);
AtomicReference<String> className = new AtomicReference<>();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM4, writer) {
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
if (internalName.startsWith("java/")) {
name = name + '$' + counter.getAndIncrement();
} else {
name = internalName + "LuaJavaImpl$" + counter.getAndIncrement();
}
cv.visit(version, access, name,
signature, superName, new String[]{internalName});
className.set(name.replace('/', '.'));
}
};
reader.accept(visitor, 0);
loader.add(className.get(), writer.toByteArray());
extender = loader.findClass(className.get());
extenders.put(internalName, extender);
String name;
if (iName.startsWith("java.")) {
name = "party.iroiro.luajava.util.SampleExtender$" + counter.getAndIncrement();
} else {
name = iName + "LuaJavaImpl$" + counter.getAndIncrement();
}
loader.add(name, SampleExtender.generateClass(
name.replace('.', '/'), iName.replace('.', '/')));
extender = loader.findClass(name);
extenders.put(iName, extender);
return extender;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new ClassNotFoundException(e.toString());
}
}

Expand All @@ -62,13 +52,25 @@ private MethodHandles.Lookup fromExtender(Class<?> extender) {
}

public MethodHandle lookup(Method method) throws IllegalAccessException {
Class<?> extender = lookupExtender(method.getDeclaringClass());
return fromExtender(extender)
.unreflectSpecial(method, extender);
try {
Class<?> extender = lookupExtender(method.getDeclaringClass());
return fromExtender(extender)
.unreflectSpecial(method, extender);
} catch (ClassNotFoundException e) {
try {
return fallback.lookup(method);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException ex) {
throw new IllegalAccessException(ex.toString());
}
}
}

public Class<?> wrap(Class<?> iClass) {
return lookupExtender(iClass);
public @Nullable Class<?> wrap(Class<?> iClass) {
try {
return lookupExtender(iClass);
} catch (ClassNotFoundException e) {
return fallback.wrap(iClass);
}
}

public ClassLoader getLoader() {
Expand Down
23 changes: 1 addition & 22 deletions luajava/src/main/java/party/iroiro/luajava/util/ClassUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.io.Externalizable;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

Expand Down Expand Up @@ -278,27 +277,7 @@ public static Class<?>[] toClassArray(@Nullable Collection<Class<?>> collection)
return collection.toArray(EMPTY_CLASS_ARRAY);
}

public final static LookupProvider lookupProvider;

static {
LookupProvider provider;
try {
String lookup = System.getProperty("luajava_lookup");
if (lookup == null || "asm".equals(lookup)) {
Class.forName("org.objectweb.asm.ClassReader");
provider = (LookupProvider)
Class.forName("party.iroiro.luajava.util.AsmLookupProvider")
.getConstructor().newInstance();
} else {
provider = new NastyLookupProvider();
}
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException |
NoSuchMethodException e) {
provider = new NastyLookupProvider();
}

lookupProvider = provider;
}
public final static LookupProvider lookupProvider = new AsmLookupProvider();

/**
* Invokes a default method from an interface
Expand Down
230 changes: 224 additions & 6 deletions luajava/src/main/java/party/iroiro/luajava/util/SampleExtender.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,229 @@
package party.iroiro.luajava.util;

import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.MethodHandles;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@SuppressWarnings("unused")
public interface SampleExtender extends MethodHandleInfo {
static MethodHandles.Lookup getLookup() {
return MethodHandles.lookup();
/**
* Generates a class
*/
public abstract class SampleExtender {
private final static byte[] CAFEBABE = {
/* The CAFEBABE magic */
(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE,
/* Minor version */
0x00, 0x00,
/* Major version: Java 8 */
0x00, 0x34,
/* Constant pool count */
0x00, 0x13,
/* Constant: Utf-8 */ // Constant 1, to be filled by us with the class name
0x01,
};

private final static byte[] SOME_CONTANTS = {
/* Constant: Class */ // Constant 2
0x07,
/* Name index */
0x00, 0x01,

/* Constant: Utf-8 */ // Constant 3
0x01,
/* Length */
0x00, 0x10,
/* "java/lang/Object" */
0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E,
0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74,

/* Constant: Class */ // Constant 4
0x07,
/* Name index */
0x00, 0x03,

/* Constant: Utf-8 */ // Constant 5, to be filled by us with the extended interface
0x01,
};

private final static byte[] CONTENT = {
/* Constant: Class */ // Constant 6
0x07,
/* Name index */
0x00, 0x05,

/* Constant: Utf-8 */ // Constant 7
0x01,
/* Length */
0x00, 0x25,
/* "java/lang/invoke/MethodHandles$Lookup" */
0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
0x69, 0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74,
0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73,
0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70,

/* Constant: Class */ // Constant 8
0x07,
/* Name index */
0x00, 0x07,

/* Constant: Utf-8 */ // Constant 9
0x01,
/* Length */
0x00, 0x1E,
/* "java/lang/invoke/MethodHandles" */
0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76, 0x6F, 0x6B,
0x65, 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x73,

/* Constant: Class */ // Constant 0x0A
0x07,
/* Name index */
0x00, 0x09,

/* Constant: Utf-8 */ // Constant 0x0B
0x01,
/* Length */
0x00, 0x06,
/* "Lookup" */
0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70,

/* Constant: Utf-8 */ // Constant 0x0C
0x01,
/* Length */
0x00, 0x09,
/* "getLookup" */
0x67, 0x65, 0x74, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70,

/* Constant: Utf-8 */ // Constant 0x0D
0x01,
/* Length */
0x00, 0x29,
/* "()Ljava/lang/invoke/MethodHandles$Lookup;" */
0x28, 0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69,
0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74, 0x68, 0x6F, 0x64, 0x48, 0x61,
0x6E, 0x64, 0x6C, 0x65, 0x73, 0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75, 0x70, 0x3B,

/* Constant: Utf-8 */ // Constant 0x0E
0x01,
/* Length */
0x00, 0x06,
/* "lookup" */
0x6C, 0x6F, 0x6F, 0x6B, 0x75, 0x70,

/* Constant: NameAndType */ // Constant 0x0F
0x0C,
/* Name index */
0x00, 0x0E,
/* Descriptor index */
0x00, 0x0D,

/* Constant: Methodref */ // Constant 0x10
0x0A,
/* Class index */
0x00, 0x0A,
/* NameAndType index */
0x00, 0x0F,

/* Constant: Utf-8 */ // Constant 0x11
0x01,
/* Length */
0x00, 0x04,
/* "Code" */
0x43, 0x6F, 0x64, 0x65,

/* Constant: Utf-8 */ // Constant 0x12
0x01,
/* Length */
0x00, 0x0C,
/* "InnerClasses" */
0x49, 0x6E, 0x6E, 0x65, 0x72, 0x43, 0x6C, 0x61, 0x73, 0x73, 0x65, 0x73,

/* Access flags */
0x06, 0x01,
/* `this` */
0x00, 0x02,
/* `super` */
0x00, 0x04,
/* Interfaces count */
0x00, 0x01,
/* Interfaces */
0x00, 0x06,
/* Fields count */
0x00, 0x00,
/* Methods count */
0x00, 0x01,
/* Method: Method flags */
0x00, 0x09,
/* Method: Name index */
0x00, 0x0C,
/* Method: Descriptor index */
0x00, 0x0D,
/* Method: Attributes count */
0x00, 0x01,
/* Method: Attribute: Attribute name index: Code */
0x00, 0x11,
/* Method: Attribute: Attribute length */
0x00, 0x00, 0x00, 0x10,
/* Method: Attribute: Code: Max stack */
0x00, 0x01,
/* Method: Attribute: Code: Max locals */
0x00, 0x00,
/* Method: Attribute: Code: Code length */
0x00, 0x00, 0x00, 0x04,
/* Method: Attribute: Code: Code */
(byte) 0xB8, 0x00, 0x10, (byte) 0xB0, // invokestatic, areturn
/* Method: Attribute: Code: Exception table length */
0x00, 0x00,
/* Method: Attribute: Code: Attribute count */
0x00, 0x00,
/* Attribute count */
0x00, 0x01,
/* Attribute: Attribute name index: InnerClasses */
0x00, 0x12,
/* Attribute: Attribute length */
0x00, 0x00, 0x00, 0x0A,
/* Attribute: InnerClasses: Number of classes */
0x00, 0x01,
/* Attribute: InnerClasses: Class: Inner class info index */
0x00, 0x08,
/* Attribute: InnerClasses: Class: Outer class info index */
0x00, 0x0A,
/* Attribute: InnerClasses: Class: Inner name index */
0x00, 0x0B,
/* Attribute: InnerClasses: Class: Access flags */
0x00, 0x19
};

/**
* Generates a sample class extending the specified interface
*
* <p>
* The names should be like {@code com/example/SomeClass$InnerClass}.
* </p>
*
* @param name the generated class name
* @param iName the interface name
* @return a generated class
* @throws IOException if {@link ByteArrayOutputStream} errs
*/
public static byte[] generateClass(String name, String iName) throws IOException {
byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
byte[] iNameBytes = iName.getBytes(StandardCharsets.UTF_8);
if (nameBytes.length > Short.MAX_VALUE || iNameBytes.length > Short.MAX_VALUE) {
throw new IOException("The class name is too lengthy");
}
ByteArrayOutputStream output = new ByteArrayOutputStream(
CAFEBABE.length
+ 2 + nameBytes.length
+ 2 + iNameBytes.length
);
output.write(CAFEBABE);
output.write((nameBytes.length & 0xFF00) >> 8);
output.write(nameBytes.length & 0x00FF);
output.write(nameBytes);
output.write(SOME_CONTANTS);
output.write((iNameBytes.length & 0xFF00) >> 8);
output.write(iNameBytes.length & 0x00FF);
output.write(iNameBytes);
output.write(CONTENT);
return output.toByteArray();
}
}
Loading

0 comments on commit d4af528

Please sign in to comment.