-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Closed
Labels
cc-experimentIntended to be merged with cc-experiment branch on originIntended to be merged with cc-experiment branch on originitype:bug
Description
Compiler version
3.1.3-RC4
Minimized code
class Unit
object unit extends Unit
type Top = {*} Any
type LazyVal[T] = {*} Unit -> T
case class Foo[T](x: T)
// Foo[□ {*} Unit -> T]
type BoxedLazyVal[T] = Foo[LazyVal[T]]
def force[A](v: BoxedLazyVal[A]): A =
// Γ ⊢ v.x : □ {*} Unit -> A
v.x(unit) // (unbox v.x)(unit), where (unbox v.x) should be untypable
Output
The code compiles but it shouldn't.
[[syntax trees at end of cc]] // issues/unbox-minimised.scala
package <empty> {
@CaptureChecked @SourceFile("issues/unbox-minimised.scala") class Unit() extends Object() {}
final lazy module val unit: unit = new unit()
@CaptureChecked @SourceFile("issues/unbox-minimised.scala") final module class unit() extends Unit() {
private[this] type $this = unit.type
private def writeReplace(): AnyRef = new scala.runtime.ModuleSerializationProxy(classOf[unit.type])
}
@CaptureChecked @SourceFile("issues/unbox-minimised.scala") case class Foo[T](x: T) extends Object(), Product, Serializable {
override def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(this)
override def equals(x$0: Any): Boolean =
this.eq(x$0.$asInstanceOf[Object]).||(
matchResult1[Boolean]:
{
case val x1: (x$0 : Any) = x$0
if x1.$isInstanceOf[Foo[T] @unchecked] then
{
case val x$0: Foo[T] = x1.$asInstanceOf[Foo[T] @unchecked]
return[matchResult1] this.x.==(x$0.x).&&(x$0.canEqual(this))
}
else ()
return[matchResult1] false
}
)
override def toString(): String = scala.runtime.ScalaRunTime._toString(this)
override def canEqual(that: Any): Boolean = that.isInstanceOf[Foo[T] @unchecked]
override def productArity: Int = 1
override def productPrefix: String = "Foo"
override def productElement(n: Int): Any =
matchResult2[T]:
{
case val x3: (n : Int) = n
if 0.==(x3) then return[matchResult2] this._1 else ()
throw new IndexOutOfBoundsException(n.toString())
}
override def productElementName(n: Int): String =
matchResult3[("x" : String)]:
{
case val x4: (n : Int) = n
if 0.==(x4) then return[matchResult3] "x" else ()
throw new IndexOutOfBoundsException(n.toString())
}
T
val x: T
def copy[T](x: T): Foo[T] = new Foo[T](x)
def copy$default$1[T]: T = Foo.this.x
def _1: T = this.x
}
final lazy module val Foo: Foo = new Foo()
@CaptureChecked @SourceFile("issues/unbox-minimised.scala") final module class Foo() extends AnyRef(), scala.deriving.Mirror.Product {
private[this] type $this = Foo.type
private def writeReplace(): AnyRef = new scala.runtime.ModuleSerializationProxy(classOf[Foo.type])
def apply[T](x: T): Foo[T] = new Foo[T](x)
def unapply[T](x$1: Foo[T]): Foo[T] = x$1
override def toString: String = "Foo"
type MirroredMonoType = Foo[? <: AnyKind]
def fromProduct(x$0: Product): Foo.MirroredMonoType = new Foo[Any](x$0.productElement(0))
}
final lazy module val unbox-minimised$package: unbox-minimised$package = new unbox-minimised$package()
@CaptureChecked @SourceFile("issues/unbox-minimised.scala") final module class unbox-minimised$package() extends Object() {
private[this] type $this = unbox-minimised$package.type
private def writeReplace(): AnyRef = new scala.runtime.ModuleSerializationProxy(classOf[unbox-minimised$package.type])
type Top = {*} Any
type LazyVal = [T] =>> {*} Unit -> T
type BoxedLazyVal = [T] =>> Foo[{*} Unit -> T]
def force[A](v: BoxedLazyVal[A]): A = v.x.apply(unit)
def main(): Unit =
{
abstract class Cap() extends Object() {
def close(): Unit = unit
}
def withCap[T](op: ({*} Cap) => T): T =
{
val cap: ? Cap =
{
final class $anon() extends Cap() {}
new Cap {...}():(? Cap)
}
val result: ? T = op.apply(cap)
cap.close()
result:({result} T)
}
def leaked: {} BoxedLazyVal[Cap] =
withCap[? Foo[{*} (x$0: ? Unit) -> ? Cap]](
{
{
def $anonfun(cap: {*} Cap): ? Foo[{cap, *} (x$0: ? Unit) -> ? Cap] =
{
val bad: {cap} (x$0: {} Unit) -> {cap} Cap =
{
def $anonfun(_$1: Unit): {cap} Cap = cap
closure($anonfun)
}
Foo.apply[{bad, cap, *} (x$0: ? Unit) -> {cap} Cap](bad)
}
closure($anonfun)
}
}
)
val leakedCap: {} Cap = force[? Cap](leaked)
()
}
}
}
-- Warning: issues/unbox-minimised.scala:1:6 -------------------------------------------------------------------------------------------------------------------------------------
1 |class Unit
| ^
| class Unit differs only in case from object unit. Such classes will overwrite one another on case-insensitive filesystems.
1 warning found
[success] Total time: 2 s, completed Jul 26, 2022, 4:25:25 PM
Expectation
This code is minimised from Ondrej's list encoding example (in #15731). In force
, the function v.x
is a boxed function of type □ {*} Unit -> A
, so we should not allow the application v.x(unit)
since we can not unbox it.
This leads to the leaking of scoped capabilities, for example:
def main() = {
abstract class Cap { def close(): Unit = unit }
def withCap[T](op: ({*} Cap) => T): T = {
val cap = new Cap {}
val result = op(cap)
cap.close()
result
}
def leaked: {} BoxedLazyVal[Cap] = withCap { cap =>
val bad = (_: Unit) => cap
Foo(bad)
}
val leakedCap: {} Cap = force(leaked)
}
odersky
Metadata
Metadata
Assignees
Labels
cc-experimentIntended to be merged with cc-experiment branch on originIntended to be merged with cc-experiment branch on originitype:bug