From 7db3a58872593526c2cc175df633161f2ce9cccb Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 27 May 2015 17:29:03 +0200 Subject: Fix illegal inlining of instructions accessing protected members There were two issues in the new inliner that would cause a VerifyError and an IllegalAccessError. First, an access to a public member of package protected class C can only be inlined if the destination class can access C. This is tested by t7582b. Second, an access to a protected member requires the receiver object to be a subtype of the class where the instruction is located. So when inlining such an access, we need to know the type of the receiver object - which we don't have. Therefore we don't inline in this case for now. This can be fixed once we have a type propagation analyis. https://github.com/scala-opt/scala/issues/13. This case is tested by t2106. Force kmpSliceSearch test to delambdafy:inline See discussion on https://github.com/scala/scala/pull/4505. The issue will go away when moving to indy-lambda. --- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 34 ++++++++++++- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 58 +++++++++++++++------- test/files/run/kmpSliceSearch.flags | 1 + test/files/run/t2106.check | 7 +++ test/files/run/t2106.flags | 2 +- .../backend/jvm/opt/InlinerIllegalAccessTest.scala | 16 +++--- 6 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 test/files/run/kmpSliceSearch.flags diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 1b9fd5e298..fffb9286b8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -213,6 +213,35 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) } + /** + * Reconstruct the classfile flags from a Java defined class symbol. + * + * The implementation of this method is slightly different that [[javaFlags]]. The javaFlags + * method is primarily used to map Scala symbol flags to sensible classfile flags that are used + * in the generated classfiles. For example, all classes emitted by the Scala compiler have + * ACC_PUBLIC. + * + * When building a [[ClassBType]] from a Java class symbol, the flags in the type's `info` have + * to correspond exactly to the flags in the classfile. For example, if the class is package + * protected (i.e., it doesn't have the ACC_PUBLIC flag), this needs to be reflected in the + * ClassBType. For example, the inliner needs the correct flags for access checks. + * + * Class flags are listed here: + * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1-200-E.1 + */ + private def javaClassfileFlags(classSym: Symbol): Int = { + assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}") + import asm.Opcodes._ + GenBCode.mkFlags( + if (classSym.isPublic) ACC_PUBLIC else 0, + if (classSym.isFinal) ACC_FINAL else 0, + if (classSym.isInterface) ACC_INTERFACE else ACC_SUPER, // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces. + if (classSym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (classSym.isArtifact) ACC_SYNTHETIC else 0, + if (classSym.hasEnumFlag) ACC_ENUM else 0 + ) + } + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { val superClassSym = if (classSym.isImplClass) ObjectClass else classSym.superClass assert( @@ -230,7 +259,10 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val interfaces = implementedInterfaces(classSym).map(classBTypeFromSymbol) - val flags = javaFlags(classSym) + val flags = { + if (classSym.isJava) javaClassfileFlags(classSym) // see comment on javaClassfileFlags + else javaFlags(classSym) + } /* The InnerClass table of a class C must contain all nested classes of C, even if they are only * declared but not otherwise referenced in C (from the bytecode or a method / field signature). diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index ac5c9ce2e6..e1f0ef0793 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -560,38 +560,62 @@ class Inliner[BT <: BTypes](val btypes: BT) { * @param memberDeclClass The class in which the member is declared (A) * @param memberRefClass The class used in the member reference (B) * + * (B0) JVMS 5.4.3.2 / 5.4.3.3: when resolving a member of class C in D, the class C is resolved + * first. According to 5.4.3.1, this requires C to be accessible in D. + * * JVMS 5.4.4 summary: A field or method R is accessible to a class D (destinationClass) iff * (B1) R is public * (B2) R is protected, declared in C (memberDeclClass) and D is a subclass of C. * If R is not static, R must contain a symbolic reference to a class T (memberRefClass), * such that T is either a subclass of D, a superclass of D, or D itself. + * Also (P) needs to be satisfied. * (B3) R is either protected or has default access and declared by a class in the same * run-time package as D. + * If R is protected, also (P) needs to be satisfied. * (B4) R is private and is declared in D. + * + * (P) When accessing a protected instance member, the target object on the stack (the receiver) + * has to be a subtype of D (destinationClass). This is enforced by classfile verification + * (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.8). + * + * TODO: we cannot currently implement (P) because we don't have the necessary information + * available. Once we have a type propagation analysis implemented, we can extract the receiver + * type from there (https://github.com/scala-opt/scala/issues/13). */ def memberIsAccessible(memberFlags: Int, memberDeclClass: ClassBType, memberRefClass: ClassBType): Either[OptimizerWarning, Boolean] = { // TODO: B3 requires "same run-time package", which seems to be package + classloader (JMVS 5.3.). is the below ok? def samePackageAsDestination = memberDeclClass.packageInternalName == destinationClass.packageInternalName - - val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags - key match { - case ACC_PUBLIC => // B1 - Right(true) - - case ACC_PROTECTED => // B2 - tryEither { - val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && { - val isStatic = (ACC_STATIC & memberFlags) != 0 - isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow + def targetObjectConformsToDestinationClass = false // needs type propagation analysis, see above + + def memberIsAccessibleImpl = { + val key = (ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE) & memberFlags + key match { + case ACC_PUBLIC => // B1 + Right(true) + + case ACC_PROTECTED => // B2 + val isStatic = (ACC_STATIC & memberFlags) != 0 + tryEither { + val condB2 = destinationClass.isSubtypeOf(memberDeclClass).orThrow && { + isStatic || memberRefClass.isSubtypeOf(destinationClass).orThrow || destinationClass.isSubtypeOf(memberRefClass).orThrow + } + Right( + (condB2 || samePackageAsDestination /* B3 (protected) */) && + (isStatic || targetObjectConformsToDestinationClass) // (P) + ) } - Right(condB2 || samePackageAsDestination) // B3 (protected) - } - case 0 => // B3 (default access) - Right(samePackageAsDestination) + case 0 => // B3 (default access) + Right(samePackageAsDestination) + + case ACC_PRIVATE => // B4 + Right(memberDeclClass == destinationClass) + } + } - case ACC_PRIVATE => // B4 - Right(memberDeclClass == destinationClass) + classIsAccessible(memberDeclClass) match { // B0 + case Right(true) => memberIsAccessibleImpl + case r => r } } diff --git a/test/files/run/kmpSliceSearch.flags b/test/files/run/kmpSliceSearch.flags new file mode 100644 index 0000000000..ac96850b69 --- /dev/null +++ b/test/files/run/kmpSliceSearch.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline \ No newline at end of file diff --git a/test/files/run/t2106.check b/test/files/run/t2106.check index f8f625ff46..66a0e707b3 100644 --- a/test/files/run/t2106.check +++ b/test/files/run/t2106.check @@ -1,3 +1,10 @@ +#partest -Ybackend:GenBCode +t2106.scala:7: warning: A::foo()Ljava/lang/Object; is annotated @inline but could not be inlined: +The callee A::foo()Ljava/lang/Object; contains the instruction INVOKEVIRTUAL java/lang/Object.clone ()Ljava/lang/Object; +that would cause an IllegalAccessError when inlined into class Test$. + def main(args: Array[String]): Unit = x.foo + ^ +#partest !-Ybackend:GenBCode t2106.scala:7: warning: Could not inline required method foo because access level required by callee not matched by caller. def main(args: Array[String]): Unit = x.foo ^ diff --git a/test/files/run/t2106.flags b/test/files/run/t2106.flags index 00d3643fd4..a2e413bb22 100644 --- a/test/files/run/t2106.flags +++ b/test/files/run/t2106.flags @@ -1 +1 @@ --optimise -Yinline-warnings +-optimise -Yinline-warnings -Yopt:l:classpath diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala index b4839dcec8..7ed0e13226 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala @@ -32,7 +32,8 @@ class InlinerIllegalAccessTest extends ClearAfterClass { import compiler.genBCode.bTypes._ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, ByteCodeRepository.Classfile) - def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i)) + def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) + throw new AssertionError(textify(i)) @Test def typeAccessible(): Unit = { @@ -176,15 +177,18 @@ class InlinerIllegalAccessTest extends ClearAfterClass { // PROTECTED - // protected accessed in same class, or protected static accessed in subclass(rgD). - // can be inlined to subclasses, and classes in the same package (gCl) - for ((m, declCls) <- Set((rcC, cCl), (rgC, cCl), (rgD, dCl)); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, declCls, c, assertEmpty) + // protected static accessed in same class, or protected static accessed in subclass(rgD). + // can be inlined to sub- and superclasses, and classes in the same package (gCl) + for ((m, declCls) <- Set((rgC, cCl), (rgD, dCl)); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, declCls, c, assertEmpty) // protected in non-subclass and different package for (m <- Set(rcC, rgC)) check(m, cCl, iCl, cOrDOwner) - // non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package - for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, dCl, c, assertEmpty) + // non-static protected accessed in subclass (rcD). + // can be inlined only if the destination class is related (sub- or superclass) or in the same package, + // AND if the receiver object is a subtype of the destination class + // TODO: we cannot check this yet, so the check flags the instruction as causing an IllegalAccess. https://github.com/scala-opt/scala/issues/13 + for ((m, declCls) <- Set((rcC, cCl), (rcD, dCl)); c <- Set(cCl, dCl, eCl, fCl, gCl)) check(m, declCls, c, cOrDOwner) // rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package for (c <- Set(hCl, iCl)) check(rcD, dCl, c, cOrDOwner) -- cgit v1.2.3