[JVM IR] Fix issue where fields are not being set to their default

values within initializer blocks.

The issue occurs in code like this:
```
class C {
  var b = true
  init {
    b = false   // Missing PUTFIELD for this statement
  }
}
```

Added a new statement origin for field initialization (at declaration)
instead of relying on `origin == null` in ExpressionCodegen to determine
whether to generate the initializations.

This was unintentionally broken in
d68a1898d0.
This commit is contained in:
Mark Punzalan
2020-02-14 13:19:59 -08:00
committed by Dmitry Petrov
parent 56c819f06e
commit 64141b8b38
17 changed files with 464 additions and 8 deletions
@@ -9,10 +9,7 @@ import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrInstanceInitializerCall
import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
@@ -79,7 +76,15 @@ abstract class InitializersLoweringBase(open val context: CommonBackendContext)
IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.type, irClass.thisReceiver!!.symbol)
else
null
IrSetFieldImpl(startOffset, endOffset, declaration.symbol, receiver, expression, context.irBuiltIns.unitType)
IrSetFieldImpl(
startOffset,
endOffset,
declaration.symbol,
receiver,
expression,
context.irBuiltIns.unitType,
IrStatementOrigin.INITIALIZE_FIELD
)
}
private fun handleAnonymousInitializer(declaration: IrAnonymousInitializer): IrStatement =
@@ -565,9 +565,7 @@ class ExpressionCodegen(
// Do not add redundant field initializers that initialize to default values.
val inPrimaryConstructor = irFunction is IrConstructor && irFunction.isPrimary
val inClassInit = irFunction.origin == JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER
// "expression.origin == null" means that the field is initialized when it is declared,
// i.e., not in an initializer block or constructor body.
val isFieldInitializer = expression.origin == null
val isFieldInitializer = expression.origin == IrStatementOrigin.INITIALIZE_FIELD
val skip = (inPrimaryConstructor || inClassInit) && isFieldInitializer && expressionValue is IrConst<*> &&
isDefaultValueForType(expression.symbol.owner.type.asmType, expressionValue.value)
return if (skip) defaultValue(expression.type) else super.visitSetField(expression, data)
@@ -91,6 +91,7 @@ interface IrStatementOrigin {
object OBJECT_LITERAL : IrStatementOriginImpl("OBJECT_LITERAL")
object INITIALIZE_PROPERTY_FROM_PARAMETER : IrStatementOriginImpl("INITIALIZE_PROPERTY_FROM_PARAMETER")
object INITIALIZE_FIELD : IrStatementOriginImpl("INITIALIZE_FIELD")
object PROPERTY_REFERENCE_FOR_DELEGATE : IrStatementOriginImpl("PROPERTY_REFERENCE_FOR_DELEGATE")
@@ -0,0 +1,34 @@
class C {
var myIntProp: Int = 1
var myByteProp: Byte = 2
var myLongProp: Long = 3L
var myShortProp: Short = 4
var myDoubleProp: Double = 5.6
var myFloatProp: Float = 7.8f
var myBooleanProp: Boolean = true
var myCharProp: Char = '9'
init {
myIntProp = 0
myByteProp = 0
myLongProp = 0L
myShortProp = 0
myDoubleProp = 0.0
myFloatProp = 0.0f
myBooleanProp = false
myCharProp = '\u0000'
}
}
fun box(): String {
val c = C()
if (c.myIntProp != 0) return "fail Int"
if (c.myByteProp != 0.toByte()) return "fail Byte"
if (c.myLongProp != 0L) return "fail Long"
if (c.myShortProp != 0.toShort()) return "fail Short"
if (c.myDoubleProp != 0.0) return "fail Double"
if (c.myFloatProp != 0.0f) return "fail Float"
if (c.myBooleanProp != false) return "fail Boolean"
if (c.myCharProp != '\u0000') return "fail Char"
return "OK"
}
@@ -0,0 +1,33 @@
object C {
var myIntProp: Int = 1
var myByteProp: Byte = 2
var myLongProp: Long = 3L
var myShortProp: Short = 4
var myDoubleProp: Double = 5.6
var myFloatProp: Float = 7.8f
var myBooleanProp: Boolean = true
var myCharProp: Char = '9'
init {
myIntProp = 0
myByteProp = 0
myLongProp = 0L
myShortProp = 0
myDoubleProp = 0.0
myFloatProp = 0.0f
myBooleanProp = false
myCharProp = '\u0000'
}
}
fun box(): String {
if (C.myIntProp != 0) return "fail Int"
if (C.myByteProp != 0.toByte()) return "fail Byte"
if (C.myLongProp != 0L) return "fail Long"
if (C.myShortProp != 0.toShort()) return "fail Short"
if (C.myDoubleProp != 0.0) return "fail Double"
if (C.myFloatProp != 0.0f) return "fail Float"
if (C.myBooleanProp != false) return "fail Boolean"
if (C.myCharProp != '\u0000') return "fail Char"
return "OK"
}
@@ -90,5 +90,8 @@ class NonRedundantInitializers {
val myFloat: Float = -0.0f
}
// There is 1 PUTSTATIC for the companion object instance.
// 0 PUTFIELD RedundantInitializersToDefault
// 2 PUTFIELD NonRedundantInitializers
// 1 PUTSTATIC
@@ -0,0 +1,95 @@
// No initializers for this class because the fields/properties are initialized to defaults.
object RedundantInitializersToDefault {
// Constants
const val constInt: Int = 0
const val constByte: Byte = 0
const val constLong: Long = 0L
const val constShort: Short = 0
const val constDouble: Double = 0.0
const val constFloat: Float = 0.0f
const val constBoolean: Boolean = false
const val constChar: Char = '\u0000'
// Properties
val myIntPropFromConst: Int = constInt
val myBytePropFromConst: Byte = constByte
val myLongPropFromConst: Long = constLong
val myShortPropFromConst: Short = constShort
val myDoublePropFromConst: Double = constDouble
val myFloatPropFromConst: Float = constFloat
val myBooleanPropFromConst: Boolean = constBoolean
val myCharPropFromConst: Char = constChar
val myIntProp: Int = 0
val myByteProp: Byte = 0
val myLongProp: Long = 0L
val myShortProp: Short = 0
val myDoubleProp: Double = 0.0
val myFloatProp: Float = 0.0f
val myBooleanProp: Boolean = false
val myCharProp: Char = '\u0000'
val myStringProp: String? = null
val myAnyProp: Any? = null
val myObjectProp: java.lang.Object? = null
val myIntegerProp: java.lang.Integer? = null
// Fields
@JvmField
val myIntFieldFromConst: Int = constInt
@JvmField
val myByteFieldFromConst: Byte = constByte
@JvmField
val myLongFieldFromConst: Long = constLong
@JvmField
val myShortFieldFromConst: Short = constShort
@JvmField
val myDoubleFieldFromConst: Double = constDouble
@JvmField
val myFloatFieldFromConst: Float = constFloat
@JvmField
val myBooleanFieldFromConst: Boolean = constBoolean
@JvmField
val myCharFieldFromConst: Char = constChar
@JvmField
val myIntField: Int = 0
@JvmField
val myByteField: Byte = 0
@JvmField
val myLongField: Long = 0L
@JvmField
val myShortField: Short = 0
@JvmField
val myDoubleField: Double = 0.0
@JvmField
val myFloatField: Float = 0.0f
@JvmField
val myBooleanField: Boolean = false
@JvmField
val myCharField: Char = '\u0000'
@JvmField
val myStringField: String? = null
@JvmField
val myAnyField: Any? = null
@JvmField
val myObjectField: java.lang.Object? = null
@JvmField
val myIntegerField: java.lang.Integer? = null
}
object NonRedundantInitializers {
// NOT redundant because the JVM's default values for floating-point types are positive 0.0.
// See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3
val myDouble: Double = -0.0
val myFloat: Float = -0.0f
}
// There is 1 additional PUTSTATIC for both classes for the object instance.
// 1 PUTSTATIC RedundantInitializersToDefault
// 3 PUTSTATIC NonRedundantInitializers
@@ -0,0 +1,99 @@
// No initializers for this class because the fields/properties are initialized to defaults.
class RedundantInitializersToDefault {
companion object {
// Constants
const val constInt: Int = 0
const val constByte: Byte = 0
const val constLong: Long = 0L
const val constShort: Short = 0
const val constDouble: Double = 0.0
const val constFloat: Float = 0.0f
const val constBoolean: Boolean = false
const val constChar: Char = '\u0000'
}
// Properties
var myIntPropFromConst: Int = constInt
var myBytePropFromConst: Byte = constByte
var myLongPropFromConst: Long = constLong
var myShortPropFromConst: Short = constShort
var myDoublePropFromConst: Double = constDouble
var myFloatPropFromConst: Float = constFloat
var myBooleanPropFromConst: Boolean = constBoolean
var myCharPropFromConst: Char = constChar
var myIntProp: Int = 0
var myByteProp: Byte = 0
var myLongProp: Long = 0L
var myShortProp: Short = 0
var myDoubleProp: Double = 0.0
var myFloatProp: Float = 0.0f
var myBooleanProp: Boolean = false
var myCharProp: Char = '\u0000'
var myStringProp: String? = null
var myAnyProp: Any? = null
var myObjectProp: java.lang.Object? = null
var myIntegerProp: java.lang.Integer? = null
// Fields
@JvmField
var myIntFieldFromConst: Int = constInt
@JvmField
var myByteFieldFromConst: Byte = constByte
@JvmField
var myLongFieldFromConst: Long = constLong
@JvmField
var myShortFieldFromConst: Short = constShort
@JvmField
var myDoubleFieldFromConst: Double = constDouble
@JvmField
var myFloatFieldFromConst: Float = constFloat
@JvmField
var myBooleanFieldFromConst: Boolean = constBoolean
@JvmField
var myCharFieldFromConst: Char = constChar
@JvmField
var myIntField: Int = 0
@JvmField
var myByteField: Byte = 0
@JvmField
var myLongField: Long = 0L
@JvmField
var myShortField: Short = 0
@JvmField
var myDoubleField: Double = 0.0
@JvmField
var myFloatField: Float = 0.0f
@JvmField
var myBooleanField: Boolean = false
@JvmField
var myCharField: Char = '\u0000'
@JvmField
var myStringField: String? = null
@JvmField
var myAnyField: Any? = null
@JvmField
var myObjectField: java.lang.Object? = null
@JvmField
var myIntegerField: java.lang.Integer? = null
}
class NonRedundantInitializers {
// NOT redundant because the JVM's default values for floating-point types are positive 0.0.
// See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3
var myDouble: Double = -0.0
var myFloat: Float = -0.0f
}
// There are 20 PUTFIELDs in RedundantInitializersToDefault are in the setters for the 20 properties that don't have @JvmField.
// There are 2 PUTFIELDs in NonRedundantInitializers are also in setters, while the other 2 are in the constructor during initialization.
// There is 1 PUTSTATIC for the companion object instance.
// 20 PUTFIELD RedundantInitializersToDefault
// 4 PUTFIELD NonRedundantInitializers
// 1 PUTSTATIC
@@ -0,0 +1,98 @@
// No initializers for this class because the fields/properties are initialized to defaults.
object RedundantInitializersToDefault {
// Constants
const val constInt: Int = 0
const val constByte: Byte = 0
const val constLong: Long = 0L
const val constShort: Short = 0
const val constDouble: Double = 0.0
const val constFloat: Float = 0.0f
const val constBoolean: Boolean = false
const val constChar: Char = '\u0000'
// Properties
var myIntPropFromConst: Int = constInt
var myBytePropFromConst: Byte = constByte
var myLongPropFromConst: Long = constLong
var myShortPropFromConst: Short = constShort
var myDoublePropFromConst: Double = constDouble
var myFloatPropFromConst: Float = constFloat
var myBooleanPropFromConst: Boolean = constBoolean
var myCharPropFromConst: Char = constChar
var myIntProp: Int = 0
var myByteProp: Byte = 0
var myLongProp: Long = 0L
var myShortProp: Short = 0
var myDoubleProp: Double = 0.0
var myFloatProp: Float = 0.0f
var myBooleanProp: Boolean = false
var myCharProp: Char = '\u0000'
var myStringProp: String? = null
var myAnyProp: Any? = null
var myObjectProp: java.lang.Object? = null
var myIntegerProp: java.lang.Integer? = null
// Fields
@JvmField
var myIntFieldFromConst: Int = constInt
@JvmField
var myByteFieldFromConst: Byte = constByte
@JvmField
var myLongFieldFromConst: Long = constLong
@JvmField
var myShortFieldFromConst: Short = constShort
@JvmField
var myDoubleFieldFromConst: Double = constDouble
@JvmField
var myFloatFieldFromConst: Float = constFloat
@JvmField
var myBooleanFieldFromConst: Boolean = constBoolean
@JvmField
var myCharFieldFromConst: Char = constChar
@JvmField
var myIntField: Int = 0
@JvmField
var myByteField: Byte = 0
@JvmField
var myLongField: Long = 0L
@JvmField
var myShortField: Short = 0
@JvmField
var myDoubleField: Double = 0.0
@JvmField
var myFloatField: Float = 0.0f
@JvmField
var myBooleanField: Boolean = false
@JvmField
var myCharField: Char = '\u0000'
@JvmField
var myStringField: String? = null
@JvmField
var myAnyField: Any? = null
@JvmField
var myObjectField: java.lang.Object? = null
@JvmField
var myIntegerField: java.lang.Integer? = null
}
object NonRedundantInitializers {
// NOT redundant because the JVM's default values for floating-point types are positive 0.0.
// See: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3
var myDouble: Double = -0.0
var myFloat: Float = -0.0f
}
// There are 20 PUTSTATUCs in RedundantInitializersToDefault are in the setters for the 20 properties that don't have @JvmField.
// There are 2 PUTSTATICs in NonRedundantInitializers are also in setters, while the other 2 are in the constructor during initialization.
// There is 1 additional PUTSTATIC for both classes for the object instance.
// 21 PUTSTATIC RedundantInitializersToDefault
// 5 PUTSTATIC NonRedundantInitializers
// 0 PUTFIELD
@@ -3458,6 +3458,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -17474,6 +17479,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");
@@ -339,6 +339,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializer.kt");
}
@TestMetadata("redundantValInitializerInObject.kt")
public void testRedundantValInitializerInObject() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializerInObject.kt");
}
@TestMetadata("redundantVarInitializer.kt")
public void testRedundantVarInitializer() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantVarInitializer.kt");
}
@TestMetadata("redundantVarInitializerInObject.kt")
public void testRedundantVarInitializerInObject() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantVarInitializerInObject.kt");
}
@TestMetadata("reifiedAsCheck.kt")
public void testReifiedAsCheck() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
@@ -3458,6 +3458,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -17474,6 +17479,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");
@@ -3438,6 +3438,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -16349,6 +16354,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");
@@ -3438,6 +3438,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -16349,6 +16354,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");
@@ -339,6 +339,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializer.kt");
}
@TestMetadata("redundantValInitializerInObject.kt")
public void testRedundantValInitializerInObject() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantValInitializerInObject.kt");
}
@TestMetadata("redundantVarInitializer.kt")
public void testRedundantVarInitializer() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantVarInitializer.kt");
}
@TestMetadata("redundantVarInitializerInObject.kt")
public void testRedundantVarInitializerInObject() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/redundantVarInitializerInObject.kt");
}
@TestMetadata("reifiedAsCheck.kt")
public void testReifiedAsCheck() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/reifiedAsCheck.kt");
@@ -2758,6 +2758,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -13324,6 +13329,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");
@@ -2758,6 +2758,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/classes/initializerBlockDImpl.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/classes/initializerBlockResetToDefault.kt");
}
@TestMetadata("innerClass.kt")
public void testInnerClass() throws Exception {
runTest("compiler/testData/codegen/box/classes/innerClass.kt");
@@ -13389,6 +13394,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/objects/initializationOrder.kt");
}
@TestMetadata("initializerBlockResetToDefault.kt")
public void testInitializerBlockResetToDefault() throws Exception {
runTest("compiler/testData/codegen/box/objects/initializerBlockResetToDefault.kt");
}
@TestMetadata("interfaceCompanion.kt")
public void testInterfaceCompanion() throws Exception {
runTest("compiler/testData/codegen/box/objects/interfaceCompanion.kt");