Skip to content

Commit 61e57e6

Browse files
authored
Merge pull request #15364 from dotty-staging/fix-15363
Fix #15363: Improve error messages for leaking of this
2 parents 662ebc9 + 73b3331 commit 61e57e6

20 files changed

+249
-124
lines changed

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

+41-12
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ object Errors:
1919
def pos(using Context): SourcePosition = trace.last.sourcePos
2020

2121
def issue(using Context): Unit =
22-
report.warning(show + stacktrace, this.pos)
22+
report.warning(show, this.pos)
2323

24-
def stacktrace(using Context): String = if trace.isEmpty then "" else " Calling trace:\n" + {
24+
def stacktrace(preamble: String = " Calling trace:\n")(using Context): String = if trace.isEmpty then "" else preamble + {
2525
var lastLineNum = -1
2626
var lines: mutable.ArrayBuffer[String] = new mutable.ArrayBuffer
2727
trace.foreach { tree =>
@@ -72,33 +72,62 @@ object Errors:
7272
case class AccessNonInit(field: Symbol, trace: Seq[Tree]) extends Error:
7373
def source: Tree = trace.last
7474
def show(using Context): String =
75-
"Access non-initialized " + field.show + "."
75+
"Access non-initialized " + field.show + "." + stacktrace()
7676

7777
override def pos(using Context): SourcePosition = field.sourcePos
7878

7979
/** Promote a value under initialization to fully-initialized */
8080
case class PromoteError(msg: String, trace: Seq[Tree]) extends Error:
81-
def show(using Context): String = msg
81+
def show(using Context): String = msg + stacktrace()
8282

8383
case class AccessCold(field: Symbol, trace: Seq[Tree]) extends Error:
8484
def show(using Context): String =
85-
"Access field on a value with an unknown initialization status."
85+
"Access field on a value with an unknown initialization status." + stacktrace()
8686

8787
case class CallCold(meth: Symbol, trace: Seq[Tree]) extends Error:
8888
def show(using Context): String =
89-
"Call method on a value with an unknown initialization" + "."
89+
"Call method on a value with an unknown initialization." + stacktrace()
9090

9191
case class CallUnknown(meth: Symbol, trace: Seq[Tree]) extends Error:
9292
def show(using Context): String =
9393
val prefix = if meth.is(Flags.Method) then "Calling the external method " else "Accessing the external field"
94-
prefix + meth.show + " may cause initialization errors" + "."
94+
prefix + meth.show + " may cause initialization errors." + stacktrace()
9595

9696
/** Promote a value under initialization to fully-initialized */
9797
case class UnsafePromotion(msg: String, trace: Seq[Tree], error: Error) extends Error:
98-
override def issue(using Context): Unit =
99-
report.warning(show, this.pos)
100-
10198
def show(using Context): String =
102-
msg + stacktrace + "\n" +
99+
msg + stacktrace() + "\n" +
103100
"Promoting the value to fully initialized failed due to the following problem:\n" +
104-
error.show + error.stacktrace
101+
error.show
102+
103+
/** Unsafe leaking a non-hot value as constructor arguments
104+
*
105+
* Invariant: argsIndices.nonEmpty
106+
*/
107+
case class UnsafeLeaking(trace: Seq[Tree], error: Error, nonHotOuterClass: Symbol, argsIndices: List[Int]) extends Error:
108+
def show(using Context): String =
109+
"Problematic object instantiation: " + argumentInfo() + stacktrace() + "\n" +
110+
"It leads to the following error during object initialization:\n" +
111+
error.show
112+
113+
private def punctuation(i: Int): String =
114+
if i == argsIndices.size - 2 then " and "
115+
else if i == argsIndices.size - 1 then ""
116+
else ", "
117+
118+
private def argumentInfo()(using Context): String =
119+
val multiple = argsIndices.size > 1 || nonHotOuterClass.exists
120+
val init =
121+
if nonHotOuterClass.exists
122+
then "the outer " + nonHotOuterClass.name.show + ".this" + punctuation(-1)
123+
else ""
124+
125+
val subject =
126+
argsIndices.zipWithIndex.foldLeft(init) { case (acc, (pos, i)) =>
127+
val text1 = "arg " + pos.toString
128+
val text2 = text1 + punctuation(i)
129+
acc + text2
130+
}
131+
val verb = if multiple then " are " else " is "
132+
val adjective = "not fully initialized."
133+
subject + verb + adjective

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

+68-47
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ object Semantic:
118118
end Warm
119119

120120
/** A function value */
121-
case class Fun(expr: Tree, thisV: Ref, klass: ClassSymbol, env: Env) extends Value
121+
case class Fun(expr: Tree, thisV: Ref, klass: ClassSymbol) extends Value
122122

123123
/** A value which represents a set of addresses
124124
*
@@ -144,7 +144,7 @@ object Semantic:
144144

145145
def hasField(f: Symbol) = fields.contains(f)
146146

147-
/** The environment for method parameters
147+
/** The environment stores values for constructor parameters
148148
*
149149
* For performance and usability, we restrict parameters to be either `Cold`
150150
* or `Hot`.
@@ -162,6 +162,9 @@ object Semantic:
162162
* key. The reason is that given the same receiver, a method or function may
163163
* be called with different arguments -- they are not decided by the receiver
164164
* anymore.
165+
*
166+
* TODO: remove Env as it is only used to pass value from `callConstructor` -> `eval` -> `init`.
167+
* It goes through `eval` for caching (termination) purposes.
165168
*/
166169
object Env:
167170
opaque type Env = Map[Symbol, Value]
@@ -588,15 +591,15 @@ object Semantic:
588591
Hot
589592

590593
case fun: Fun =>
591-
report.error("unexpected tree in selecting a function, fun = " + fun.expr.show, fun.expr)
594+
report.error("[Internal error] unexpected tree in selecting a function, fun = " + fun.expr.show, fun.expr)
592595
Hot
593596

594597
case RefSet(refs) =>
595598
refs.map(_.select(field)).join
596599
}
597600
}
598601

599-
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", args = " + args, printer, (_: Value).show) {
602+
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
600603
def promoteArgs(): Contextual[Unit] = args.foreach(_.promote)
601604

602605
def isSyntheticApply(meth: Symbol) =
@@ -704,21 +707,19 @@ object Semantic:
704707
else
705708
value.select(target, needResolve = false)
706709

707-
case Fun(body, thisV, klass, env) =>
710+
case Fun(body, thisV, klass) =>
708711
// meth == NoSymbol for poly functions
709712
if meth.name.toString == "tupled" then value // a call like `fun.tupled`
710713
else
711714
promoteArgs()
712-
withEnv(env) {
713-
eval(body, thisV, klass, cacheResult = true)
714-
}
715+
eval(body, thisV, klass, cacheResult = true)
715716

716717
case RefSet(refs) =>
717718
refs.map(_.call(meth, args, receiver, superType)).join
718719
}
719720
}
720721

