Classifying capabilities #23463
Replies: 8 comments 1 reply
-
What this mans for the implementation
|
Beta Was this translation helpful? Give feedback.
-
This looks like a lot of machinery to typecheck |
Beta Was this translation helpful? Give feedback.
-
One possible generalization would be to have besides |
Beta Was this translation helpful? Give feedback.
-
One possible simplification is to specialize the treatment to control capabilites:
This would make sense if we can convince ourselves that the only capability masking operations we envision mask specifically control capabilities. Can we? |
Beta Was this translation helpful? Give feedback.
-
One observation: [C <: {cap.as[Mutable]}] // all elements of the set must be transitively `Mutable`.
[C <: {cap.as[Sharable]} // all elements of the set must be transitively `Sharable`.
[C <: {cap.as[Control]} // all elements of the set must be transitively `Control` capabilties. |
Beta Was this translation helpful? Give feedback.
-
And it is convenient to restrict capabilities of function types. For instance, when doing separation checking we might want to accept only functions over sharable capabilities. This can now be expressed: A ->{cap.as[Sharable]} B We could probably make this nicer by defining capture set aliases: type sharable^ = {cap.as[Sharable]}
A ->{sharable} B @bracevac Do you think this would work? Overall I am coming to believe that this is a fundamental addition to capture checking that solves multiple expressiveness problems we had so far. So I tend to think we should do a trial implementation. |
Beta Was this translation helpful? Give feedback.
-
Maybe we need a more expressive language for what can go into an |
Beta Was this translation helpful? Give feedback.
-
This is a mechanism that enables limiting what can be closed over up to a certain threshold, which is very useful. Without it, the next best thing one could do in the current system is using some form of branding through a variable in context (like we tried with the sub-regions example), but it's quite awkward and I don't think it scales well. Classifiers seem to be less ad hoc, and it'd be good if we allowed users to extend the classifier hierarchy. I think classifiers subsume the advanced use cases from the 2nd-class values works. With finer-grained closure up to, we can emulate, e.g., Fig. 7 in the paper I linked: @classifier trait Read extends Capability
@classifier trait ReadWrite extends Capability
trait File extends Capability:
type R <: Read
type RW <: ReadWrite
given r: R
given rw: RW
// operations guarded by capability members
def read(using this.R): Byte
def write(using this.RW)(b: Byte): Unit
def withFile[U](block: File^ => U): U // unrestricted use of files & other capabilities
def parReduce[U](xs: Seq[U])(op: U ->{cap.only[Read]} U): U // only Read-classified allowed
withFile("foo.txt"): f =>
parReduce(1 to 1000): (a, b) =>
f.write(a) // error ReadWrite is excluded
a + b + f.read() // ok
f.write(0x42) // ok access unrestricted We can understand the proposed forms in terms of set operations involving transitive capture closure, However, the E.g., // File1.scala:
// Here, the universe of classifiers consists of the standard ones: Shareable, Mutable, Control
def mkPure(f: () ->{cap.except[Shareable,Mutable,Control] Unit): () ->{} Unit =
f // everything except all the known classifiers means nothing, so we are "pure" can be subverted if we extend the classifiers elsewhere: // File2.scala
@classifier Sneaky extends Capability
val s: Sneaky = ...
val fakePure: () -> Unit =
File1.mkPure(() => s.boom()) // ok!, since {s}.except[Shareable,Mutable,Control] = {s} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The motivation for this is to come up with a type for
Try.apply
orFuture.apply
that reflects the capability structure.Try.apply
would have a type like this one:The
???
should capture all control capabilities ofbody
(such asLabel
s orCanThrow
s). After theapply
exits, the capabilities owned by body are spent, except for those control capabilities. These are retained in the failure part of aTry
since they will be re-thrown when theTry
is accessed by aget
or a pattern match.The problem is we are lacking a way to express those control capability slices, nor do we have a way to reason about them (i.e. know which ones subsume which other ones, or which ones can be discarded).
The idea is to add a new kind of capability acting as a filter. If
c
is a capability, thenc.only[C]
would stand for all capabilities implied byc
that have a type deriving from classC
. Specialized to control, we could introduce a new traitthat is a base class of control capability classes such as
Label
,CanThrow
, orAsync
. With that structure in place, we could give the following signature toTry.apply
:c.only[C]
is called a filtered capability. Generally,c.only[C]
is a new capability qualifier, alongsidec.rd
andc*
. If multiple qualifiers are given then.only
comes after*
but before.rd
, i.e.c*.only[C].rd
would be in the correct order. There can only be a singleonly
-qualifier on a capability.We now need to develop subsumption rules for filtered-capabilities. We clearly should have
What about the other combinations? When is
c <: c.only[C]
? We do want this to hold sometimes. For instance ifasync: Async
whereAsync
derives fromControl
then it would be nice to be able to conclude thatasync <: async.only[Control]
.Another issue is how to detect disjointness. For instance, a particular operation
op
might capture a Labellbl
and a mutable matrixm
.What is the type of
Try.apply(op())
? Consulting the signature ofTry.apply
we getReplacing
op
with the actual argument, we get{lbl.only[Control], m.only[Control]}
as the capture set of the result.lbl.only[Control]
should simplify tolbl
by the reasoning we outlined before.m.only[Control]
should ideally simplify to nothing, since a mutable matrix would not expected to capture a control capability. So, ideallyTo get there, we introduce classifier capability classes. These are classes (or traits) that declare
ClassifierCapability
as a direct parent class. In particular we would have:A class can not directly or indirectly extend two unrelated classifier capability classes.
Now, looking at subsumption rules, we first need this one:
if
D
is a classifier capability class andc
is classified asD
.Definition
A capability
c
is classified as a classifier capability classD
if thetransitive capture set of
c
only contains capabilities that derive fromD
.Definition The transitive capture set
tcs(c)
of an object capabilityc
consists ofc
itself plus the transitive capture sets of all capabilities captured by the type ofc
. The transitive capture set of a root capabilityc
is just{c}
. The transitivecapture set of
c.rd
is{x.rd | x in tcs(c)}
Definition A capability
c
derives from a classifier classD
if one of the following applies:c
is an object capability of typeT
andT <: D
(alternatively,T
hasD
in its parent classes),c
is a root capabilitycap
which originated from a typeC^{cap, ...}
whereC <: D
.c
is a read capabilityc1.rd
andc1
derives fromD
.c
is a capture set variableX
and all elements in the alias or upper bound ofX
derive fromD
.To make this sound, we have to restrict possible values of
FreshCap
capabilities. If aFreshCap
starts life in the capture set of a classC
that extends a classifier classD
then theFreshCap
instance can subsume only capabilities classified asD
. So FreshCap instances now carry restrictions for disjointness (in separation checking), levels, as well as classifiers.Example
because the
tcs(lbl) = {lbl, cap}
where thecap
is a root capability associated withLabel
which is a subclass of the classifier classControl
.Furthermore, we have
if
D
is a classifier capability class andtcs(c)
only contains capabilities that derive from classifier class unrelated toC
.So
because
m
is classified asMutable
, which is unrelated toControl
.Beta Was this translation helpful? Give feedback.
All reactions