Skip to content

Commit

Permalink
Emit missing POP in backend when accessing static fields
Browse files Browse the repository at this point in the history
Since 2.13 we emit fields in objects as static. The backend thus
has some support for static fields.

ASTs that access a static field are usually limited to object
fields and only occur from within the module itself (`GETSTATIC`
in the getter method, `PUTSTATIC` in `clinit`). In this case the
qualifier of the field access AST is always a pure expression
and therefore nothing is emitted (`genLoadQualUnlessElidable`).

Compiler plugins can however generate ASTs where static fields
are accessed with an impure qualifier. This triggers a bug in
the backend where `genLoadQualUnlessElidable` loads the qualifier
reference onto the stack without consuming it. This leads to
either a crash in the backend or at runtime.

The test case is a macro that imitates such a scenario. Before
the fix it crashes at runtime ("Bad type on operand stack").
  • Loading branch information
lrytz committed Apr 2, 2024
1 parent 4e03eb5 commit 911d375
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 3 deletions.
Expand Up @@ -381,7 +381,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
val sym = tree.symbol
generatedType = symInfoTK(sym)
val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
def genLoadQualUnlessElidable(): Unit = { if (!qualSafeToElide) { genLoadQualifier(tree) } }
def genLoadQualUnlessElidable(): Unit = { if (!qualSafeToElide) { genLoadQualifier(tree, drop = true) } }
// receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError, scala/bug#4283
def receiverClass = qualifier.tpe.typeSymbol
if (sym.isModule) {
Expand Down Expand Up @@ -1054,10 +1054,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
}

/* Emit code to Load the qualifier of `tree` on top of the stack. */
def genLoadQualifier(tree: Tree): Unit = {
def genLoadQualifier(tree: Tree, drop: Boolean = false): Unit = {
lineNumber(tree)
tree match {
case Select(qualifier, _) => genLoad(qualifier)
case Select(qualifier, _) => genLoad(qualifier, if (drop) UNIT else tpeTK(qualifier))
case _ => abort(s"Unknown qualifier $tree")
}
}
Expand Down
3 changes: 3 additions & 0 deletions test/files/run/staticQualifier.check
@@ -0,0 +1,3 @@
42
hai
42
21 changes: 21 additions & 0 deletions test/files/run/staticQualifier/A_1.scala
@@ -0,0 +1,21 @@
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

object A {
def foo(x: Int): Int = macro foo_impl

def foo_impl(c: Context)(x: c.Expr[Int]): c.Tree = {
val g = c.universe.asInstanceOf[scala.tools.nsc.Global]
import g._
import scala.tools.nsc.symtab.Flags._

val t = x.tree.asInstanceOf[Tree] match {
case s @ Select(_, n) if n.toString == "f2" =>
val field = s.symbol.accessed
field.setFlag(STATIC).resetFlag(PRIVATE | LOCAL)
s.setSymbol(field)
}

t.asInstanceOf[c.Tree]
}
}
24 changes: 24 additions & 0 deletions test/files/run/staticQualifier/Test_2.scala
@@ -0,0 +1,24 @@
import scala.tools.partest.BytecodeTest
import scala.tools.testkit.ASMConverters._
import scala.tools.asm.Opcodes._

object Test extends BytecodeTest {
val f2 = 42

def getT: Test.type = {
println("hai")
Test
}

def show(): Unit = {
println(A.foo(Test.f2))
println(A.foo(getT.f2))

val ins = instructionsFromMethod(getMethod(loadClassNode("Test$"), "show"))
val gs = ins.count {
case Field(GETSTATIC, "Test$", "f2", _) => true
case _ => false
}
assert(gs == 2)
}
}

0 comments on commit 911d375

Please sign in to comment.