package scala.tools.nsc
package backend.jvm
import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.reflect.internal.util.Position
import scala.util.control.ControlThrowable
/**
* Interface for emitting inline warnings. The interface is required because the implementation
* depends on Global, which is not available in BTypes (only in BTypesFromSymbols).
*/
sealed abstract class BackendReporting {
def inlinerWarning(pos: Position, message: String): Unit
}
final class BackendReportingImpl(val global: Global) extends BackendReporting {
import global._
def inlinerWarning(pos: Position, message: String): Unit = {
currentRun.reporting.inlinerWarning(pos, message)
}
}
/**
* Utilities for error reporting.
*
* Defines some tools to make error reporting with Either easier. Would be subsumed by a right-biased
* Either in the standard library (or scalaz \/) (Validation is different, it accumulates multiple
* errors).
*/
object BackendReporting {
def methodSignature(classInternalName: InternalName, name: String, desc: String) = {
classInternalName + "::" + name + desc
}
def methodSignature(classInternalName: InternalName, method: MethodNode): String = {
methodSignature(classInternalName, method.name, method.desc)
}
def assertionError(message: String): Nothing = throw new AssertionError(message)
implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal {
def map[U](f: B => U) = v.right.map(f)
def flatMap[BB](f: B => Either[A, BB]) = v.right.flatMap(f)
def filter(f: B => Boolean)(implicit empty: A): Either[A, B] = v match {
case Left(_) => v
case Right(e) => if (f(e)) v else Left(empty) // scalaz.\/ requires an implicit Monoid m to get m.empty
}
def foreach[U](f: B => U) = v.right.foreach(f)
def getOrElse[BB >: B](alt: => BB): BB = v.right.getOrElse(alt)
/**
* Get the value, fail with an assertion if this is an error.
*/
def get: B = {
assert(v.isRight, v.left.get)
v.right.get
}
/**
* Get the right value of an `Either` by throwing a potential error message. Can simplify the
* implementation of methods that act on multiple `Either` instances. Instead of flat-mapping,
* the first error can be collected as
*
* tryEither {
* eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow
* }
*/
def orThrow: B = v match {
case Left(m) => throw Invalid(m)
case Right(t) => t
}
}
case class Invalid[A](e: A) extends ControlThrowable
/**
* See documentation of orThrow above.
*/
def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) }
final case class WarnSettings(atInlineFailed: Boolean, noInlineMixed: Boolean, noInlineMissingBytecode: Boolean, noInlineMissingScalaInlineInfoAttr: Boolean)
sealed trait OptimizerWarning {
def emitWarning(settings: WarnSettings): Boolean
}
// Method filter in RightBiasedEither requires an implicit empty value. Taking the value here
// in scope allows for-comprehensions that desugar into filter calls (for example when using a
// tuple de-constructor).
implicit object emptyOptimizerWarning extends OptimizerWarning {
def emitWarning(settings: WarnSettings): Boolean = false
}
sealed trait MissingBytecodeWarning extends OptimizerWarning {
override def toString = this match {
case ClassNotFound(internalName, definedInJavaSource) =>
s"The classfile for $internalName could not be found on the compilation classpath." + {
if (definedInJavaSource) "\nThe class is defined in a Java source file that is being compiled (mixed compilation), therefore no bytecode is available."
else ""
}
case MethodNotFound(name, descriptor, ownerInternalName, missingClasses) =>
val (javaDef, others) = missingClasses.partition(_.definedInJavaSource)
s"The method $name$descriptor could not be found in the class $ownerInternalName or any of its parents." +
(if (others.isEmpty) "" else others.map(_.internalName).mkString("\nNote that the following parent classes could not be found on the classpath: ", ", ", "")) +
(if (javaDef.isEmpty) "" else javaDef.map(_.internalName).mkString("\nNote that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: ", ",", ""))
case FieldNotFound(name, descriptor, ownerInternalName, missingClass) =>
s"The field node $name$descriptor could not be found because the classfile $ownerInternalName cannot be found on the classpath." +
missingClass.map(c => s" Reason:\n$c").getOrElse("")
}
def emitWarning(settings: WarnSettings): Boolean = this match {
case ClassNotFound(_, javaDefined) =>
if (javaDefined) settings.noInlineMixed
else settings.noInlineMissingBytecode
case m @ MethodNotFound(_, _, _, missing) =>
if (m.isArrayMethod) false
else settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
case FieldNotFound(_, _, _, missing) =>
settings.noInlineMissingBytecode || missing.exists(_.emitWarning(settings))
}
}
case class ClassNotFound(internalName: InternalName, definedInJavaSource: Boolean) extends MissingBytecodeWarning
case class MethodNotFound(name: String, descriptor: String, ownerInternalNameOrArrayDescriptor: InternalName, missingClasses: List[ClassNotFound]) extends MissingBytecodeWarning {
def isArrayMethod = ownerInternalNameOrArrayDescriptor.charAt(0) == '['
}
case class FieldNotFound(name: String, descriptor: String, ownerInternalName: InternalName, missingClass: Option[ClassNotFound]) extends MissingBytecodeWarning
sealed trait NoClassBTypeInfo extends OptimizerWarning {
override def toString = this match {
case NoClassBTypeInfoMissingBytecode(cause) =>
cause.toString
case NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName) =>
s"Failed to get the type of class symbol $classFullName due to SI-9111."
}
def emitWarning(settings: WarnSettings): Boolean = this match {
case NoClassBTypeInfoMissingBytecode(cause) => cause.emitWarning(settings)
case NoClassBTypeInfoClassSymbolInfoFailedSI9111(_) => settings.noInlineMissingBytecode
}
}
case class NoClassBTypeInfoMissingBytecode(cause: MissingBytecodeWarning) extends NoClassBTypeInfo
case class NoClassBTypeInfoClassSymbolInfoFailedSI9111(classFullName: String) extends NoClassBTypeInfo
/**
* Used in the CallGraph for nodes where an issue occurred determining the callee information.
*/
sealed trait CalleeInfoWarning extends OptimizerWarning {
def declarationClass: InternalName
def name: String
def descriptor: String
def warningMessageSignature = BackendReporting.methodSignature(declarationClass, name, descriptor)
override def toString = this match {
case MethodInlineInfoIncomplete(_, _, _, cause) =>
s"The inline information for $warningMessageSignature may be incomplete:\n" + cause
case MethodInlineInfoMissing(_, _, _, cause) =>
s"No inline information for method $warningMessageSignature could be found." +
cause.map(" Possible reason:\n" + _).getOrElse("")
case MethodInlineInfoError(_, _, _, cause) =>
s"Error while computing the inline information for method $warningMessageSignature:\n" + cause
case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) =>
cause.toString
}
def emitWarning(settings: WarnSettings): Boolean = this match {
case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings)
case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings)
case MethodInlineInfoMissing(_, _, _, None) => settings.noInlineMissingBytecode
case MethodInlineInfoError(_, _, _, cause) => cause.emitWarning(settings)
case RewriteTraitCallToStaticImplMethodFailed(_, _, _, cause) => cause.emitWarning(settings)
}
}
case class MethodInlineInfoIncomplete(declarationClass: InternalName, name: String, descriptor: String, cause: ClassInlineInfoWarning) extends CalleeInfoWarning
case class MethodInlineInfoMissing(declarationClass: InternalName, name: String, descriptor: String, cause: Option[ClassInlineInfoWarning]) extends CalleeInfoWarning
case class MethodInlineInfoError(declarationClass: InternalName, name: String, descriptor: String, cause: NoClassBTypeInfo) extends CalleeInfoWarning
case class RewriteTraitCallToStaticImplMethodFailed(declarationClass: InternalName, name: String, descriptor: String, cause: OptimizerWarning) extends CalleeInfoWarning
sealed trait CannotInlineWarning extends OptimizerWarning {
def calleeDeclarationClass: InternalName
def name: String
def descriptor: String
def calleeMethodSig = BackendReporting.methodSignature(calleeDeclarationClass, name, descriptor)
override def toString = this match {
case IllegalAccessInstruction(_, _, _, callsiteClass, instruction) =>
s"The callee $calleeMethodSig contains the instruction ${AsmUtils.textify(instruction)}" +
s"\nthat would cause an IllegalAccessError when inlined into class $callsiteClass."
case IllegalAccessCheckFailed(_, _, _, callsiteClass, instruction, cause) =>
s"Failed to check if $calleeMethodSig can be safely inlined to $callsiteClass without causing an IllegalAccessError. Checking instruction ${AsmUtils.textify(instruction)} failed:\n" + cause
case MethodWithHandlerCalledOnNonEmptyStack(_, _, _, callsiteClass, callsiteName, callsiteDesc) =>
s"""The operand stack at the callsite in ${BackendReporting.methodSignature(callsiteClass, callsiteName, callsiteDesc)} contains more values than the
|arguments expected by the callee $calleeMethodSig. These values would be discarded
|when entering an exception handler declared in the inlined method.""".stripMargin
case SynchronizedMethod(_, _, _) =>
s"Method $calleeMethodSig cannot be inlined because it is synchronized."
}
def emitWarning(settings: WarnSettings): Boolean = this match {
case _: IllegalAccessInstruction | _: MethodWithHandlerCalledOnNonEmptyStack | _: SynchronizedMethod =>
settings.atInlineFailed
case IllegalAccessCheckFailed(_, _, _, _, _, cause) =>
cause.emitWarning(settings)
}
}
case class IllegalAccessInstruction(calleeDeclarationClass: InternalName, name: String, descriptor: String,
callsiteClass: InternalName, instruction: AbstractInsnNode) extends CannotInlineWarning
case class IllegalAccessCheckFailed(calleeDeclarationClass: InternalName, name: String, descriptor: String,
callsiteClass: InternalName, instruction: AbstractInsnNode, cause: OptimizerWarning) extends CannotInlineWarning
case class MethodWithHandlerCalledOnNonEmptyStack(calleeDeclarationClass: InternalName, name: String, descriptor: String,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
case class SynchronizedMethod(calleeDeclarationClass: InternalName, name: String, descriptor: String) extends CannotInlineWarning
/**
* Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information.
*/
sealed trait ClassInlineInfoWarning extends OptimizerWarning {
override def toString = this match {
case NoInlineInfoAttribute(internalName) =>
s"The Scala classfile $internalName does not have a ScalaInlineInfo attribute."
case ClassSymbolInfoFailureSI9111(classFullName) =>
s"Failed to get the type of a method of class symbol $classFullName due to SI-9111."
case ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass) =>
s"Failed to build the inline information: $missingClass."
case UnknownScalaInlineInfoVersion(internalName, version) =>
s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler."
}
def emitWarning(settings: WarnSettings): Boolean = this match {
case NoInlineInfoAttribute(_) => settings.noInlineMissingScalaInlineInfoAttr
case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings)
case ClassSymbolInfoFailureSI9111(_) => settings.noInlineMissingBytecode
case UnknownScalaInlineInfoVersion(_, _) => settings.noInlineMissingScalaInlineInfoAttr
}
}
case class NoInlineInfoAttribute(internalName: InternalName) extends ClassInlineInfoWarning
case class ClassSymbolInfoFailureSI9111(classFullName: String) extends ClassInlineInfoWarning
case class ClassNotFoundWhenBuildingInlineInfoFromSymbol(missingClass: ClassNotFound) extends ClassInlineInfoWarning
case class UnknownScalaInlineInfoVersion(internalName: InternalName, version: Int) extends ClassInlineInfoWarning
}