From 911d3757556750716b6e236bc4671349ea35ba52 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Tue, 2 Apr 2024 14:57:28 +0200 Subject: [PATCH] Emit missing `POP` in backend when accessing static fields 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"). --- .../nsc/backend/jvm/BCodeBodyBuilder.scala | 6 ++--- test/files/run/staticQualifier.check | 3 +++ test/files/run/staticQualifier/A_1.scala | 21 ++++++++++++++++ test/files/run/staticQualifier/Test_2.scala | 24 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 test/files/run/staticQualifier.check create mode 100644 test/files/run/staticQualifier/A_1.scala create mode 100644 test/files/run/staticQualifier/Test_2.scala diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 48ed602817ba..77b2d186c309 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -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) { @@ -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") } } diff --git a/test/files/run/staticQualifier.check b/test/files/run/staticQualifier.check new file mode 100644 index 000000000000..fe81901643b0 --- /dev/null +++ b/test/files/run/staticQualifier.check @@ -0,0 +1,3 @@ +42 +hai +42 diff --git a/test/files/run/staticQualifier/A_1.scala b/test/files/run/staticQualifier/A_1.scala new file mode 100644 index 000000000000..d056596542f5 --- /dev/null +++ b/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] + } +} diff --git a/test/files/run/staticQualifier/Test_2.scala b/test/files/run/staticQualifier/Test_2.scala new file mode 100644 index 000000000000..bff989413cdb --- /dev/null +++ b/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) + } +}