Skip to content

Commit 2de5d49

Browse files
committed
Ensure downgrading classes also provides necessary stubs to consumers
1 parent 030cccf commit 2de5d49

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

recaf-core/src/main/java/software/coley/recaf/util/JavaDowngraderUtil.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,29 @@
22

33
import jakarta.annotation.Nonnull;
44
import jakarta.annotation.Nullable;
5+
import org.objectweb.asm.ClassReader;
6+
import org.objectweb.asm.ClassVisitor;
57
import org.objectweb.asm.ClassWriter;
8+
import org.objectweb.asm.MethodVisitor;
69
import org.objectweb.asm.Type;
710
import org.objectweb.asm.tree.ClassNode;
811
import org.slf4j.Logger;
12+
import software.coley.recaf.RecafConstants;
913
import software.coley.recaf.analytics.logging.Logging;
14+
import software.coley.recaf.info.JvmClassInfo;
1015
import software.coley.recaf.services.inheritance.InheritanceGraph;
1116
import software.coley.recaf.util.visitors.WorkspaceClassWriter;
17+
import software.coley.recaf.workspace.model.bundle.JvmClassBundle;
18+
import software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;
1219
import xyz.wagyourtail.jvmdg.ClassDowngrader;
1320
import xyz.wagyourtail.jvmdg.cli.Flags;
1421
import xyz.wagyourtail.jvmdg.util.Pair;
22+
import xyz.wagyourtail.jvmdg.version.map.FullyQualifiedMemberNameAndDesc;
1523
import xyz.wagyourtail.jvmdg.version.map.MemberNameAndDesc;
1624

