Skip to content

Commit 190aaca

Browse files
committed
Use deep capturesets for separation checking.
When checking whether two items overlap we should always check their deep capture sets. Buried aliases should count as well.
1 parent 70074c4 commit 190aaca

File tree

5 files changed

+53
-18
lines changed

5 files changed

+53
-18
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ object CaptureSet:
10511051
def getElems(v: Var): Option[Refs] = elemsMap.get(v)
10521052

10531053
/** Record elements, return whether this was allowed.
1054-
* By default, recording is allowed in regular both not in frozen states.
1054+
* By default, recording is allowed in regular but not in frozen states.
10551055
*/
10561056
def putElems(v: Var, elems: Refs): Boolean = { elemsMap(v) = elems; true }
10571057

@@ -1062,7 +1062,7 @@ object CaptureSet:
10621062
def getDeps(v: Var): Option[Deps] = depsMap.get(v)
10631063

10641064
/** Record dependent sets, return whether this was allowed.
1065-
* By default, recording is allowed in regular both not in frozen states.
1065+
* By default, recording is allowed in regular but not in frozen states.
10661066
*/
10671067
def putDeps(v: Var, deps: Deps): Boolean = { depsMap(v) = deps; true }
10681068

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

+18-14
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,28 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
6666

6767
recur(refs)
6868
end hidden
69+
70+
/** Deduct the footprint of `sym` and `sym*` from `refs` */
71+
private def deductSym(sym: Symbol)(using Context) =
72+
val ref = sym.termRef
73+
if ref.isTrackableRef then refs -- CaptureSet(ref, ref.reach).elems.footprint
74+
else refs
75+
76+
/** Deduct the footprint of all captures of `deps` from `refs` */
77+
private def deductCapturesOf(deps: List[Tree])(using Context): Refs =
78+
deps.foldLeft(refs): (refs, dep) =>
79+
refs -- captures(dep).footprint
6980
end extension
7081

7182
/** The captures of an argument or prefix widened to the formal parameter, if
7283
* the latter contains a cap.
7384
*/
7485
private def formalCaptures(arg: Tree)(using Context): Refs =
75-
val argType = arg.formalType.orElse(arg.nuType)
76-
(if argType.hasUseAnnot then argType.deepCaptureSet else argType.captureSet)
77-
.elems
86+
arg.formalType.orElse(arg.nuType).deepCaptureSet.elems
7887

7988
/** The captures of a node */
8089
private def captures(tree: Tree)(using Context): Refs =
81-
val tpe = tree.nuType
82-
(if tree.formalType.hasUseAnnot then tpe.deepCaptureSet else tpe.captureSet)
83-
.elems
90+
tree.nuType.deepCaptureSet.elems
8491

