Skip to content

Commit 030cccf

Browse files
committed
More version hacks to make sure the new downgrade backend always runs
Even on versions it technically does not support
1 parent 8228956 commit 030cccf

File tree

1 file changed

+53
-7
lines changed

1 file changed

+53
-7
lines changed

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

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
import jakarta.annotation.Nonnull;
44
import jakarta.annotation.Nullable;
55
import org.objectweb.asm.ClassWriter;
6+
import org.objectweb.asm.Type;
67
import org.objectweb.asm.tree.ClassNode;
78
import org.slf4j.Logger;
89
import software.coley.recaf.analytics.logging.Logging;
910
import software.coley.recaf.services.inheritance.InheritanceGraph;
1011
import software.coley.recaf.util.visitors.WorkspaceClassWriter;
1112
import xyz.wagyourtail.jvmdg.ClassDowngrader;
1213
import xyz.wagyourtail.jvmdg.cli.Flags;
14+
import xyz.wagyourtail.jvmdg.util.Pair;
15+
import xyz.wagyourtail.jvmdg.version.map.MemberNameAndDesc;
1316

1417
import java.io.IOException;
1518
import java.util.Arrays;
19+
import java.util.List;
1620
import java.util.Map;
21+
import java.util.Set;
1722
import java.util.concurrent.atomic.AtomicReference;
1823
import java.util.function.BiConsumer;
1924

@@ -71,23 +76,24 @@ public static void downgrade(int targetJavaVersion,
7176
flags.classVersion = targetClassVersion;
7277

7378
try (ClassDowngrader downgrader = new RecafClassDowngrader(flags, inheritanceGraph)) {
79+
int maxClassFileVersion = downgrader.maxVersion();
7480
classes.forEach((className, classBytes) -> {
75-
// Convert class file version to Java version.
76-
int classJavaVersion = classBytes[7] - JavaVersion.VERSION_OFFSET;
81+
int classFileVersion = classBytes[7];
7782

7883
// Hack to ensure downgrader will run on bleeding edge versions of Java.
7984
// This will cause brand-new features to not be properly downgraded, but
8085
// not all bleeding edge classes use all the brand-new features. At least
8186
// trying is better than failing outright for most use cases.
82-
if (classJavaVersion > downgrader.maxVersion()) {
87+
if (classFileVersion > maxClassFileVersion) {
8388
classBytes = Arrays.copyOf(classBytes, classBytes.length);
84-
classBytes[7] = (byte) (downgrader.maxVersion() + JavaVersion.VERSION_OFFSET);
89+
classBytes[7] = (byte) (maxClassFileVersion);
8590
}
8691

87-
// Transform if class version is greater than the target.
88-
if (classJavaVersion > targetJavaVersion) {
92+
// Transform if current class's version is greater than the target.
93+
if (classFileVersion > targetClassVersion) {
8994
try {
90-
Map<String, byte[]> downgraded = downgrader.downgrade(new AtomicReference<>(className), classBytes, false, classes::get);
95+
Map<String, byte[]> downgraded = downgrader.downgrade(new AtomicReference<>(className), classBytes, false,
96+
key -> getBytes(maxClassFileVersion, classes, key));
9197
if (downgraded != null)
9298
downgraded.forEach(transformConsumer);
9399
} catch (Throwable t) {
@@ -98,13 +104,53 @@ public static void downgrade(int targetJavaVersion,
98104
}
99105
}
100106

107+
@Nullable
108+
private static byte[] getBytes(int maxClassFileVersion, @Nonnull Map<String, byte[]> classes, @Nonnull String key) {
109+
byte[] classBytes = classes.get(key);
110+
if (classBytes != null) {
111+
// Same version hack as above.
112+
int classFileVersion = classBytes[7];
113+
if (classFileVersion > maxClassFileVersion) {
114+
classBytes = Arrays.copyOf(classBytes, classBytes.length);
115+
classBytes[7] = (byte) (maxClassFileVersion);
116+
}
117+
}
118+
return classBytes;
119+
}
120+
101121
private static class RecafClassDowngrader extends ClassDowngrader {
102122
private final InheritanceGraph inheritanceGraph;
103123

104124
public RecafClassDowngrader(@Nonnull Flags flags, @Nullable InheritanceGraph inheritanceGraph) {
105125
super(flags);
106126

107127
this.inheritanceGraph = inheritanceGraph;
128+
129+
// Compute the max-version. For the override methods below there are some cases where the base
130+
// implementation does version checks on classes used only for computing basic structure data.
131+
// We want these to at least have the chance to run vs failing outright, so we will cap the passed
132+
// version to the max supported by this downgrader.
133+
maxVersion();
134+
}
135+
136+
@Override
137+
public Set<MemberNameAndDesc> getMembers(int version, Type type, Set<String> warnings) throws IOException {
138+
return super.getMembers(Math.min(maxVersion, version), type, warnings);
139+
}
140+
141+
@Override
142+
public List<Pair<Type, Boolean>> getSupertypes(int version, Type type, Set<String> warnings) throws IOException {
143+
return super.getSupertypes(Math.min(maxVersion, version), type, warnings);
144+
}
145+
146+
@Override
147+
public Boolean isInterface(int version, Type type, Set<String> warnings) throws IOException {
148+
return super.isInterface(Math.min(maxVersion, version), type, warnings);
149+
}
150+
151+
@Override
152+
public Type stubClass(int version, Type type, Set<String> warnings) {
153+
return super.stubClass(Math.min(maxVersion, version), type, warnings);
108154
}
109155

110156
@Override

0 commit comments

Comments
 (0)