diff --git a/app/src/main/java/javax/microedition/lcdui/Canvas.java b/app/src/main/java/javax/microedition/lcdui/Canvas.java
index 1cd6a57cc..6347f1ce1 100644
--- a/app/src/main/java/javax/microedition/lcdui/Canvas.java
+++ b/app/src/main/java/javax/microedition/lcdui/Canvas.java
@@ -200,7 +200,7 @@ public static void setSettings(ProfileModel settings) {
parallelRedraw = (mode == 0 || mode == 3) && settings.parallelRedrawScreen;
}
- /** Enables the diagnostic only when the loaded artifact advertises the timing transform ABI. */
+ /** Enables the diagnostic only when the loaded artifact has the current universal transform. */
public static void setTimingOverlayEnabled(boolean enabled) {
timingOverlayEnabled = enabled;
}
diff --git a/app/src/main/java/javax/microedition/shell/GuestTimingBridge.java b/app/src/main/java/javax/microedition/shell/GuestTimingBridge.java
index fc931727f..5008cbe03 100644
--- a/app/src/main/java/javax/microedition/shell/GuestTimingBridge.java
+++ b/app/src/main/java/javax/microedition/shell/GuestTimingBridge.java
@@ -33,8 +33,6 @@
* provide or shadow this class.
*/
public final class GuestTimingBridge {
- public static final int ABI_VERSION = 4;
-
private static final Object LOCK = new Object();
private static TimingSession activeSession;
diff --git a/app/src/main/java/javax/microedition/shell/MicroLoader.java b/app/src/main/java/javax/microedition/shell/MicroLoader.java
index a80683e7d..aa8821bfa 100644
--- a/app/src/main/java/javax/microedition/shell/MicroLoader.java
+++ b/app/src/main/java/javax/microedition/shell/MicroLoader.java
@@ -82,7 +82,7 @@
import javax.microedition.shell.timing.EmulationSpeed;
import javax.microedition.shell.timing.TimingSession;
import javax.microedition.shell.timing.TimingMode;
-import javax.microedition.shell.timing.TimingTransformMetadata;
+import javax.microedition.shell.transform.MidletTransformMetadata;
public class MicroLoader {
private static final String TAG = MicroLoader.class.getName();
@@ -200,9 +200,9 @@ private boolean hasCompatibleTimingTransform() {
try {
Descriptor descriptor = new Descriptor(
new File(appDir, Config.MIDLET_MANIFEST_FILE), false);
- return TimingTransformMetadata.isCompatible(descriptor.getAttrs());
+ return MidletTransformMetadata.isCompatible(descriptor.getAttrs());
} catch (IOException | RuntimeException e) {
- Log.w(TAG, "Timing transform metadata is unavailable", e);
+ Log.w(TAG, "MIDlet transform metadata is unavailable", e);
return false;
}
}
diff --git a/app/src/main/java/javax/microedition/shell/MidletSystem.java b/app/src/main/java/javax/microedition/shell/MidletSystem.java
index ad4bca441..b594bb15d 100644
--- a/app/src/main/java/javax/microedition/shell/MidletSystem.java
+++ b/app/src/main/java/javax/microedition/shell/MidletSystem.java
@@ -35,4 +35,19 @@ public static String getProperty(String key, String def) {
return value;
}
+ /**
+ * Handles an advisory garbage-collection request made by a MIDlet.
+ *
+ *
Forwarding every guest request to Android can start a compacting collection inside hot
+ * MIDlet loops. ART still collects normally when allocation pressure requires it.
+ */
+ public static void gc() {
+ // Java ME only requires the VM to make a best effort, so the emulator may ignore the hint.
+ }
+
+ /** Preserves the receiver null check while suppressing {@link Runtime#gc()} from guest code. */
+ public static void gc(Runtime runtime) {
+ if (runtime == null) throw new NullPointerException("runtime");
+ }
+
}
diff --git a/app/src/main/java/javax/microedition/shell/timing/TimingTransformMetadata.java b/app/src/main/java/javax/microedition/shell/timing/TimingTransformMetadata.java
deleted file mode 100644
index 989c3f93d..000000000
--- a/app/src/main/java/javax/microedition/shell/timing/TimingTransformMetadata.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package javax.microedition.shell.timing;
-
-import java.util.Map;
-
-import javax.microedition.shell.GuestTimingBridge;
-
-/**
- * Timing-specific compatibility marker stored in the converted descriptor next to the DEX
- * artifact.
- *
- * This is intentionally not a universal "converted" bit. A future bytecode feature must use
- * its own namespaced transform/ABI entries, or participate in an explicit transform manifest, so
- * that enabling or revving one feature cannot make an unrelated transformed artifact appear
- * compatible.
- */
-public final class TimingTransformMetadata {
- /** Version 6 virtualizes Date.class.newInstance() with caller-aware access checks. */
- public static final int TRANSFORM_VERSION = 6;
- public static final String TRANSFORM_VERSION_ATTRIBUTE = "JLMod-Timing-Transform-Version";
- public static final String BRIDGE_ABI_ATTRIBUTE = "JLMod-Timing-Bridge-ABI";
-
- private TimingTransformMetadata() {
- }
-
- public static void mark(Map attributes) {
- if (attributes == null) {
- throw new NullPointerException("attributes");
- }
- attributes.put(TRANSFORM_VERSION_ATTRIBUTE, Integer.toString(TRANSFORM_VERSION));
- attributes.put(BRIDGE_ABI_ATTRIBUTE, Integer.toString(GuestTimingBridge.ABI_VERSION));
- }
-
- public static boolean isCompatible(Map attributes) {
- if (attributes == null) {
- return false;
- }
- return Integer.toString(TRANSFORM_VERSION).equals(
- attributes.get(TRANSFORM_VERSION_ATTRIBUTE))
- && Integer.toString(GuestTimingBridge.ABI_VERSION).equals(
- attributes.get(BRIDGE_ABI_ATTRIBUTE));
- }
-}
diff --git a/app/src/main/java/javax/microedition/shell/transform/MidletTransformMetadata.java b/app/src/main/java/javax/microedition/shell/transform/MidletTransformMetadata.java
new file mode 100644
index 000000000..24d77135e
--- /dev/null
+++ b/app/src/main/java/javax/microedition/shell/transform/MidletTransformMetadata.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.microedition.shell.transform;
+
+import java.util.Map;
+
+/**
+ * Universal compatibility marker for an installed MIDlet's transformed bytecode.
+ *
+ * Increment {@link #TRANSFORM_VERSION} whenever the converter changes emitted bytecode or a
+ * runtime bridge contract used by that bytecode. Installed MIDlets with an older or missing marker
+ * are then rebuilt by the normal app reconversion path when their retained source is available.
+ */
+public final class MidletTransformMetadata {
+ /** Version 1 includes guest-time virtualization and suppression of advisory explicit GC. */
+ public static final int TRANSFORM_VERSION = 1;
+ public static final String TRANSFORM_VERSION_ATTRIBUTE = "JLMod-Transform-Version";
+ private static final String LEGACY_TIMING_TRANSFORM_ATTRIBUTE =
+ "JLMod-Timing-Transform-Version";
+ private static final String LEGACY_TIMING_BRIDGE_ATTRIBUTE = "JLMod-Timing-Bridge-ABI";
+
+ private MidletTransformMetadata() {
+ }
+
+ public static void mark(Map attributes) {
+ if (attributes == null) {
+ throw new NullPointerException("attributes");
+ }
+ attributes.remove(LEGACY_TIMING_TRANSFORM_ATTRIBUTE);
+ attributes.remove(LEGACY_TIMING_BRIDGE_ATTRIBUTE);
+ attributes.put(TRANSFORM_VERSION_ATTRIBUTE, Integer.toString(TRANSFORM_VERSION));
+ }
+
+ public static boolean isCompatible(Map attributes) {
+ return attributes != null
+ && Integer.toString(TRANSFORM_VERSION).equals(
+ attributes.get(TRANSFORM_VERSION_ATTRIBUTE));
+ }
+}
diff --git a/app/src/main/java/ru/playsoftware/j2meloader/config/ConfigActivity.java b/app/src/main/java/ru/playsoftware/j2meloader/config/ConfigActivity.java
index fdaaef101..bd8233a0f 100644
--- a/app/src/main/java/ru/playsoftware/j2meloader/config/ConfigActivity.java
+++ b/app/src/main/java/ru/playsoftware/j2meloader/config/ConfigActivity.java
@@ -53,7 +53,7 @@
import java.util.List;
import java.util.Set;
-import javax.microedition.shell.timing.TimingTransformMetadata;
+import javax.microedition.shell.transform.MidletTransformMetadata;
import javax.microedition.util.ContextHolder;
import kotlin.io.FilesKt;
@@ -996,7 +996,7 @@ private boolean hasCompatibleTimingTransform() {
try {
Descriptor descriptor = new Descriptor(
new File(appDir, Config.MIDLET_MANIFEST_FILE), false);
- return TimingTransformMetadata.isCompatible(descriptor.getAttrs());
+ return MidletTransformMetadata.isCompatible(descriptor.getAttrs());
} catch (IOException | RuntimeException e) {
return false;
}
diff --git a/app/src/main/java/ru/woesss/j2me/installer/AppInstaller.java b/app/src/main/java/ru/woesss/j2me/installer/AppInstaller.java
index 78b7e7913..e1b1b24f9 100644
--- a/app/src/main/java/ru/woesss/j2me/installer/AppInstaller.java
+++ b/app/src/main/java/ru/woesss/j2me/installer/AppInstaller.java
@@ -62,7 +62,7 @@
import ru.woesss.j2me.jar.Descriptor;
import ru.woesss.util.TextUtils;
import ru.woesss.util.zip.ZipFile;
-import javax.microedition.shell.timing.TimingTransformMetadata;
+import javax.microedition.shell.transform.MidletTransformMetadata;
public class AppInstaller {
private static final String TAG = AppInstaller.class.getSimpleName();
@@ -431,7 +431,7 @@ void install(SingleEmitter emitter) throws ConverterException, IOExcept
manifest.merge(newDesc);
newDesc = manifest;
}
- TimingTransformMetadata.mark(newDesc.getAttrs());
+ MidletTransformMetadata.mark(newDesc.getAttrs());
File resJar = child(tmpDir, Config.MIDLET_RES_FILE);
FileUtils.copyFileUsingChannel(srcJar, resJar);
diff --git a/app/src/main/java/ru/woesss/j2me/installer/AppReconverter.java b/app/src/main/java/ru/woesss/j2me/installer/AppReconverter.java
index 0c5a9782c..40211f3b9 100644
--- a/app/src/main/java/ru/woesss/j2me/installer/AppReconverter.java
+++ b/app/src/main/java/ru/woesss/j2me/installer/AppReconverter.java
@@ -27,7 +27,7 @@
import java.io.IOException;
import java.util.jar.JarFile;
-import javax.microedition.shell.timing.TimingTransformMetadata;
+import javax.microedition.shell.transform.MidletTransformMetadata;
import ru.playsoftware.j2meloader.config.Config;
import ru.playsoftware.j2meloader.librarydb.LibraryIconOverride;
@@ -61,9 +61,9 @@ public static boolean needsReconversion(File appDir) {
if (payload == null || !Config.isUsableFile(descriptorFile)) return true;
try {
Descriptor descriptor = new Descriptor(descriptorFile, false);
- return !TimingTransformMetadata.isCompatible(descriptor.getAttrs());
+ return !MidletTransformMetadata.isCompatible(descriptor.getAttrs());
} catch (IOException | RuntimeException error) {
- Log.w(TAG, "Unable to validate converted timing marker: " + appDir, error);
+ Log.w(TAG, "Unable to validate converted transform marker: " + appDir, error);
return true;
}
}
@@ -129,7 +129,7 @@ public static void reconvert(File requestedAppDir) throws IOException, Converter
if (!generatedPayload.isFile() || generatedPayload.length() <= 0L) {
throw new ConverterException("DX produced no converted MIDlet payload");
}
- TimingTransformMetadata.mark(descriptor.getAttrs());
+ MidletTransformMetadata.mark(descriptor.getAttrs());
FileUtils.copyFileUsingChannel(retainedJar, fileWithSuffix(staging, Config.MIDLET_RES_FILE));
extractIcon(descriptor, retainedJar, staging);
descriptor.writeTo(fileWithSuffix(staging, Config.MIDLET_MANIFEST_FILE));
diff --git a/app/src/test/java/javax/microedition/shell/MidletSystemTest.java b/app/src/test/java/javax/microedition/shell/MidletSystemTest.java
new file mode 100644
index 000000000..7e6f94f9c
--- /dev/null
+++ b/app/src/test/java/javax/microedition/shell/MidletSystemTest.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.microedition.shell;
+
+import org.junit.Test;
+
+public class MidletSystemTest {
+ @Test
+ public void advisoryGcRequestsReturnWithoutCollecting() {
+ MidletSystem.gc();
+ MidletSystem.gc(Runtime.getRuntime());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void runtimeGcBridgePreservesReceiverNullCheck() {
+ MidletSystem.gc((Runtime) null);
+ }
+}
diff --git a/app/src/test/java/javax/microedition/shell/timing/TimingTransformMetadataTest.java b/app/src/test/java/javax/microedition/shell/timing/TimingTransformMetadataTest.java
deleted file mode 100644
index 3f620761f..000000000
--- a/app/src/test/java/javax/microedition/shell/timing/TimingTransformMetadataTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package javax.microedition.shell.timing;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class TimingTransformMetadataTest {
- @Test
- public void markedMetadataIsCompatible() {
- Map attributes = new HashMap<>();
- TimingTransformMetadata.mark(attributes);
-
- assertTrue(TimingTransformMetadata.isCompatible(attributes));
- }
-
- @Test
- public void missingOrChangedMetadataIsRejected() {
- Map attributes = new HashMap<>();
- TimingTransformMetadata.mark(attributes);
- attributes.put(TimingTransformMetadata.BRIDGE_ABI_ATTRIBUTE, "999");
-
- assertFalse(TimingTransformMetadata.isCompatible(attributes));
- assertFalse(TimingTransformMetadata.isCompatible(null));
- }
-
- @Test
- public void previousTransformerVersionIsRejectedForAutomaticReconversion() {
- Map attributes = new HashMap<>();
- attributes.put(TimingTransformMetadata.TRANSFORM_VERSION_ATTRIBUTE, "4");
- attributes.put(TimingTransformMetadata.BRIDGE_ABI_ATTRIBUTE, "4");
-
- assertFalse(TimingTransformMetadata.isCompatible(attributes));
- }
-
- @Test
- public void versionFiveIsRejectedAfterCallerAwareReflectionRewrite() {
- Map attributes = new HashMap<>();
- attributes.put(TimingTransformMetadata.TRANSFORM_VERSION_ATTRIBUTE, "5");
- attributes.put(TimingTransformMetadata.BRIDGE_ABI_ATTRIBUTE, "4");
-
- assertFalse(TimingTransformMetadata.isCompatible(attributes));
- }
-}
diff --git a/app/src/test/java/javax/microedition/shell/transform/MidletTransformMetadataTest.java b/app/src/test/java/javax/microedition/shell/transform/MidletTransformMetadataTest.java
new file mode 100644
index 000000000..62a2ac199
--- /dev/null
+++ b/app/src/test/java/javax/microedition/shell/transform/MidletTransformMetadataTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.microedition.shell.transform;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MidletTransformMetadataTest {
+ @Test
+ public void currentVersionIsCompatibleAfterMarking() {
+ Map attributes = new HashMap<>();
+ MidletTransformMetadata.mark(attributes);
+
+ assertTrue(MidletTransformMetadata.isCompatible(attributes));
+ }
+
+ @Test
+ public void missingOrChangedUniversalVersionIsRejected() {
+ Map attributes = new HashMap<>();
+ MidletTransformMetadata.mark(attributes);
+ attributes.put(MidletTransformMetadata.TRANSFORM_VERSION_ATTRIBUTE, "999");
+
+ assertFalse(MidletTransformMetadata.isCompatible(attributes));
+ assertFalse(MidletTransformMetadata.isCompatible(new HashMap<>()));
+ assertFalse(MidletTransformMetadata.isCompatible(null));
+ }
+
+ @Test
+ public void legacyTimingOnlyMarkerTriggersUniversalReconversion() {
+ Map attributes = new HashMap<>();
+ attributes.put("JLMod-Timing-Transform-Version", "6");
+ attributes.put("JLMod-Timing-Bridge-ABI", "4");
+
+ assertFalse(MidletTransformMetadata.isCompatible(attributes));
+
+ MidletTransformMetadata.mark(attributes);
+
+ assertTrue(MidletTransformMetadata.isCompatible(attributes));
+ assertNull(attributes.get("JLMod-Timing-Transform-Version"));
+ assertNull(attributes.get("JLMod-Timing-Bridge-ABI"));
+ }
+}
diff --git a/dexlib/src/main/java/org/microemu/android/asm/AndroidMethodVisitor.java b/dexlib/src/main/java/org/microemu/android/asm/AndroidMethodVisitor.java
index 9019795b6..29c5f2edd 100644
--- a/dexlib/src/main/java/org/microemu/android/asm/AndroidMethodVisitor.java
+++ b/dexlib/src/main/java/org/microemu/android/asm/AndroidMethodVisitor.java
@@ -24,7 +24,7 @@
* limitations.
*
* Modified in JL-Mod Plus to preserve Java ME bytecode return semantics and route guest timing
- * call sites through the parent-owned emulator bridge during DEX conversion.
+ * and runtime call sites through emulator-owned bridges during DEX conversion.
*
* @version $Id$
*/
@@ -240,7 +240,20 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc,
name = "setListenerCompat";
}
break;
+ case "java/lang/Runtime":
+ if (opcode == INVOKEVIRTUAL && name.equals("gc") && desc.equals("()V")) {
+ // The static overload consumes the original receiver and preserves its null check.
+ mv.visitMethodInsn(INVOKESTATIC, "javax/microedition/shell/MidletSystem",
+ "gc", "(Ljava/lang/Runtime;)V", false);
+ return;
+ }
+ break;
case "java/lang/System":
+ if (opcode == INVOKESTATIC && name.equals("gc") && desc.equals("()V")) {
+ mv.visitMethodInsn(INVOKESTATIC, "javax/microedition/shell/MidletSystem",
+ "gc", "()V", false);
+ return;
+ }
if (opcode == INVOKESTATIC && name.equals("currentTimeMillis") && desc.equals("()J")) {
mv.visitMethodInsn(INVOKESTATIC, "javax/microedition/shell/GuestTimingBridge",
"currentTimeMillis", "()J", false);
diff --git a/dexlib/src/test/java/org/microemu/android/asm/AndroidMethodVisitorTest.java b/dexlib/src/test/java/org/microemu/android/asm/AndroidMethodVisitorTest.java
index 189637390..30aa54940 100644
--- a/dexlib/src/test/java/org/microemu/android/asm/AndroidMethodVisitorTest.java
+++ b/dexlib/src/test/java/org/microemu/android/asm/AndroidMethodVisitorTest.java
@@ -42,6 +42,22 @@ public void guestClockAndExplicitSleepUseParentOwnedBridge() {
assertEquals(4, calls.size());
}
+ @Test
+ public void guestExplicitGcUsesAdvisoryEmulatorBridge() {
+ byte[] transformed = transform(createExplicitGcClass());
+ List calls = methodCalls(transformed, "collect");
+
+ assertTrue(calls.contains(
+ "INVOKESTATIC javax/microedition/shell/MidletSystem.gc()V"));
+ assertTrue(calls.contains(
+ "INVOKESTATIC java/lang/Runtime.getRuntime()Ljava/lang/Runtime;"));
+ assertTrue(calls.contains(
+ "INVOKESTATIC javax/microedition/shell/MidletSystem.gc(Ljava/lang/Runtime;)V"));
+ assertFalse(calls.contains("INVOKESTATIC java/lang/System.gc()V"));
+ assertFalse(calls.contains("INVOKEVIRTUAL java/lang/Runtime.gc()V"));
+ assertEquals(3, calls.size());
+ }
+
@Test
public void yieldRewriteUsesNonThrowingBridge() {
byte[] source = createYieldClass();
@@ -164,6 +180,24 @@ private static byte[] createYieldClass() {
return writer.toByteArray();
}
+ private static byte[] createExplicitGcClass() {
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "sample/ExplicitGc", null,
+ "java/lang/Object", null);
+ MethodVisitor method = writer.visitMethod(
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "collect", "()V", null, null);
+ method.visitCode();
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "gc", "()V", false);
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime", "getRuntime",
+ "()Ljava/lang/Runtime;", false);
+ method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime", "gc", "()V", false);
+ method.visitInsn(Opcodes.RETURN);
+ method.visitMaxs(1, 0);
+ method.visitEnd();
+ writer.visitEnd();
+ return writer.toByteArray();
+ }
+
private static byte[] createWaitClass() {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, "sample/Wait", null, "java/lang/Object", null);
diff --git a/dexlib/src/test/java/org/microemu/android/asm/AndroidProducerDexTest.java b/dexlib/src/test/java/org/microemu/android/asm/AndroidProducerDexTest.java
index e76fdc453..c00c6fa7e 100644
--- a/dexlib/src/test/java/org/microemu/android/asm/AndroidProducerDexTest.java
+++ b/dexlib/src/test/java/org/microemu/android/asm/AndroidProducerDexTest.java
@@ -34,7 +34,7 @@
public class AndroidProducerDexTest {
@Test
- public void transformedTimingCallsitesProduceReadableDex() throws Exception {
+ public void transformedGuestCallsitesProduceReadableDex() throws Exception {
Path root = Files.createTempDirectory("jlmod-dex-transform-");
Path classDirectory = Files.createDirectories(root.resolve("sample"));
Path classFile = classDirectory.resolve("Timing.class");
@@ -63,6 +63,10 @@ public void transformedTimingCallsitesProduceReadableDex() throws Exception {
// allocation/constructor verifier shape valid for subclasses and non-canonical code.
assertEquals(1, countMethods(dex, bridge, "currentTimeMillis"));
assertEquals(2, countMethods(dex, bridge, "calendarInstance"));
+ String systemBridge = "Ljavax/microedition/shell/MidletSystem;";
+ assertEquals(2, countMethods(dex, systemBridge, "gc"));
+ assertEquals(0, countMethods(dex, "Ljava/lang/System;", "gc"));
+ assertEquals(0, countMethods(dex, "Ljava/lang/Runtime;", "gc"));
} finally {
Files.deleteIfExists(dexFile);
Files.deleteIfExists(classFile);
@@ -116,12 +120,26 @@ private static byte[] createTimingClass() {
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "sample/Timing", null,
"java/lang/Object", null);
createClockAndSleepMethod(writer);
+ createExplicitGcMethod(writer);
createWaitMethod(writer);
createDateAndCalendarMethod(writer);
writer.visitEnd();
return writer.toByteArray();
}
+ private static void createExplicitGcMethod(ClassWriter writer) {
+ MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
+ "collect", "()V", null, null);
+ method.visitCode();
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "gc", "()V", false);
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Runtime", "getRuntime",
+ "()Ljava/lang/Runtime;", false);
+ method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Runtime", "gc", "()V", false);
+ method.visitInsn(Opcodes.RETURN);
+ method.visitMaxs(1, 0);
+ method.visitEnd();
+ }
+
private static byte[] createSimpleClass(String name) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null,