Skip to content

Commit edeee0e

Browse files
committed
Ensure that read-only methods don't use exclusive capabilities
Ensure that read-only methods don't use non-local exclusive capabilities Fixes #24310
1 parent 2f97633 commit edeee0e

File tree

6 files changed

+89
-4
lines changed

6 files changed

+89
-4
lines changed

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

Lines changed: 21 additions & 1 deletion
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"):
@@ -486,6 +486,13 @@ sealed abstract class CaptureSet extends Showable:
486486
if elems.exists(isBadRoot(upto, _)) then handler()
487487
this
488488

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)
495+
489496
/** Invoke handler on the elements to ensure wellformedness of the capture set.
490497
* The handler might add additional elements to the capture set.
491498
*/
@@ -734,6 +741,10 @@ object CaptureSet:
734741
/** A handler to be invoked if the root reference `cap` is added to this set */
735742
var rootAddedHandler: () => Context ?=> Unit = () => ()
736743

744+
/** A handler to be invoked when a new element is added to this set */
745+
var checkAddedElemHandler: Capability => Context ?=> Unit =
746+
elem => ()
747+
737748
/** The limit deciding which capture roots are bad (i.e. cannot be contained in this set).
738749
* @see isBadRoot for details.
739750
*/
@@ -816,6 +827,7 @@ object CaptureSet:
816827
catch case ex: AssertionError =>
817828
println(i"error for incl $elem in $this, ${summon[VarState].toString}")
818829
throw ex
830+
checkAddedElemHandler(elem)
819831
if isBadRoot(elem) then
820832
rootAddedHandler()
821833
val normElem = if isMaybeSet then elem else elem.stripMaybe
@@ -870,6 +882,14 @@ object CaptureSet:
870882
rootAddedHandler = handler
871883
super.disallowBadRoots(upto)(handler)
872884

885+
override def checkAddedElems(handler: Capability => Context ?=> Unit)(using Context): Unit =
886+
val prevHandler = checkAddedElemHandler
887+
checkAddedElemHandler =
888+
elem =>
889+
prevHandler(elem)
890+
handler(elem)
891+
super.checkAddedElems(handler)
892+
873893
override def ensureWellformed(handler: Capability => (Context) ?=> Unit)(using Context): this.type =
874894
newElemAddedHandler = handler
875895
super.ensureWellformed(handler)

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

Lines changed: 10 additions & 0 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.
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)