Skip to content

Commit 569ad50

Browse files
authored
Ensure that read-only methods don't use exclusive capabilities (#24311)
Ensure that read-only methods don't use non-local exclusive capabilities Fixes #24310
2 parents d20dd28 + c632780 commit 569ad50

File tree

7 files changed

+89
-25
lines changed

7 files changed

+89
-25
lines changed

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

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ sealed abstract class CaptureSet extends Showable:
152152
final def isExclusive(using Context): Boolean =
153153
elems.exists(_.isExclusive)
154154

155-
/** Similar to isExlusive, but also includes capture set variables
155+
/** Similar to isExclusive, but also includes capture set variables
156156
* with unknown status.
157157
*/
158158
final def maybeExclusive(using Context): Boolean = reporting.trace(i"mabe exclusive $this"):
@@ -482,9 +482,16 @@ sealed abstract class CaptureSet extends Showable:
482482
* Fresh instances count as good as long as their ccOwner is outside `upto`.
483483
* If `upto` is NoSymbol, all Fresh instances are admitted.
484484
*/
485-
def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
486-
if elems.exists(isBadRoot(upto, _)) then handler()
487-
this
485+
def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): Unit =
486+
checkAddedElems: elem =>
487+
if isBadRoot(upto, elem) then handler()
488+
489+
/** Invoke handler for each element currently in the set and each element
490+
* added to it in the future.
491+
*/
492+
def checkAddedElems(handler: Capability => Context ?=> Unit)(using Context): Unit =
493+
elems.foreach: elem =>
494+
handler(elem)
488495

489496
/** Invoke handler on the elements to ensure wellformedness of the capture set.
490497
* The handler might add additional elements to the capture set.
@@ -731,8 +738,8 @@ object CaptureSet:
731738
elems -= empty
732739
this
733740

734-
/** A handler to be invoked if the root reference `cap` is added to this set */
735-
var rootAddedHandler: () => Context ?=> Unit = () => ()
741+
/** A list of handlers to be invoked when a new element is added to this set */
742+
var newElemAddedHandlers: List[Capability => Context ?=> Unit] = Nil
736743

737744
/** The limit deciding which capture roots are bad (i.e. cannot be contained in this set).
738745
* @see isBadRoot for details.
@@ -761,9 +768,6 @@ object CaptureSet:
761768
|| super.tryClassifyAs(cls)
762769
&& { narrowClassifier(cls); true }
763770

764-
/** A handler to be invoked when new elems are added to this set */
765-
var newElemAddedHandler: Capability => Context ?=> Unit = _ => ()
766-
767771
var description: String = ""
768772

769773
private var myRepr: Name | Null = null
@@ -816,8 +820,7 @@ object CaptureSet:
816820
catch case ex: AssertionError =>
817821
println(i"error for incl $elem in $this, ${summon[VarState].toString}")
818822
throw ex
819-
if isBadRoot(elem) then
820-
rootAddedHandler()
823+
newElemAddedHandlers.foreach(_(elem))
821824
val normElem = if isMaybeSet then elem else elem.stripMaybe
822825
// assert(id != 5 || elems.size != 3, this)
823826
val res = deps.forall: dep =>
@@ -865,14 +868,13 @@ object CaptureSet:
865868
|| isConst
866869
|| varState.canRecord && { includeDep(cs); true }
867870

868-
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
871+
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): Unit =
869872
rootLimit = upto
870-
rootAddedHandler = handler
871873
super.disallowBadRoots(upto)(handler)
872874

873-
override def ensureWellformed(handler: Capability => (Context) ?=> Unit)(using Context): this.type =
874-
newElemAddedHandler = handler
875-
super.ensureWellformed(handler)
875+
override def checkAddedElems(handler: Capability => Context ?=> Unit)(using Context): Unit =
876+
newElemAddedHandlers = handler :: newElemAddedHandlers
877+
super.checkAddedElems(handler)
876878

877879
private var computingApprox = false
878880

@@ -1012,8 +1014,7 @@ object CaptureSet:
10121014
* Test case: Without that tweak, logger.scala would not compile.
10131015
*/
10141016
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) =
1015-
if isRefining then this
1016-
else super.disallowBadRoots(upto)(handler)
1017+
if !isRefining then super.disallowBadRoots(upto)(handler)
10171018

10181019
end ProperVar
10191020

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,14 @@ class CheckCaptures extends Recheck, SymTransformer:
553553
// fresh capabilities. We do check that they hide no parameter reach caps in checkEscapingUses
554554
case _ =>
555555

556+
def checkReadOnlyMethod(included: CaptureSet, env: Env): Unit =
557+
included.checkAddedElems: elem =>
558+
if elem.isExclusive then
559+
report.error(
560+
em"""Read-only ${env.owner} accesses exclusive capability $elem;
561+
|${env.owner} should be declared an update method to allow this.""",
562+
tree.srcPos)
563+
556564
def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit =
557565
if env.kind != EnvKind.Boxed && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then
558566
// Only captured references that are visible from the environment
@@ -570,6 +578,8 @@ class CheckCaptures extends Recheck, SymTransformer:
570578
if !isOfNestedMethod(env) then
571579
val nextEnv = nextEnvToCharge(env)
572580
if nextEnv != null && !nextEnv.owner.isStaticOwner then
581+
if env.owner.isReadOnlyMethod && nextEnv.owner != env.owner then
582+
checkReadOnlyMethod(included, env)
573583
recur(included, nextEnv, env)
574584
// Under deferredReaches, don't propagate out of methods inside terms.
575585
// The use set of these methods will be charged when that method is called.
@@ -2093,9 +2103,7 @@ class CheckCaptures extends Recheck, SymTransformer:
20932103
if !(pos.span.isSynthetic && ctx.reporter.errorsReported)
20942104
&& !arg.typeSymbol.name.is(WildcardParamName)
20952105
then
2096-
CheckCaptures.disallowBadRootsIn(arg, NoSymbol,
2097-
"Array", "have element type", "",
2098-
pos)
2106+
disallowBadRootsIn(arg, NoSymbol, "Array", "have element type", "", pos)
20992107
traverseChildren(t)
21002108
case defn.RefinedFunctionOf(rinfo: MethodType) =>
21012109
traverse(rinfo)

