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 }