Fix for KT-16614: Report inability to inline 1.8 bytecode into 1.6 bytecode as an error, no as an exception

This commit is contained in:
Mikhael Bogdanov
2017-03-03 12:44:13 +01:00
parent 5e4459f41d
commit ff9fe85507
15 changed files with 255 additions and 40 deletions
@@ -66,7 +66,6 @@ public class AnonymousObjectTransformer extends ObjectTransformer<AnonymousObjec
createClassReader().accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
InlineCodegenUtil.assertVersionNotGreaterThanGeneratedOne(version, name, inliningContext.state);
classBuilder.defineClass(null, version, access, name, signature, superName, interfaces);
if(CoroutineCodegenUtilKt.COROUTINE_IMPL_ASM_TYPE.getInternalName().equals(superName)) {
inliningContext.setContinuation(true);
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
@@ -202,7 +203,7 @@ public class InlineCodegen extends CallGenerator {
MethodNode node = nodeAndSmap != null ? nodeAndSmap.getNode() : null;
throw new CompilationException(
"Couldn't inline method call '" + functionDescriptor.getName() + "' into\n" +
contextDescriptor + "\n" +
DescriptorRenderer.DEBUG_TEXT.render(contextDescriptor) + "\n" +
(element != null ? element.getText() : "<no source>") +
(generateNodeText ? ("\nCause: " + InlineCodegenUtil.getNodeText(node)) : ""),
e, callElement
@@ -321,7 +322,7 @@ public class InlineCodegen extends CallGenerator {
}
});
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), classId, state);
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), classId);
}
assert callableDescriptor instanceof DeserializedCallableMemberDescriptor : "Not a deserialized function or proper: " + callableDescriptor;
@@ -348,7 +349,7 @@ public class InlineCodegen extends CallGenerator {
});
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId, state);
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId);
}
@NotNull
@@ -92,8 +92,7 @@ public class InlineCodegenUtil {
byte[] classData,
final String methodName,
final String methodDescriptor,
ClassId classId,
final @NotNull GenerationState state
ClassId classId
) {
ClassReader cr = new ClassReader(classData);
final MethodNode[] node = new MethodNode[1];
@@ -103,10 +102,6 @@ public class InlineCodegenUtil {
lines[1] = Integer.MIN_VALUE;
//noinspection PointlessBitwiseExpression
cr.accept(new ClassVisitor(API) {
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
assertVersionNotGreaterThanGeneratedOne(version, name, state);
}
@Override
public void visitSource(String source, String debug) {
@@ -151,16 +146,6 @@ public class InlineCodegenUtil {
return new SMAPAndMethodNode(node[0], smap);
}
public static void assertVersionNotGreaterThanGeneratedOne(int version, String internalName, @NotNull GenerationState state) {
// TODO: report a proper diagnostic
if (version > state.getClassFileVersion() && !"true".equals(System.getProperty("kotlin.skip.bytecode.version.check"))) {
throw new UnsupportedOperationException(
"Cannot inline bytecode of class " + internalName + " which has version " + version + ". " +
"This compiler can only inline Java 1.6 bytecode (version " + Opcodes.V1_6 + ")"
);
}
}
public static void initDefaultSourceMappingIfNeeded(
@NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state
) {
@@ -66,7 +66,6 @@ class WhenMappingTransformer(
val fieldNode = transformationInfo.fieldNode
classReader.accept(object : ClassVisitor(InlineCodegenUtil.API, classBuilder.visitor) {
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<String>) {
InlineCodegenUtil.assertVersionNotGreaterThanGeneratedOne(version, name, state)
classBuilder.defineClass(null, version, access, name, signature, superName, interfaces)
}
@@ -123,7 +123,8 @@ class GenerationState @JvmOverloads constructor(
extraJvmDiagnosticsTrace.bindingContext.diagnostics
}
val isJvm8Target: Boolean = configuration.get(JVMConfigurationKeys.JVM_TARGET) == JvmTarget.JVM_1_8
val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) ?: JvmTarget.DEFAULT
val isJvm8Target: Boolean = target == JvmTarget.JVM_1_8
val isJvm8TargetWithDefaults: Boolean = isJvm8Target && configuration.getBoolean(JVMConfigurationKeys.JVM8_TARGET_WITH_DEFAULTS)
val generateDefaultImplsForJvm8: Boolean = configuration.getBoolean(JVMConfigurationKeys.INTERFACE_COMPATIBILITY)
@@ -162,7 +163,7 @@ class GenerationState @JvmOverloads constructor(
val rootContext: CodegenContext<*> = RootContext(this)
val classFileVersion: Int = if (isJvm8Target) Opcodes.V1_8 else Opcodes.V1_6
val classFileVersion: Int = target.bytecodeVersion
val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA)
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2017 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.kotlin.resolve.jvm.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor
class InlinePlatformCompatibilityChecker: CallChecker {
private val doCheck = doCheck()
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
if (!doCheck) return
val resultingDescriptor = resolvedCall.resultingDescriptor as? CallableMemberDescriptor ?: return
if (!InlineUtil.isInline(resultingDescriptor)) {
if (resultingDescriptor is PropertyDescriptor && InlineUtil.isInline(resultingDescriptor.getter)) {
//TODO: we should distinguish setter usage from getter one, now we could report wrong diagnostic on non-inline setter
//var prop: Int
// inline get
// set
//
// prop - resolved call with property descriptor and we should report error
// prop = 1 - resolved call with setter for whole expression and property descriptor for left part,
// so we couldn't distinguish is this expression for setter or for getter and will report wrong diagnostic
}
else {
return
}
}
val propertyOrFun = DescriptorUtils.getDirectMember(resultingDescriptor)
val inliningBytecodeVersion = getBytecodeVersionIfDeserializedDescriptor(propertyOrFun) ?: return
val compilingTarget = context.compilerConfiguration[JVMConfigurationKeys.JVM_TARGET] ?: JvmTarget.DEFAULT
val compilingBytecodeVersion = compilingTarget.bytecodeVersion
if (compilingBytecodeVersion < inliningBytecodeVersion) {
context.trace.report(ErrorsJvm.INLINE_FROM_HIGHER_PLATFORM.on(reportOn, JvmTarget.getDescription(inliningBytecodeVersion), JvmTarget.getDescription(compilingBytecodeVersion)))
}
}
companion object {
fun doCheck() = "true" != System.getProperty("kotlin.skip.bytecode.version.check")
fun getBytecodeVersionIfDeserializedDescriptor(funOrProperty: DeclarationDescriptor): Int? {
if (funOrProperty !is DeserializedCallableMemberDescriptor) return null
val containingDeclaration = funOrProperty.containingDeclaration as ClassOrPackageFragmentDescriptor
val source = containingDeclaration.source
val binaryClass =
when (source) {
is KotlinJvmBinarySourceElement -> source.binaryClass
is KotlinJvmBinaryPackageSourceElement -> source.getContainingBinaryClass(funOrProperty)
else -> null
} as? FileBasedKotlinClass ?: return null
return binaryClass.classVersion
}
}
}
@@ -115,6 +115,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(ErrorsJvm.DEFAULT_METHOD_CALL_FROM_JAVA6_TARGET, "Super calls to Java default methods are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'");
MAP.put(ErrorsJvm.INTERFACE_STATIC_METHOD_CALL_FROM_JAVA6_TARGET, "Calls to static methods in Java interfaces are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'");
MAP.put(ErrorsJvm.INLINE_FROM_HIGHER_PLATFORM, "Cannot inline bytecode built with {0} into bytecode that is being built with {1}. Please specify proper ''-jvm-target'' option", Renderers.TO_STRING, Renderers.TO_STRING);
}
@NotNull
@@ -92,6 +92,8 @@ public interface ErrorsJvm {
DiagnosticFactory0<PsiElement> DEFAULT_METHOD_CALL_FROM_JAVA6_TARGET = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> INTERFACE_STATIC_METHOD_CALL_FROM_JAVA6_TARGET = DiagnosticFactory0.create(WARNING);
DiagnosticFactory2<PsiElement, String, String> INLINE_FROM_HIGHER_PLATFORM = DiagnosticFactory2.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.resolve.PlatformConfigurator
import org.jetbrains.kotlin.resolve.calls.checkers.InlineCheckerWrapper
import org.jetbrains.kotlin.resolve.calls.checkers.ReifiedTypeParameterSubstitutionChecker
import org.jetbrains.kotlin.resolve.checkers.HeaderImplDeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.MissingDependencyClassChecker
@@ -55,7 +56,8 @@ object JvmPlatformConfigurator : PlatformConfigurator(
UnsupportedSyntheticCallableReferenceChecker(),
SuperCallWithDefaultArgumentsChecker(),
ProtectedSyntheticExtensionCallChecker,
ReifiedTypeParameterSubstitutionChecker()
ReifiedTypeParameterSubstitutionChecker(),
InlinePlatformCompatibilityChecker()
),
additionalTypeCheckers = listOf(
@@ -0,0 +1,39 @@
// JVM_TARGET: 1.8
package a
inline fun inlineFun(p: () -> Unit) {
p()
}
var inlineGetter: Int
inline get() = 1
set(varue) {}
var inlineSetter: Int
get() = 1
inline set(varue) {}
var allInline: Int
inline get() = 1
inline set(varue) {}
class A {
inline fun inlineFun(p: () -> Unit) {
p()
}
var inlineGetter: Int
inline get() = 1
set(varue) {}
var inlineSetter: Int
get() = 1
inline set(varue) {}
var allInline: Int
inline get() = 1
inline set(varue) {}
}
@@ -0,0 +1,37 @@
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:6:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
inlineFun {}
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:7:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
inlineGetter
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:8:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
inlineGetter = 1
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:11:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
inlineSetter = 1
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:13:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
allInline
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:14:5: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
allInline = 1
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:17:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.inlineFun {}
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:18:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.inlineGetter
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:19:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.inlineGetter = 1
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:22:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.inlineSetter = 1
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:24:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.allInline
^
compiler/testData/compileKotlinAgainstCustomBinaries/wrongInlineTarget/source.kt:25:7: error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option
a.allInline = 1
^
COMPILATION_ERROR
@@ -0,0 +1,28 @@
package usage
import a.*
fun baz() {
inlineFun {}
inlineGetter
inlineGetter = 1
inlineSetter
inlineSetter = 1
allInline
allInline = 1
val a = A()
a.inlineFun {}
a.inlineGetter
a.inlineGetter = 1
a.inlineSetter
a.inlineSetter = 1
a.allInline
a.allInline = 1
}
@@ -83,13 +83,10 @@ class ReplCompilerJava8Test : TestCase() {
val configuration = makeConfiguration().apply {
put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_6)
}
try {
runTest(configuration)
Assert.fail("Should fail due to bytecode incompatibility check")
}
catch (e: CompilationException) {
Assert.assertTrue(e.message!!.contains("This compiler can only inline Java 1.6 bytecode (version 50)"))
}
val result = runTest(configuration)
Assert.assertTrue(result is ReplCompileResult.Error)
Assert.assertTrue((result as ReplCompileResult.Error).message.contains("error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6"))
}
@Test
@@ -98,11 +95,9 @@ class ReplCompilerJava8Test : TestCase() {
val configuration = makeConfiguration()
System.setProperty(KOTLIN_REPL_JVM_TARGET_PROPERTY, "1.6")
try {
runTest(configuration)
Assert.fail("Should fail due to bytecode incompatibility check")
}
catch (e: CompilationException) {
Assert.assertTrue(e.message!!.contains("This compiler can only inline Java 1.6 bytecode (version 50)"))
val result = runTest(configuration)
Assert.assertTrue(result is ReplCompileResult.Error)
Assert.assertTrue((result as ReplCompileResult.Error).message.contains("error: cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6"))
}
finally {
System.clearProperty(KOTLIN_REPL_JVM_TARGET_PROPERTY)
@@ -89,15 +89,19 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
@NotNull
private File compileLibrary(@NotNull String sourcePath, @NotNull File... extraClassPath) {
return compileLibrary(sourcePath, Collections.<String>emptyList(), extraClassPath);
}
private File compileLibrary(@NotNull String sourcePath, List<String> additionalOptions, @NotNull File... extraClassPath) {
File destination = new File(tmpdir, sourcePath + ".jar");
compileLibrary(new K2JVMCompiler(), sourcePath, destination, extraClassPath);
compileLibrary(new K2JVMCompiler(), sourcePath, destination, additionalOptions, extraClassPath);
return destination;
}
private void compileLibrary(
@NotNull CLICompiler<?> compiler, @NotNull String sourcePath, @NotNull File destination, @NotNull File... extraClassPath
@NotNull CLICompiler<?> compiler, @NotNull String sourcePath, @NotNull File destination, List<String> additionalOptions, @NotNull File... extraClassPath
) {
Pair<String, ExitCode> output = compileKotlin(compiler, sourcePath, destination, Collections.<String>emptyList(), extraClassPath);
Pair<String, ExitCode> output = compileKotlin(compiler, sourcePath, destination, additionalOptions, extraClassPath);
Assert.assertEquals(normalizeOutput(new Pair<String, ExitCode>("", ExitCode.OK)), normalizeOutput(output));
}
@@ -324,7 +328,7 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
try {
System.setProperty(TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY, "true");
compileLibrary(compiler, libraryName, destination);
compileLibrary(compiler, libraryName, destination, Collections.<String>emptyList());
}
finally {
System.clearProperty(TEST_IS_PRE_RELEASE_SYSTEM_PROPERTY);
@@ -629,4 +633,14 @@ public class CompileKotlinAgainstCustomBinariesTest extends TestCaseWithTmpdir {
Pair<String, ExitCode> output = compileKotlin("source.kt", tmpdir, library1);
KotlinTestUtils.assertEqualsToFile(new File(getTestDataDirectory(), "output.txt"), normalizeOutput(output));
}
public void testWrongInlineTarget() throws Exception {
File library = compileLibrary("library", Arrays.asList("-jvm-target", "1.8"));
Pair<String, ExitCode> outputMain = compileKotlin("source.kt", tmpdir, library);
KotlinTestUtils.assertEqualsToFile(
new File(getTestDataDirectory(), "output.txt"), normalizeOutput(outputMain)
);
}
}
@@ -17,17 +17,36 @@
package org.jetbrains.kotlin.config
import org.jetbrains.kotlin.utils.DescriptionAware
import org.jetbrains.org.objectweb.asm.Opcodes
enum class JvmTarget(override val description: String) : DescriptionAware {
JVM_1_6("1.6"),
JVM_1_8("1.8"),
;
val bytecodeVersion: Int
get() = when(this) {
JVM_1_6 -> Opcodes.V1_6
JVM_1_8 -> Opcodes.V1_8
}
companion object {
@JvmField
val DEFAULT = JVM_1_6
@JvmStatic
fun fromString(string: String) = values().find { it.description == string }
fun getDescription(bytecodeVersion: Int): String {
val platformDescription = values().find { it.bytecodeVersion == bytecodeVersion }?.description ?:
when (bytecodeVersion) {
Opcodes.V1_7 -> "1.7"
Opcodes.V1_8 + 1 -> "1.9"
else -> null
}
return if (platformDescription != null) "JVM target $platformDescription"
else "JVM bytecode version $bytecodeVersion"
}
}
}