Valhalla breaks minimal-j framework
Ethan McCue
ethan at mccue.dev
Tue Dec 2 04:50:38 UTC 2025
There is a parallel thread going on which touches on similar things - there
people are wanting "record component references."
I adapted the code from
https://github.com/rilindbicaj/fluentmapper/blob/main/fluentmapper-provider/src/main/java/dev/bici/fluentmapper/provider/expression/parser/ExpressionParser.java
to use the new classfile api + work for a quick demo of the approach.
Basically, you can get at the code in the body of a lambda via some
serialization hackery. The degree to which this is actually supported is to
me unknown.
import java.io.IOException;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.classfile.instruction.FieldInstruction;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public final class FieldReference {
private final Class<?> root;
private final List<Field> fields;
private FieldReference(Class<?> root, List<Field> fields) {
this.root = root;
this.fields = fields;
}
public Class<?> root() {
return root;
}
public List<Field> fields() {
return fields;
}
private static SerializedLambda toSerializedLambda(Expression<?, ?>
expression) {
try {
var writeReplaceMethod =
expression.getClass().getDeclaredMethod("writeReplace");
writeReplaceMethod.setAccessible(true);
return (SerializedLambda) writeReplaceMethod.invoke(expression);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("Could not extract
SerializedLambda from the provided expression;", e);
}
}
public static <T> FieldReference of(Class<T> klass, Expression<T, ?>
expression) {
// TODO: This work can likely be cached.
var serializedLambda = toSerializedLambda(expression);
var containingClass = serializedLambda.getImplClass();
var lambdaSignature = serializedLambda.getImplMethodName();
// TODO: Unsure if this is the right class loader to use
var classLoader = klass.getClassLoader();
byte[] classBytes;
try (var classResource = classLoader.getResourceAsStream(
containingClass.replace('.', '/') + ".class"
)) {
if (classResource == null) {
throw new IllegalArgumentException("Could not find
class-file resource in class loader");
}
classBytes = classResource.readAllBytes();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
ClassModel classModel = ClassFile.of().parse(classBytes);
var methodModel = classModel.methods().stream()
.filter(method ->
lambdaSignature.equals(method.methodName().toString()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Could not
find the implementing method for the expression lambda"));
var code = methodModel.code().orElseThrow();
// TODO: This could use a lot of prechecks
var fields = new ArrayList<Field>();
Class<?> lastType = klass;
for (var element : code.elementList()) {
if (element instanceof FieldInstruction fieldInstruction) {
try {
fields.add(
lastType.getDeclaredField(fieldInstruction.field().name().toString())
);
// TODO: This is almost certainly not sound
if
(fieldInstruction.typeSymbol().packageName().isEmpty()) {
lastType = classLoader.loadClass(
fieldInstruction.typeSymbol().displayName()
);
}
else {
lastType = classLoader.loadClass(
fieldInstruction.typeSymbol().packageName()
+ "."
+
fieldInstruction.typeSymbol().displayName()
);
}
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException("Inaccessable
field", e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
return new FieldReference(klass, List.copyOf(fields));
}
@FunctionalInterface
public interface Expression<A, B> extends Serializable {
B get(A a);
}
@Override
public String toString() {
return "FieldReference[" +
"root=" + root.getName() +
", fields=" + fields.stream().map(Field::getName).toList() +
']';
}
}
And you can use all of that with a lambda that only accesses fields to
emulate "field references."
class A {
String aa;
B b;
}
class B {
C c;
}
class C {
String v;
D d;
}
class D {
E e;
}
class E {
F f;
}
class F {
G g;
}
class G {
String v;
}
public class Test {
public static void main(String[] args) {
var refA = FieldReference.of(A.class, a -> a.aa);
IO.println(refA);
var refB = FieldReference.of(A.class, a -> a.b.c.d);
IO.println(refB);
}
}
FieldReference[root=A, fields=[aa]]
FieldReference[root=A, fields=[b, c, d]]
So that is one approach to look into.
On Mon, Dec 1, 2025, 8:38 PM Bruno Eberhard <bruno.eberhard at pop.ch> wrote:
> Am 01.12.2025 um 22:00 schrieb Ethan McCue:
> > On that note, i'd say you have somewhat of a bigger problem w.r.t. enums
> > [..]
> > As with all things in jdk.unsupported, I can't help but wonder if there
> > are any VM level invariants you run afoul of by making extra enum
> > instances.
>
> I don't know about the VM internals. I did never run into problems with
> these extra enums.
>
> > My first stab at #2 is to add something like this record [..]
> Thank you for your input. I have to try this.
>
> > I think it's also worth asking - do you have any usage statistics on
> > your library? (Maven central used to offer download stats, etc.) If you
> > are going to be broken anyways it is probably useful to know the blast
> > radius.
>
> Very good point. As far as I know minimal-j is only used by me for a
> smaller and one bigger App. In the bigger App ( An ERP called
> lisheane.ch , currently only available in german ) I invested quite some
> time. I would have to rewrite parts of it.
>
> So the world will not be standing still if minimal-j doesn't work
> anymore or has to be changed.
>
> I would still like the idea of field references ( Person::name ,
> Person::address::city ) in the java language as equivalent for the $
> constant. It is really nice to use. But I don't have the knowledge how
> to add it to the language or how to propose the feature.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-dev/attachments/20251201/617b9788/attachment.htm>
More information about the valhalla-dev
mailing list