All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.internal.bytecode.NativeCallHandler Maven / Gradle / Ivy

There is a newer version: 4.14.1
Show newest version
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.
 *
 * 

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 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(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy