Skip to content

Commit 254076f

Browse files
committed
Modify Var rule to plug unsoundness hole
1 parent 5d0eb54 commit 254076f

File tree

4 files changed

+47
-23
lines changed

4 files changed

+47
-23
lines changed

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -233,28 +233,34 @@ extension (tp: Type)
233233
assert(tp.isTrackableRef)
234234
TermRef(tp, nme.CC_REACH, defn.Any_ccReach)
235235

236-
/** If `ref` is a trackable capture ref, replace all covariant occurrences of a
237-
* universal capture set in `tp` by `{ref*}`. This implements the new aspect of
238-
* the (Var) rule, which can now be stated as follows:
236+
/** If `ref` is a trackable capture ref, and `tp` has only covariant occurrences of a
237+
* universal capture set, replace all these occurrences by `{ref*}`. This implements
238+
* the new aspect of the (Var) rule, which can now be stated as follows:
239239
*
240240
* x: T in E
241241
* -----------
242242
* E |- x: T'
243243
*
244244
* where T' is T with (1) the toplevel capture set replaced by `{x}` and
245-
* (2) all covariant occurrences of cap replaced by `x*`. (1) is standard,
246-
* whereas (2) is new.
245+
* (2) all covariant occurrences of cap replaced by `x*`, provided there
246+
* are no occurrences in `T` at other variances. (1) is standard, whereas
247+
* (2) is new.
247248
*
248249
* Why is this sound? Covariant occurrences of cap must represent capabilities
249250
* that are reachable from `x`, so they are included in the meaning of `{x*}`.
250251
* At the same time, encapsulation is still maintained since no covariant
251252
* occurrences of cap are allowed in instance types of type variables.
252253
*/
253254
def withReachCaptures(ref: Type)(using Context): Type =
254-
val narrowCaps = new TypeMap:
255+
object narrowCaps extends TypeMap:
256+
var ok = true
255257
def apply(t: Type) = t.dealias match
256-
case t1 @ CapturingType(p, cs) if cs.isUniversal && variance > 0 =>
257-
t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet)
258+
case t1 @ CapturingType(p, cs) if cs.isUniversal =>
259+
if variance > 0 then
260+
t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet)
261+
else
262+
ok = false
263+
t
258264
case _ => t match
259265
case t @ CapturingType(p, cs) =>
260266
t.derivedCapturingType(apply(p), cs) // don't map capture set variables
@@ -263,8 +269,12 @@ extension (tp: Type)
263269
ref match
264270
case ref: CaptureRef if ref.isTrackableRef =>
265271
val tp1 = narrowCaps(tp)
266-
if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1")
267-
tp1
272+
if narrowCaps.ok then
273+
if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1")
274+
tp1
275+
else
276+
capt.println(i"cannot narrow $tp of $ref to $tp1")
277+
tp
268278
case _ =>
269279
tp
270280

docs/_docs/internals/cc/alternatives-to-sealed.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ A capture checking variant
1919
E |- x: T'
2020

2121
where T' is T with (1) the toplevel capture set replaced by `{x}` and
22-
(2) all covariant occurrences of cap replaced by `x*`. (1) is standard,
22+
(2) all covariant occurrences of cap replaced by `x*`, provided there
23+
are no occurrences in `T` at other variances. (1) is standard,
2324
whereas (2) is new.
2425

2526
- Why is this sound? Covariant occurrences of cap must represent capabilities that are reachable from `x`, so they are included in the meaning of `{x*}`. At the same time, encapsulation is still maintained since no covariant occurrences of cap are allowed in instance types of
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:22:11 --------------------------------------
2-
22 | cur = (() => f.write()) :: Nil // error since {f*} !<: {xs*}
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:21:11 --------------------------------------
2+
21 | cur = (() => f.write()) :: Nil // error since {f*} !<: {xs*}
33
| ^^^^^^^^^^^^^^^^^^^^^^^
44
| Found: List[box () ->{f} Unit]
55
| Required: List[box () ->{xs*} Unit]
66
|
77
| longer explanation available when compiling with `-explain`
8-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:33:7 ---------------------------------------
9-
33 | (() => f.write()) :: Nil // error since {f*} !<: {xs*}
8+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:7 ---------------------------------------
9+
32 | (() => f.write()) :: Nil // error since {f*} !<: {xs*}
1010
| ^^^^^^^^^^^^^^^^^^^^^^^
1111
| Found: List[box () ->{f} Unit]
1212
| Required: box List[box () ->{xs*} Unit]^?
@@ -15,22 +15,29 @@
1515
| cannot be included in outer capture set {xs*} of value cur which is associated with method runAll1
1616
|
1717
| longer explanation available when compiling with `-explain`
18-
-- Error: tests/neg-custom-args/captures/reaches.scala:36:6 ------------------------------------------------------------
19-
36 | var cur: List[Proc] = xs // error: Illegal type for var
18+
-- Error: tests/neg-custom-args/captures/reaches.scala:35:6 ------------------------------------------------------------
19+
35 | var cur: List[Proc] = xs // error: Illegal type for var
2020
| ^
2121
| Mutable variable cur cannot have type List[box () => Unit] since
2222
| the part box () => Unit of that type captures the root capability `cap`.
23-
-- Error: tests/neg-custom-args/captures/reaches.scala:43:15 -----------------------------------------------------------
24-
43 | val cur = Ref[List[Proc]](xs) // error: illegal type for type argument to Ref
23+
-- Error: tests/neg-custom-args/captures/reaches.scala:42:15 -----------------------------------------------------------
24+
42 | val cur = Ref[List[Proc]](xs) // error: illegal type for type argument to Ref
2525
| ^^^^^^^^^^^^^^^
2626
| Sealed type variable T cannot be instantiated to List[box () => Unit] since
2727
| the part box () => Unit of that type captures the root capability `cap`.
2828
| This is often caused by a local capability in an argument of constructor Ref
2929
| leaking as part of its result.
30-
-- Error: tests/neg-custom-args/captures/reaches.scala:53:31 -----------------------------------------------------------
31-
53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error
30+
-- Error: tests/neg-custom-args/captures/reaches.scala:52:31 -----------------------------------------------------------
31+
52 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error
3232
| ^^^^^^^^^^^^^^^^^^^^
3333
| Sealed type variable A cannot be instantiated to box () => Unit since
3434
| that type captures the root capability `cap`.
3535
| This is often caused by a local capability in an argument of constructor Id
3636
| leaking as part of its result.
37+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:60:27 --------------------------------------
38+
60 | val f1: File^{id*} = id(f) // error
39+
| ^^^^^
40+
| Found: File^{id, f}
41+
| Required: File^{id*}
42+
|
43+
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/reaches.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def runAll0(xs: List[Proc]): Unit =
1717
next()
1818
cur = cur.tail: List[() ->{xs*} Unit]
1919

20-
2120
usingFile: f =>
2221
cur = (() => f.write()) :: Nil // error since {f*} !<: {xs*}
2322

@@ -52,4 +51,11 @@ class Id[sealed -A, sealed +B >: A]():
5251
def test =
5352
val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error
5453
usingFile: f =>
55-
id(() => f.write()) // escape, if it was not for the error above
54+
id(() => f.write()) // escape, if it was not for the error above
55+
56+
def attack2 =
57+
val id: File^ -> File^ = x => x
58+
59+
val leaked = usingFile[File^{id*}]: f =>
60+
val f1: File^{id*} = id(f) // error
61+
f1

0 commit comments

Comments
 (0)