1 /*
2 * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import java.lang.classfile.Attributes;
25 import java.lang.classfile.ClassFile;
26 import java.lang.classfile.ClassModel;
27 import java.lang.classfile.CodeModel;
28 import java.lang.classfile.MethodModel;
29 import java.lang.classfile.MethodTransform;
30 import java.lang.classfile.TypeKind;
31 import java.lang.classfile.constantpool.ClassEntry;
32 import java.lang.classfile.instruction.InvokeDynamicInstruction;
33 import java.lang.constant.ClassDesc;
34 import java.lang.constant.ConstantDesc;
35 import java.lang.constant.ConstantDescs;
36 import java.lang.constant.DirectMethodHandleDesc;
37 import java.lang.constant.DirectMethodHandleDesc.Kind;
38 import java.lang.constant.DynamicCallSiteDesc;
39 import java.lang.constant.MethodHandleDesc;
40 import java.lang.constant.MethodTypeDesc;
41 import java.lang.reflect.AccessFlag;
42 import java.nio.file.Files;
43 import java.nio.file.Path;
44 import java.util.*;
45
46 import jdk.incubator.code.Reflect;
47 import jdk.incubator.code.Op;
48 import jdk.incubator.code.dialect.java.JavaType;
49 import jdk.incubator.code.dialect.java.MethodRef;
50 import jdk.incubator.code.runtime.CodeModelBootstraps;
51 import jdk.incubator.code.runtime.ReflectableLambdaMetafactory;
52
53 public final class Unreflect {
54
55 static final ClassDesc CD_Reflect = Reflect.class.describeConstable().get();
56 static final ClassDesc CD_CodeModelBootstraps = CodeModelBootstraps.class.describeConstable().get();
57 static final ClassDesc CD_ReflectableLambdaMetafactory = ReflectableLambdaMetafactory.class.describeConstable().get();
58 static final ClassDesc CD_CallerSensitive = ClassDesc.of("jdk.internal.reflect.CallerSensitive");
59
60 static final DirectMethodHandleDesc DMHD_codeModelExecutor = ConstantDescs.ofCallsiteBootstrap(
61 CD_CodeModelBootstraps, "codeModelExecutor", ConstantDescs.CD_CallSite, ConstantDescs.CD_MethodHandle);
62
63 static boolean isReflective(MethodModel mm) {
64 return mm.findAttribute(Attributes.runtimeVisibleAnnotations())
65 .map(aa -> aa.annotations().stream().anyMatch(a -> a.classSymbol().equals(CD_Reflect)))
66 .orElse(false);
67 }
68
69 static byte[] transform(ClassModel clm) {
70 Set<String> modelAccessors = new HashSet<>();
71 MethodTypeDesc modelType = MethodTypeDesc.of(Op.class.describeConstable().orElseThrow());
72 clm.methods().stream().filter(m -> m.methodTypeSymbol().equals(modelType))
73 .forEach(m -> modelAccessors.add(m.methodName().stringValue()));
74 return ClassFile.of(ClassFile.ConstantPoolSharingOption.NEW_POOL).transformClass(clm, (clb, cle) -> {
75 if (cle instanceof MethodModel mm) {
76 if (mm.methodName().equalsString("<init>") || mm.code().isEmpty()
77 || mm.findAttribute(Attributes.runtimeVisibleAnnotations())
78 .map(a -> a.annotations().stream().anyMatch(ann -> ann.classSymbol().equals(CD_CallerSensitive)))
79 .orElse(false)) {
80 clb.with(mm);
81 } else if (isReflective(mm) || modelAccessors.contains(modelAccessorName(clm, mm))) {
82 clb.transformMethod(mm, MethodTransform.dropping(me -> me instanceof CodeModel)
83 .andThen(MethodTransform.endHandler(mb -> mb.withCode(cob -> {
84 MethodTypeDesc mts = mm.methodTypeSymbol();
85 boolean hasReceiver = !mm.flags().has(AccessFlag.STATIC);
86 if (hasReceiver) {
87 cob.aload(cob.receiverSlot());
88 }
89 for (int i = 0; i < mts.parameterCount(); i++) {
90 cob.loadLocal(TypeKind.from(mts.parameterType(i)), cob.parameterSlot(i));
91 }
92 boolean isInterface = clm.flags().has(AccessFlag.INTERFACE);
93 Kind kind = hasReceiver
94 ? (isInterface ? Kind.INTERFACE_VIRTUAL : Kind.VIRTUAL)
95 : (isInterface ? Kind.INTERFACE_STATIC : Kind.STATIC);
96 cob.invokedynamic(DynamicCallSiteDesc.of(DMHD_codeModelExecutor,
97 ConstantDescs.DEFAULT_NAME,
98 hasReceiver
99 ? mts.insertParameterTypes(0, clm.thisClass().asSymbol())
100 : mts,
101 MethodHandleDesc.ofMethod(kind,
102 clm.thisClass().asSymbol(),
103 mm.methodName().stringValue(),
104 mts)));
105 cob.return_(TypeKind.from(mts.returnType()));
106 }))));
107 } else {
108 clb.transformMethod(mm, MethodTransform.transformingCode((cob, coe) -> {
109 DirectMethodHandleDesc bsm;
110 if (coe instanceof InvokeDynamicInstruction i
111 && (bsm = i.bootstrapMethod()).owner().equals(CD_ReflectableLambdaMetafactory)) {
112 // redirect metafactory and altMetafactory
113 cob.invokedynamic(DynamicCallSiteDesc.of(MethodHandleDesc.ofMethod(Kind.STATIC,
114 CD_CodeModelBootstraps,
115 bsm.methodName().equals("metafactory")
116 ? "lambdaMetafactory"
117 : "lambdaAltMetafactory",
118 bsm.invocationType()),
119 i.name().stringValue(),
120 MethodTypeDesc.ofDescriptor(i.type().stringValue()),
121 i.bootstrapArgs().toArray(ConstantDesc[]::new)));
122 } else {
123 cob.with(coe);
124 }
125 }));
126 }
127 } else {
128 clb.with(cle);
129 }
130 });
131 }
132
133 static String modelAccessorName(ClassModel clm, MethodModel mm) {
134 MethodTypeDesc type = mm.methodTypeSymbol();
135 return MethodRef.method(JavaType.type(clm.thisClass().asSymbol()), mm.methodName().stringValue(),
136 JavaType.type(type.returnType()), type.parameterList().stream().map(JavaType::type).toList())
137 .toString().replace('.', '$').replace(';', '$').replace('[', '$').replace('/', '$');
138 }
139
140 public static void main(String[] args) throws Exception {
141 // process class files from arguments
142 var toUnreflect = new ArrayDeque<String>();
143 for (String arg : args) {
144 Path path = Path.of(arg);
145 if (Files.isDirectory(path)) {
146 try (var files = Files.walk(path)) {
147 for (Path file : files.filter(Files::isRegularFile)
148 .filter(p -> p.toString().endsWith(".class")).toList()) {
149 System.out.println("unreflecting " + file);
150 Files.write(file, transform(ClassFile.of().parse(Files.readAllBytes(file))));
151 }
152 }
153 } else {
154 toUnreflect.add(arg);
155 }
156 }
157 var done = new HashSet<String>();
158 while (!toUnreflect.isEmpty()) {
159 String arg = toUnreflect.pop();
160 if (!arg.endsWith(".class")) arg += ".class";
161 if (done.add(arg)) {
162 System.out.println("unreflecting " + arg);
163 Path clsFile = Path.of(Unreflect.class.getResource(arg).toURI());
164 ClassModel clm = ClassFile.of().parse(Files.readAllBytes(clsFile));
165 // unreflect all nest members
166 clm.findAttribute(Attributes.nestMembers())
167 .ifPresent(nma -> toUnreflect.addAll(
168 nma.nestMembers().stream().map(ClassEntry::asInternalName).toList()));
169 Files.write(clsFile, transform(clm));
170 }
171 }
172 }
173 }