1725
import java.io.IOException;
1826
import java.util.Arrays;
27+
import java.util.HashSet;
1928
import java.util.List;
2029
import java.util.Map;
2130
import java.util.Set;
@@ -29,6 +38,11 @@
2938
*/
3039
public class JavaDowngraderUtil {
3140
private static final Logger logger = Logging.get(JavaDowngraderUtil.class);
41+
private static final String[] undesirableStubs = {
42+
// Array of stubs (owner;name;desc) that we do not want to pass back to callers.
43+
"Lxyz/wagyourtail/jvmdg/j18/stub/java_base/J_L_System;getProperty;(Ljava/lang/String;)Ljava/lang/String;",
44+
"Lxyz/wagyourtail/jvmdg/j18/stub/java_base/J_L_System;getProperties;()Ljava/util/Properties;"
45+
};
3246

3347
/**
3448
* Downgrade the provided classes.
@@ -74,6 +88,8 @@ public static void downgrade(int targetJavaVersion,
7488

7589
Flags flags = new Flags();
7690
flags.classVersion = targetClassVersion;
91+
for (String undesirableStub : undesirableStubs)
92+
flags.debugSkipStub.add(FullyQualifiedMemberNameAndDesc.of(undesirableStub));
7793

7894
try (ClassDowngrader downgrader = new RecafClassDowngrader(flags, inheritanceGraph)) {
7995
int maxClassFileVersion = downgrader.maxVersion();
@@ -94,6 +110,7 @@ public static void downgrade(int targetJavaVersion,
94110
try {
95111
Map<String, byte[]> downgraded = downgrader.downgrade(new AtomicReference<>(className), classBytes, false,
96112
key -> getBytes(maxClassFileVersion, classes, key));
113+
fillStubClasses(downgraded);
97114
if (downgraded != null)
98115
downgraded.forEach(transformConsumer);
99116
} catch (Throwable t) {
@@ -118,6 +135,40 @@ private static byte[] getBytes(int maxClassFileVersion, @Nonnull Map<String, byt
118135
return classBytes;
119136
}
120137

138+
private static void fillStubClasses(@Nullable Map<String, byte[]> downgraded) {
139+
if (downgraded == null || downgraded.isEmpty())
140+
return;
141+
142+
// Find all referenced stubs from the downgraded classes.
143+
Set<String> referencedStubs = new HashSet<>();
144+
for (byte[] classBytes : downgraded.values()) {
145+
ClassReader reader = new ClassReader(classBytes);
146+
reader.accept(new ClassVisitor(RecafConstants.getAsmVersion()) {
147+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
148+
return new MethodVisitor(RecafConstants.getAsmVersion()) {
149+
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
150+
if (owner.startsWith("xyz/wagyourtail/jvmdg/") && owner.contains("/stub/"))
151+
referencedStubs.add(owner);
152+
}
153+
154+
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
155+
if (owner.startsWith("xyz/wagyourtail/jvmdg/") && owner.contains("/stub/"))
156+
referencedStubs.add(owner.replace('/', '.'));
157+
}
158+
};
159+
}
160+
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
161+
}
162+
163+
// Add all referenced stubs to the downgraded output.
164+
JvmClassBundle runtimeBundle = RuntimeWorkspaceResource.getInstance().getJvmClassBundle();
165+
for (String referencedStub : referencedStubs) {
166+
JvmClassInfo cls = runtimeBundle.get(referencedStub);
167+
if (cls != null)
168+
downgraded.put(cls.getName(), cls.getBytecode());
169+
}
170+
}
171+
121172
private static class RecafClassDowngrader extends ClassDowngrader {
122173
private final InheritanceGraph inheritanceGraph;
123174

recaf-core/src/test/java/software/coley/recaf/services/compile/JavacCompilerTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public class HelloWorld {
7272
public static void main(String[] args) {
7373
if (args.length < 1)
7474
return;
75-
String amount = args[0];
75+
String amountProperty = System.getProperty(args[0]);
76+
String amount = amountProperty;
7677
7778
// List.of should be replaced since it was added in Java 9
7879
List<String> values = new ArrayList<>(switch (amount) {
@@ -123,6 +124,10 @@ else if (name.equals("removeLast"))
123124
};
124125
}
125126
}, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
127+
128+
// Assert the necessary stubs were added
129+
assertNotNull(result.getCompilations().get("xyz/wagyourtail/jvmdg/j9/stub/java_base/J_U_List"), "Missing stub for Java 9: List.of");
130+
assertNotNull(result.getCompilations().get("xyz/wagyourtail/jvmdg/j21/stub/java_base/J_U_List"), "Missing stub for Java 21: List.removeLast");
126131
}
127132

128133
@Test

recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/ChangeClassVersionPopup.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package software.coley.recaf.ui.control.popup;
22

3+
import atlantafx.base.theme.Styles;
34
import com.google.common.util.concurrent.AtomicDouble;
45
import jakarta.annotation.Nonnull;
56
import jakarta.annotation.Nullable;
@@ -243,8 +244,15 @@ private GridPane createPane(@Nonnull String suffix, @Nonnull UpdateHandler versi
243244

244245
layout.add(labelVersion, 0, 0);
245246
layout.add(versionCombo, 1, 0);
246-
layout.add(applyButton, 0, 2, 2, 1);
247-
layout.add(progressBar, 0, 3, 2, 1);
247+
248+
if (suffix.equals("down")) {
249+
Label labelNotice = new BoundLabel(getBinding("java.targetversion.notice." + suffix));
250+
labelNotice.getStyleClass().addAll(Styles.TEXT_SUBTLE, Styles.TEXT_ITALIC);
251+
layout.add(labelNotice, 0, layout.getRowCount(), 2, 1);
252+
}
253+
254+
layout.add(applyButton, 0, layout.getRowCount(), 2, 1);
255+
layout.add(progressBar, 0, layout.getRowCount(), 2, 1);
248256
layout.setPadding(new Insets(10));
249257
layout.setAlignment(Pos.TOP_CENTER);
250258
return layout;

recaf-ui/src/main/resources/translations/en_US.lang

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ java.savewitherrors=It seems like this is your first time making changes that re
386386
java.savewitherrors.title=Regarding recompile errors
387387
java.decompiler=Decompiler
388388
java.targetversion=Compile target version
389+
java.targetversion.notice.down=Downgrading will add stub classes to emulate missing APIs
389390
java.targetversion.auto=Match class file version
390391
java.targetdownsampleversion=Downsample target version
391392
java.targetdownsampleversion.disabled=Disabled

0 commit comments

Comments
 (0)