Generate not-null assertions on method parameters
Intrinsics.checkParameterIsNotNull() gets its caller's class and method names from the stack trace to render them in an exception message. Fix codegen tests because now it's now allowed to pass null to non-null argument in tests
This commit is contained in:
@@ -129,6 +129,7 @@ public class SpecialFiles {
|
||||
|
||||
excludedFiles.add("doGenerateAssertions.kt"); // Multi-file + Java
|
||||
excludedFiles.add("doNotGenerateAssertions.kt"); // Multi-file + Java
|
||||
excludedFiles.add("doGenerateParamAssertions.kt"); // Java
|
||||
}
|
||||
|
||||
private SpecialFiles() {
|
||||
|
||||
@@ -422,6 +422,31 @@ public class AsmUtil {
|
||||
genMethodThrow(mv, STUB_EXCEPTION, STUB_EXCEPTION_MESSAGE);
|
||||
}
|
||||
|
||||
public static void genNotNullAssertionsForParameters(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
@NotNull FunctionDescriptor descriptor,
|
||||
@NotNull FrameMap frameMap
|
||||
) {
|
||||
if (!state.isGenerateNotNullParamAssertions()) return;
|
||||
|
||||
// Private method is not accessible from other classes, no assertions needed
|
||||
if (getVisibilityAccessFlag(descriptor) == ACC_PRIVATE) return;
|
||||
|
||||
for (ValueParameterDescriptor parameter : descriptor.getValueParameters()) {
|
||||
JetType type = parameter.getReturnType();
|
||||
if (type == null || type.isNullable()) continue;
|
||||
|
||||
int index = frameMap.getIndex(parameter);
|
||||
Type asmType = state.getTypeMapper().mapReturnType(type);
|
||||
if (asmType.getSort() == Type.OBJECT || asmType.getSort() == Type.ARRAY) {
|
||||
v.load(index, asmType);
|
||||
v.visitLdcInsn(descriptor.getName().getName());
|
||||
v.invokestatic("jet/runtime/Intrinsics", "checkParameterIsNotNull", "(Ljava/lang/Object;Ljava/lang/String;)V");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void genNotNullAssertionForField(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
|
||||
@@ -211,6 +211,8 @@ public class FunctionCodegen extends GenerationStateAware {
|
||||
|
||||
createSharedVarsForParameters(mv, functionDescriptor, frameMap, localVariablesInfo);
|
||||
|
||||
genNotNullAssertionsForParameters(new InstructionAdapter(mv), state, functionDescriptor, frameMap);
|
||||
|
||||
ExpressionCodegen codegen = new ExpressionCodegen(mv, frameMap, asmMethod.getReturnType(), context, state);
|
||||
codegen.returnExpression(fun.getBodyExpression());
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package test;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class doGenerateParamAssertions {
|
||||
|
||||
public abstract void bar(@NotNull String s);
|
||||
|
||||
public static String foo(doGenerateParamAssertions a) {
|
||||
try {
|
||||
a.bar(null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "OK";
|
||||
}
|
||||
return "Fail: AssertionError expected";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import test.doGenerateParamAssertions as C
|
||||
|
||||
class A : C() {
|
||||
override fun bar(s: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun box() = C.foo(A())
|
||||
@@ -0,0 +1,4 @@
|
||||
class A {
|
||||
fun foo(s: String) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
class A {
|
||||
private fun foo(s: String) {
|
||||
}
|
||||
}
|
||||
@@ -41,11 +41,12 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
private void setUpEnvironment(boolean generateAssertions, File... extraClassPath) {
|
||||
private void setUpEnvironment(boolean generateAssertions, boolean generateParamAssertions, File... extraClassPath) {
|
||||
CompilerConfiguration configuration = CompileCompilerDependenciesTest.compilerConfigurationForTests(
|
||||
ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, extraClassPath);
|
||||
|
||||
configuration.put(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, generateAssertions);
|
||||
configuration.put(JVMConfigurationKeys.GENERATE_NOT_NULL_PARAMETER_ASSERTIONS, generateParamAssertions);
|
||||
|
||||
myEnvironment = new JetCoreEnvironment(getTestRootDisposable(), configuration);
|
||||
}
|
||||
@@ -53,7 +54,7 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
|
||||
private void doTestGenerateAssertions(boolean generateAssertions, String ktFile) throws Exception {
|
||||
File javaClassesTempDirectory = compileJava("notNullAssertions/A.java");
|
||||
|
||||
setUpEnvironment(generateAssertions, javaClassesTempDirectory);
|
||||
setUpEnvironment(generateAssertions, false, javaClassesTempDirectory);
|
||||
|
||||
blackBoxMultiFile("OK", false, "notNullAssertions/AssertionChecker.kt", ktFile);
|
||||
}
|
||||
@@ -67,7 +68,7 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
|
||||
}
|
||||
|
||||
public void testNoAssertionsForKotlinFromSource() throws Exception {
|
||||
setUpEnvironment(true);
|
||||
setUpEnvironment(true, false);
|
||||
|
||||
loadFiles("notNullAssertions/noAssertionsForKotlin.kt", "notNullAssertions/noAssertionsForKotlinMain.kt");
|
||||
|
||||
@@ -83,13 +84,37 @@ public class GenerateNotNullAssertionsTest extends CodegenTestCase {
|
||||
File compiledDirectory = new File(FileUtil.getTempDirectory(), "kotlin-classes");
|
||||
CompileEnvironmentUtil.writeToOutputDirectory(state.getFactory(), compiledDirectory);
|
||||
|
||||
setUpEnvironment(true, compiledDirectory);
|
||||
setUpEnvironment(true, false, compiledDirectory);
|
||||
|
||||
loadFile("notNullAssertions/noAssertionsForKotlinMain.kt");
|
||||
|
||||
assertNoIntrinsicsMethodIsCalled("namespace");
|
||||
}
|
||||
|
||||
public void testGenerateParamAssertions() throws Exception {
|
||||
File javaClassesTempDirectory = compileJava("notNullAssertions/doGenerateParamAssertions.java");
|
||||
|
||||
setUpEnvironment(false, true, javaClassesTempDirectory);
|
||||
|
||||
blackBoxFile("notNullAssertions/doGenerateParamAssertions.kt");
|
||||
}
|
||||
|
||||
public void testDoNotGenerateParamAssertions() throws Exception {
|
||||
setUpEnvironment(false, false);
|
||||
|
||||
loadFile("notNullAssertions/doNotGenerateParamAssertions.kt");
|
||||
|
||||
assertNoIntrinsicsMethodIsCalled("A");
|
||||
}
|
||||
|
||||
public void testNoParamAssertionForPrivateMethod() throws Exception {
|
||||
setUpEnvironment(false, true);
|
||||
|
||||
loadFile("notNullAssertions/noAssertionForPrivateMethod.kt");
|
||||
|
||||
assertNoIntrinsicsMethodIsCalled("A");
|
||||
}
|
||||
|
||||
private void assertNoIntrinsicsMethodIsCalled(String className) {
|
||||
ClassFileFactory classes = generateClassesInFile();
|
||||
ClassReader reader = new ClassReader(classes.asBytes(className + ".class"));
|
||||
|
||||
@@ -38,8 +38,6 @@ public class StringsTest extends CodegenTestCase {
|
||||
// System.out.println(generateToText());
|
||||
Method foo = generateFunction();
|
||||
assertEquals("something", foo.invoke(null, "something"));
|
||||
assertEquals("null", foo.invoke(null, new Object[]{null}));
|
||||
|
||||
}
|
||||
|
||||
public void testNullableAnyToString () throws InvocationTargetException, IllegalAccessException {
|
||||
@@ -71,9 +69,7 @@ public class StringsTest extends CodegenTestCase {
|
||||
// System.out.println(text);
|
||||
Method foo = generateFunction();
|
||||
assertEquals("something239120", foo.invoke(null, "something", 239));
|
||||
assertEquals("null239120", foo.invoke(null, null, 239));
|
||||
assertEquals("239null120", foo.invoke(null, "239", null));
|
||||
assertEquals("nullnull120", foo.invoke(null, null, null));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,23 @@ public class Intrinsics {
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkParameterIsNotNull(Object value, String paramName) {
|
||||
if (value == null) {
|
||||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||
|
||||
// #0 is Thread.getStackTrace(), #1 is Intrinsics.checkParameterIsNotNull, #2 is our caller
|
||||
StackTraceElement caller = stackTraceElements[2];
|
||||
String className = caller.getClassName();
|
||||
String methodName = caller.getMethodName();
|
||||
|
||||
IllegalArgumentException exception =
|
||||
new IllegalArgumentException("Parameter specified as non-null contains null: " +
|
||||
"method " + className + "." + methodName +
|
||||
", parameter " + paramName);
|
||||
throw sanitizeStackTrace(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Class<T> getJavaClass(T self) {
|
||||
return (Class<T>) self.getClass();
|
||||
}
|
||||
@@ -74,7 +91,7 @@ public class Intrinsics {
|
||||
}
|
||||
|
||||
private static final Set<String> METHOD_NAMES_TO_SKIP = new HashSet<String>(Arrays.asList(
|
||||
"throwNpe", "checkReturnedValueIsNotNull", "checkFieldIsNotNull"
|
||||
"throwNpe", "checkReturnedValueIsNotNull", "checkFieldIsNotNull", "checkParameterIsNotNull"
|
||||
));
|
||||
|
||||
private static <T extends Throwable> T sanitizeStackTrace(T throwable) {
|
||||
|
||||
Reference in New Issue
Block a user