721-
def callConstructor(ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("call " + ctor.show + ", args = " + args, printer, (_: Value).show) {
722+
def callConstructor(ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("call " + ctor.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
722723
// init "fake" param fields for the secondary constructor
723724
def addParamsAsFields(env: Env, ref: Ref, ctorDef: DefDef) = {
724725
val paramSyms = ctorDef.termParamss.flatten.map(_.symbol)
@@ -775,7 +776,27 @@ object Semantic:
775776
}
776777

777778
/** Handle a new expression `new p.C` where `p` is abstracted by `value` */
778-
def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", value = " + value + ", args = " + args, printer, (_: Value).show) {
779+
def instantiate(klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", value = " + value + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
780+
def tryLeak(warm: Warm, nonHotOuterClass: Symbol, argValues: List[Value]): Contextual[Value] =
781+
val argInfos2 = args.zip(argValues).map { (argInfo, v) => argInfo.copy(value = v) }
782+
val errors = Reporter.stopEarly {
783+
given Trace = Trace.empty
784+
warm.callConstructor(ctor, argInfos2)
785+
}
786+
if errors.nonEmpty then
787+
val indices =
788+
for
789+
(arg, i) <- argValues.zipWithIndex
790+
if arg.isCold
791+
yield
792+
i + 1
793+
794+
val error = UnsafeLeaking(trace.toVector, errors.head, nonHotOuterClass, indices)
795+
reporter.report(error)
796+
Hot
797+
else
798+
warm
799+
779800
if promoted.isCurrentObjectPromoted then Hot
780801
else value match {
781802
case Hot =>
@@ -792,9 +813,7 @@ object Semantic:
792813
else
793814
val outer = Hot
794815
val warm = Warm(klass, outer, ctor, args2).ensureObjectExists()
795-
val argInfos2 = args.zip(args2).map { (argInfo, v) => argInfo.copy(value = v) }
796-
warm.callConstructor(ctor, argInfos2)
797-
warm
816+
tryLeak(warm, NoSymbol, args2)
798817

799818
case Cold =>
800819
val error = CallCold(ctor, trace.toVector)
@@ -810,13 +829,16 @@ object Semantic:
810829
case _ => ref
811830

812831
val argsWidened = args.map(_.value).widenArgs
813-
val argInfos2 = args.zip(argsWidened).map { (argInfo, v) => argInfo.copy(value = v) }
814832
val warm = Warm(klass, outer, ctor, argsWidened).ensureObjectExists()
815-
warm.callConstructor(ctor, argInfos2)
816-
warm
833+
if argsWidened.exists(_.isCold) then
834+
tryLeak(warm, klass.owner.lexicallyEnclosingClass, argsWidened)
835+
else
836+
val argInfos2 = args.zip(argsWidened).map { (argInfo, v) => argInfo.copy(value = v) }
837+
warm.callConstructor(ctor, argInfos2)
838+
warm
817839

818-
case Fun(body, thisV, klass, env) =>
819-
report.error("unexpected tree in instantiating a function, fun = " + body.show, trace.toVector.last)
840+
case Fun(body, thisV, klass) =>
841+
report.error("[Internal error] unexpected tree in instantiating a function, fun = " + body.show, trace.toVector.last)
820842
Hot
821843

822844
case RefSet(refs) =>
@@ -830,21 +852,14 @@ object Semantic:
830852
val sym = tmref.symbol
831853

832854
if sym.is(Flags.Param) && sym.owner.isConstructor then
833-
// if we can get the field from the Ref (which can only possibly be
834-
// a secondary constructor parameter), then use it.
835-
if (ref.objekt.hasField(sym))
836-
ref.objekt.field(sym)
837-
// instances of local classes inside secondary constructors cannot
838-
// reach here, as those values are abstracted by Cold instead of Warm.
839-
// This enables us to simplify the domain without sacrificing
840-
// expressiveness nor soundess, as local classes inside secondary
841-
// constructors are uncommon.
842-
else if sym.isContainedIn(klass) then
843-
env.lookup(sym)
844-
else
845-
// We don't know much about secondary constructor parameters in outer scope.
846-
// It's always safe to approximate them with `Cold`.
847-
Cold
855+
val enclosingClass = sym.owner.enclosingClass.asClass
856+
val thisValue2 = resolveThis(enclosingClass, ref, klass)
857+
thisValue2 match
858+
case Hot => Hot
859+
case ref: Ref => ref.objekt.field(sym)
860+
case _ =>
861+
report.error("[Internal error] unexpected this value accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show, trace.toVector.last)
862+
Hot
848863
else if sym.is(Flags.Param) then
849864
Hot
850865
else
@@ -861,7 +876,7 @@ object Semantic:
861876
case ref: Ref => eval(vdef.rhs, ref, enclosingClass)
862877

863878
case _ =>
864-
report.error("unexpected defTree when accessing local variable, sym = " + sym.show + ", defTree = " + sym.defTree.show, trace.toVector.last)
879+
report.error("[Internal error] unexpected this value when accessing local variable, sym = " + sym.show + ", thisValue = " + thisValue2.show, trace.toVector.last)
865880
Hot
866881
end match
867882

@@ -932,10 +947,10 @@ object Semantic:
932947
if errors.nonEmpty then promoted.remove(warm)
933948
reporter.reportAll(errors)
934949

935-
case fun @ Fun(body, thisV, klass, env) =>
950+
case fun @ Fun(body, thisV, klass) =>
936951
if !promoted.contains(fun) then
937952
val errors = Reporter.stopEarly {
938-
val res = withEnv(env) {
953+
val res = {
939954
given Trace = Trace.empty
940955
eval(body, thisV, klass)
941956
}
@@ -1120,7 +1135,7 @@ object Semantic:
11201135
args.foreach { arg =>
11211136
val res =
11221137
if arg.isByName then
1123-
Fun(arg.tree, thisV, klass, env)
1138+
Fun(arg.tree, thisV, klass)
11241139
else
11251140
eval(arg.tree, thisV, klass)
11261141

@@ -1226,10 +1241,10 @@ object Semantic:
12261241
}
12271242

12281243
case closureDef(ddef) =>
1229-
Fun(ddef.rhs, thisV, klass, env)
1244+
Fun(ddef.rhs, thisV, klass)
12301245

12311246
case PolyFun(body) =>
1232-
Fun(body, thisV, klass, env)
1247+
Fun(body, thisV, klass)
12331248

12341249
case Block(stats, expr) =>
12351250
eval(stats, thisV, klass)
@@ -1302,8 +1317,8 @@ object Semantic:
13021317
Hot
13031318

13041319
case _ =>
1305-
throw new Exception("unexpected tree: " + expr.show)
1306-
1320+
report.error("[Internal error] unexpected tree", expr)
1321+
Hot
13071322

13081323
/** Handle semantics of leaf nodes */
13091324
def cases(tp: Type, thisV: Ref, klass: ClassSymbol): Contextual[Value] = log("evaluating " + tp.show, printer, (_: Value).show) {
@@ -1331,7 +1346,8 @@ object Semantic:
13311346
Hot
13321347

13331348
case _ =>
1334-
throw new Exception("unexpected type: " + tp)
1349+
report.error("[Internal error] unexpected type " + tp, trace.toVector.last)
1350+
Hot
13351351
}
13361352

13371353
/** Resolve C.this that appear in `klass` */
@@ -1345,15 +1361,15 @@ object Semantic:
13451361
val obj = ref.objekt
13461362
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
13471363
if !obj.hasOuter(klass) then
1348-
val error = PromoteError("outer not yet initialized, target = " + target + ", klass = " + klass + ", object = " + obj, trace.toVector)
1349-
report.error(error.show + error.stacktrace, trace.toVector.last)
1364+
val error = PromoteError("[Internal error] outer not yet initialized, target = " + target + ", klass = " + klass + ", object = " + obj, trace.toVector)
1365+
report.error(error.show, trace.toVector.last)
13501366
Hot
13511367
else
13521368
resolveThis(target, obj.outer(klass), outerCls)
13531369
case RefSet(refs) =>
13541370
refs.map(ref => resolveThis(target, ref, klass)).join
13551371
case fun: Fun =>
1356-
report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, trace.toVector.last)
1372+
report.error("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", klass = " + klass.show, trace.toVector.last)
13571373
Cold
13581374
case Cold => Cold
13591375

@@ -1381,14 +1397,15 @@ object Semantic:
13811397
resolveThis(target, thisV, cur)
13821398

13831399
case None =>
1384-
report.warning("unexpected outerSelect, thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, trace.toVector.last.srcPos)
1400+
// TODO: use error once we fix https://github.com/lampepfl/dotty/issues/15465
1401+
report.warning("[Internal error] unexpected outerSelect, thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, trace.toVector.last.srcPos)
13851402
Cold
13861403

13871404
case RefSet(refs) =>
13881405
refs.map(ref => resolveOuterSelect(target, ref, hops)).join
13891406

13901407
case fun: Fun =>
1391-
report.warning("unexpected thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, trace.toVector.last.srcPos)
1408+
report.error("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + ", hops = " + hops, trace.toVector.last.srcPos)
13921409
Cold
13931410

13941411
case Cold => Cold
@@ -1516,6 +1533,10 @@ object Semantic:
15161533
eval(tree, thisV, klass)
15171534
}
15181535

1536+
// ensure we try promotion once even if class body is empty
1537+
if fieldsChanged && thisV.isThisRef then
1538+
thisV.asInstanceOf[ThisRef].tryPromoteCurrentObject()
1539+
15191540
// The result value is ignored, use Hot to avoid futile fixed point computation
15201541
Hot
15211542
}

compiler/src/dotty/tools/dotc/typer/Implicits.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ object Implicits:
8686
*/
8787
abstract class ImplicitRefs(initctx: Context) {
8888
val irefCtx =
89-
if (initctx == NoContext) initctx else initctx.retractMode(Mode.ImplicitsEnabled)
89+
if (initctx eq NoContext) initctx else initctx.retractMode(Mode.ImplicitsEnabled)
9090
protected given Context = irefCtx
9191

9292
/** The nesting level of this context. Non-zero only in ContextialImplicits */

tests/init/neg/apply2.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ object O:
33
println(n)
44

55
class B:
6-
val a = A(this)
6+
val a = A(this) // error
77

88
val b = new B
9-
val n = 10 // error
9+
val n = 10
1010
end O
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
final class MyAsInstanceOfClass(o: MyAsInstanceOfClass) {
22
val other: MyAsInstanceOfClass = {
3-
if (o.asInstanceOf[MyAsInstanceOfClass].oRef ne null) o // error
4-
else new MyAsInstanceOfClass(this)
3+
if (o.asInstanceOf[MyAsInstanceOfClass].oRef ne null) o
4+
else new MyAsInstanceOfClass(this) // error
55
}
66
val oRef = o
77
}

0 commit comments

Comments
 (0)