Skip to content

Commit

Permalink
Lay the groundwork to use preinstrumented android-all jars in Robolec…
Browse files Browse the repository at this point in the history
…tric.

PiperOrigin-RevId: 350029856
  • Loading branch information
hoisie committed Jan 22, 2021
1 parent 428b984 commit d2f51aa
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.robolectric.internal.bytecode;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
Expand All @@ -13,11 +15,17 @@
* significantly faster than a {@link org.objectweb.asm.tree.ClassNode} object.
*/
public class ClassDetails {
private static final String SHADOWED_OBJECT_INTERNAL_NAME =
ShadowedObject.class.getName().replace('.', '/');

private final ClassReader classReader;
private final String className;
private final byte[] classBytes;
private Set<String> annotations;
private Set<String> interfaces;

public ClassDetails(byte[] classBytes) {
this.classBytes = classBytes;
this.classReader = new ClassReader(classBytes);
this.className = classReader.getClassName().replace('/', '.');
}
Expand All @@ -38,23 +46,34 @@ public boolean hasAnnotation(Class<? extends Annotation> annotationClass) {
if (annotations == null) {
this.annotations = new HashSet<>();
classReader.accept(
new AnnotationVisitor(annotations),
new AnnotationCollector(annotations),
ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
}
String internalName = "L" + annotationClass.getName().replace('.', '/') + ";";
return this.annotations.contains(internalName);
}

private static class AnnotationVisitor extends ClassVisitor {
public boolean isInstrumented() {
if (this.interfaces == null) {
this.interfaces = new HashSet<>(Arrays.asList(classReader.getInterfaces()));
}
return this.interfaces.contains(SHADOWED_OBJECT_INTERNAL_NAME);
}

public byte[] getClassBytes() {
return classBytes;
}

private static class AnnotationCollector extends ClassVisitor {
private final Set<String> annotations;

public AnnotationVisitor(Set<String> annotations) {
public AnnotationCollector(Set<String> annotations) {
super(Opcodes.ASM9);
this.annotations = annotations;
}

@Override
public org.objectweb.asm.AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if (visible) {
annotations.add(descriptor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import org.objectweb.asm.ClassReader;
Expand All @@ -16,6 +17,7 @@
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
Expand All @@ -30,6 +32,7 @@
public abstract class ClassInstrumentor {
private static final String ROBO_INIT_METHOD_NAME = "$$robo$init";
static final Type OBJECT_TYPE = Type.getType(Object.class);
static final String NATIVE_METHOD_DESCRIPTOR = Type.getDescriptor(NativeMethod.class);
private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
final Decorator decorator;

Expand Down Expand Up @@ -76,11 +79,14 @@ public String map(final String internalName) {
}

public byte[] instrument(
byte[] origBytes, InstrumentationConfiguration config, ClassNodeProvider classNodeProvider) {
ClassDetails classDetails,
InstrumentationConfiguration config,
ClassNodeProvider classNodeProvider) {
PerfStatsCollector perfStats = PerfStatsCollector.getInstance();
MutableClass mutableClass =
perfStats.measure(
"analyze class", () -> analyzeClass(origBytes, config, classNodeProvider));
"analyze class",
() -> analyzeClass(classDetails.getClassBytes(), config, classNodeProvider));
byte[] instrumentedBytes =
perfStats.measure("instrument class", () -> instrumentToBytes(mutableClass));
recordPackageStats(perfStats, mutableClass);
Expand Down Expand Up @@ -351,16 +357,28 @@ protected void instrumentNormalMethod(MutableClass mutableClass, MethodNode meth
if ((method.access & Opcodes.ACC_ABSTRACT) == 0) {
method.access = method.access | Opcodes.ACC_FINAL;
}
if ((method.access & Opcodes.ACC_NATIVE) != 0) {
boolean isNativeMethod = (method.access & Opcodes.ACC_NATIVE) != 0;
if (isNativeMethod) {
instrumentNativeMethod(mutableClass, method);
}

// todo figure out
String originalName = method.name;
method.name = directMethodName(mutableClass, originalName);

MethodNode delegatorMethodNode = new MethodNode(method.access, originalName, method.desc, method.signature, exceptionArray(method));
MethodNode delegatorMethodNode =
new MethodNode(
method.access, originalName, method.desc, method.signature, exceptionArray(method));
delegatorMethodNode.visibleAnnotations = method.visibleAnnotations;
// Add a @NativeMethod annotation to the original method to indicate that this method was
// previously native. This is useful for some ClassInstrumentor plugins that do some additional
// instrumentation on native methods.
if (isNativeMethod) {
if (delegatorMethodNode.visibleAnnotations == null) {
delegatorMethodNode.visibleAnnotations = new ArrayList<>();
}
delegatorMethodNode.visibleAnnotations.add(new AnnotationNode(NATIVE_METHOD_DESCRIPTOR));
}
delegatorMethodNode.access &= ~(Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL);

makeMethodPrivate(method);
Expand All @@ -386,7 +404,7 @@ protected void instrumentNativeMethod(MutableClass mutableClass, MethodNode meth
generator.returnValue();
}

private static String directMethodName(MutableClass mutableClass, String originalName) {
protected static String directMethodName(MutableClass mutableClass, String originalName) {
return SHADOW_IMPL.directMethodName(mutableClass.getName(), originalName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public boolean shouldInstrument(ClassDetails classDetails) {
&& !classesToNotInstrument.contains(classDetails.getName())
&& !isInPackagesToNotInstrument(classDetails.getName())
&& !classMatchesExclusionRegex(classDetails.getName())
&& !classDetails.isInstrumented()
&& !classDetails.hasAnnotation(DoNotInstrument.class)
&& (isInInstrumentedPackage(classDetails.getName())
|| instrumentedClasses.contains(classDetails.getName())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.robolectric.internal.bytecode;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.Opcodes;
Expand All @@ -19,7 +20,9 @@ public class MutableClass {
final Type classType;
final ImmutableSet<String> foundMethods;

MutableClass(ClassNode classNode, InstrumentationConfiguration config,
public MutableClass(
ClassNode classNode,
InstrumentationConfiguration config,
ClassNodeProvider classNodeProvider) {
this.classNode = classNode;
this.config = config;
Expand Down Expand Up @@ -55,6 +58,12 @@ public void addMethod(MethodNode methodNode) {
classNode.methods.add(methodNode);
}

public void removeMethod(String name, String desc) {
Iterables.removeIf(
classNode.methods,
methodNode -> name.equals(methodNode.name) && desc.equals(methodNode.desc));
}

public List<FieldNode> getFields() {
return classNode.fields;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.robolectric.internal.bytecode;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Added to instrumented methods to indicate that the original was a native method. This can be used
* by tools that do further processing on Robolectric instrumented classes related to native
* methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NativeMethod {}
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ protected Class<?> maybeInstrumentClass(String className) throws ClassNotFoundEx
try {
final byte[] bytes;
ClassDetails classDetails = new ClassDetails(origClassBytes);
if (config.shouldInstrument(classDetails)) {
bytes = classInstrumentor.instrument(origClassBytes, config, classNodeProvider);
if (shouldInstrument(classDetails)) {
bytes = classInstrumentor.instrument(classDetails, config, classNodeProvider);
maybeDumpClassBytes(classDetails, bytes);
} else {
bytes = postProcessUninstrumentedClass(classDetails, origClassBytes);
bytes = postProcessUninstrumentedClass(classDetails);
}
ensurePackage(className);
return defineClass(className, bytes, 0, bytes.length);
Expand All @@ -171,9 +171,12 @@ private void maybeDumpClassBytes(ClassDetails classDetails, byte[] classBytes) {
}
}

protected byte[] postProcessUninstrumentedClass(
ClassDetails classDetails, byte[] origClassBytes) {
return origClassBytes;
protected boolean shouldInstrument(ClassDetails classDetails) {
return config.shouldInstrument(classDetails);
}

protected byte[] postProcessUninstrumentedClass(ClassDetails classDetails) {
return classDetails.getClassBytes();
}

protected byte[] getByteCode(String className) throws ClassNotFoundException {
Expand Down

0 comments on commit d2f51aa

Please sign in to comment.