blob: 72382ceb6af93fda95ec71328e736960abb16846 [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.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
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.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
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.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import java.util.function.BiConsumer;
import org.junit.Test;
public class NullabilityTest extends SmaliTestBase {
private final String CLASS_NAME = "Example";
private static final InternalOptions TEST_OPTIONS = new InternalOptions();
private void buildAndTest(
SmaliBuilder builder,
MethodSignature signature,
BiConsumer<AppInfoWithSubtyping, TypeAnalysis> inspector)
throws Exception {
AndroidApp app = builder.build();
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
.read().toDirect();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(dexApplication);
DexInspector dexInspector = new DexInspector(appInfo.app);
DexEncodedMethod foo = dexInspector.clazz(CLASS_NAME).method(signature).getMethod();
IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
analysis.run();
inspector.accept(appInfo, analysis);
}
private static void verifyClassTypeLattice(
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices,
DexType receiverType,
Value v,
TypeLatticeElement l) {
assertTrue(l instanceof ClassTypeLatticeElement);
ClassTypeLatticeElement lattice = (ClassTypeLatticeElement) l;
// Receiver
if (lattice.classType.equals(receiverType)) {
assertFalse(l.isNullable());
} else {
TypeLatticeElement expected = expectedLattices.get(v.definition.getClass());
if (expected != null) {
assertEquals(expected, l);
}
}
}
@Test
public void nonNullAfterSafeInvokes() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addInstanceMethod("void", "foo", ImmutableList.of("java.lang.String"), 1,
"invoke-virtual {p1}, Ljava/lang/String;->toString()Ljava/lang/String;",
// Successful invocation above means p1 is not null.
"if-nez p1, :not_null",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":not_null",
"return-void"
);
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// TODO(b/70795205): Can be refined by using control-flow info.
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, example, v, l));
});
}
@Test
public void stillNullAfterExceptionCatch_invoke() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addInstanceMethod("void", "foo", ImmutableList.of("java.lang.String"), 1,
":try_start",
"invoke-virtual {p1}, Ljava/lang/String;->toString()Ljava/lang/String;",
"if-nez p1, :return",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":try_end",
".catch Ljava/lang/Throwable; {:try_start .. :try_end} :return",
":return",
// p1 could be still null at the outside of try-catch.
"invoke-virtual {p1}, Ljava/lang/String;->hashCode()I",
"return-void"
);
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NewInstance.class, new ClassTypeLatticeElement(assertionErrorType, false));
typeAnalysis.forEach((v, l) -> verifyClassTypeLattice(expectedLattices, example, v, l));
});
}
@Test
public void nonNullAfterSafeArrayAccess() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addInstanceMethod("void", "foo", ImmutableList.of("java.lang.String[]"), 1,
"const/4 v0, 0",
"aget-object v0, p1, v0",
// Successful array access above means p1 is not null.
"if-nez p1, :not_null",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":not_null",
"return-void"
);
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
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 instanceof ArrayTypeLatticeElement) {
ArrayTypeLatticeElement lattice = (ArrayTypeLatticeElement) l;
assertEquals(
appInfo.dexItemFactory.stringType,
lattice.getArrayElementType(appInfo.dexItemFactory));
// TODO(b/70795205): Can be refined by using control-flow info.
assertTrue(l.isNullable());
} else if (l instanceof ClassTypeLatticeElement) {
verifyClassTypeLattice(expectedLattices, example, v, l);
}
});
});
}
@Test
public void stillNullAfterExceptionCatch_aget() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addInstanceMethod("void", "foo", ImmutableList.of("java.lang.String[]"), 1,
":try_start",
"const/4 v0, 0",
"aget-object v0, p1, v0",
"if-nez p1, :return",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":try_end",
".catch Ljava/lang/Throwable; {:try_start .. :try_end} :return",
":return",
// p1 could be still null at the outside of try-catch.
"invoke-virtual {p1}, [Ljava/lang/String;->hashCode()I",
"return-void"
);
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
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 instanceof ArrayTypeLatticeElement) {
ArrayTypeLatticeElement lattice = (ArrayTypeLatticeElement) l;
assertEquals(
appInfo.dexItemFactory.stringType,
lattice.getArrayElementType(appInfo.dexItemFactory));
assertTrue(l.isNullable());
} else if (l instanceof ClassTypeLatticeElement) {
verifyClassTypeLattice(expectedLattices, example, v, l);
}
});
});
}
@Test
public void nonNullAfterSafeFieldAccess() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addStaticMethod("void", "foo", ImmutableList.of("Test"), 1,
"iget-object v0, p0, LTest;->bar:Ljava/lang/String;",
// Successful field access above means p0 is not null.
"if-nez p0, :not_null",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":not_null",
"return-void"
);
builder.addClass("Test");
builder.addInstanceField("bar", "Ljava/lang/String;");
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
DexType testType = appInfo.dexItemFactory.createType("LTest;");
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// TODO(b/70795205): Can be refined by using control-flow info.
Argument.class, new ClassTypeLatticeElement(testType, true),
// 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, example, v, l));
});
}
@Test
public void stillNullAfterExceptionCatch_iget() throws Exception {
SmaliBuilder builder = new SmaliBuilder(CLASS_NAME);
MethodSignature signature =
builder.addStaticMethod("void", "foo", ImmutableList.of("Test"), 1,
":try_start",
"iget-object v0, p0, LTest;->bar:Ljava/lang/String;",
"if-nez p0, :return",
"new-instance v0, Ljava/lang/AssertionError;",
"throw v0",
":try_end",
".catch Ljava/lang/Throwable; {:try_start .. :try_end} :return",
":return",
// p0 could be still null at the outside of try-catch.
"invoke-virtual {p0}, LTest;->hashCode()I",
"return-void"
);
builder.addClass("Test");
builder.addInstanceField("bar", "Ljava/lang/String;");
buildAndTest(builder, signature, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType example = appInfo.dexItemFactory.createType("LExample;");
DexType testType = appInfo.dexItemFactory.createType("LTest;");
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
Argument.class, new ClassTypeLatticeElement(testType, true),
// 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, example, v, l));
});
}
}