| // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.utils.codeinspector; |
| |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import java.util.List; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import org.hamcrest.Description; |
| import org.hamcrest.Matcher; |
| import org.hamcrest.TypeSafeMatcher; |
| |
| public class CodeMatchers { |
| |
| public static Matcher<MethodSubject> accessesField(FieldSubject targetSubject) { |
| if (!targetSubject.isPresent()) { |
| throw new IllegalArgumentException(); |
| } |
| DexField target = targetSubject.getField().field; |
| return new TypeSafeMatcher<MethodSubject>() { |
| @Override |
| protected boolean matchesSafely(MethodSubject subject) { |
| if (!subject.isPresent()) { |
| return false; |
| } |
| if (!subject.getMethod().hasCode()) { |
| return false; |
| } |
| return subject.streamInstructions().anyMatch(isFieldAccessWithTarget(target)); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| description.appendText("accesses field `" + target.toSourceString() + "`"); |
| } |
| |
| @Override |
| public void describeMismatchSafely(final MethodSubject subject, Description description) { |
| description.appendText("method did not"); |
| } |
| }; |
| } |
| |
| public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) { |
| return instantiatesClass(clazz.getTypeName()); |
| } |
| |
| public static Matcher<MethodSubject> instantiatesClass(String clazz) { |
| return new TypeSafeMatcher<MethodSubject>() { |
| @Override |
| protected boolean matchesSafely(MethodSubject subject) { |
| if (!subject.isPresent()) { |
| return false; |
| } |
| if (!subject.getMethod().hasCode()) { |
| return false; |
| } |
| return subject |
| .streamInstructions() |
| .anyMatch(instruction -> instruction.isNewInstance(clazz)); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| description.appendText("instantiates class `" + clazz + "`"); |
| } |
| |
| @Override |
| public void describeMismatchSafely(final MethodSubject subject, Description description) { |
| description.appendText("method did not"); |
| } |
| }; |
| } |
| |
| public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) { |
| if (!targetSubject.isPresent()) { |
| throw new IllegalArgumentException(); |
| } |
| DexMethod target = targetSubject.getMethod().method; |
| return new TypeSafeMatcher<MethodSubject>() { |
| @Override |
| protected boolean matchesSafely(MethodSubject subject) { |
| if (!subject.isPresent()) { |
| return false; |
| } |
| if (!subject.getMethod().hasCode()) { |
| return false; |
| } |
| return subject.streamInstructions().anyMatch(isInvokeWithTarget(target)); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| description.appendText("invokes method `" + target.toSourceString() + "`"); |
| } |
| |
| @Override |
| public void describeMismatchSafely(final MethodSubject subject, Description description) { |
| description.appendText("method did not"); |
| } |
| }; |
| } |
| |
| public static Matcher<MethodSubject> invokesMethod( |
| String returnType, String holderType, String methodName, List<String> parameterTypes) { |
| return new TypeSafeMatcher<MethodSubject>() { |
| @Override |
| protected boolean matchesSafely(MethodSubject subject) { |
| if (!subject.isPresent()) { |
| return false; |
| } |
| if (!subject.getMethod().hasCode()) { |
| return false; |
| } |
| return subject |
| .streamInstructions() |
| .anyMatch(isInvokeWithTarget(returnType, holderType, methodName, parameterTypes)); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| StringBuilder text = |
| new StringBuilder("invokes method `") |
| .append(returnType != null ? returnType : "*") |
| .append(" ") |
| .append(holderType != null ? holderType : "*") |
| .append(".") |
| .append(methodName != null ? methodName : "*") |
| .append("("); |
| if (parameterTypes != null) { |
| text.append( |
| parameterTypes.stream() |
| .map(parameterType -> parameterType != null ? parameterType : "*") |
| .collect(Collectors.joining(", "))); |
| } else { |
| text.append("..."); |
| } |
| text.append(")`"); |
| description.appendText(text.toString()); |
| } |
| |
| @Override |
| public void describeMismatchSafely(final MethodSubject subject, Description description) { |
| description.appendText("method did not"); |
| } |
| }; |
| } |
| |
| public static Matcher<MethodSubject> invokesMethodWithName(String name) { |
| return invokesMethod(null, null, name, null); |
| } |
| |
| public static Predicate<InstructionSubject> isInvokeWithTarget(MethodSubject target) { |
| return isInvokeWithTarget(target.getMethod().getReference()); |
| } |
| |
| public static Predicate<InstructionSubject> isInvokeWithTarget(DexMethod target) { |
| return instruction -> instruction.isInvoke() && instruction.getMethod() == target; |
| } |
| |
| public static Predicate<InstructionSubject> isInvokeWithTarget( |
| String returnType, String holderType, String methodName, List<String> parameterTypes) { |
| return instruction -> { |
| if (!instruction.isInvoke()) { |
| return false; |
| } |
| DexMethod invokedMethod = instruction.getMethod(); |
| if (returnType != null |
| && !invokedMethod.getReturnType().toSourceString().equals(returnType)) { |
| return false; |
| } |
| if (holderType != null |
| && !invokedMethod.getHolderType().toSourceString().equals(holderType)) { |
| return false; |
| } |
| if (methodName != null && !invokedMethod.getName().toSourceString().equals(methodName)) { |
| return false; |
| } |
| if (parameterTypes != null) { |
| if (parameterTypes.size() != invokedMethod.getArity()) { |
| return false; |
| } |
| for (int i = 0; i < parameterTypes.size(); i++) { |
| String parameterType = parameterTypes.get(i); |
| if (parameterType != null |
| && !invokedMethod.getParameter(i).toSourceString().equals(parameterType)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| }; |
| } |
| |
| public static Predicate<InstructionSubject> isFieldAccessWithTarget(DexField target) { |
| return instruction -> instruction.isFieldAccess() && instruction.getField() == target; |
| } |
| } |