blob: b899ccc41d32e39306d491025ccf036b9e2e4a69 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.android.desugar.scan;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableSet;
import javax.annotation.Nullable;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
/** {@link ClassVisitor} that records references to classes starting with a given prefix. */
class PrefixReferenceScanner extends ClassVisitor {
/**
* Returns references with the given prefix in the given class.
*
* @param prefix an internal name prefix, typically a package such as {@code com/google/}
*/
public static ImmutableSet<KeepReference> scan(ClassReader reader, String prefix) {
PrefixReferenceScanner scanner = new PrefixReferenceScanner(prefix);
// Frames irrelevant for Android so skip them. Don't skip debug info in case the class we're
// visiting has local variable tables (typically it doesn't anyways).
reader.accept(scanner, ClassReader.SKIP_FRAMES);
return scanner.roots.build();
}
private final ImmutableSet.Builder<KeepReference> roots = ImmutableSet.builder();
private final PrefixReferenceMethodVisitor mv = new PrefixReferenceMethodVisitor();
private final PrefixReferenceFieldVisitor fv = new PrefixReferenceFieldVisitor();
private final PrefixReferenceAnnotationVisitor av = new PrefixReferenceAnnotationVisitor();
private final String prefix;
public PrefixReferenceScanner(String prefix) {
super(Opcodes.ASM6);
this.prefix = prefix;
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
checkArgument(!name.startsWith(prefix));
if (superName != null) {
classReference(superName);
}
classReferences(interfaces);
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
classReference(owner);
if (desc != null) {
typeReference(Type.getMethodType(desc));
}
}
@Override
public AnnotationVisitor visitTypeAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
classReference(name);
if (outerName != null) {
classReference(outerName);
}
}
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
typeReference(desc);
return fv;
}
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
typeReference(Type.getMethodType(desc));
classReferences(exceptions);
return mv;
}
private void classReferences(@Nullable String[] internalNames) {
if (internalNames != null) {
for (String itf : internalNames) {
classReference(itf);
}
}
}
// The following methods are package-private so they don't incur bridge methods when called from
// inner classes below.
void classReference(String internalName) {
checkArgument(internalName.charAt(0) != '[' && internalName.charAt(0) != '(', internalName);
checkArgument(!internalName.endsWith(";"), internalName);
if (internalName.startsWith(prefix)) {
roots.add(KeepReference.classReference(internalName));
}
}
void objectReference(String internalName) {
// don't call this for method types, convert to Type instead
checkArgument(internalName.charAt(0) != '(', internalName);
if (internalName.charAt(0) == '[') {
typeReference(internalName);
} else {
classReference(internalName);
}
}
void typeReference(String typeDesc) {
// don't call this for method types, convert to Type instead
checkArgument(typeDesc.charAt(0) != '(', typeDesc);
int lpos = typeDesc.lastIndexOf('[') + 1;
if (typeDesc.charAt(lpos) == 'L') {
checkArgument(typeDesc.endsWith(";"), typeDesc);
classReference(typeDesc.substring(lpos, typeDesc.length() - 1));
} else {
// else primitive or primitive array
checkArgument(typeDesc.length() == lpos + 1, typeDesc);
switch (typeDesc.charAt(lpos)) {
case 'B':
case 'C':
case 'S':
case 'I':
case 'J':
case 'D':
case 'F':
case 'Z':
break;
default:
throw new AssertionError("Unexpected type descriptor: " + typeDesc);
}
}
}
void typeReference(Type type) {
switch (type.getSort()) {
case Type.ARRAY:
typeReference(type.getElementType());
break;
case Type.OBJECT:
classReference(type.getInternalName());
break;
case Type.METHOD:
for (Type param : type.getArgumentTypes()) {
typeReference(param);
}
typeReference(type.getReturnType());
break;
default:
break;
}
}
void fieldReference(String owner, String name, String desc) {
objectReference(owner);
typeReference(desc);
if (owner.startsWith(prefix)) {
roots.add(KeepReference.memberReference(owner, name, desc));
}
}
void methodReference(String owner, String name, String desc) {
checkArgument(desc.charAt(0) == '(', desc);
objectReference(owner);
typeReference(Type.getMethodType(desc));
if (owner.startsWith(prefix)) {
roots.add(KeepReference.memberReference(owner, name, desc));
}
}
void handleReference(Handle handle) {
switch (handle.getTag()) {
case Opcodes.H_GETFIELD:
case Opcodes.H_GETSTATIC:
case Opcodes.H_PUTFIELD:
case Opcodes.H_PUTSTATIC:
fieldReference(handle.getOwner(), handle.getName(), handle.getDesc());
break;
default:
methodReference(handle.getOwner(), handle.getName(), handle.getDesc());
break;
}
}
private class PrefixReferenceMethodVisitor extends MethodVisitor {
public PrefixReferenceMethodVisitor() {
super(Opcodes.ASM6);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return av;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public AnnotationVisitor visitTypeAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public void visitTypeInsn(int opcode, String type) {
objectReference(type);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
fieldReference(owner, name, desc);
}
@Override
@SuppressWarnings("deprecation") // Implementing deprecated method to be sure
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
visitMethodInsn(opcode, owner, name, desc, opcode == Opcodes.INVOKEINTERFACE);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
methodReference(owner, name, desc);
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
typeReference(Type.getMethodType(desc));
handleReference(bsm);
for (Object bsmArg : bsmArgs) {
visitConstant(bsmArg);
}
}
@Override
public void visitLdcInsn(Object cst) {
visitConstant(cst);
}
private void visitConstant(Object cst) {
if (cst instanceof Type) {
typeReference((Type) cst);
} else if (cst instanceof Handle) {
handleReference((Handle) cst);
} else {
// Check for other expected types as javadoc recommends
checkArgument(
cst instanceof String
|| cst instanceof Integer
|| cst instanceof Long
|| cst instanceof Float
|| cst instanceof Double,
"Unexpected constant: ", cst);
}
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
typeReference(desc);
}
@Override
public AnnotationVisitor visitInsnAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (type != null) {
classReference(type);
}
}
@Override
public AnnotationVisitor visitTryCatchAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public void visitLocalVariable(
String name, String desc, String signature, Label start, Label end, int index) {
typeReference(desc);
}
@Override
public AnnotationVisitor visitLocalVariableAnnotation(
int typeRef,
TypePath typePath,
Label[] start,
Label[] end,
int[] index,
String desc,
boolean visible) {
typeReference(desc);
return av;
}
}
private class PrefixReferenceFieldVisitor extends FieldVisitor {
public PrefixReferenceFieldVisitor() {
super(Opcodes.ASM6);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
typeReference(desc);
return av;
}
@Override
public AnnotationVisitor visitTypeAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
typeReference(desc);
return av;
}
}
private class PrefixReferenceAnnotationVisitor extends AnnotationVisitor {
public PrefixReferenceAnnotationVisitor() {
super(Opcodes.ASM6);
}
@Override
public void visit(String name, Object value) {
if (value instanceof Type) {
typeReference((Type) value);
}
}
@Override
public void visitEnum(String name, String desc, String value) {
fieldReference(desc.substring(1, desc.length() - 1), value, desc);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
typeReference(desc);
return av;
}
@Override
public AnnotationVisitor visitArray(String name) {
return av;
}
}
}