Skip to content
This repository was archived by the owner on May 12, 2024. It is now read-only.

Commit 8957214

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents ecae3cc + de0dbc6 commit 8957214

7 files changed

Lines changed: 128 additions & 44 deletions

File tree

bootstrap/src/main/java/net/raphimc/javadowngrader/bootstrap/AgentMain.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import net.raphimc.javadowngrader.impl.classtransform.util.ClassNameUtil;
2424
import net.raphimc.javadowngrader.runtime.RuntimeRoot;
2525
import net.raphimc.javadowngrader.util.Constants;
26+
import net.raphimc.javadowngrader.util.JavaVersion;
2627

2728
import java.io.IOException;
2829
import java.io.UncheckedIOException;
@@ -65,6 +66,17 @@ public static void agentmain(String args, Instrumentation instrumentation) throw
6566
final TransformerManager transformerManager = new TransformerManager(new InstrumentationClassProvider(instrumentation));
6667
transformerManager.addBytecodeTransformer(new JavaDowngraderTransformer(transformerManager));
6768
transformerManager.hookInstrumentation(instrumentation);
69+
70+
if (System.getProperty("spoofJavaVersion") != null) {
71+
final JavaVersion spoofedJavaVersion = JavaVersion.getByName(System.getProperty("spoofJavaVersion"));
72+
if (spoofedJavaVersion == null) {
73+
System.err.println("Unable to find version '" + System.getProperty("spoofJavaVersion") + "'");
74+
System.exit(-1);
75+
}
76+
System.setProperty("java.version", spoofedJavaVersion.getFakeJavaVersionName());
77+
System.setProperty("java.class.version", String.valueOf(spoofedJavaVersion.getVersion()));
78+
System.setProperty("java.specification.version", spoofedJavaVersion.getFakeSpecificationVersionName());
79+
}
6880
}
6981

7082
}

runtime-dep/src/main/java/net/raphimc/javadowngrader/runtime/java/lang/StackWalker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package net.raphimc.javadowngrader.runtime.java.lang;
2626

2727
import java.lang.invoke.MethodHandles;
28+
import java.util.Set;
2829

