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("kt529.kt"); // Bug
excludedFiles.add("noClassObjectForJavaClass.kt"); excludedFiles.add("noClassObjectForJavaClass.kt");
excludedFiles.add("doGenerateAssertions.kt"); // Multi-file + Java
excludedFiles.add("doNotGenerateAssertions.kt"); // Multi-file + Java
} }
private SpecialFiles() { private SpecialFiles() {
@@ -27,12 +27,14 @@ import org.jetbrains.asm4.MethodVisitor;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.jet.codegen.binding.CalculatedClosure; 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.codegen.state.JetTypeMapper;
import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants; import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver; import org.jetbrains.jet.lang.resolve.calls.ResolvedCall;
import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.lang.resolve.java.*;
import org.jetbrains.jet.lang.resolve.java.JvmPrimitiveType; 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.lang.types.lang.JetStandardLibrary;
import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.lexer.JetTokens;
@@ -417,4 +419,35 @@ public class AsmUtil {
public static void genStubCode(MethodVisitor mv) { public static void genStubCode(MethodVisitor mv) {
genMethodThrow(mv, STUB_EXCEPTION, STUB_EXCEPTION_MESSAGE); 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.Type;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.jet.codegen.signature.JvmMethodSignature; 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.JvmAbi;
import org.jetbrains.jet.lang.resolve.java.JvmClassName; import org.jetbrains.jet.lang.resolve.java.JvmClassName;
@@ -96,17 +98,26 @@ public class CallableMethod implements Callable {
return receiverParameterType; return receiverParameterType;
} }
void invoke(InstructionAdapter v) { private void invoke(InstructionAdapter v) {
v.visitMethodInsn(getInvokeOpcode(), owner.getInternalName(), getSignature().getAsmMethod().getName(), v.visitMethodInsn(getInvokeOpcode(), owner.getInternalName(), getSignature().getAsmMethod().getName(),
getSignature().getAsmMethod().getDescriptor()); getSignature().getAsmMethod().getDescriptor());
} }
public void invokeWithNotNullAssertion(
@NotNull InstructionAdapter v,
@NotNull GenerationState state,
@NotNull ResolvedCall resolvedCall
) {
invoke(v);
AsmUtil.genNotNullAssertionForMethod(v, state, resolvedCall);
}
@Nullable @Nullable
public Type getGenerateCalleeType() { public Type getGenerateCalleeType() {
return generateCalleeType; return generateCalleeType;
} }
public void invokeWithDefault(InstructionAdapter v, int mask) { private void invokeDefault(InstructionAdapter v, int mask) {
if (defaultImplOwner == null || defaultImplParam == null) { if (defaultImplOwner == null || defaultImplParam == null) {
throw new IllegalStateException(); 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() { public boolean isNeedsThis() {
return thisClass != null && generateCalleeType == null; return thisClass != null && generateCalleeType == null;
} }
@@ -1863,10 +1863,10 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
int mask = pushMethodArguments(resolvedCall, callableMethod.getValueParameterTypes()); int mask = pushMethodArguments(resolvedCall, callableMethod.getValueParameterTypes());
if (mask == 0) { if (mask == 0) {
callableMethod.invoke(v); callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
} }
else { 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()); pushMethodArguments(resolvedCall, callable.getValueParameterTypes());
callable.invoke(v); callable.invokeWithNotNullAssertion(v, state, resolvedCall);
if (keepReturnValue) { if (keepReturnValue) {
value.store(callable.getReturnType(), v); value.store(callable.getReturnType(), v);
} }
@@ -2570,7 +2570,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
Type type = expressionType(expression.getBaseExpression()); Type type = expressionType(expression.getBaseExpression());
value.put(type, v); value.put(type, v);
callableMethod.invoke(v); callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
value.store(callableMethod.getReturnType(), v); value.store(callableMethod.getReturnType(), v);
value.put(type, v); value.put(type, v);
return StackValue.onStack(type); return StackValue.onStack(type);
@@ -2588,7 +2589,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
assert resolvedCall != null; assert resolvedCall != null;
genThisAndReceiverFromResolvedCall(StackValue.none(), resolvedCall, callable); genThisAndReceiverFromResolvedCall(StackValue.none(), resolvedCall, callable);
pushMethodArguments(resolvedCall, callable.getValueParameterTypes()); pushMethodArguments(resolvedCall, callable.getValueParameterTypes());
callable.invoke(v); callable.invokeWithNotNullAssertion(v, state, resolvedCall);
return returnValueAsStackValue(op, callable.getSignature().getAsmMethod().getReturnType()); return returnValueAsStackValue(op, callable.getSignature().getAsmMethod().getReturnType());
} }
@@ -2676,7 +2678,8 @@ public class ExpressionCodegen extends JetVisitor<StackValue, StackValue> implem
} }
CallableMethod callableMethod = (CallableMethod) callable; CallableMethod callableMethod = (CallableMethod) callable;
callableMethod.invoke(v); callableMethod.invokeWithNotNullAssertion(v, state, resolvedCall);
value.store(callableMethod.getReturnType(), v); value.store(callableMethod.getReturnType(), v);
return StackValue.onStack(type); return StackValue.onStack(type);
} }
@@ -1341,7 +1341,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
iv.load(nextVar, t); iv.load(nextVar, t);
nextVar += t.getSize(); nextVar += t.getSize();
} }
superCallable.invoke(codegen.v); superCallable.invokeWithNotNullAssertion(codegen.v, state, resolvedCall);
} }
else { else {
codegen.invokeMethodWithArguments(superCallable, (JetCallElement) superCall, StackValue.none()); codegen.invokeMethodWithArguments(superCallable, (JetCallElement) superCall, StackValue.none());
@@ -39,8 +39,7 @@ import org.jetbrains.jet.lexer.JetTokens;
import java.util.List; import java.util.List;
import static org.jetbrains.asm4.Opcodes.*; import static org.jetbrains.asm4.Opcodes.*;
import static org.jetbrains.jet.codegen.AsmUtil.boxType; import static org.jetbrains.jet.codegen.AsmUtil.*;
import static org.jetbrains.jet.codegen.AsmUtil.isIntPrimitive;
import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.*; import static org.jetbrains.jet.lang.resolve.java.AsmTypeConstants.*;
/** /**
@@ -678,7 +677,7 @@ public abstract class StackValue {
throw new UnsupportedOperationException("no getter specified"); throw new UnsupportedOperationException("no getter specified");
} }
if (getter instanceof CallableMethod) { if (getter instanceof CallableMethod) {
((CallableMethod) getter).invoke(v); ((CallableMethod) getter).invokeWithNotNullAssertion(v, state, resolvedGetCall);
} }
else { else {
((IntrinsicMethod) getter).generate(codegen, v, type, null, null, null, state); ((IntrinsicMethod) getter).generate(codegen, v, type, null, null, null, state);
@@ -696,7 +695,7 @@ public abstract class StackValue {
Method asmMethod = method.getSignature().getAsmMethod(); Method asmMethod = method.getSignature().getAsmMethod();
Type[] argumentTypes = asmMethod.getArgumentTypes(); Type[] argumentTypes = asmMethod.getArgumentTypes();
coerce(topOfStackType, argumentTypes[argumentTypes.length - 1], v); coerce(topOfStackType, argumentTypes[argumentTypes.length - 1], v);
method.invoke(v); method.invokeWithNotNullAssertion(v, state, resolvedSetCall);
Type returnType = asmMethod.getReturnType(); Type returnType = asmMethod.getReturnType();
if (returnType != Type.VOID_TYPE) { if (returnType != Type.VOID_TYPE) {
pop(returnType, v); 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.lang.resolve.java.AnalyzerFacadeForJVM;
import org.jetbrains.jet.parsing.JetParsingTest; import org.jetbrains.jet.parsing.JetParsingTest;
import org.jetbrains.jet.utils.ExceptionUtils; import org.jetbrains.jet.utils.ExceptionUtils;
import org.jetbrains.jet.utils.Progress;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -356,7 +357,11 @@ public abstract class CodegenTestCase extends UsefulTestCase {
BuiltinsScopeExtensionMode.ALL); BuiltinsScopeExtensionMode.ALL);
analyzeExhaust.throwIfError(); analyzeExhaust.throwIfError();
AnalyzingUtils.throwExceptionOnErrors(analyzeExhaust.getBindingContext()); 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); GenerationStrategy.STANDARD.compileCorrectFiles(alreadyGenerated, CompilationErrorHandler.THROW_EXCEPTION);
return alreadyGenerated; 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 -6
View File
@@ -18,9 +18,7 @@ package jet.runtime;
import jet.Function0; import jet.Function0;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Iterator;
/** /**
* @author alex.tkachman * @author alex.tkachman
@@ -37,6 +35,14 @@ public class Intrinsics {
throw new JetNullPointerException(); 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) { public static <T> Class<T> getJavaClass(T self) {
return (Class<T>) self.getClass(); 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(); StackTraceElement[] stackTrace = throwable.getStackTrace();
ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>(); ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>();
boolean skip = true; boolean skip = true;
@@ -68,8 +78,10 @@ public class Intrinsics {
list.add(ste); list.add(ste);
} }
else { else {
if("jet.runtime.Intrinsics".equals(ste.getClassName()) && "throwNpe".equals(ste.getMethodName())) { if ("jet.runtime.Intrinsics".equals(ste.getClassName())) {
skip = false; if (METHOD_NAMES_TO_SKIP.contains(ste.getMethodName())) {
skip = false;
}
} }
} }
} }