| // Copyright (c) 2021, 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.apimodel; |
| |
| import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod; |
| import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.notIf; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.TestCompilerBuilder; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.ThrowableConsumer; |
| import com.android.tools.r8.references.MethodReference; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.CodeMatchers; |
| import com.android.tools.r8.utils.codeinspector.FoundClassSubject; |
| import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.BiConsumer; |
| import java.util.stream.Collectors; |
| |
| public abstract class ApiModelingTestHelper { |
| |
| public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForMethod(Method method, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options |
| .apiModelingOptions() |
| .methodApiMapping |
| .put(Reference.methodFromMethod(method), apiLevel); |
| }); |
| }; |
| } |
| |
| public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForMethod( |
| MethodReference method, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().methodApiMapping.put(method, apiLevel); |
| }); |
| }; |
| } |
| |
| public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForMethod( |
| Constructor<?> constructor, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options |
| .apiModelingOptions() |
| .methodApiMapping |
| .put(Reference.methodFromMethod(constructor), apiLevel); |
| }); |
| }; |
| } |
| |
| static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForDefaultInstanceInitializer( |
| Class<?> clazz, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options |
| .apiModelingOptions() |
| .methodApiMapping |
| .put( |
| Reference.method( |
| Reference.classFromClass(clazz), "<init>", ImmutableList.of(), null), |
| apiLevel); |
| }); |
| }; |
| } |
| |
| static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForField(Field field, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options |
| .apiModelingOptions() |
| .fieldApiMapping |
| .put(Reference.fieldFromField(field), apiLevel); |
| }); |
| }; |
| } |
| |
| public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> setMockApiLevelForClass(Class<?> clazz, AndroidApiLevel apiLevel) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options |
| .apiModelingOptions() |
| .classApiMapping |
| .put(Reference.classFromClass(clazz), apiLevel); |
| }); |
| }; |
| } |
| |
| public static void enableApiCallerIdentification( |
| TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().enableApiCallerIdentification = true; |
| }); |
| } |
| |
| static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().enableApiCallerIdentification = true; |
| options.apiModelingOptions().enableStubbingOfClasses = true; |
| }); |
| } |
| |
| static void enableOutliningOfMethods(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().enableApiCallerIdentification = true; |
| options.apiModelingOptions().enableOutliningOfMethods = true; |
| }); |
| } |
| |
| static void disableCheckAllApiReferencesAreNotUnknown( |
| TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().enableApiCallerIdentification = true; |
| options.apiModelingOptions().checkAllApiReferencesAreSet = false; |
| }); |
| } |
| |
| public static void disableOutliningAndStubbing( |
| TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| disableStubbingOfClasses(compilerBuilder); |
| disableOutlining(compilerBuilder); |
| } |
| |
| public static void disableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> options.apiModelingOptions().enableStubbingOfClasses = false); |
| } |
| |
| public static void disableOutlining(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) { |
| compilerBuilder.addOptionsModification( |
| options -> options.apiModelingOptions().enableOutliningOfMethods = false); |
| } |
| |
| static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> |
| ThrowableConsumer<T> addTracedApiReferenceLevelCallBack( |
| BiConsumer<MethodReference, AndroidApiLevel> consumer) { |
| return compilerBuilder -> { |
| compilerBuilder.addOptionsModification( |
| options -> { |
| options.apiModelingOptions().tracedMethodApiLevelCallback = |
| (methodReference, computedApiLevel) -> { |
| consumer.accept( |
| methodReference, |
| computedApiLevel.isKnownApiLevel() |
| ? computedApiLevel.asKnownApiLevel().getApiLevel() |
| : null); |
| }; |
| }); |
| }; |
| } |
| |
| static ApiModelingClassVerificationHelper verifyThat( |
| CodeInspector inspector, TestParameters parameters, Class<?> clazz) { |
| return new ApiModelingClassVerificationHelper(inspector, parameters, clazz); |
| } |
| |
| static ApiModelingMethodVerificationHelper verifyThat( |
| CodeInspector inspector, TestParameters parameters, Method method) { |
| return new ApiModelingMethodVerificationHelper( |
| inspector, parameters, Reference.methodFromMethod(method)); |
| } |
| |
| public static void assertNoSynthesizedClasses(CodeInspector inspector) { |
| assertEquals( |
| Collections.emptySet(), |
| inspector.allClasses().stream() |
| .filter(FoundClassSubject::isSynthetic) |
| .collect(Collectors.toSet())); |
| } |
| |
| public static class ApiModelingClassVerificationHelper { |
| |
| private final CodeInspector inspector; |
| private final Class<?> classOfInterest; |
| private final TestParameters parameters; |
| |
| public ApiModelingClassVerificationHelper( |
| CodeInspector inspector, TestParameters parameters, Class<?> classOfInterest) { |
| this.inspector = inspector; |
| this.parameters = parameters; |
| this.classOfInterest = classOfInterest; |
| } |
| |
| public void stubbedUntil(AndroidApiLevel finalApiLevel) { |
| assertThat( |
| inspector.clazz(classOfInterest), |
| notIf( |
| isPresent(), |
| parameters.isCfRuntime() |
| || parameters.getApiLevel().isGreaterThanOrEqualTo(finalApiLevel))); |
| } |
| } |
| |
| public static class ApiModelingMethodVerificationHelper { |
| |
| private final CodeInspector inspector; |
| private final MethodReference methodOfInterest; |
| private final TestParameters parameters; |
| |
| private ApiModelingMethodVerificationHelper( |
| CodeInspector inspector, TestParameters parameters, MethodReference methodOfInterest) { |
| this.inspector = inspector; |
| this.methodOfInterest = methodOfInterest; |
| this.parameters = parameters; |
| } |
| |
| public ApiModelingMethodVerificationHelper setHolder(FoundClassSubject classSubject) { |
| return new ApiModelingMethodVerificationHelper( |
| inspector, |
| parameters, |
| Reference.method( |
| classSubject.getFinalReference(), |
| methodOfInterest.getMethodName(), |
| methodOfInterest.getFormalTypes(), |
| methodOfInterest.getReturnType())); |
| } |
| |
| void inlinedIntoFromApiLevel(Method method, AndroidApiLevel apiLevel) { |
| if (parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)) { |
| inlinedInto(method); |
| } else { |
| notInlinedInto(method); |
| } |
| } |
| |
| void notInlinedInto(Method method) { |
| MethodSubject candidate = inspector.method(methodOfInterest); |
| assertThat(candidate, isPresent()); |
| MethodSubject target = inspector.method(method); |
| assertThat(target, isPresent()); |
| assertThat(target, CodeMatchers.invokesMethod(candidate)); |
| } |
| |
| void inlinedInto(Method method) { |
| MethodSubject candidate = inspector.method(methodOfInterest); |
| if (!candidate.isPresent()) { |
| return; |
| } |
| MethodSubject target = inspector.method(method); |
| assertThat(target, isPresent()); |
| assertThat(target, not(CodeMatchers.invokesMethod(candidate))); |
| } |
| |
| void isOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) { |
| if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) { |
| isOutlinedFrom(method); |
| } else { |
| isNotOutlinedFrom(method); |
| } |
| } |
| |
| void isOutlinedFrom(Method method) { |
| // Check that the call is in a synthetic class. |
| List<FoundMethodSubject> outlinedMethod = |
| inspector.allClasses().stream() |
| .flatMap(clazz -> clazz.allMethods().stream()) |
| .filter( |
| methodSubject -> |
| methodSubject.isSynthetic() |
| && invokesMethodWithName(methodOfInterest.getMethodName()) |
| .matches(methodSubject)) |
| .collect(Collectors.toList()); |
| assertEquals(1, outlinedMethod.size()); |
| // Assert that method invokes the outline |
| MethodSubject caller = inspector.method(method); |
| assertThat(caller, isPresent()); |
| assertThat(caller, invokesMethod(outlinedMethod.get(0))); |
| } |
| |
| void isNotOutlinedFrom(Method method) { |
| MethodSubject caller = inspector.method(method); |
| assertThat(caller, isPresent()); |
| assertThat(caller, invokesMethodWithName(methodOfInterest.getMethodName())); |
| } |
| } |
| } |