33import jakarta .annotation .Nonnull ;
44import jakarta .annotation .Nullable ;
55import org .objectweb .asm .ClassWriter ;
6+ import org .objectweb .asm .Type ;
67import org .objectweb .asm .tree .ClassNode ;
78import org .slf4j .Logger ;
89import software .coley .recaf .analytics .logging .Logging ;
910import software .coley .recaf .services .inheritance .InheritanceGraph ;
1011import software .coley .recaf .util .visitors .WorkspaceClassWriter ;
1112import xyz .wagyourtail .jvmdg .ClassDowngrader ;
1213import xyz .wagyourtail .jvmdg .cli .Flags ;
14+ import xyz .wagyourtail .jvmdg .util .Pair ;
15+ import xyz .wagyourtail .jvmdg .version .map .MemberNameAndDesc ;
1316
1417import java .io .IOException ;
1518import java .util .Arrays ;
19+ import java .util .List ;
1620import java .util .Map ;
21+ import java .util .Set ;
1722import java .util .concurrent .atomic .AtomicReference ;
1823import 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