Generate not-null assertions after method calls
If a method comes from Java and is annotated as returning NotNull, after calling it we should check if it actually returned something other than null. Introduce checkReturnedValueIsNotNull() in jet/runtime/Intrinsics which does exactly that. CallableMethod's invoke() and invokeDefault() are now private, use asserted versions instead
This commit is contained in:
@@ -126,6 +126,9 @@ public class SpecialFiles {
|
||||
excludedFiles.add("kt529.kt"); // Bug
|
||||
|
||||
excludedFiles.add("noClassObjectForJavaClass.kt");
|
||||
|
||||
excludedFiles.add("doGenerateAssertions.kt"); // Multi-file + Java
|
||||
excludedFiles.add("doNotGenerateAssertions.kt"); // Multi-file + Java
|
||||
}
|
||||
|
||||
private SpecialFiles() {
|
||||
|
||||
@@ -27,12 +27,14 @@ import org.jetbrains.asm4.MethodVisitor;
|
||||
import org.jetbrains.asm4.Type;
|
||||
import org.jetbrains.asm4.commons.InstructionAdapter;
|
||||
import org.jetbrains.jet.codegen.binding.CalculatedClosure;
|
||||
import org.jetbrains.jet.codegen.state.GenerationState;
|
||||
import org.jetbrains.jet.codegen.state.JetTypeMapper;
|
||||
import org.jetbrains.jet.lang.descriptors.*;
|
||||
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
|
||||
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver;
|
||||
import org.jetbrains.jet.lang.resolve.java.JvmAbi;
|
||||
import org.jetbrains.jet.lang.resolve.java.JvmPrimitiveType;
|
||||
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
|
||||
import org.jetbrains.jet.lang.resolve.calls.ResolvedCall;
|
||||
import org.jetbrains.jet.lang.resolve.java.*;
|
||||
import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe;
|
||||
import org.jetbrains.jet.lang.types.JetType;
|
||||
import org.jetbrains.jet.lang.types.lang.JetStandardLibrary;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
|
||||
@@ -417,4 +419,35 @@ public class AsmUtil {
|
||||
public static void genStubCode(MethodVisitor mv) {
|
||||
genMethodThrow(mv, STUB_EXCEPTION, STUB_EXCEPTION_MESSAGE);
|
||||
}
|
||||
|
||||
public static void genNotNullAssertionForMethod(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
@NotNull ResolvedCall resolvedCall
|
||||
) {
|
||||
CallableDescriptor descriptor = resolvedCall.getResultingDescriptor();
|
||||
if (descriptor instanceof ConstructorDescriptor) return;
|
||||
|
||||
genNotNullAssertion(v, state, descriptor, "checkReturnedValueIsNotNull");
|
||||
}
|
||||
|
||||
private static void genNotNullAssertion(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
@NotNull CallableDescriptor descriptor,
|
||||
@NotNull String assertMethodToCall
|
||||
) {
|
||||
if (!state.isGenerateNotNullAssertions()) return;
|
||||
|
||||
JetType type = descriptor.getReturnType();
|
||||
if (type == null || type.isNullable()) return;
|
||||
|
||||
Type asmType = state.getTypeMapper().mapReturnType(type);
|
||||
if (asmType.getSort() == Type.OBJECT || asmType.getSort() == Type.ARRAY) {
|
||||
v.dup();
|
||||
v.visitLdcInsn(descriptor.getContainingDeclaration().getName().getName());
|
||||
v.visitLdcInsn(descriptor.getName().getName());
|
||||
v.invokestatic("jet/runtime/Intrinsics", assertMethodToCall, "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.asm4.Type;
|
||||
import org.jetbrains.asm4.commons.InstructionAdapter;
|
||||
import org.jetbrains.jet.codegen.signature.JvmMethodSignature;
|
||||
import org.jetbrains.jet.codegen.state.GenerationState;
|
||||
import org.jetbrains.jet.lang.resolve.calls.ResolvedCall;
|
||||
import org.jetbrains.jet.lang.resolve.java.JvmAbi;
|
||||
import org.jetbrains.jet.lang.resolve.java.JvmClassName;
|
||||
|
||||
@@ -96,17 +98,26 @@ public class CallableMethod implements Callable {
|
||||
return receiverParameterType;
|
||||
}
|
||||
|
||||
void invoke(InstructionAdapter v) {
|
||||
private void invoke(InstructionAdapter v) {
|
||||
v.visitMethodInsn(getInvokeOpcode(), owner.getInternalName(), getSignature().getAsmMethod().getName(),
|
||||
getSignature().getAsmMethod().getDescriptor());
|
||||
}
|
||||
|
||||
public void invokeWithNotNullAssertion(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
@NotNull ResolvedCall resolvedCall
|
||||
) {
|
||||
invoke(v);
|
||||
AsmUtil.genNotNullAssertionForMethod(v, state, resolvedCall);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Type getGenerateCalleeType() {
|
||||
return generateCalleeType;
|
||||
}
|
||||
|
||||
public void invokeWithDefault(InstructionAdapter v, int mask) {
|
||||
private void invokeDefault(InstructionAdapter v, int mask) {
|
||||
if (defaultImplOwner == null || defaultImplParam == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
@@ -125,6 +136,16 @@ public class CallableMethod implements Callable {
|
||||
}
|
||||
}
|
||||
|
||||
public void invokeDefaultWithNotNullAssertion(
|
||||
@NotNull InstructionAdapter v,
|
||||
@NotNull GenerationState state,
|
||||
@NotNull ResolvedCall resolvedCall,
|
||||
int mask
|
||||
) {
|
||||
invokeDefault(v, mask);
|
||||
AsmUtil.genNotNullAssertionForMethod(v, state, resolvedCall);
|
||||
}
|
||||
|
||||
public boolean isNeedsThis() {
|
||||
return thisClass != null && generateCalleeType == null;
|
||||
}
|
||||
|
||||
@@ -1863,10 +1863,10 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
|
||||
int mask = pushMethodArguments(resolvedCall, callableMethod.getValueParameterTypes());
|
||||
if (mask == 0) {
|
||||
callableMethod.invoke(v);
|
||||
callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
|
||||
}
|
||||
else {
|
||||
callableMethod.invokeWithDefault(v, mask);
|
||||
callableMethod.invokeDefaultWithNotNullAssertion(v, state, resolvedCall, mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2499,7 +2499,7 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
}
|
||||
|
||||
pushMethodArguments(resolvedCall, callable.getValueParameterTypes());
|
||||
callable.invoke(v);
|
||||
callable.invokeWithNotNullAssertion(v, state, resolvedCall);
|
||||
if (keepReturnValue) {
|
||||
value.store(callable.getReturnType(), v);
|
||||
}
|
||||
@@ -2570,7 +2570,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
|
||||
Type type = expressionType(expression.getBaseExpression());
|
||||
value.put(type, v);
|
||||
callableMethod.invoke(v);
|
||||
callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
|
||||
|
||||
value.store(callableMethod.getReturnType(), v);
|
||||
value.put(type, v);
|
||||
return StackValue.onStack(type);
|
||||
@@ -2588,7 +2589,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
assert resolvedCall != null;
|
||||
genThisAndReceiverFromResolvedCall(StackValue.none(), resolvedCall, callable);
|
||||
pushMethodArguments(resolvedCall, callable.getValueParameterTypes());
|
||||
callable.invoke(v);
|
||||
callable.invokeWithNotNullAssertion(v, state, resolvedCall);
|
||||
|
||||
return returnValueAsStackValue(op, callable.getSignature().getAsmMethod().getReturnType());
|
||||
}
|
||||
|
||||
@@ -2676,7 +2678,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
|
||||
}
|
||||
|
||||
CallableMethod callableMethod = (CallableMethod) callable;
|
||||
callableMethod.invoke(v);
|
||||
callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
|
||||
|
||||
value.store(callableMethod.getReturnType(), v);
|
||||
return StackValue.onStack(type);
|
||||
}
|
||||
|
||||
@@ -1341,7 +1341,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
|
||||
iv.load(nextVar, t);
|
||||
nextVar += t.getSize();
|
||||
}
|
||||
superCallable.invoke(codegen.v);
|
||||
superCallable.invokeWithNotNullAssertion(codegen.v, state, resolvedCall);
|
||||
}
|
||||
else {
|
||||
codegen.invokeMethodWithArguments(superCallable, (JetCallElement) superCall, StackValue.none());
|
||||
|
||||
@@ -39,8 +39,7 @@ import org.jetbrains.jet.lexer.JetTokens;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.asm4.Opcodes.*;
|
||||
import static org.jetbrains.jet.codegen.AsmUtil.boxType;
|
||||
import static org.jetbrains.jet.codegen.AsmUtil.isIntPrimitive;
|
||||
import static org.jetbrains.jet.codegen.AsmUtil.*;
|
||||
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.*;
|
||||
|
||||
/**
|
||||
@@ -678,7 +677,7 @@ public abstract class StackValue {
|
||||
throw new UnsupportedOperationException("no getter specified");
|
||||
}
|
||||
if (getter instanceof CallableMethod) {
|
||||
((CallableMethod) getter).invoke(v);
|
||||
((CallableMethod) getter).invokeWithNotNullAssertion(v, state, resolvedGetCall);
|
||||
}
|
||||
else {
|
||||
((IntrinsicMethod) getter).generate(codegen, v, type, null, null, null, state);
|
||||
@@ -696,7 +695,7 @@ public abstract class StackValue {
|
||||
Method asmMethod = method.getSignature().getAsmMethod();
|
||||
Type[] argumentTypes = asmMethod.getArgumentTypes();
|
||||
coerce(topOfStackType, argumentTypes[argumentTypes.length - 1], v);
|
||||
method.invoke(v);
|
||||
method.invokeWithNotNullAssertion(v, state, resolvedSetCall);
|
||||
Type returnType = asmMethod.getReturnType();
|
||||
if (returnType != Type.VOID_TYPE) {
|
||||
pop(returnType, v);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import jet.runtime.typeinfo.KotlinSignature;
|
||||
|
||||
public class A {
|
||||
@KotlinSignature("fun foo() : String")
|
||||
public String foo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@KotlinSignature("fun staticFoo() : String")
|
||||
public static String staticFoo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@KotlinSignature("fun plus(a: A) : A")
|
||||
public A plus(A a) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@KotlinSignature("fun inc() : A")
|
||||
public A inc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@KotlinSignature("fun get(o: Any) : Any")
|
||||
public Object get(Object o) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
class AssertionChecker(val illegalStateExpected: Boolean) {
|
||||
fun invoke(name: String, f: () -> Unit) {
|
||||
try {
|
||||
f()
|
||||
} catch (e: IllegalStateException) {
|
||||
if (!illegalStateExpected) throw AssertionError("Unexpected IllegalStateException on calling $name")
|
||||
return
|
||||
}
|
||||
if (illegalStateExpected) throw AssertionError("IllegalStateException expected on calling $name")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
trait Tr {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
class Derived : A(), Tr {
|
||||
override fun foo() = super<A>.foo()
|
||||
}
|
||||
|
||||
class Delegated : Tr by Derived() {
|
||||
}
|
||||
|
||||
|
||||
fun checkAssertions(val illegalStateExpected: Boolean) {
|
||||
val check = AssertionChecker(illegalStateExpected)
|
||||
|
||||
// simple call
|
||||
check("foo") { A().foo() }
|
||||
|
||||
// simple static call
|
||||
check("staticFoo") { A.staticFoo() }
|
||||
|
||||
// supercall
|
||||
check("foo") { Derived().foo() }
|
||||
|
||||
// delegated call
|
||||
check("foo") { Delegated().foo() }
|
||||
|
||||
// collection element
|
||||
check("get") { A()[""] }
|
||||
|
||||
// binary expression
|
||||
check("plus") { A() + A() }
|
||||
|
||||
// postfix expression
|
||||
check("inc") { var a = A(); a++ }
|
||||
|
||||
// prefix expression
|
||||
check("inc") { var a = A(); ++a }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun box(): String {
|
||||
checkAssertions(true)
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun box(): String {
|
||||
checkAssertions(false)
|
||||
return "OK"
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import org.jetbrains.jet.lang.resolve.ScriptNameUtil;
|
||||
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
|
||||
import org.jetbrains.jet.parsing.JetParsingTest;
|
||||
import org.jetbrains.jet.utils.ExceptionUtils;
|
||||
import org.jetbrains.jet.utils.Progress;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -356,7 +357,11 @@ public abstract class CodegenTestCase extends UsefulTestCase {
|
||||
BuiltinsScopeExtensionMode.ALL);
|
||||
analyzeExhaust.throwIfError();
|
||||
AnalyzingUtils.throwExceptionOnErrors(analyzeExhaust.getBindingContext());
|
||||
alreadyGenerated = new GenerationState(myEnvironment.getProject(), classBuilderFactory, analyzeExhaust, myFiles.getPsiFiles());
|
||||
alreadyGenerated = new GenerationState(
|
||||
myEnvironment.getProject(), classBuilderFactory, Progress.DEAF, analyzeExhaust, myFiles.getPsiFiles(),
|
||||
myEnvironment.getConfiguration().get(JVMConfigurationKeys.BUILTIN_TO_JAVA_TYPES_MAPPING_KEY, BuiltinToJavaTypesMapping.ENABLED),
|
||||
myEnvironment.getConfiguration().get(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, true)
|
||||
);
|
||||
GenerationStrategy.STANDARD.compileCorrectFiles(alreadyGenerated, CompilationErrorHandler.THROW_EXCEPTION);
|
||||
return alreadyGenerated;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.jet.codegen;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.CompileCompilerDependenciesTest;
|
||||
import org.jetbrains.jet.ConfigurationKind;
|
||||
import org.jetbrains.jet.JetTestUtils;
|
||||
import org.jetbrains.jet.TestJdkKind;
|
||||
import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
|
||||
import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment;
|
||||
import org.jetbrains.jet.config.CompilerConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author udalov
|
||||
*/
|
||||
public class GenerateNotNullAssertionsTest extends CodegenTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
private void setUpEnvironment(boolean generateAssertions, File... extraClassPath) {
|
||||
CompilerConfiguration configuration = CompileCompilerDependenciesTest.compilerConfigurationForTests(
|
||||
ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, extraClassPath);
|
||||
|
||||
configuration.put(JVMConfigurationKeys.GENERATE_NOT_NULL_ASSERTIONS, generateAssertions);
|
||||
|
||||
myEnvironment = new JetCoreEnvironment(getTestRootDisposable(), configuration);
|
||||
}
|
||||
|
||||
private void doTestGenerateAssertions(boolean generateAssertions, String ktFile) throws Exception {
|
||||
File javaClassesTempDirectory = compileJava("notNullAssertions/A.java");
|
||||
|
||||
setUpEnvironment(generateAssertions, javaClassesTempDirectory);
|
||||
|
||||
blackBoxMultiFile("OK", false, "notNullAssertions/AssertionChecker.kt", ktFile);
|
||||
}
|
||||
|
||||
public void testGenerateAssertions() throws Exception {
|
||||
doTestGenerateAssertions(true, "notNullAssertions/doGenerateAssertions.kt");
|
||||
}
|
||||
|
||||
public void testDoNotGenerateAssertions() throws Exception {
|
||||
doTestGenerateAssertions(false, "notNullAssertions/doNotGenerateAssertions.kt");
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ package jet.runtime;
|
||||
|
||||
import jet.Function0;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author alex.tkachman
|
||||
@@ -36,7 +34,15 @@ public class Intrinsics {
|
||||
public static void throwNpe() {
|
||||
throw new JetNullPointerException();
|
||||
}
|
||||
|
||||
|
||||
public static void checkReturnedValueIsNotNull(Object value, String className, String methodName) {
|
||||
if (value == null) {
|
||||
IllegalStateException exception =
|
||||
new IllegalStateException("Method specified as non-null returned null: " + className + "." + methodName);
|
||||
throw sanitizeStackTrace(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Class<T> getJavaClass(T self) {
|
||||
return (Class<T>) self.getClass();
|
||||
}
|
||||
@@ -59,7 +65,11 @@ public class Intrinsics {
|
||||
}
|
||||
}
|
||||
|
||||
private static Throwable sanitizeStackTrace(Throwable throwable) {
|
||||
private static final Set<String> METHOD_NAMES_TO_SKIP = new HashSet<String>(Arrays.asList(
|
||||
"throwNpe", "checkReturnedValueIsNotNull"
|
||||
));
|
||||
|
||||
private static <T extends Throwable> T sanitizeStackTrace(T throwable) {
|
||||
StackTraceElement[] stackTrace = throwable.getStackTrace();
|
||||
ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
|
||||
boolean skip = true;
|
||||
@@ -68,8 +78,10 @@ public class Intrinsics {
|
||||
list.add(ste);
|
||||
}
|
||||
else {
|
||||
if("jet.runtime.Intrinsics".equals(ste.getClassName()) && "throwNpe".equals(ste.getMethodName())) {
|
||||
skip = false;
|
||||
if ("jet.runtime.Intrinsics".equals(ste.getClassName())) {
|
||||
if (METHOD_NAMES_TO_SKIP.contains(ste.getMethodName())) {
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user