Skip to content

Commit 8b5d445

Browse files
EnzeXingEnzeXing
andauthored
Global object init checker gives warning when accessing an object before its super constructor finishes (#24349)
Accessing an object before its super constructor finishes returns `null` and may lead to exceptions when constructing the object (see #24201). This PR lets global object init checker gives warning for this case --------- Co-authored-by: EnzeXing <[email protected]>
1 parent aaf3dc4 commit 8b5d445

File tree

13 files changed

+103
-16
lines changed

13 files changed

+103
-16
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ class Objects(using Context @constructorOnly):
144144

145145
def isObjectRef: Boolean = this.isInstanceOf[ObjectRef]
146146

147+
def asObjectRef: ObjectRef = this.asInstanceOf[ObjectRef]
148+
147149
def valValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym)
148150

149151
def varValue(sym: Symbol)(using Heap.MutableData): Value = Heap.readVal(this, sym)
@@ -178,6 +180,12 @@ class Objects(using Context @constructorOnly):
178180

179181
/** A reference to a static object */
180182
case class ObjectRef private (klass: ClassSymbol)(using Trace) extends Ref:
183+
var afterSuperCall = false
184+
185+
def isAfterSuperCall = afterSuperCall
186+
187+
def setAfterSuperCall(): Unit = afterSuperCall = true
188+
181189
def owner = klass
182190

183191
def show(using Context) = "ObjectRef(" + klass.show + ")"
@@ -1447,9 +1455,15 @@ class Objects(using Context @constructorOnly):
14471455
/** Check an individual object */
14481456
private def accessObject(classSym: ClassSymbol)(using Context, State.Data, Trace, Heap.MutableData, EnvMap.EnvMapMutableData): ObjectRef = log("accessing " + classSym.show, printer, (_: Value).show) {
14491457
if classSym.hasSource then
1450-
State.checkObjectAccess(classSym)
1458+
val obj = State.checkObjectAccess(classSym)
1459+
if !obj.isAfterSuperCall then
1460+
report.warning("Accessing " + obj.klass + " before the super constructor of the object finishes! " + Trace.show, Trace.position)
1461+
end if
1462+
obj
14511463
else
1452-
ObjectRef(classSym)
1464+
val obj = ObjectRef(classSym)
1465+
obj.setAfterSuperCall()
1466+
obj
14531467
}
14541468

14551469

@@ -2112,6 +2126,10 @@ class Objects(using Context @constructorOnly):
21122126
tasks.foreach(task => task())
21132127
end if
21142128

2129+
if thisV.isInstanceOf[ObjectRef] && klass == thisV.klass then
2130+
thisV.asObjectRef.setAfterSuperCall()
2131+
end if
2132+
21152133
// class body
21162134
tpl.body.foreach {
21172135
case vdef : ValDef if !vdef.symbol.is(Flags.Lazy) && !vdef.rhs.isEmpty =>

tests/init-global/pos/multiple-by-name.scala

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,3 @@ object O {
1818
val c = foo2(new Y)
1919
val d = foo3(new Y)
2020
}
21-
22-
/**
23-
* Pass arg to by-name parameter: create a Fun where body is the argument expression
24-
* Read value of by-name parameter: call 'apply' on every possible Fun value of the by-name parameter
25-
* Solution: Add special EnvRefs for by-name params;
26-
* differentiate these EnvRefs by the arg tree passed to the by-name param
27-
*/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C(i: Int = 42, j: Int = 27)
2+
3+
object X extends C(j = X.foo()): // warn
4+
def foo() = 5
5+
6+
@main def test = println:
7+
X
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class C(j: Int)
2+
3+
object A:
4+
def foo = X.k // warn
5+
6+
object X extends C(A.foo):
7+
def k = 5
8+
9+
@main def test = println:
10+
X
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class C(i: Int = 42, j: Int = 27) {
2+
val f = X.foo() // warn
3+
}
4+
5+
object X extends C(j = 5):
6+
def foo() = 5
7+
8+
@main def test = println:
9+
X
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Warning: tests/init-global/warn/global-cycle10.scala:13:14 ----------------------------------------------------------
2+
13 | def foo() = new Inner // warn
3+
| ^^^^^^^^^
4+
| Accessing module class O$ before the super constructor of the object finishes! Calling trace:
5+
| ├── object O extends Base { // error [ global-cycle10.scala:7 ]
6+
| │ ^
7+
| ├── abstract class Base { [ global-cycle10.scala:1 ]
8+
| │ ^
9+
| ├── foo() [ global-cycle10.scala:4 ]
10+
| │ ^^^^^
11+
| └── def foo() = new Inner // warn [ global-cycle10.scala:13 ]
12+
| ^^^^^^^^^
13+
-- Warning: tests/init-global/warn/global-cycle10.scala:10:12 ----------------------------------------------------------
14+
10 | println(msg) // warn
15+
| ^^^
16+
| Accessing module class O$ before the super constructor of the object finishes! Calling trace:
17+
| ├── object O extends Base { // error [ global-cycle10.scala:7 ]
18+
| │ ^
19+
| ├── abstract class Base { [ global-cycle10.scala:1 ]
20+
| │ ^
21+
| ├── foo() [ global-cycle10.scala:4 ]
22+
| │ ^^^^^
23+
| ├── def foo() = new Inner // warn [ global-cycle10.scala:13 ]
24+
| │ ^^^^^^^^^
25+
| ├── class Inner { [ global-cycle10.scala:9 ]
26+
| │ ^
27+
| └── println(msg) // warn [ global-cycle10.scala:10 ]
28+
| ^^^

tests/init-global/pos/global-cycle10.scala renamed to tests/init-global/warn/global-cycle10.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ abstract class Base {
77
object O extends Base { // error
88

99
class Inner {
10-
println(msg)
10+
println(msg) // warn
1111
}
1212

13-
def foo() = new Inner
13+
def foo() = new Inner // warn
1414
}
1515

1616
@main

tests/init-global/warn/i9176.check

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@
44
| Cyclic initialization: object A -> object B -> object A. Calling trace:
55
| ├── case object A extends Foo(B) // warn [ i9176.scala:2 ]
66
| │ ^
7-
| └── case object B extends Foo(A) [ i9176.scala:3 ]
7+
| └── case object B extends Foo(A) // warn [ i9176.scala:3 ]
88
| ^
9+
-- Warning: tests/init-global/warn/i9176.scala:3:26 --------------------------------------------------------------------
10+
3 |case object B extends Foo(A) // warn
11+
| ^
12+
| Accessing module class A$ before the super constructor of the object finishes! Calling trace:
13+
| └── case object B extends Foo(A) // warn [ i9176.scala:3 ]
14+
| ^

tests/init-global/warn/i9176.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Foo(val opposite: Foo)
22
case object A extends Foo(B) // warn
3-
case object B extends Foo(A)
3+
case object B extends Foo(A) // warn
44
object Test {
55
def main(args: Array[String]): Unit = {
66
println(A.opposite)

tests/init-global/warn/resolve-parent-this.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
-- Warning: tests/init-global/warn/resolve-parent-this.scala:3:18 ------------------------------------------------------
2+
3 | val f: O.type = O // warn
3+
| ^
4+
| Accessing module class O$ before the super constructor of the object finishes! Calling trace:
5+
| ├── object O extends Delegate { [ resolve-parent-this.scala:6 ]
6+
| │ ^
7+
| ├── class Delegate { [ resolve-parent-this.scala:1 ]
8+
| │ ^
9+
| └── val f: O.type = O // warn [ resolve-parent-this.scala:3 ]
10+
| ^
111
-- Warning: tests/init-global/warn/resolve-parent-this.scala:7:21 ------------------------------------------------------
212
7 | val a: Int = foo().a // warn
313
| ^^^^^^^

0 commit comments

Comments
 (0)