diff --git a/compiler/android-tests/tests/org/jetbrains/jet/compiler/android/SpecialFiles.java b/compiler/android-tests/tests/org/jetbrains/jet/compiler/android/SpecialFiles.java index 1182de82c29..b9c6acb45e4 100644 --- a/compiler/android-tests/tests/org/jetbrains/jet/compiler/android/SpecialFiles.java +++ b/compiler/android-tests/tests/org/jetbrains/jet/compiler/android/SpecialFiles.java @@ -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() { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/AsmUtil.java b/compiler/backend/src/org/jetbrains/jet/codegen/AsmUtil.java index eae5c2cde51..434604b0436 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/AsmUtil.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/AsmUtil.java @@ -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"); + } + } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/CallableMethod.java b/compiler/backend/src/org/jetbrains/jet/codegen/CallableMethod.java index 738fd755069..f63244c7eff 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/CallableMethod.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/CallableMethod.java @@ -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; } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java index a2b9086a0e4..f99c05d6573 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ExpressionCodegen.java @@ -1863,10 +1863,10 @@ public class ExpressionCodegen extends JetVisitor 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 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 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 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 implem } CallableMethod callableMethod = (CallableMethod) callable; - callableMethod.invoke(v); + callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall); + value.store(callableMethod.getReturnType(), v); return StackValue.onStack(type); } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index bf2b8fa5d2c..1a604e86434 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -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()); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/StackValue.java b/compiler/backend/src/org/jetbrains/jet/codegen/StackValue.java index 6e0307c9145..22bd220363b 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/StackValue.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/StackValue.java @@ -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); diff --git a/compiler/testData/codegen/notNullAssertions/A.java b/compiler/testData/codegen/notNullAssertions/A.java new file mode 100644 index 00000000000..4d21176cfac --- /dev/null +++ b/compiler/testData/codegen/notNullAssertions/A.java @@ -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; + } +} diff --git a/compiler/testData/codegen/notNullAssertions/AssertionChecker.kt b/compiler/testData/codegen/notNullAssertions/AssertionChecker.kt new file mode 100644 index 00000000000..2cb57cc50f3 --- /dev/null +++ b/compiler/testData/codegen/notNullAssertions/AssertionChecker.kt @@ -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.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 } +} diff --git a/compiler/testData/codegen/notNullAssertions/doGenerateAssertions.kt b/compiler/testData/codegen/notNullAssertions/doGenerateAssertions.kt new file mode 100644 index 00000000000..d00d6be30e7 --- /dev/null +++ b/compiler/testData/codegen/notNullAssertions/doGenerateAssertions.kt @@ -0,0 +1,4 @@ +fun box(): String { + checkAssertions(true) + return "OK" +} diff --git a/compiler/testData/codegen/notNullAssertions/doNotGenerateAssertions.kt b/compiler/testData/codegen/notNullAssertions/doNotGenerateAssertions.kt new file mode 100644 index 00000000000..078f4285452 --- /dev/null +++ b/compiler/testData/codegen/notNullAssertions/doNotGenerateAssertions.kt @@ -0,0 +1,4 @@ +fun box(): String { + checkAssertions(false) + return "OK" +} diff --git a/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java b/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java index c75fccfe81e..0c611760842 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java +++ b/compiler/tests/org/jetbrains/jet/codegen/CodegenTestCase.java @@ -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; } diff --git a/compiler/tests/org/jetbrains/jet/codegen/GenerateNotNullAssertionsTest.java b/compiler/tests/org/jetbrains/jet/codegen/GenerateNotNullAssertionsTest.java new file mode 100644 index 00000000000..54d459cb29d --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/codegen/GenerateNotNullAssertionsTest.java @@ -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"); + } +} diff --git a/runtime/src/jet/runtime/Intrinsics.java b/runtime/src/jet/runtime/Intrinsics.java index e80589159a8..8db661d85ef 100644 --- a/runtime/src/jet/runtime/Intrinsics.java +++ b/runtime/src/jet/runtime/Intrinsics.java @@ -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 Class getJavaClass(T self) { return (Class) self.getClass(); } @@ -59,7 +65,11 @@ public class Intrinsics { } } - private static Throwable sanitizeStackTrace(Throwable throwable) { + private static final Set METHOD_NAMES_TO_SKIP = new HashSet(Arrays.asList( + "throwNpe", "checkReturnedValueIsNotNull" + )); + + private static T sanitizeStackTrace(T throwable) { StackTraceElement[] stackTrace = throwable.getStackTrace(); ArrayList list = new ArrayList(); 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; + } } } }