aboutsummaryrefslogtreecommitdiff
path: root/sandbox
diff options
context:
space:
mode:
authorMichael Hoisie <hoisie@google.com>2024-01-25 09:39:02 -0800
committerCopybara-Service <copybara-worker@google.com>2024-01-25 09:39:37 -0800
commitf11f710b333969cf1413833bee6d98c87e02272f (patch)
treec20b676f2a3ef673fa7845e5601dd46b20c9e91c /sandbox
parentce2bc5153c204248415f5b70ae368a5cb9afc94e (diff)
downloadrobolectric-f11f710b333969cf1413833bee6d98c87e02272f.tar.gz
Remove exemptionlist-based throw-on-native method functionality
Now that native-backed shadows for graphics and SQLite are using callNativeMethodsByDefault, any native methods that are not properly shadowed will automatically throw an UnsatisfiedLinkError, This makes the exemptionlist-based system for throwing on native methods redundant as an UnsatisfiedLinkError will be thrown instead of a NativeMethodNotFoundException. The only potential drawback is that throw-on-native functionality will not be enabled for legacy graphics and legacy SQLite shadows. However: 1) Legacy graphics and SQLite shadows are in maintenance mode and no new functionality is being added to them. 2) The exemptionlist-based system could not differentiate between legacy and native-backed shadows, which meant that exemptions were often added for legacy shadows, and prevented throw-on-native from occurring in native-backed shadows, where it was most useful. 3) It would be straightforward to add a warning for legacy shadows of native methods at runtime. This is because ShadowWrangler now has information about whether a method was originally native or not. PiperOrigin-RevId: 601474699
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java17
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java137
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java17
-rw-r--r--sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java18
-rw-r--r--sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java71
-rw-r--r--sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java218
6 files changed, 1 insertions, 477 deletions
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
index b3a251514..a9b532a26 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
@@ -38,7 +38,6 @@ import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
-import org.robolectric.sandbox.NativeMethodNotFoundException;
import org.robolectric.util.PerfStatsCollector;
/**
@@ -54,7 +53,6 @@ public class ClassInstrumentor {
protected static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
final Decorator decorator;
- private NativeCallHandler nativeCallHandler;
static {
String className = Type.getInternalName(InvokeDynamicSupport.class);
@@ -566,17 +564,6 @@ public class ClassInstrumentor {
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
- if (nativeCallHandler != null) {
- String descriptor =
- String.format("%s#%s%s", mutableClass.getName(), method.name, method.desc);
- nativeCallHandler.logNativeCall(descriptor);
- if (nativeCallHandler.shouldThrow(descriptor)) {
- String message =
- nativeCallHandler.getExceptionMessage(descriptor, mutableClass.getName(), method.name);
- generator.throwException(Type.getType(NativeMethodNotFoundException.class), message);
- }
- }
-
Type returnType = generator.getReturnType();
generator.pushDefaultReturnValueToStack(returnType);
generator.returnValue();
@@ -780,10 +767,6 @@ public class ClassInstrumentor {
return -1;
}
- public void setNativeCallHandler(NativeCallHandler nativeCallHandler) {
- this.nativeCallHandler = nativeCallHandler;
- }
-
public interface Decorator {
void decorate(MutableClass mutableClass);
}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java
deleted file mode 100644
index 89034d63f..000000000
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.robolectric.internal.bytecode;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Set;
-import java.util.TreeSet;
-import javax.annotation.Nonnull;
-
-/**
- * Handler for native calls instrumented by ClassInstrumentor.
- *
- * <p>Native Calls can either be instrumented as no-op calls (returning a default value or 0 or
- * null) or throw an exception. This helper class helps maintain a list of exemptions to indicates
- * which native calls should be no-op and never throw.
- */
-public class NativeCallHandler {
-
- private final File exemptionsFile;
- private final boolean writeExemptions;
- private final boolean throwOnNatives;
- private final Set<String> descriptors = new TreeSet<>();
-
- /**
- * Initializes the native calls handler.
- *
- * @param exemptionsFile The exemptions file to read from and/or to generate.
- * @param writeExemptions When true, native calls are added to the exemption list.
- * @param throwOnNatives Whether native calls should throw by default unless their signature is
- * listed in the exemption list. When false, all native calls become no-op.
- * @throws IOException if there's an issue reading an existing exemption list.
- */
- public NativeCallHandler(
- @Nonnull File exemptionsFile, boolean writeExemptions, boolean throwOnNatives)
- throws IOException {
- this.exemptionsFile = exemptionsFile;
- this.writeExemptions = writeExemptions;
- this.throwOnNatives = throwOnNatives;
-
- if (exemptionsFile.exists()) {
- readExemptionsList(exemptionsFile);
- }
- }
-
- private String getExemptionFileName() {
- return exemptionsFile.getName();
- }
-
- private void readExemptionsList(File exemptionsFile) throws IOException {
- try (BufferedReader reader =
- new BufferedReader(new FileReader(exemptionsFile.getPath(), UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- // Sanitize input. Ignore empty lines and commented lines starting with #.
- line = sanitize(line.trim());
- if (line.isEmpty() || line.charAt(0) == '#') {
- continue;
- }
- descriptors.add(line);
- }
- }
- System.out.println(
- "Loaded " + descriptors.size() + " exemptions from " + exemptionsFile.getPath());
- }
-
- public void writeExemptionsList() throws IOException {
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- for (String descriptor : descriptors) {
- writer.write(descriptor);
- writer.write('\n');
- }
- }
- System.out.println(
- "Wrote " + descriptors.size() + " exemptions to " + exemptionsFile.getPath());
- }
-
- /**
- * Adds the method description to the native call exemption list if {@link #writeExemptions} is
- * set.
- */
- public void logNativeCall(@Nonnull String descriptor) {
- if (!writeExemptions) {
- return;
- }
- descriptors.add(sanitize(descriptor));
- }
-
- /** Returns whether the ClassInstrumentor should generate an exception or a no-op bytecode. */
- public boolean shouldThrow(@Nonnull String descriptor) {
- return throwOnNatives && !descriptors.contains(sanitize(descriptor));
- }
-
- private String sanitize(String descriptor) {
- // Post-processing of the exemptions files is made complicated by the presence of $ signs
- // in the FQCN. Instead of escaping them, just replace them by another unused character
- // that is not so sensitive to shell or make mangling.
- return descriptor.replace('$', '^');
- }
-
- /**
- * Returns the detailed message to be used by the ClassInstrumentor in the generated bytecode.
- *
- * @param descriptor The ASM descriptor as it should be written in the exemption file.
- * @param className The fully qualified class name, used for the user description.
- * @param methodName The method name, used for the user description.
- */
- public String getExceptionMessage(
- @Nonnull String descriptor, @Nonnull String className, @Nonnull String methodName) {
- // The shadow message is merely a hint based on the last component of the FQCN, which is
- // typically the pattern used for shadow classes.
- String shadowHint =
- "Shadow" + className.replaceAll("[^.]+\\.", "").replaceAll("\\$.*", "") + ".java";
- // The message below tries to educate the user that shadow overrides are not necessarily
- // needed nor desired for trivial cases that are better covered by a no-op return operation.
- return "Unexpected Robolectric native method call to '"
- + className
- + "#"
- + methodName
- + "()'.\n"
- + "Option 1: If customizing this method is useful, add an implementation in "
- + shadowHint
- + ".\n"
- + "Option 2: If this method just needs to trivially return 0 or null, please add an"
- + " exemption entry for\n"
- + " "
- + sanitize(descriptor)
- + "\n"
- + "to exemption file "
- + getExemptionFileName();
- }
-}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
index 5e03aa945..1c6d8c19c 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
@@ -23,7 +23,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Priority;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.ReflectorObject;
-import org.robolectric.sandbox.NativeMethodNotFoundException;
import org.robolectric.sandbox.ShadowMatcher;
import org.robolectric.util.Function;
import org.robolectric.util.PerfStatsCollector;
@@ -183,21 +182,7 @@ public class ShadowWrangler implements ClassHandler {
} else {
RobolectricInternals.performStaticInitialization(clazz);
}
- } catch (InvocationTargetException e) {
- // Note: target exception originates from the sandbox classloader.
- // "instanceof" does not check class equality across classloaders (since they differ).
- // A simple workaround is to check the class FQCN instead.
- String nativeMethodNotFoundException = NativeMethodNotFoundException.class.getName();
-
- for (Throwable t = e.getTargetException(); t != null; ) {
- if (nativeMethodNotFoundException.equals(t.getClass().getName())) {
- throw (RuntimeException) t;
- }
-
- t = t.getCause();
- }
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
diff --git a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java b/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java
deleted file mode 100644
index ad04d959f..000000000
--- a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.robolectric.sandbox;
-
-/**
- * Thrown when a particular Robolectric native method cannot be found.
- *
- * <p>Instrumented native methods throw this exception when the NativeCallHandler is set to
- * throw-on-native and that the dedicated method signature has not been exempted.
- */
-public class NativeMethodNotFoundException extends RuntimeException {
-
- public NativeMethodNotFoundException() {
- super();
- }
-
- public NativeMethodNotFoundException(String message) {
- super(message);
- }
-}
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
index bd4bbe08e..0d89cba47 100644
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
+++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
@@ -1,13 +1,8 @@
package org.robolectric.internal.bytecode;
import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.Iterables;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -59,72 +54,6 @@ public class ClassInstrumentorTest {
}
@Test
- public void instrumentNativeMethod_withoutExemption_generatesThrowException() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- }
-
- NativeCallHandler nativeCallHandler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
- instrumentor.setNativeCallHandler(nativeCallHandler);
-
- ClassNode classNode = createClassWithNativeMethod();
- MutableClass clazz =
- new MutableClass(
- classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
- instrumentor.instrument(clazz);
-
- String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction");
- MethodNode methodNode = findMethodNode(classNode, someFunctionName);
- // Side effect: original method has been made private.
- assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
- // Side effect: instructions have been rewritten to throw and return.
- assertThat(methodNode.instructions.size()).isEqualTo(7);
- assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.NEW);
- assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.DUP);
- assertThat(methodNode.instructions.get(2).getOpcode()).isEqualTo(Opcodes.LDC);
- assertThat(methodNode.instructions.get(3).getOpcode()).isEqualTo(Opcodes.INVOKESPECIAL);
- assertThat(methodNode.instructions.get(4).getOpcode()).isEqualTo(Opcodes.ATHROW);
- assertThat(methodNode.instructions.get(5).getOpcode()).isEqualTo(Opcodes.ICONST_0);
- assertThat(methodNode.instructions.get(6).getOpcode()).isEqualTo(Opcodes.IRETURN);
- }
-
- @Test
- public void instrumentNativeMethod_withExemption_generatesNoOpReturn() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- writer.write("org.example.MyClass#someFunction()I\n");
- }
-
- NativeCallHandler nativeCallHandler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
- instrumentor.setNativeCallHandler(nativeCallHandler);
-
- ClassNode classNode = createClassWithNativeMethod();
-
- MutableClass clazz =
- new MutableClass(
- classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
- instrumentor.instrument(clazz);
-
- String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction");
- MethodNode methodNode = findMethodNode(classNode, someFunctionName);
-
- // Side effect: original method has been made private.
- assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
- // Side effect: instructions have been rewritten to return 0.
- assertThat(methodNode.instructions.size()).isEqualTo(2);
- assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.ICONST_0);
- assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.IRETURN);
- }
-
- @Test
public void instrumentNativeMethod_generatesNativeBindingMethod() {
ClassNode classNode = createClassWithNativeMethod();
MutableClass clazz =
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java
deleted file mode 100644
index 04035346e..000000000
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package org.robolectric.internal.bytecode;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.io.Files;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Test for {@link NativeCallHandler}. */
-@RunWith(JUnit4.class)
-public class NativeCallHandlerTest {
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Test
- public void jarInstrumentorLegacyUsage() throws IOException {
- // CUJ: Legacy jarInstrumentor usage; there is no exemption file, native methods do not throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- assertThat(exemptionsFile.delete()).isTrue();
-
- // Create handler, which loads exemptions from file. It's fine for the file to be missing.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false);
-
- // No method descriptor should throw.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse();
- }
-
- @Test
- public void jarInstrumentorUsage_throwOnNativesEnabled() throws IOException {
- // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should
- // throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n");
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- // empty or white-space lines are ignored
- writer.write("\n");
- writer.write(" \t \n");
- // A # prefix denotes a comment and is ignored too
- writer.write("# org.example.Ignored#comment()V\n");
- writer.write(" # org.example.Ignored#thisIsACommentToo()V \n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // Test exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
-
- // Test non-exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isTrue();
-
- // Empty lines and comments are ignored and not present in the exemption list.
- assertThat(handler.shouldThrow("")).isTrue();
- assertThat(handler.shouldThrow(" \t ")).isTrue();
- assertThat(handler.shouldThrow("# org.example.Ignored#comment()V")).isTrue();
- assertThat(handler.shouldThrow(" # org.example.Ignored#thisIsACommentToo()V ")).isTrue();
- }
-
- @Test
- public void jarInstrumentorUsage_throwOnNativesDisabled() throws IOException {
- // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should
- // throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n");
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is disabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false);
-
- // Test exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
-
- // Test non-exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse();
- }
-
- @Test
- public void jarInstrumentorUsage_logNativeCall_ignored() throws IOException {
- // When not writing the exemption list, logNativeCall calls are no-op.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // No methods are exempted -- initial list is empty.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- // LogNativeCall did not capture. These methods are still not exempted.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
- }
-
- @Test
- public void exemptionListGeneratorUsage_logNativeCall_capturesCalls() throws IOException {
- // CUJ: jarInstrumentor called to generate the exemption list.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ true);
-
- // No methods are exempted -- initial list is empty.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- // These methods are now exempted.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isFalse();
- }
-
- @Test
- public void exemptionListGeneratorUsage_writeExemptionFile() throws IOException {
- // CUJ: jarInstrumentor called to generate the exemption list.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is disabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ false);
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- // Multiple calls with same value are idempotent.
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(II)V");
- handler.logNativeCall("libcore.io.Linux#chmod(Ljava/lang/String;I)V");
- // Case of a nested class with $ in the FQCN.
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- handler.writeExemptionsList();
-
- // Note: due to how the generated files are manipulated in the shell/makefile build system,
- // '$' characters are a problem and would need to be escaped (and potentially differently for
- // shell vs makefiles). The workaround is to have '$' rewritten as '^'.
-
- assertThat(Files.asCharSource(exemptionsFile, UTF_8).read())
- .isEqualTo(
- "android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n"
- // Font$Builder gets written as Font^Builder.
- + "android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n"
- + "libcore.io.Linux#chmod(Ljava/lang/String;I)V\n"
- + "libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n"
- + "org.example.MyClass#someOtherMethod()V\n"
- + "org.example.MyClass#someOtherMethod(I)V\n"
- + "org.example.MyClass#someOtherMethod(II)V\n");
- }
-
- @Test
- public void getExceptionMessage() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // Test generated exception message for non-exempted methods.
- assertThat(
- handler.getExceptionMessage(
- "org.example.MyClass$1#someOtherMethod(II)V",
- "org.example.MyClass$1",
- "someOtherMethod"))
- .isEqualTo(
- "Unexpected Robolectric native method call to"
- + " 'org.example.MyClass$1#someOtherMethod()'.\n"
- + "Option 1: If customizing this method is useful, add an implementation in"
- + " ShadowMyClass.java.\n"
- + "Option 2: If this method just needs to trivially return 0 or null, please add an"
- + " exemption entry for\n"
- + " org.example.MyClass^1#someOtherMethod(II)V\n"
- + "to exemption file natives.txt");
- }
-}