Skip to content

Commit 39684dc

Browse files
committed
Prevent leaking FreshCaps created for parameters
1 parent c409c5d commit 39684dc

File tree

5 files changed

+36
-17
lines changed

5 files changed

+36
-17
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ object Capabilities:
843843
case LambdaActual(restp: Type)
844844
case OverriddenType(member: Symbol)
845845
case DeepCS(ref: TypeRef)
846+
case Parameter(param: Symbol)
846847
case Unknown
847848

848849
def explanation(using Context): String = this match
@@ -876,6 +877,8 @@ object Capabilities:
876877
i" when instantiating upper bound of member overridden by $member"
877878
case DeepCS(ref: TypeRef) =>
878879
i" when computing deep capture set of $ref"
880+
case Parameter(param) =>
881+
i" of parameter $param of ${param.owner}"
879882
case Unknown =>
880883
""
881884
end Origin
@@ -942,8 +945,8 @@ object Capabilities:
942945
CapToFresh(origin)(tp)
943946

944947
/** Maps fresh to cap */
945-
def freshToCap(tp: Type)(using Context): Type =
946-
CapToFresh(Origin.Unknown).inverse(tp)
948+
def freshToCap(param: Symbol, tp: Type)(using Context): Type =
949+
CapToFresh(Origin.Parameter(param)).inverse(tp)
947950

948951
/** Map top-level free existential variables one-to-one to Fresh instances */
949952
def resultToFresh(tp: Type, origin: Origin)(using Context): Type =

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ object CaptureSet:
720720
private def narrowClassifier(cls: ClassSymbol)(using Context): Unit =
721721
val newClassifier = leastClassifier(classifier, cls)
722722
if newClassifier == defn.NothingClass then
723-
println(i"conflicting classifications for $this, was $classifier, now $cls")
723+
capt.println(i"conflicting classifications for $this, was $classifier, now $cls")
724724
myClassifier = newClassifier
725725

726726
override def adoptClassifier(cls: ClassSymbol)(using Context): Unit =

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ object CheckCaptures:
181181
check.traverse(tp)
182182
end disallowBadRootsIn
183183

184+
private def ownerStr(owner: Symbol)(using Context): String =
185+
if owner.isAnonymousFunction then "enclosing function" else owner.show
186+
184187
trait CheckerAPI:
185188
/** Complete symbol info of a val or a def */
186189
def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type
@@ -920,7 +923,7 @@ class CheckCaptures extends Recheck, SymTransformer:
920923
assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}")
921924
for (argType, param) <- argTypes.lazyZip(params) do
922925
val paramTpt = param.asInstanceOf[ValDef].tpt
923-
val paramType = freshToCap(paramTpt.nuType)
926+
val paramType = freshToCap(param.symbol, paramTpt.nuType)
924927
checkConformsExpr(argType, paramType, param)
925928
.showing(i"compared expected closure formal $argType against $param with ${paramTpt.nuType}", capt)
926929
if !pt.isInstanceOf[RefinedType]
@@ -1993,7 +1996,13 @@ class CheckCaptures extends Recheck, SymTransformer:
19931996
case c: DerivedCapability =>
19941997
checkElem(c.underlying)
19951998
case c: FreshCap =>
1996-
check(c.hiddenSet)
1999+
c.origin match
2000+
case Origin.Parameter(param) =>
2001+
report.error(
2002+
em"Local $c created in type of $param leaks into capture scope of ${ownerStr(param.owner)}",
2003+
tree.srcPos)
2004+
case _ =>
2005+
check(c.hiddenSet)
19972006
case _ =>
19982007

19992008
check(uses)

compiler/src/dotty/tools/dotc/cc/Setup.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,17 +636,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
636636
def ownerChanges =
637637
ctx.owner.name.is(TryOwnerName)
638638

639-
def paramsToCap(mt: Type)(using Context): Type = mt match
639+
def paramsToCap(psymss: List[List[Symbol]], mt: Type)(using Context): Type = mt match
640640
case mt: MethodType =>
641641
try
642642
mt.derivedLambdaType(
643-
paramInfos = mt.paramInfos.map(freshToCap),
644-
resType = paramsToCap(mt.resType))
643+
paramInfos =
644+
psymss.head.lazyZip(mt.paramInfos).map(freshToCap),
645+
resType = paramsToCap(psymss.tail, mt.resType))
645646
catch case ex: AssertionError =>
646647
println(i"error while mapping params ${mt.paramInfos} of $sym")
647648
throw ex
648649
case mt: PolyType =>
649-
mt.derivedLambdaType(resType = paramsToCap(mt.resType))
650+
mt.derivedLambdaType(resType = paramsToCap(psymss.tail, mt.resType))
650651
case _ => mt
651652

652653
// If there's a change in the signature or owner, update the info of `sym`
@@ -658,7 +659,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
658659
toResultInResults(sym, report.error(_, tree.srcPos)):
659660
if sym.is(Method) then
660661
inContext(ctx.withOwner(sym)):
661-
paramsToCap(methodType(paramSymss, localReturnType))
662+
paramsToCap(paramSymss, methodType(paramSymss, localReturnType))
662663
else tree.tpt.nuType
663664
if tree.tpt.isInstanceOf[InferredTypeTree]
664665
&& !sym.is(Param) && !sym.is(ParamAccessor)

tests/neg-custom-args/captures/leak-problem-unboxed.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import language.experimental.captureChecking
2-
import caps.use
32

43
// Some capabilities that should be used locally
54
trait Async:
@@ -19,16 +18,23 @@ def useBoxedAsync1[C^](x: Box[Async^{C}]): Unit = x.get.read() // ok
1918
def test(): Unit =
2019

2120
val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error
22-
val t0: Box[Async^] => Unit = x => useBoxedAsync(x) // TODO hould be error!
21+
val f0: Box[Async^] => Unit = x => useBoxedAsync(x) // // error
2322

24-
val t1: Box[Async^] => Unit = useBoxedAsync(_) // TODO should be error!
25-
val t2: Box[Async^] => Unit = useBoxedAsync // TODO should be error!
26-
val t3 = useBoxedAsync(_) // was error, now ok
27-
val t4 = useBoxedAsync // was error, now ok
23+
val f1: Box[Async^] => Unit = useBoxedAsync(_) // error
24+
val f2: Box[Async^] => Unit = useBoxedAsync // error
25+
val f3 = useBoxedAsync(_) // was error, now ok, but bang below fails
26+
val f4 = useBoxedAsync // was error, now ok, but bang2 below fails
2827

2928
def boom(x: Async^): () ->{f} Unit =
3029
() => f(Box(x))
3130

3231
val leaked = usingAsync[() ->{f} Unit](boom)
3332

34-
leaked() // scope violation
33+
leaked() // was scope violation
34+
35+
def bang(x: Async^) =
36+
() => f3(Box(x)) // error
37+
38+
def bang2(x: Async^) =
39+
() => f3(Box(x)) // error
40+

0 commit comments

Comments
 (0)