Skip to content

Dotty sometimes loads denotation from classpath, even when a more recent version is being typechecked #2137

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
smarter opened this issue Mar 22, 2017 · 0 comments

Comments

@smarter
Copy link
Member

smarter commented Mar 22, 2017

Suppose that some file defines an object:

package dummy
object Foo

While typechecking this file, a term for Foo is entered in the scope of dummy, but no corresponding type is entered because there is no class Foo. However, once compilation is done, we output:

./dummy/Foo.class
./dummy/Foo$.class

If the current directory is on the classpath, this means that any subsequent compiler run will enter a type Foo in the scope of dummy because of how PackageLoader works. Of course, dummy.Foo is not a valid class, TypeAssigner contains a method reallyExists to determine this:

  /** A denotation exists really if it exists and does not point to a stale symbol. */
  final def reallyExists(denot: Denotation)(implicit ctx: Context): Boolean = try
    denot match {
      case denot: SymDenotation =>
        denot.exists && {
          denot.ensureCompleted
          !denot.isAbsent
        }
      case denot: SingleDenotation =>
        val sym = denot.symbol
        (sym eq NoSymbol) || reallyExists(sym.denot)
      case _ =>
        true
    }
  catch {
    case ex: StaleSymbol => false
  }

I think there is a fatal flaw in reallyExists: it requires forcing the info of the potentially-stale/absent symbol, this means we might unpickle trees from the classpath and pollute the scope with things that no longer exist. Here's a demonstration of how this might lead the compiler to crash:

outerFoo_1.scala:

package outer
class Foo

outerinnerFoo_1.scala:

package outer
package inner
object Foo {
  val a: Int = 1
}

outerinnerFoo_2.scala:

package outer
package inner
object Foo {
  // val a: Int = 1
}

outerinnerTest_2.scala

package outer
package inner
object Test {
  val x: Foo = new Foo
}
$ dotc outerFoo_1.scala outerinnerFoo_1.scala
$ dotc outerinnerTest_2.scala outerinnerFoo_2.scala
exception occurred while typechecking outerinnerFoo_2.scala

exception occurred while compiling outerinnerTest_2.scala, outerinnerFoo_2.scala
Exception in thread "main" java.util.NoSuchElementException: None.get
        at scala.None$.get(Option.scala:347)
        at scala.None$.get(Option.scala:345)
        at dotty.tools.dotc.typer.Typer.localTyper(Typer.scala:1500)
        at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:1519)
        at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1577)

Full stacktrace: https://gist.github.com/smarter/c854842847e1e4d12794adabee5ad7c0

I don't know exactly why this is crashing but the root cause here is that when I do val x: Foo in Test, typedIdent will first try outer.inner.Foo, which means calling reallyExists on it and thus unpickling outer.inner.Foo and outer.inner.Foo$ from outer/inner/Foo.class even though we are also compiling outerinnerFoo_2.scala which contains a more recent version of the object outer.inner.Foo$.

The only solution I can think of is that whenever we enter the symbol for a class or object without companion, we should mark the companion as absent to make very sure that we never try to load it from the classpath.

smarter added a commit to dotty-staging/dotty that referenced this issue Mar 23, 2017
… a real one

Previously, we sometimes ended up forcing a companion class symbol from
a previous run or from the classpath which lead to weird issues like in
`tests/pos-special/false-companion`. Even if we end up not forcing such
a symbol, its presence can still lead to issue: before this commit
incremental compilation of `dotty-compiler-bootstrapped` was broken
because we recorded a false dependency on the non-bootstrapped
`dotty-compiler` jar.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 23, 2017
… a real one

Previously, we sometimes ended up forcing a companion class symbol from
a previous run or from the classpath which lead to weird issues like in
`tests/pos-special/false-companion`. Even if we end up not forcing such
a symbol, its presence can still lead to issue: before this commit
incremental compilation of `dotty-compiler-bootstrapped` was broken
because we recorded a false dependency on the non-bootstrapped
`dotty-compiler` jar.
smarter added a commit to dotty-staging/dotty that referenced this issue Mar 23, 2017
… a real one

Previously, we sometimes ended up forcing a companion class symbol from
a previous run or from the classpath which lead to weird issues like in
`tests/pos-special/false-companion`. Even if we end up not forcing such
a symbol, its presence can still lead to issue: before this commit
incremental compilation of `dotty-compiler-bootstrapped` was broken
because we recorded a false dependency on the non-bootstrapped
`dotty-compiler` jar.
smarter added a commit that referenced this issue Mar 28, 2017
Fix #2137: Create dummy companions for top-level objects without a real one
OlivierBlanvillain pushed a commit to dotty-staging/dotty that referenced this issue Mar 30, 2017
… a real one

Previously, we sometimes ended up forcing a companion class symbol from
a previous run or from the classpath which lead to weird issues like in
`false-companion`. Even if we end up not forcing such a symbol, its
presence can still lead to issue: before this commit incremental
compilation of `dotty-compiler-bootstrapped` was broken because we
recorded a false dependency on the non-bootstrapped `dotty-compiler`
jar.

The added test is currently marked pending because it does not work with
JUnit (which doesn't handle separate compilation), only partest. I
didn't managed to get it to work right, and this won't be necessary once
our testing framework is overhauled by
scala#2125 anyway, so I'll just have to
remember to enable this test afterwards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant