blob: 443a20e56c611c0320198ee460e79826e06592c7 [file] [log] [blame]
// Copyright (c) 2017, 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.ir.analysis.type;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.NonNull;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.junit.Test;
public class NullabilityTest extends TestBase {
private static final InternalOptions TEST_OPTIONS = new InternalOptions();
private void buildAndTest(
Class<?> mainClass,
MethodSignature signature,
boolean npeCaught,
BiConsumer<AppInfo, TypeAnalysis> inspector)
throws Exception {
AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
.read().toDirect();
AppInfo appInfo = new AppInfo(dexApplication);
DexInspector dexInspector = new DexInspector(appInfo.app);
DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
NonNullTracker nonNullTracker = new NonNullTracker();
nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
inspector.accept(appInfo, analysis);
verifyLastInvoke(irCode, analysis, npeCaught);
}
private static void verifyClassTypeLattice(
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices,
DexType receiverType,
Value v,
TypeLatticeElement l) {
// Due to the last invocation that will check nullability of the argument,
// there is one exceptional mapping to PRIMITIVE.
if (l.isPrimitive()) {
return;
}
assertTrue(l.isClassTypeLatticeElement());
ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
// Receiver
if (lattice.getClassType().equals(receiverType)) {
assertFalse(l.isNullable());
} else {
Instruction definition = v.definition;
if (definition != null) {
TypeLatticeElement expected = expectedLattices.get(v.definition.getClass());
if (expected != null) {
assertEquals(expected, l);
}
}
}
}
private void verifyLastInvoke(IRCode code, TypeAnalysis analysis, boolean npeCaught) {
InstructionIterator it = code.instructionIterator();
boolean metInvokeVirtual = false;
while (it.hasNext()) {
Instruction instruction = it.next();
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
if (invoke.getInvokedMethod().name.toString().contains("hash")) {
metInvokeVirtual = true;
TypeLatticeElement l = analysis.getLatticeElement(invoke.getReceiver());
assertEquals(npeCaught, l.isNullable());
}
}
}
assertTrue(metInvokeVirtual);
}
@Test
public void nonNullAfterSafeInvokes() throws Exception {
MethodSignature signature =
new MethodSignature("foo", "int", new String[]{"java.lang.String"});
buildAndTest(NonNullAfterInvoke.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@Test
public void stillNullAfterExceptionCatch_invoke() throws Exception {
MethodSignature signature =
new MethodSignature("bar", "int", new String[]{"java.lang.String"});
buildAndTest(NonNullAfterInvoke.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@Test
public void nonNullAfterSafeArrayAccess() throws Exception {
MethodSignature signature =
new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
buildAndTest(NonNullAfterArrayAccess.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> {
if (l.isArrayTypeLatticeElement()) {
ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
assertEquals(
appInfo.dexItemFactory.stringType,
lattice.getArrayElementType(appInfo.dexItemFactory));
assertEquals(v.definition.isArgument(), l.isNullable());
} else if (l.isClassTypeLatticeElement()) {
verifyClassTypeLattice(expectedLattices, mainClass, v, l);
}
});
});
}
@Test
public void stillNullAfterExceptionCatch_aget() throws Exception {
MethodSignature signature =
new MethodSignature("bar", "int", new String[]{"java.lang.String[]"});
buildAndTest(NonNullAfterArrayAccess.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> {
if (l.isArrayTypeLatticeElement()) {
ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
assertEquals(
appInfo.dexItemFactory.stringType,
lattice.getArrayElementType(appInfo.dexItemFactory));
assertEquals(v.definition.isArgument(), l.isNullable());
} else if (l.isClassTypeLatticeElement()) {
verifyClassTypeLattice(expectedLattices, mainClass, v, l);
}
});
});
}
@Test
public void nonNullAfterSafeFieldAccess() throws Exception {
MethodSignature signature = new MethodSignature("foo", "int",
new String[]{FieldAccessTest.class.getCanonicalName()});
buildAndTest(NonNullAfterFieldAccess.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
DexType testClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
Argument.class, new ClassTypeLatticeElement(testClass, true),
NonNull.class, new ClassTypeLatticeElement(testClass, false),
// instance may not be initialized.
InstanceGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@Test
public void stillNullAfterExceptionCatch_iget() throws Exception {
MethodSignature signature = new MethodSignature("bar", "int",
new String[]{FieldAccessTest.class.getCanonicalName()});
buildAndTest(NonNullAfterFieldAccess.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
DexType testClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
Argument.class, new ClassTypeLatticeElement(testClass, true),
NonNull.class, new ClassTypeLatticeElement(testClass, false),
// instance may not be initialized.
InstanceGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
}