Skip to content

Commit eff2f41

Browse files
committed
Fix #15363: Improve error messages of leaked non-hot values
1 parent 6635929 commit eff2f41

File tree

6 files changed

+56
-14
lines changed

6 files changed

+56
-14
lines changed

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

Lines changed: 14 additions & 4 deletions
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 + stacktrace(), 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 =>
@@ -99,6 +99,16 @@ object Errors:
9999
report.warning(show, this.pos)
100100

101101
def show(using Context): String =
102-
msg + stacktrace + "\n" +
102+
msg + stacktrace() + "\n" +
103103
"Promoting the value to fully initialized failed due to the following problem:\n" +
104-
error.show + error.stacktrace
104+
error.show + error.stacktrace()
105+
106+
/** Unsafe leaking a non-hot value as constructor arguments */
107+
case class UnsafeLeaking(trace: Seq[Tree], error: Error) extends Error:
108+
override def issue(using Context): Unit =
109+
report.warning(show, this.pos)
110+
111+
def show(using Context): String =
112+
"Unsafe leaking of uninitialized value: the leaked value is used. " + stacktrace() + "\n" +
113+
"The leaked uninitialized value is used as follows:\n" +
114+
error.stacktrace(preamble = "")

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,16 @@ object Semantic:
793793
val outer = Hot
794794
val warm = Warm(klass, outer, ctor, args2).ensureObjectExists()
795795
val argInfos2 = args.zip(args2).map { (argInfo, v) => argInfo.copy(value = v) }
796-
warm.callConstructor(ctor, argInfos2)
797-
warm
796+
val errors = Reporter.stopEarly {
797+
given Trace = Trace.empty
798+
warm.callConstructor(ctor, argInfos2)
799+
}
800+
if errors.nonEmpty then
801+
val error = UnsafeLeaking(trace.toVector, errors.head)
802+
reporter.report(error)
803+
Hot
804+
else
805+
warm
798806

799807
case Cold =>
800808
val error = CallCold(ctor, trace.toVector)

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

Lines changed: 8 additions & 8 deletions
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 */
@@ -549,16 +549,16 @@ object Implicits:
549549
override def msg(using Context) = _msg
550550
def explanation(using Context) = msg.toString
551551

552-
/** A search failure type for failed synthesis of terms for special types */
552+
/** A search failure type for failed synthesis of terms for special types */
553553
class SynthesisFailure(reasons: List[String], val expectedType: Type) extends SearchFailureType:
554554
def argument = EmptyTree
555555

556-
private def formatReasons =
557-
if reasons.length > 1 then
558-
reasons.mkString("\n\t* ", "\n\t* ", "")
559-
else
556+
private def formatReasons =
557+
if reasons.length > 1 then
558+
reasons.mkString("\n\t* ", "\n\t* ", "")
559+
else
560560
reasons.mkString
561-
561+
562562
def explanation(using Context) = em"Failed to synthesize an instance of type ${clarify(expectedType)}: ${formatReasons}"
563563

564564
end Implicits
@@ -871,7 +871,7 @@ trait Implicits:
871871
SearchFailure(new SynthesisFailure(errors, formal), span).tree
872872
else
873873
tree.orElse(failed)
874-
874+
875875

876876
/** Search an implicit argument and report error if not found */
877877
def implicitArgTree(formal: Type, span: Span)(using Context): Tree = {

tests/init/neg/i15363.check

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Warning: tests/init/neg/i15363.scala:3:10 -----------------------------------
2+
3 | val b = new B(this) // error
3+
| ^^^^^^^^^^^
4+
|Unsafe leaking of uninitialized values: the leaked value is used. Calling trace:
5+
|-> class A: [ i15363.scala:1 ]
6+
| ^
7+
|-> val b = new B(this) // error [ i15363.scala:3 ]
8+
| ^^^^^^^^^^^
9+
|
10+
|The leaked uninitialized value is used as follows:
11+
|-> class B(a: A): [ i15363.scala:7 ]
12+
| ^
13+
|-> val x = a.m [ i15363.scala:8 ]
14+
| ^^^
15+
1 warning found

tests/init/neg/i15363.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class A:
2+
// should report one error here
3+
val b = new B(this) // error
4+
val m = 10
5+
val n = 20
6+
7+
class B(a: A):
8+
val x = a.m
9+
val y = a.n
File renamed without changes.

0 commit comments

Comments
 (0)