You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -726,41 +726,139 @@ Reach capabilities take the form `x*` where `x` is syntactically a regular capab
726
726
It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources
727
727
`Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`:
728
728
```scala
729
-
classSource[X^]:
730
-
privatevarlisteners:Set[Listener^{X^}] =Set.empty
731
-
defregister(x: Listener^{X^}):Unit=
732
-
listeners += x
729
+
classSource[cap X]:
730
+
privatevarlisteners:Set[Listener^{X}] =Set.empty
731
+
defregister(x: Listener^{X}):Unit=
732
+
listeners += x
733
733
734
-
defallListeners:Set[Listener^{X^}] = listeners
734
+
defallListeners:Set[Listener^{X}] = listeners
735
735
```
736
-
The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above
737
-
we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type
736
+
The type variable `cap X` (with `cap` being a soft modifier) can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above
737
+
we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type
738
738
and assigns it to the variable.
739
739
740
-
Captureset variables `X^` are represented as regular type variables with a
741
-
special upper bound `CapSet`. For instance, `Source` could be equivalently
740
+
Capture-set variables `cap X` are represented as regular type variables within the special interval
741
+
`>: CapSet <: CapSet^`. For instance, `Source` could be equivalently
742
742
defined as follows:
743
743
```scala
744
-
classSource[X<:CapSet^]:
745
-
...
744
+
classSource[X>:CapSet<:CapSet^]:
745
+
...
746
746
```
747
-
`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing
748
-
type `CapSet`. For instance:
747
+
`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only
748
+
purpose is to identify capture-set type variables and types. This representation based on `CapSet` is subject to change and
749
+
its direct use is discouraged.
750
+
751
+
Capture-set variables can be inferred like regular type variables. When they should be instantiated
752
+
explicitly one supplies a concrete capture set. For instance:
749
753
```scala
750
-
classAsyncextends caps.Capability
754
+
classAsyncextends caps.Capability
751
755
752
-
deflistener(async: Async):Listener^{async} =???
756
+
deflistener(async: Async):Listener^{async} =???
753
757
754
-
deftest1(async1: Async, others: List[Async]) =
755
-
valsrc=Source[CapSet^{async1, others*}]
756
-
...
758
+
deftest1(async1: Async, others: List[Async]) =
759
+
valsrc=Source[{async1, others*}]
760
+
...
757
761
```
758
762
Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows:
// This is a 'brand" capability to mark what can be mentioned in trusted code
786
+
objecttrustedextends caps.Capability
787
+
788
+
// These capabilities are trusted:
789
+
valtrustedLogger:Logger^{trusted}
790
+
valtrustedChannel:Channel[String]^{trusted}
791
+
// These aren't:
792
+
valuntrustedLogger:Logger^
793
+
valuntrustedChannel:Channel[String]^
794
+
795
+
runSecure: () =>
796
+
trustedLogger.log("Hello from trusted code") // ok
797
+
798
+
runSecure: () =>
799
+
trustedChannel.send("I can send") // ok
800
+
trustedLogger.log(trustedChannel.recv()) // ok
801
+
802
+
runSecure: () =>"I am pure and that's ok"// ok
803
+
804
+
runSecure: () =>
805
+
untrustedLogger.log("I can't be used") // error
806
+
untrustedChannel.send("I can't be used") // error
807
+
```
808
+
The idea is that every capability derived from the marker capability `trusted` (and only those) are eligible to be used in the `block` closure
809
+
passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`
810
+
811
+
## Capability Members
812
+
813
+
Just as parametrization by types can be equally expressed with type members, we could
814
+
also define the `Source[cap X]` class above could using a _capability member_:
Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members,
822
+
capability members can be upper- and lower-bounded with capture sets:
823
+
```scala
824
+
traitThread:
825
+
cap typeCap
826
+
defrun(block: () ->{this.Cap} ->Unit):Unit
827
+
828
+
traitGPUThreadextendsThread:
829
+
cap typeCap>: {cudaMalloc, cudaFree} <: {caps.cap}
830
+
```
831
+
832
+
833
+
We conclude with a more advanced example, showing how capability members and paths to these members can prevent leakage
834
+
of labels for lexically-delimited control operators:
835
+
```scala
836
+
traitLabelextendsCapability:
837
+
cap typeFv// the capability set occurring freely in the `block` passed to `boundary` below.
838
+
839
+
defboundary[T, cap C](block: Label{cap typeFv= {C} } ->{C} T):T=???// ensure free caps of label and block match
840
+
defsuspend[U](label: Label)[cap D<: {label.Fv}](handler: () ->{D} U):U=???// may only capture the free capabilities of label
841
+
842
+
deftest=
843
+
valx=1
844
+
boundary: outer =>
845
+
valy=2
846
+
boundary: inner =>
847
+
valz=3
848
+
valw= suspend(outer) {() => z} // ok
849
+
valv= suspend(inner) {() => y} // ok
850
+
valu= suspend(inner): () =>
851
+
suspend(outer) {() => w + v} // ok
852
+
y
853
+
suspend(outer): () =>
854
+
println(inner) // error (would leak the inner label)
855
+
x + y + z
856
+
```
857
+
A key property is that `suspend` (think `shift` from delimited continuations) targeting a specific label (such as `outer`) should not accidentally close over labels from a nested `boundary` (such as `inner`), because they would escape their defining scope this way.
858
+
By leveraging capability polymorphism, capability members, and path-dependent capabilities, we can prevent such leaks from occurring at compile time:
859
+
860
+
*`Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`.
861
+
* When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels.
0 commit comments