| /* |
| * Copyright 2014-present Facebook, Inc. |
| * |
| * 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.facebook.buck.tools.dxanalysis; |
| |
| import com.android.common.annotations.Nullable; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.commons.AnalyzerAdapter; |
| import org.objectweb.asm.tree.ClassNode; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| |
| /** |
| * A static analyzer to find uses of reference equality checks on the RegisterSpec class, |
| * including its superclasses, interfaces, superclass interfaces, interface superinterfaces... |
| */ |
| public class RegisterSpecAnalyzer { |
| |
| // TODO(user): Infer this automatically. |
| private static final ImmutableSet<String> RISKY_TYPES = ImmutableSet.of( |
| "com/android/dx/rop/code/RegisterSpec", |
| "com/android/dx/rop/type/TypeBearer", |
| "com/android/dx/util/ToHuman", |
| "java/lang/Comparable", |
| "java/lang/Object"); |
| |
| /** |
| * All classes under analysis. |
| */ |
| private final ImmutableMap<String, ClassNode> allClasses; |
| |
| /** |
| * Log of messages. |
| */ |
| private List<String> log = new ArrayList<>(); |
| |
| public RegisterSpecAnalyzer(ImmutableMap<String, ClassNode> allClasses) { |
| this.allClasses = allClasses; |
| } |
| |
| public static RegisterSpecAnalyzer analyze( |
| ImmutableMap<String, ClassNode> allClasses) { |
| RegisterSpecAnalyzer analyzer = new RegisterSpecAnalyzer(allClasses); |
| analyzer.go(); |
| return analyzer; |
| } |
| |
| public ImmutableList<String> getLog() { |
| return ImmutableList.copyOf(log); |
| } |
| |
| private void go() { |
| for (ClassNode klass : allClasses.values()) { |
| klass.accept(new RegSpecClassVisitor()); |
| } |
| } |
| |
| private class RegSpecClassVisitor extends ClassVisitor { |
| @Nullable private String className; |
| |
| public RegSpecClassVisitor() { |
| super(Opcodes.ASM4); |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| super.visit(version, access, name, signature, superName, interfaces); |
| |
| Preconditions.checkState(this.className == null); |
| this.className = name; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String desc, String signature, String[] exceptions) { |
| RegSpecMethodVisitor mv = new RegSpecMethodVisitor(className, name); |
| AnalyzerAdapter adapter = new AnalyzerAdapter(className, access, name, desc, mv); |
| mv.adapter = adapter; |
| return adapter; |
| } |
| } |
| |
| private class RegSpecMethodVisitor extends MethodVisitor { |
| private final String className; |
| private final String methodName; |
| @Nullable AnalyzerAdapter adapter; |
| |
| public RegSpecMethodVisitor(String className, String methodName) { |
| super(Opcodes.ASM4); |
| this.className = className; |
| this.methodName = methodName; |
| } |
| |
| @Override |
| public void visitJumpInsn(int opcode, Label label) { |
| // There are only two opcodes for comparing object references. |
| if (opcode != Opcodes.IF_ACMPEQ && opcode != Opcodes.IF_ACMPNE) { |
| return; |
| } |
| // There should be two arguments on the stack. |
| Preconditions.checkState(adapter.stack.size() >= 2); |
| Object val1 = adapter.stack.get(adapter.stack.size() - 2); |
| Object val2 = adapter.stack.get(adapter.stack.size() - 1); |
| // If either is known null, then the comparison is benign. |
| if (val1 == Opcodes.NULL || val2 == Opcodes.NULL) { |
| return; |
| } |
| // If not, they both should be strings (class names). |
| Preconditions.checkState(val1 instanceof String); |
| Preconditions.checkState(val2 instanceof String); |
| // If both are risky, report. |
| if (RISKY_TYPES.contains(val1) && RISKY_TYPES.contains(val2)) { |
| log.add(String.format((Locale) null, "RegisterSpec comparison: %s.%s", |
| className, methodName)); |
| } |
| } |
| } |
| } |