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:
Alexander Udalov
2012-10-01 20:15:59 +04:00
parent 1cf5e92981
commit 95ec2448eb
13 changed files with 252 additions and 25 deletions
@@ -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");
}
}
+19 -7
View File
@@ -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;
}
}
}
}