8592
private def sepApplyError(fn: Tree, args: List[Tree], argIdx: Int,
8693
overlap: Refs, hiddenInArg: Refs, footprints: List[(Refs, Int)],
@@ -144,7 +151,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
144151

145152
def sepUseError(tree: Tree, used: Refs, globalOverlap: Refs)(using Context): Unit =
146153
val individualChecks = for mdefs <- previousDefs.iterator; mdef <- mdefs.iterator yield
147-
val hiddenByDef = captures(mdef.tpt).hidden
154+
val hiddenByDef = captures(mdef.tpt).hidden.footprint
148155
val overlap = defUseOverlap(hiddenByDef, used, tree.symbol)
149156
if !overlap.isEmpty then
150157
def resultStr = if mdef.isInstanceOf[DefDef] then " result" else ""
@@ -172,20 +179,16 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
172179
val footprints = mutable.ListBuffer[(Refs, Int)]((footprint, 0))
173180
val indexedArgs = args.zipWithIndex
174181

175-
def subtractDeps(elems: Refs, arg: Tree): Refs =
176-
deps(arg).foldLeft(elems): (elems, dep) =>
177-
elems -- captures(dep).footprint
178-
179182
for (arg, idx) <- indexedArgs do
180183
if !arg.needsSepCheck then
181-
footprint = footprint ++ subtractDeps(captures(arg).footprint, arg)
184+
footprint = footprint ++ captures(arg).footprint.deductCapturesOf(deps(arg))
182185
footprints += ((footprint, idx + 1))
183186
for (arg, idx) <- indexedArgs do
184187
if arg.needsSepCheck then
185188
val ac = formalCaptures(arg)
186189
val hiddenInArg = ac.hidden.footprint
187190
//println(i"check sep $arg: $ac, footprint so far = $footprint, hidden = $hiddenInArg")
188-
val overlap = subtractDeps(hiddenInArg.overlapWith(footprint), arg)
191+
val overlap = hiddenInArg.overlapWith(footprint).deductCapturesOf(deps(arg))
189192
if !overlap.isEmpty then
190193
sepApplyError(fn, args, idx, overlap, hiddenInArg, footprints.toList, deps)
191194
footprint ++= captures(arg).footprint
@@ -267,7 +270,8 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
267270
case tree: ValOrDefDef =>
268271
traverseChildren(tree)
269272
if previousDefs.nonEmpty && !tree.symbol.isOneOf(TermParamOrAccessor) then
270-
defsShadow ++= captures(tree.tpt).hidden.footprint
273+
capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hidden.footprint}")
274+
defsShadow ++= captures(tree.tpt).hidden.footprint.deductSym(tree.symbol)
271275
resultType(tree.symbol) = tree.tpt.nuType
272276
previousDefs.head += tree
273277
case _ =>

tests/neg-custom-args/captures/lazyref.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
|and captures {cap2}, but this capability is also passed separately
3636
|in the function prefix with type (LazyRef[Int]{val elem: () ->{ref2*} Int} | (ref1 : LazyRef[Int]{val elem: () ->{cap1} Int}^{cap1}))^{ref2}.
3737
|
38-
| Capture set of function prefix : {ref1, ref2}
38+
| Capture set of function prefix : {ref1, ref2, ref2*}
3939
| Hidden set of current argument : {cap2}
40-
| Footprint of function prefix : {ref1, ref2, cap1, cap2}
40+
| Footprint of function prefix : {ref1, ref2, ref2*, cap1, cap2}
4141
| Hidden footprint of current argument : {cap2}
4242
| Declared footprint of current argument: {}
4343
| Undeclared overlap of footprints : {cap2}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:7:10 ---------------------------------------------------------
2+
7 | println(c) // error
3+
| ^
4+
| Separation failure: Illegal access to {c} which is hidden by the previous definition
5+
| of value xs with type List[box () => Unit].
6+
| This type hides capabilities {xs*, c}
7+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:10:33 --------------------------------------------------------
8+
10 | foo((() => println(c)) :: Nil, c) // error
9+
| ^
10+
| Separation failure: argument of type (c : Object^)
11+
| to method foo: (xs: List[box () => Unit], y: Object^): Nothing
12+
| corresponds to capture-polymorphic formal parameter y of type Object^
13+
| and captures {c}, but this capability is also passed separately
14+
| in the first argument with type List[box () ->{c} Unit].
15+
|
16+
| Capture set of first argument : {c}
17+
| Hidden set of current argument : {c}
18+
| Footprint of first argument : {c}
19+
| Hidden footprint of current argument : {c}
20+
| Declared footprint of current argument: {}
21+
| Undeclared overlap of footprints : {c}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import language.future // sepchecks on
2+
3+
def foo(xs: List[() => Unit], y: Object^) = ???
4+
5+
def Test(c: Object^) =
6+
val xs: List[() => Unit] = (() => println(c)) :: Nil
7+
println(c) // error
8+
9+
def Test2(c: Object^) =
10+
foo((() => println(c)) :: Nil, c) // error

0 commit comments

Comments
 (0)