2930
public class StackWalker {
3031

@@ -39,6 +40,14 @@ public static StackWalker getInstance(final Option option) {
3940
return new StackWalker();
4041
}
4142

43+
public static StackWalker getInstance(final Set<Option> options) {
44+
return new StackWalker();
45+
}
46+
47+
public static StackWalker getInstance(final Set<Option> options, final int estimatedDepth) {
48+
return new StackWalker();
49+
}
50+
4251
public Class<?> getCallerClass() {
4352
return MethodHandles.lookup().lookupClass();
4453
}

src/main/java/net/raphimc/javadowngrader/transformer/j15/RecordReplacer.java

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,14 @@
1818
package net.raphimc.javadowngrader.transformer.j15;
1919

2020
import net.raphimc.javadowngrader.util.ASMUtil;
21-
import org.objectweb.asm.Label;
22-
import org.objectweb.asm.MethodVisitor;
23-
import org.objectweb.asm.Opcodes;
24-
import org.objectweb.asm.Type;
21+
import net.raphimc.javadowngrader.util.Constants;
22+
import org.objectweb.asm.*;
23+
import org.objectweb.asm.tree.AbstractInsnNode;
2524
import org.objectweb.asm.tree.ClassNode;
26-
import org.objectweb.asm.tree.RecordComponentNode;
25+
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
26+
import org.objectweb.asm.tree.MethodNode;
2727

28-
import java.util.Collections;
29-
import java.util.HashMap;
30-
import java.util.Map;
31-
import java.util.Objects;
28+
import java.util.*;
3229

3330
public class RecordReplacer {
3431

@@ -61,9 +58,12 @@ public static boolean replace(final ClassNode classNode) {
6158
classNode.recordComponents = Collections.emptyList();
6259
}
6360

64-
classNode.methods.remove(ASMUtil.getMethod(classNode, "equals", EQUALS_DESC));
65-
final MethodVisitor equals = classNode.visitMethod(Opcodes.ACC_PUBLIC, "equals", EQUALS_DESC, null, null);
66-
{
61+
final MethodNode defaultEquals = ASMUtil.getMethod(classNode, "equals", EQUALS_DESC);
62+
if (defaultEquals == null) throw new IllegalStateException("Could not find default equals method");
63+
RecordField[] equalsFields = getFields(defaultEquals);
64+
if (equalsFields != null) {
65+
classNode.methods.remove(defaultEquals);
66+
final MethodVisitor equals = classNode.visitMethod(Opcodes.ACC_PUBLIC, "equals", EQUALS_DESC, null, null);
6767
equals.visitCode();
6868

6969
equals.visitVarInsn(Opcodes.ALOAD, 0);
@@ -88,12 +88,12 @@ public static boolean replace(final ClassNode classNode) {
8888
equals.visitVarInsn(Opcodes.ASTORE, 2);
8989

9090
final Label notEqualLabel = new Label();
91-
for (final RecordComponentNode component : classNode.recordComponents) {
91+
for (RecordField field : equalsFields) {
9292
equals.visitVarInsn(Opcodes.ALOAD, 0);
93-
equals.visitFieldInsn(Opcodes.GETFIELD, classNode.name, component.name, component.descriptor);
93+
equals.visitFieldInsn(Opcodes.GETFIELD, classNode.name, field.name, field.descriptor);
9494
equals.visitVarInsn(Opcodes.ALOAD, 2);
95-
equals.visitFieldInsn(Opcodes.GETFIELD, classNode.name, component.name, component.descriptor);
96-
if (Type.getType(component.descriptor).getSort() >= Type.ARRAY) { // ARRAY or OBJECT
95+
equals.visitFieldInsn(Opcodes.GETFIELD, classNode.name, field.name, field.descriptor);
96+
if (Type.getType(field.descriptor).getSort() >= Type.ARRAY) { // ARRAY or OBJECT
9797
equals.visitMethodInsn(
9898
Opcodes.INVOKESTATIC,
9999
Type.getInternalName(Objects.class),
@@ -103,29 +103,29 @@ public static boolean replace(final ClassNode classNode) {
103103
);
104104
equals.visitJumpInsn(Opcodes.IFEQ, notEqualLabel);
105105
continue;
106-
} else if ("BSCIZ".contains(component.descriptor)) {
106+
} else if ("BSCIZ".contains(field.descriptor)) {
107107
equals.visitJumpInsn(Opcodes.IF_ICMPNE, notEqualLabel);
108108
continue;
109-
} else if (component.descriptor.equals("F")) {
109+
} else if (field.descriptor.equals("F")) {
110110
equals.visitMethodInsn(
111111
Opcodes.INVOKESTATIC,
112112
Type.getInternalName(Float.class),
113113
"compare",
114114
"(FF)I",
115115
false
116116
);
117-
} else if (component.descriptor.equals("D")) {
117+
} else if (field.descriptor.equals("D")) {
118118
equals.visitMethodInsn(
119119
Opcodes.INVOKESTATIC,
120120
Type.getInternalName(Double.class),
121121
"compare",
122122
"(DD)I",
123123
false
124124
);
125-
} else if (component.descriptor.equals("J")) {
125+
} else if (field.descriptor.equals("J")) {
126126
equals.visitInsn(Opcodes.LCMP);
127127
} else {
128-
throw new AssertionError("Unknown descriptor " + component.descriptor);
128+
throw new AssertionError("Unknown descriptor " + field.descriptor);
129129
}
130130
equals.visitJumpInsn(Opcodes.IFNE, notEqualLabel);
131131
}
@@ -138,23 +138,26 @@ public static boolean replace(final ClassNode classNode) {
138138
equals.visitEnd();
139139
}
140140

141-
classNode.methods.remove(ASMUtil.getMethod(classNode, "hashCode", HASHCODE_DESC));
142-
final MethodVisitor hashCode = classNode.visitMethod(Opcodes.ACC_PUBLIC, "hashCode", HASHCODE_DESC, null, null);
143-
{
141+
final MethodNode defaultHashCode = ASMUtil.getMethod(classNode, "hashCode", HASHCODE_DESC);
142+
if (defaultHashCode == null) throw new IllegalStateException("Could not find default hashCode method");
143+
RecordField[] hashCodeFields = getFields(defaultHashCode);
144+
if (hashCodeFields != null) {
145+
classNode.methods.remove(defaultHashCode);
146+
final MethodVisitor hashCode = classNode.visitMethod(Opcodes.ACC_PUBLIC, "hashCode", HASHCODE_DESC, null, null);
144147
hashCode.visitCode();
145148

146149
hashCode.visitInsn(Opcodes.ICONST_0);
147-
for (final RecordComponentNode component : classNode.recordComponents) {
150+
for (RecordField field : hashCodeFields) {
148151
hashCode.visitIntInsn(Opcodes.BIPUSH, 31);
149152
hashCode.visitInsn(Opcodes.IMUL);
150153
hashCode.visitVarInsn(Opcodes.ALOAD, 0);
151-
hashCode.visitFieldInsn(Opcodes.GETFIELD, classNode.name, component.name, component.descriptor);
152-
final String owner = PRIMITIVE_WRAPPERS.get(component.descriptor);
154+
hashCode.visitFieldInsn(Opcodes.GETFIELD, classNode.name, field.name, field.descriptor);
155+
final String owner = PRIMITIVE_WRAPPERS.get(field.descriptor);
153156
hashCode.visitMethodInsn(
154157
Opcodes.INVOKESTATIC,
155158
owner != null ? owner : "java/util/Objects",
156159
"hashCode",
157-
"(" + (owner != null ? component.descriptor : "Ljava/lang/Object;") + ")I",
160+
"(" + (owner != null ? field.descriptor : "Ljava/lang/Object;") + ")I",
158161
false
159162
);
160163
hashCode.visitInsn(Opcodes.IADD);
@@ -164,9 +167,12 @@ public static boolean replace(final ClassNode classNode) {
164167
hashCode.visitEnd();
165168
}
166169

167-
classNode.methods.remove(ASMUtil.getMethod(classNode, "toString", TOSTRING_DESC));
168-
final MethodVisitor toString = classNode.visitMethod(Opcodes.ACC_PUBLIC, "toString", TOSTRING_DESC, null, null);
169-
{
170+
final MethodNode defaultToString = ASMUtil.getMethod(classNode, "toString", TOSTRING_DESC);
171+
if (defaultToString == null) throw new IllegalStateException("Could not find default toString method");
172+
RecordField[] toStringFields = getFields(defaultToString);
173+
if (toStringFields != null) {
174+
classNode.methods.remove(defaultToString);
175+
final MethodVisitor toString = classNode.visitMethod(Opcodes.ACC_PUBLIC, "toString", TOSTRING_DESC, null, null);
170176
toString.visitCode();
171177

172178
final StringBuilder formatString = new StringBuilder("%s[");
@@ -200,17 +206,17 @@ public static boolean replace(final ClassNode classNode) {
200206
);
201207
toString.visitInsn(Opcodes.AASTORE);
202208
int i = 1;
203-
for (final RecordComponentNode component : classNode.recordComponents) {
209+
for (RecordField field : toStringFields) {
204210
toString.visitInsn(Opcodes.DUP);
205211
toString.visitIntInsn(Opcodes.SIPUSH, i);
206212
toString.visitVarInsn(Opcodes.ALOAD, 0);
207-
toString.visitFieldInsn(Opcodes.GETFIELD, classNode.name, component.name, component.descriptor);
208-
final String owner = PRIMITIVE_WRAPPERS.get(component.descriptor);
213+
toString.visitFieldInsn(Opcodes.GETFIELD, classNode.name, field.name, field.descriptor);
214+
final String owner = PRIMITIVE_WRAPPERS.get(field.descriptor);
209215
toString.visitMethodInsn(
210216
Opcodes.INVOKESTATIC,
211217
owner != null ? owner : "java/util/Objects",
212218
"toString",
213-
"(" + (owner != null ? component.descriptor : "Ljava/lang/Object;") + ")Ljava/lang/String;",
219+
"(" + (owner != null ? field.descriptor : "Ljava/lang/Object;") + ")Ljava/lang/String;",
214220
false
215221
);
216222
toString.visitInsn(Opcodes.AASTORE);
@@ -232,4 +238,34 @@ public static boolean replace(final ClassNode classNode) {
232238
return true;
233239
}
234240

241+
private static RecordField[] getFields(final MethodNode method) {
242+
for (AbstractInsnNode instruction : method.instructions) {
243+
if (!(instruction instanceof InvokeDynamicInsnNode)) continue;
244+
final InvokeDynamicInsnNode invokeDynamic = (InvokeDynamicInsnNode) instruction;
245+
if (!invokeDynamic.bsm.getOwner().equals("java/lang/runtime/ObjectMethods")) continue;
246+
if (!invokeDynamic.bsm.getName().equals("bootstrap")) continue;
247+
if (!invokeDynamic.bsm.getDesc().equals(Constants.OBJECTMETHODS_BOOTSTRAP_DESC)) continue;
248+
249+
List<RecordField> fields = new ArrayList<>();
250+
for (int i = 2; i < invokeDynamic.bsmArgs.length; i++) {
251+
if (!(invokeDynamic.bsmArgs[i] instanceof Handle)) throw new IllegalStateException("bsm arg " + i + " is not a handle");
252+
final Handle handle = (Handle) invokeDynamic.bsmArgs[i];
253+
fields.add(new RecordField(handle.getName(), handle.getDesc()));
254+
}
255+
return fields.toArray(new RecordField[0]);
256+
}
257+
return null; // You can override equals/hashCode/toString, we should not replace them with the default impl
258+
}
259+
260+
261+
private static class RecordField {
262+
private final String name;
263+
private final String descriptor;
264+
265+
private RecordField(final String name, final String descriptor) {
266+
this.name = name;
267+
this.descriptor = descriptor;
268+
}
269+
}
270+
235271
}

src/main/java/net/raphimc/javadowngrader/util/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ public class Constants {
2323
public static final String JAVADOWNGRADER_RUNTIME_ROOT = "RuntimeRoot.class";
2424

2525
public static final String METAFACTORY_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
26+
public static final String OBJECTMETHODS_BOOTSTRAP_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;";
2627

2728
}

standalone/src/main/java/net/raphimc/javadowngrader/standalone/JavaVersion.java renamed to src/main/java/net/raphimc/javadowngrader/util/JavaVersion.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* You should have received a copy of the GNU General Public License
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
18-
package net.raphimc.javadowngrader.standalone;
18+
package net.raphimc.javadowngrader.util;
1919

2020
import org.objectweb.asm.Opcodes;
2121

@@ -37,14 +37,14 @@ public enum JavaVersion {
3737
JAVA_21("21", Opcodes.V21),
3838
;
3939

40+
private final String name;
41+
private final int version;
42+
4043
JavaVersion(final String name, final int version) {
4144
this.name = name;
4245
this.version = version;
4346
}
4447

45-
private final String name;
46-
private final int version;
47-
4848
public String getName() {
4949
return this.name;
5050
}
@@ -53,4 +53,28 @@ public int getVersion() {
5353
return this.version;
5454
}
5555

56+
public String getFakeJavaVersionName() {
57+
if (this.ordinal() <= JAVA_8.ordinal()) {
58+
return "1." + this.name + ".0";
59+
} else {
60+
return this.version + ".0.0";
61+
}
62+
}
63+
64+
public String getFakeSpecificationVersionName() {
65+
if (this.ordinal() <= JAVA_8.ordinal()) {
66+
return "1." + this.name;
67+
} else {
68+
return this.name;
69+
}
70+
}
71+
72+
public static JavaVersion getByName(String name) {
73+
name = name.toLowerCase().replace("java", "").replace("j", "");
74+
for (JavaVersion version : JavaVersion.values()) {
75+
if (version.getName().equalsIgnoreCase(name)) return version;
76+
}
77+
return null;
78+
}
79+
5680
}

standalone/src/main/java/net/raphimc/javadowngrader/standalone/JavaVersionEnumConverter.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@
1919

2020
import joptsimple.ValueConversionException;
2121
import joptsimple.ValueConverter;
22+
import net.raphimc.javadowngrader.util.JavaVersion;
2223

2324
import java.util.StringJoiner;
2425

2526
public class JavaVersionEnumConverter implements ValueConverter<JavaVersion> {
2627

2728
@Override
28-
public JavaVersion convert(String s) {
29-
final String input = s.toLowerCase().replace("java", "").replace("j", "");
30-
for (JavaVersion version : JavaVersion.values()) {
31-
if (version.getName().equalsIgnoreCase(input)) return version;
29+
public JavaVersion convert(final String name) {
30+
final JavaVersion version = JavaVersion.getByName(name);
31+
if (version == null) {
32+
throw new ValueConversionException("Unable to find version '" + name + "'");
3233
}
33-
throw new ValueConversionException("Unable to find version '" + s + "'");
34+
return version;
3435
}
3536

3637
@Override

standalone/src/main/java/net/raphimc/javadowngrader/standalone/Main.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.raphimc.javadowngrader.runtime.RuntimeRoot;
3030
import net.raphimc.javadowngrader.standalone.progress.MultiThreadedProgressBar;
3131
import net.raphimc.javadowngrader.standalone.util.GeneralUtil;
32+
import net.raphimc.javadowngrader.util.JavaVersion;
3233
import org.slf4j.Logger;
3334
import org.slf4j.LoggerFactory;
3435

0 commit comments

Comments
 (0)