docs/_docs/reference/experimental/capture-checking/mutability.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Ref(init: Int) extends Mutable:
4646
def get: Int = current
4747
update def set(x: Int): Unit = current = x
4848
```
49-
`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters.
49+
`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined in the method itself or passed to it in parameters.
5050

5151
In class `Ref`, the `set` method should be declared as an update method since it accesses `this` as an exclusive write capability by writing to the variable `this.current` in its environment.
5252

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Error: tests/neg-custom-args/captures/i24310.scala:10:16 ------------------------------------------------------------
2+
10 | def run() = f() // error <- note the missing update
3+
| ^
4+
| Read-only method run accesses exclusive capability (Matrix.this.f : () => Int);
5+
| method run should be declared an update method to allow this.
6+
|
7+
| where: => refers to a fresh root capability in the type of value f
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import caps.*
2+
3+
class Ref extends Mutable:
4+
private var value: Int = 0
5+
def get(): Int = value
6+
update def set(v: Int): Unit = value = v
7+
8+
class Matrix(val f: () => Int) extends Mutable:
9+
self: Matrix^ =>
10+
def run() = f() // error <- note the missing update
11+
update def add(): Unit = ()
12+
13+
14+
@main def test =
15+
val r: Ref^ = Ref()
16+
val m: Matrix^ = Matrix(() => 42)
17+
val m2: Matrix^ = Matrix(() => m.run())
18+
val m3: Matrix^ = Matrix(() => r.get())
19+
20+
def par(f1: () => Int, f2: () => Int): Unit =
21+
println(s"par results: ${f1()} and ${f2()}")
22+
23+
def g(m: Matrix^): Unit =
24+
par(m.run, m.run) // <- should be rejected
25+
26+
g(m2) // ok
27+
g(m3) // ok

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
| where: ^ and cap refer to a fresh root capability classified as Mutable in the type of value self2
2323
|
2424
| longer explanation available when compiling with `-explain`
25+
-- Error: tests/neg-custom-args/captures/mutability.scala:11:13 --------------------------------------------------------
26+
11 | self2.set(x) // error
27+
| ^^^^^^^^^^^^
28+
| Read-only method sneakyHide accesses exclusive capability (Ref.this : Ref[T]^);
29+
| method sneakyHide should be declared an update method to allow this.
30+
|
31+
| where: ^ refers to a fresh root capability classified as Mutable in the type of class Ref
2532
-- Error: tests/neg-custom-args/captures/mutability.scala:14:12 --------------------------------------------------------
2633
14 | self3().set(x) // error
2734
| ^^^^^^^^^^^
@@ -42,6 +49,13 @@
4249
| ^ and cap refer to a fresh root capability classified as Mutable in the type of value self4
4350
|
4451
| longer explanation available when compiling with `-explain`
52+
-- Error: tests/neg-custom-args/captures/mutability.scala:16:15 --------------------------------------------------------
53+
16 | self4().set(x) // error
54+
| ^^^^^^^^^^^^^^
55+
| Read-only method sneakyHide accesses exclusive capability (Ref.this : Ref[T]^);
56+
| method sneakyHide should be declared an update method to allow this.
57+
|
58+
| where: ^ refers to a fresh root capability classified as Mutable in the type of class Ref
4559
-- Error: tests/neg-custom-args/captures/mutability.scala:19:12 --------------------------------------------------------
4660
19 | self5().set(x) // error
4761
| ^^^^^^^^^^^
@@ -61,6 +75,13 @@
6175
| where: ^ and cap refer to a fresh root capability classified as Mutable in the result type of method self6
6276
|
6377
| longer explanation available when compiling with `-explain`
78+
-- Error: tests/neg-custom-args/captures/mutability.scala:21:15 --------------------------------------------------------
79+
21 | self6().set(x) // error
80+
| ^^^^^^^^^^^^^^
81+
| Read-only method sneakyHide accesses exclusive capability (Ref.this : Ref[T]^);
82+
| method sneakyHide should be declared an update method to allow this.
83+
|
84+
| where: ^ refers to a fresh root capability classified as Mutable in the type of class Ref
6485
-- Error: tests/neg-custom-args/captures/mutability.scala:25:25 --------------------------------------------------------
6586
25 | def set(x: T) = this.x.set(x) // error
6687
| ^^^^^^^^^^

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ class Ref[T](init: T) extends caps.Mutable:
88
val self = this
99
self.set(x) // error
1010
val self2: Ref[T]^ = this // error
11-
self2.set(x)
11+
self2.set(x) // error
1212

1313
val self3 = () => this
1414
self3().set(x) // error
1515
val self4: () => Ref[T]^ = () => this // error
16-
self4().set(x)
16+
self4().set(x) // error
1717

1818
def self5() = this
1919
self5().set(x) // error
2020
def self6(): Ref[T]^ = this // error
21-
self6().set(x)
21+
self6().set(x) // error
2222

2323
class Ref2[T](init: T) extends caps.Mutable:
2424
val x = Ref[T](init)

0 commit comments

Comments
 (0)