Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ require("./tests/testTypedArrayArgs");
require("./tests/dex-interface-implementation");
require("./tests/testClassForNameDiscovery");
require("./tests/testPrebuiltExtendClasses");
require("./tests/testProxyRegistrationGc");
require("./tests/testInterfaceImplementation");
require("./tests/testPrimitiveCallbackReturns");
require("./tests/testRuntimeImplementedAPIs");
Expand Down
82 changes: 82 additions & 0 deletions test-app/app/src/main/assets/app/tests/testProxyRegistrationGc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
describe("Proxy registration GC guard", function () {
var Guard = com.tns.ProxyRegistrationGc;

function maps(names) {
var text = names.map(function (name, i) {
return "7f0000" + i + "000-7f0000" + i + "fff rw-p 00000000 00:00 0 " + name;
}).join("\n");
return new java.io.BufferedReader(new java.io.StringReader(text));
}

var markCompact = [
"[anon:dalvik-main space]",
"[anon:dalvik-Concurrent mark-compact compaction buffers]",
"[anon:dalvik-Concurrent Mark Compact live words bitmap]",
"/apex/com.android.art/lib64/libart.so"
];
var concurrentCopying = [
"[anon:dalvik-main space (region space)]",
"[anon:dalvik-concurrent copying gc mark stack]",
"[anon:dalvik-region space live bitmap]",
"/apex/com.android.art/lib64/libart.so"
];

it("collects under the mark-compact collector", function () {
expect(Guard.isRequired(36, maps(markCompact))).toBe(true);
});

it("skips the collection under the concurrent-copying collector", function () {
expect(Guard.isRequired(33, maps(concurrentCopying))).toBe(false);
});

it("never collects before ART became updatable", function () {
expect(Guard.isRequired(30, maps(markCompact))).toBe(false);
expect(Guard.isRequired(21, maps([]))).toBe(false);
});

it("collects when the heap mappings are unnamed", function () {
expect(Guard.isRequired(33, maps(["/apex/com.android.art/lib64/libart.so", "[stack]"]))).toBe(true);
});

it("collects when no known collector is recognized", function () {
expect(Guard.isRequired(36, maps(["[anon:dalvik-main space]", "[anon:dalvik-card table]"]))).toBe(true);
});

it("collects when mark-compact and region space names both appear", function () {
expect(Guard.isRequired(36, maps(concurrentCopying.concat(markCompact)))).toBe(true);
});

it("matches the collector this process is running", function () {
var reader = new java.io.BufferedReader(new java.io.FileReader("/proc/self/maps"));
var sawMarkCompact = false;
var sawConcurrentCopying = false;
try {
var line;
while ((line = reader.readLine()) !== null) {
var lower = String(line).toLowerCase();
if (lower.indexOf("[anon:dalvik-") < 0) {
continue;
}
if (lower.indexOf("mark-compact") >= 0 || lower.indexOf("mark compact") >= 0) {
sawMarkCompact = true;
}
if (lower.indexOf("concurrent copying") >= 0 || lower.indexOf("(region space)") >= 0) {
sawConcurrentCopying = true;
}
}
} finally {
reader.close();
}

var sdk = android.os.Build.VERSION.SDK_INT;
if (sdk < 31) {
expect(Guard.isRequired()).toBe(false);
} else if (sawMarkCompact) {
expect(Guard.isRequired()).toBe(true);
} else if (sawConcurrentCopying) {
expect(Guard.isRequired()).toBe(false);
} else {
expect(Guard.isRequired()).toBe(true);
}
});
});
10 changes: 8 additions & 2 deletions test-app/runtime/src/main/java/com/tns/DexFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,14 @@ public Class<?> resolveClass(String baseClassName, String name, String className
Class<?> result = null;
String classNameToLoad = isInterface ? fullClassName : desiredDexClassName;

if (injectIntoParentClassLoader && classLoader instanceof BaseDexClassLoader
&& injectDexIntoClassLoader((BaseDexClassLoader) classLoader, jarFilePath)) {
boolean injectIntoParent = injectIntoParentClassLoader && classLoader instanceof BaseDexClassLoader;
if (injectIntoParent && ProxyRegistrationGc.isRequired()) {
// The dex is registered by the first loadClass below, so the blocking
// collection has to stay immediately before it.
java.lang.Runtime.getRuntime().gc();
}

if (injectIntoParent && injectDexIntoClassLoader((BaseDexClassLoader) classLoader, jarFilePath)) {
try {
result = classLoader.loadClass(classNameToLoad);
} catch (ClassNotFoundException e) {
Expand Down
77 changes: 77 additions & 0 deletions test-app/runtime/src/main/java/com/tns/ProxyRegistrationGc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.tns;

import android.os.Build;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Locale;

/**
* Whether DexFactory has to run a blocking collection before it registers a
* generated dex with the app's long-lived class loader.
*
* ART's concurrent mark-compact collector can lose the DexCache location store
* when a collection overlaps ClassLinker::RegisterDexFile, and the app then dies
* with a SIGSEGV in RegisterDexFileLocked. That collector only reaches devices
* through the ART module, which is updatable from API 31.
*
* The collection is skipped only when the process's heap mappings positively
* identify the concurrent-copying collector. A collector that can't be
* identified, including future ART builds with renamed mappings, is treated as
* affected.
*/
public final class ProxyRegistrationGc {
private static final int FIRST_UPDATABLE_ART_SDK = 31;

private static volatile Boolean required;

private ProxyRegistrationGc() {
}

public static boolean isRequired() {
Boolean result = required;
if (result == null) {
result = detect();
required = result;
}
return result;
}

private static boolean detect() {
if (Build.VERSION.SDK_INT < FIRST_UPDATABLE_ART_SDK) {
return false;
}
try (BufferedReader maps = new BufferedReader(new FileReader("/proc/self/maps"))) {
return isRequired(Build.VERSION.SDK_INT, maps);
} catch (IOException | RuntimeException e) {
return true;
}
}

/**
* @param maps the contents of /proc/self/maps; ART names its heap mappings
* "[anon:dalvik-...]" on kernels that support anonymous VMA names
*/
public static boolean isRequired(int sdkInt, BufferedReader maps) throws IOException {
if (sdkInt < FIRST_UPDATABLE_ART_SDK) {
return false;
}
boolean concurrentCopying = false;
String line;
while ((line = maps.readLine()) != null) {
int nameStart = line.indexOf("[anon:dalvik-");
if (nameStart < 0) {
continue;
}
String name = line.substring(nameStart).toLowerCase(Locale.ROOT);
if (name.contains("mark-compact") || name.contains("mark compact")) {
return true;
}
if (name.contains("concurrent copying") || name.contains("(region space)")) {
concurrentCopying = true;
}
}
return !concurrentCopying;
}
}
Loading