-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Turn on separation checking for applications
- Use unsafeAssumeSeparate(...) as an escape hatch
- Loading branch information
Showing
25 changed files
with
259 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package dotty.tools | ||
package dotc | ||
package cc | ||
import ast.tpd | ||
import collection.mutable | ||
|
||
import core.* | ||
import Symbols.*, Types.* | ||
import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* | ||
import CaptureSet.{Refs, emptySet} | ||
import config.Printers.capt | ||
import StdNames.nme | ||
|
||
class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: | ||
import tpd.* | ||
import checker.* | ||
|
||
extension (cs: CaptureSet) | ||
def footprint(using Context): CaptureSet = | ||
def recur(elems: CaptureSet.Refs, newElems: List[CaptureRef]): CaptureSet.Refs = newElems match | ||
case newElem :: newElems1 => | ||
val superElems = newElem.captureSetOfInfo.elems.filter: superElem => | ||
!superElem.isMaxCapability && !elems.contains(superElem) | ||
recur(superElems ++ elems, superElems.toList ++ newElems1) | ||
case Nil => elems | ||
val elems: CaptureSet.Refs = cs.elems.filter(!_.isMaxCapability) | ||
CaptureSet(recur(elems, elems.toList)) | ||
|
||
def overlapWith(other: CaptureSet)(using Context): CaptureSet.Refs = | ||
val refs1 = cs.elems | ||
val refs2 = other.elems | ||
def common(refs1: CaptureSet.Refs, refs2: CaptureSet.Refs) = | ||
refs1.filter: ref => | ||
ref.isExclusive && refs2.exists(_.stripReadOnly eq ref) | ||
common(refs1, refs2) ++ common(refs2, refs1) | ||
|
||
private def hidden(elem: CaptureRef)(using Context): CaptureSet.Refs = elem match | ||
case Fresh.Cap(hcs) => hcs.elems.filter(!_.isRootCapability) ++ hidden(hcs) | ||
case ReadOnlyCapability(ref) => hidden(ref).map(_.readOnly) | ||
case _ => emptySet | ||
|
||
private def hidden(cs: CaptureSet)(using Context): CaptureSet.Refs = | ||
val seen: util.EqHashSet[CaptureRef] = new util.EqHashSet | ||
|
||
def hiddenByElem(elem: CaptureRef): CaptureSet.Refs = | ||
if seen.add(elem) then elem match | ||
case Fresh.Cap(hcs) => hcs.elems.filter(!_.isRootCapability) ++ recur(hcs) | ||
case ReadOnlyCapability(ref) => hiddenByElem(ref).map(_.readOnly) | ||
case _ => emptySet | ||
else emptySet | ||
|
||
def recur(cs: CaptureSet): CaptureSet.Refs = | ||
(emptySet /: cs.elems): (elems, elem) => | ||
elems ++ hiddenByElem(elem) | ||
|
||
recur(cs) | ||
end hidden | ||
|
||
private def checkApply(fn: Tree, args: List[Tree])(using Context): Unit = | ||
val fnCaptures = fn.nuType.deepCaptureSet | ||
|
||
def captures(arg: Tree) = | ||
val argType = arg.nuType | ||
argType match | ||
case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot => | ||
argType.deepCaptureSet | ||
case _ => | ||
argType.captureSet | ||
|
||
val argCaptures = args.map(captures) | ||
capt.println(i"check separate $fn($args), fnCaptures = $fnCaptures, argCaptures = $argCaptures") | ||
var footprint = argCaptures.foldLeft(fnCaptures.footprint): (fp, ac) => | ||
fp ++ ac.footprint | ||
val paramNames = fn.nuType.widen match | ||
case MethodType(pnames) => pnames | ||
case _ => args.indices.map(nme.syntheticParamName(_)) | ||
for (arg, ac, pname) <- args.lazyZip(argCaptures).lazyZip(paramNames) do | ||
if arg.needsSepCheck then | ||
val hiddenInArg = CaptureSet(hidden(ac)) | ||
//println(i"check sep $arg / $footprint / $hiddenInArg") | ||
val overlap = hiddenInArg.footprint.overlapWith(footprint) | ||
if !overlap.isEmpty then | ||
def whatStr = if overlap.size == 1 then "this capability" else "these capabilities" | ||
def funStr = | ||
if fn.symbol.exists then i"${fn.symbol}" | ||
else "the function" | ||
report.error( | ||
em"""Separation failure: argument to capture-polymorphic parameter $pname: ${arg.nuType} | ||
|captures ${CaptureSet(overlap)} and also passes $whatStr separately to $funStr""", | ||
arg.srcPos) | ||
footprint ++= hiddenInArg | ||
|
||
private def traverseApply(tree: Tree, argss: List[List[Tree]])(using Context): Unit = tree match | ||
case Apply(fn, args) => traverseApply(fn, args :: argss) | ||
case TypeApply(fn, args) => traverseApply(fn, argss) // skip type arguments | ||
case _ => | ||
if argss.nestedExists(_.needsSepCheck) then | ||
checkApply(tree, argss.flatten) | ||
|
||
def traverse(tree: Tree)(using Context): Unit = | ||
tree match | ||
case tree: GenericApply => | ||
if tree.symbol != defn.Caps_unsafeAssumeSeparate then | ||
tree.tpe match | ||
case _: MethodOrPoly => | ||
case _ => traverseApply(tree, Nil) | ||
traverseChildren(tree) | ||
case _ => | ||
traverseChildren(tree) | ||
end SepChecker | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-- Error: tests/neg-custom-args/captures/cc-dep-param.scala:8:6 -------------------------------------------------------- | ||
8 | foo(a, useA) // error: separation failure | ||
| ^ | ||
| Separation failure: argument to capture-polymorphic parameter x$0: Foo[Int]^ | ||
| captures {a} and also passes this capability separately to method foo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import language.experimental.captureChecking | ||
|
||
trait Foo[T] | ||
def test(): Unit = | ||
val a: Foo[Int]^ = ??? | ||
val useA: () ->{a} Unit = ??? | ||
def foo[X](x: Foo[X]^, op: () ->{x} Unit): Unit = ??? | ||
foo(a, useA) // error: separation failure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
-- Error: tests/neg-custom-args/captures/filevar-expanded.scala:34:19 -------------------------------------------------- | ||
34 | withFile(io3): f => // error: separation failure | ||
| ^ | ||
| Separation failure: argument to capture-polymorphic parameter x$1: (f: test2.File^{io3}) => Unit | ||
| captures {io3} and also passes this capability separately to method withFile | ||
35 | val o = Service(io3) | ||
36 | o.file = f // this is a bit dubious. It's legal since we treat class refinements | ||
37 | // as capture set variables that can be made to include refs coming from outside. | ||
38 | o.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-- Error: tests/neg-custom-args/captures/function-combinators.scala:15:22 ---------------------------------------------- | ||
15 | val b2 = g1.andThen(g1); // error: separation failure | ||
| ^^ | ||
| Separation failure: argument to capture-polymorphic parameter x$0: Int => Int | ||
| captures {ctx1} and also passes this capability separately to method andThen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
class ContextClass | ||
type Context = ContextClass^ | ||
import caps.unsafe.unsafeAssumePure | ||
|
||
def Test(using ctx1: Context, ctx2: Context) = | ||
val f: Int => Int = identity | ||
val g1: Int ->{ctx1} Int = identity | ||
val g2: Int ->{ctx2} Int = identity | ||
val h: Int -> Int = identity | ||
val a1 = f.andThen(f); val _: Int ->{f} Int = a1 | ||
val a2 = f.andThen(g1); val _: Int ->{f, g1} Int = a2 | ||
val a3 = f.andThen(g2); val _: Int ->{f, g2} Int = a3 | ||
val a4 = f.andThen(h); val _: Int ->{f} Int = a4 | ||
val b1 = g1.andThen(f); val _: Int ->{f, g1} Int = b1 | ||
val b2 = g1.andThen(g1); // error: separation failure | ||
val _: Int ->{g1} Int = b2 | ||
val b3 = g1.andThen(g2); val _: Int ->{g1, g2} Int = b3 | ||
val b4 = g1.andThen(h); val _: Int ->{g1} Int = b4 | ||
val c1 = h.andThen(f); val _: Int ->{f} Int = c1 | ||
val c2 = h.andThen(g1); val _: Int ->{g1} Int = c2 | ||
val c3 = h.andThen(g2); val _: Int ->{g2} Int = c3 | ||
val c4 = h.andThen(h); val _: Int -> Int = c4 | ||
|
||
val f2: (Int, Int) => Int = _ + _ | ||
val f2c = f2.curried; val _: Int -> Int ->{f2} Int = f2c | ||
val f2t = f2.tupled; val _: ((Int, Int)) ->{f2} Int = f2t | ||
|
||
val f3: (Int, Int, Int) => Int = ??? | ||
val f3c = f3.curried; val _: Int -> Int -> Int ->{f3} Int = f3c | ||
val f3t = f3.tupled; val _: ((Int, Int, Int)) ->{f3} Int = f3t |
Oops, something went wrong.