Skip to content

Leaking capability in Scott-encoded wrapper #15923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Linyxus opened this issue Aug 26, 2022 · 2 comments · Fixed by #15938 or #23198
Closed

Leaking capability in Scott-encoded wrapper #15923

Linyxus opened this issue Aug 26, 2022 · 2 comments · Fixed by #15938 or #23198
Assignees
Labels
cc-experiment Intended to be merged with cc-experiment branch on origin itype:bug
Milestone

Comments

@Linyxus
Copy link
Contributor

Linyxus commented Aug 26, 2022

Compiler version

cc-experiment branch

Minimized code

trait Cap { def use(): Int }
type Id[X] = [T] -> (op: X => T) -> T
def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x)

def bar() = {
  def withCap[X](op: ({*} Cap) => X): X = {
    val cap: {*} Cap = new Cap { def use() = { println("cap is used"); 0 } }
    val result = op(cap)
    result
  }

  val leak = withCap(cap => mkId(cap))
  leak { cap => cap.use() }   // bad
}

Output

It compiles.

Expectation

It should not compile. We expect the type of val leak to be Id[□ {*} Cap], so it will expect a function of (□ {*} Cap) => Unit in leak(...). It will not be possible to adapt cap => cap.use() : ({*} Cap) -> Unit to the type (□ {*} Cap) => Unit since the adaption cap => (unbox cap).use() tries to unbox a reference capturing universal capability.

We should issue an error on leak(...).

@Linyxus Linyxus added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label cc-experiment Intended to be merged with cc-experiment branch on origin and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 26, 2022
@Linyxus
Copy link
Contributor Author

Linyxus commented Aug 26, 2022

Note: the following code using a case class fails the compilation, as expected:

trait Cap { def use(): Int }
case class Id[X](x: X) {
  def apply[T](op: X => T): T = op(x)
}
def mkId[X](x: X): Id[X] = Id(x)

def bar() = {
  def withCap[X](op: ({*} Cap) => X): X = {
    val cap: {*} Cap = new Cap { def use() = { println("cap is used"); 0 } }
    val result = op(cap)
    result
  }

  val leak = withCap(cap => mkId(cap))
  leak { c => c.use() }
}

@Linyxus Linyxus self-assigned this Aug 31, 2022
odersky added a commit that referenced this issue Sep 4, 2022
@Kordyjan Kordyjan added this to the 3.2.2 milestone Aug 1, 2023
@odersky
Copy link
Contributor

odersky commented May 15, 2025

Until recently, this used to give an error at the expected line leak { cap => cap.use() }, which is a bit strange since the original fix was to box some arguments and boxing no longer works with type aliases such as Id.

With #23171 that error is gone. I tried to make a class based version that re-introduces boxing and that would hopefully give an error again:

trait Cap { def use(): Int }
class Id[X](val value: [T] -> (op: X => T) -> T)
def mkId[X](x: X): Id[X] = Id([T] => (op: X => T) => op(x))

def bar() = {
  def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = {
    val cap: Cap = new Cap { def use() = { println("cap is used"); 0 } }
    val result = op(using caps.cap)(cap)
    result
  }

  val leak = withCap(cap => mkId(cap))
}

That gives a crash due to a stack-overflow. The stacktrace seems to indicate a cycle in capture set propagation. That crash is present already in main.

The original and class based versions are in #23171 as tests/pending/neg-custom-args/captures/i15938.scala and i15938a.scala.

@odersky odersky reopened this May 15, 2025
odersky added a commit to dotty-staging/dotty that referenced this issue May 20, 2025
This fixes problems encountered in scala#15923 where we got infinite recursions
caused by infinite streams of new ResultCap instances that were added to capture sets.
The analysis of the problem showed that there is a cycle of dependencies involving
both forwards and backwards propagations of ResultCap instances between
BiMapped capture sets linked by SubstBindingMaps. Each propagation would
create a new derived ResultCap instance.

We could try to solve the problem by having SubstBindingMaps remember their mappings
and have their inverses work backwards. But that could still produce an infinite stream
if there was a cycle of SubstBindingMaps of length > 2. So we fix the problem at the
root by allowing only one derived ResultCap instance per original ResultCap / binder pair.

Fixes scala#15923
odersky added a commit to dotty-staging/dotty that referenced this issue May 21, 2025
This fixes problems encountered in scala#15923 where we got infinite recursions
caused by infinite streams of new ResultCap instances that were added to capture sets.
The analysis of the problem showed that there is a cycle of dependencies involving
both forwards and backwards propagations of ResultCap instances between
BiMapped capture sets linked by SubstBindingMaps. Each propagation would
create a new derived ResultCap instance.

We could try to solve the problem by having SubstBindingMaps remember their mappings
and have their inverses work backwards. But that could still produce an infinite stream
if there was a cycle of SubstBindingMaps of length > 2. So we fix the problem at the
root by allowing only one derived ResultCap instance per original ResultCap / binder pair.

Fixes scala#15923
Linyxus added a commit that referenced this issue May 21, 2025
This fixes problems encountered for #15923 where we got infinite
recursions
caused by infinite streams of new ResultCap instances that were added to
capture sets.
The analysis of the problem showed that there is a cycle of dependencies
involving
both forwards and backwards propagations of ResultCap instances between
BiMapped capture sets linked by SubstBindingMaps. Each propagation would
create a new derived ResultCap instance.

We could try to solve the problem by having SubstBindingMaps remember
their mappings
and have their inverses work backwards. But that could still produce an
infinite stream
if there was a cycle of SubstBindingMaps of length > 2. So we fix the
problem at the
root by allowing only one derived ResultCap instance per original
ResultCap / binder pair.

Fixes #15923

Based on #23184. Only last commit is new.
EnzeXing pushed a commit to EnzeXing/dotty that referenced this issue May 27, 2025
This fixes problems encountered in scala#15923 where we got infinite recursions
caused by infinite streams of new ResultCap instances that were added to capture sets.
The analysis of the problem showed that there is a cycle of dependencies involving
both forwards and backwards propagations of ResultCap instances between
BiMapped capture sets linked by SubstBindingMaps. Each propagation would
create a new derived ResultCap instance.

We could try to solve the problem by having SubstBindingMaps remember their mappings
and have their inverses work backwards. But that could still produce an infinite stream
if there was a cycle of SubstBindingMaps of length > 2. So we fix the problem at the
root by allowing only one derived ResultCap instance per original ResultCap / binder pair.

Fixes scala#15923
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cc-experiment Intended to be merged with cc-experiment branch on origin itype:bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants