/* NSC -- new Scala compiler
 * Copyright 2005-2014 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc
package backend.jvm

import scala.tools.nsc.Global
import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo, InlineInfo}
import BackendReporting.ClassSymbolInfoFailureSI9111
import scala.tools.asm

/**
 * This trait contains code shared between GenBCode and GenASM that depends on types defined in
 * the compiler cake (Global).
 */
final class BCodeAsmCommon[G <: Global](val global: G) {
  import global._
  import definitions._

  val ExcludedForwarderFlags = {
    import scala.tools.nsc.symtab.Flags._
    // Should include DEFERRED but this breaks findMember.
    SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO
  }

  /**
   * True for classes generated by the Scala compiler that are considered top-level in terms of
   * the InnerClass / EnclosingMethod classfile attributes. See comment in BTypes.
   */
  def considerAsTopLevelImplementationArtifact(classSym: Symbol) = {
    classSym.isImplClass || classSym.isSpecialized
  }

  /**
   * Cache the value of delambdafy == "inline" for each run. We need to query this value many
   * times, so caching makes sense.
   */
  object delambdafyInline {
    private var runId = -1
    private var value = false

    def apply(): Boolean = {
      if (runId != global.currentRunId) {
        runId = global.currentRunId
        value = settings.Ydelambdafy.value == "inline"
      }
      value
    }
  }

  /**
   * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a
   * member class. This method is used to decide if we should emit an EnclosingMethod attribute.
   * It is also used to decide whether the "owner" field in the InnerClass attribute should be
   * null.
   */
  def isAnonymousOrLocalClass(classSym: Symbol): Boolean = {
    assert(classSym.isClass, s"not a class: $classSym")
    val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass
    if (r && settings.Ybackend.value == "GenBCode") {
      // this assertion only holds in GenBCode. lambda lift renames symbols and may accidentally
      // introduce `$lambda` into a class name, making `isDelambdafyFunction` true. under GenBCode
      // we prevent this, see `nonAnon` in LambdaLift.
      // phase travel necessary: after flatten, the name includes the name of outer classes.
      // if some outer name contains $lambda, a non-lambda class is considered lambda.
      assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name)
    }
    r
  }

  /**
   * The next enclosing definition in the source structure. Includes anonymous function classes
   * under delambdafy:inline, even though they are only generated during UnCurry.
   */
  def nextEnclosing(sym: Symbol): Symbol = {
    val origOwner = sym.originalOwner
    // phase travel necessary: after flatten, the name includes the name of outer classes.
    // if some outer name contains $anon, a non-anon class is considered anon.
    if (delambdafyInline() && sym.rawowner.isAnonymousFunction) {
      // SI-9105: special handling for anonymous functions under delambdafy:inline.
      //
      //   class C { def t = () => { def f { class Z } } }
      //
      //   class C { def t = byNameMethod { def f { class Z } } }
      //
      // In both examples, the method f lambda-lifted into the anonfun class.
      //
      // In both examples, the enclosing method of Z is f, the enclosing class is the anonfun.
      // So nextEnclosing needs to return the following chain:  Z - f - anonFunClassSym - ...
      //
      // In the first example, the initial owner of f is a TermSymbol named "$anonfun" (note: not the anonFunClassSym!)
      // In the second, the initial owner of f is t (no anon fun term symbol for by-name args!).
      //
      // In both cases, the rawowner of class Z is the anonFunClassSym. So the check in the `if`
      // above makes sure we don't jump over the anonymous function in the by-name argument case.
      //
      // However, we cannot directly return the rawowner: if `sym` is Z, we need to include method f
      // in the result. This is done by comparing the rawowners (read: lambdalift-targets) of `sym`
      // and `sym.originalOwner`: if they are the same, then the originalOwner is "in between", and
      // we need to return it.
      // If the rawowners are different, the symbol was not in between. In the first example, the
      // originalOwner of `f` is the anonfun-term-symbol, whose rawowner is C. So the nextEnclosing
      // of `f` is its rawowner, the anonFunClassSym.
      //
      // In delambdafy:method we don't have that problem. The f method is lambda-lifted into C,
      // not into the anonymous function class. The originalOwner chain is Z - f - C.
      if (sym.originalOwner.rawowner == sym.rawowner) sym.originalOwner
      else sym.rawowner
    } else {
      origOwner
    }
  }

  def nextEnclosingClass(sym: Symbol): Symbol = {
    if (sym.isClass) sym
    else nextEnclosingClass(nextEnclosing(sym))
  }

  def classOriginallyNestedInClass(nestedClass: Symbol, enclosingClass: Symbol) ={
    nextEnclosingClass(nextEnclosing(nestedClass)) == enclosingClass
  }

  /**
   * Returns the enclosing method for non-member classes. In the following example
   *
   * class A {
   *   def f = {
   *     class B {
   *       class C
   *     }
   *   }
   * }
   *
   * the method returns Some(f) for B, but None for C, because C is a member class. For non-member
   * classes that are not enclosed by a method, it returns None:
   *
   * class A {
   *   { class B }
   * }
   *
   * In this case, for B, we return None.
   *
   * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes).
   * This is a source-level property, so we need to use the originalOwner chain to reconstruct it.
   */
  private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = {
    assert(classSym.isClass, classSym)

    def doesNotExist(method: Symbol) = {
      // (1) SI-9124, some trait methods don't exist in the generated interface. see comment in BTypes.
      // (2) Value classes. Member methods of value classes exist in the generated box class. However,
      //     nested methods lifted into a value class are moved to the companion object and don't exist
      //     in the value class itself. We can identify such nested methods: the initial enclosing class
      //     is a value class, but the current owner is some other class (the module class).
      method.owner.isTrait && method.isImplOnly || { // (1)
        val enclCls = nextEnclosingClass(method)
        exitingPickler(enclCls.isDerivedValueClass) && method.owner != enclCls // (2)
      }
    }

    def enclosingMethod(sym: Symbol): Option[Symbol] = {
      if (sym.isClass || sym == NoSymbol) None
      else if (sym.isMethod) {
        if (doesNotExist(sym)) None else Some(sym)
      }
      else enclosingMethod(nextEnclosing(sym))
    }
    enclosingMethod(nextEnclosing(classSym))
  }

  /**
   * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level
   * property, this method looks at the originalOwner chain. See doc in BTypes.
   */
  private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = {
    assert(classSym.isClass, classSym)
    val r = nextEnclosingClass(nextEnclosing(classSym))
    // this should be an assertion, but we are more cautious for now as it was introduced before the 2.11.6 minor release
    if (considerAsTopLevelImplementationArtifact(r)) devWarning(s"enclosing class of $classSym should not be an implementation artifact class: $r")
    r
  }

  final case class EnclosingMethodEntry(owner: String, name: String, methodDescriptor: String)

  /**
   * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not
   * an anonymous or local class). See doc in BTypes.
   *
   * The class is parametrized by two functions to obtain a bytecode class descriptor for a class
   * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend
   * on the implementation of GenASM / GenBCode, so they need to be passed in.
   */
  def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = {
    // trait impl classes are always top-level, see comment in BTypes
    if (isAnonymousOrLocalClass(classSym) && !considerAsTopLevelImplementationArtifact(classSym)) {
      val enclosingClass = enclosingClassForEnclosingMethodAttribute(classSym)
      val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) match {
        case some @ Some(m) =>
          if (m.owner != enclosingClass) {
            // This should never happen. In case it does, it prevents emitting an invalid
            // EnclosingMethod attribute: if the attribute specifies an enclosing method,
            // it needs to exist in the specified enclosing class.
            devWarning(s"the owner of the enclosing method ${m.locationString} should be the same as the enclosing class $enclosingClass")
            None
          } else some
        case none => none
      }
      Some(EnclosingMethodEntry(
        classDesc(enclosingClass),
        methodOpt.map(_.javaSimpleName.toString).orNull,
        methodOpt.map(methodDesc).orNull))
    } else {
      None
    }
  }

  /**
   * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain.
   *
   * The problem is that we are interested in a source-level property. Various phases changed the
   * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner.
   * Therefore, `sym.isStatic` is not what we want. For example, in
   *   object T { def f { object U } }
   * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
   */
  def isOriginallyStaticOwner(sym: Symbol): Boolean = {
    sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
  }

  /**
   * Reconstruct the classfile flags from a Java defined class symbol.
   *
   * The implementation of this method is slightly different that `javaFlags` in BTypesFromSymbols.
   * 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
   */
  def javaClassfileFlags(classSym: Symbol): Int = {
    assert(classSym.isJava, s"Expected Java class symbol, got ${classSym.fullName}")
    import asm.Opcodes._
    def enumFlags = ACC_ENUM | {
      // Java enums have the `ACC_ABSTRACT` flag if they have a deferred method.
      // We cannot trust `hasAbstractFlag`: the ClassfileParser adds `ABSTRACT` and `SEALED` to all
      // Java enums for exhaustiveness checking.
      val hasAbstractMethod = classSym.info.decls.exists(s => s.isMethod && s.isDeferred)
      if (hasAbstractMethod) ACC_ABSTRACT else 0
    }
    GenBCode.mkFlags(
      if (classSym.isPublic)                                 ACC_PUBLIC    else 0,
      if (classSym.isFinal)                                  ACC_FINAL     else 0,
      // see the link above. javac does the same: ACC_SUPER for all classes, but not interfaces.
      if (classSym.isInterface)                              ACC_INTERFACE else ACC_SUPER,
      // for Java enums, we cannot trust `hasAbstractFlag` (see comment in enumFlags)
      if (!classSym.hasEnumFlag && classSym.hasAbstractFlag) ACC_ABSTRACT  else 0,
      if (classSym.isArtifact)                               ACC_SYNTHETIC else 0,
      if (classSym.hasEnumFlag)                              enumFlags     else 0
    )
  }

  /**
   * The member classes of a class symbol. Note that the result of this method depends on the
   * current phase, for example, after lambdalift, all local classes become member of the enclosing
   * class.
   *
   * Impl classes are always considered top-level, see comment in BTypes.
   */
  def memberClassesForInnerClassTable(classSymbol: Symbol): List[Symbol] = classSymbol.info.decls.collect({
    case sym if sym.isClass && !considerAsTopLevelImplementationArtifact(sym) =>
      sym
    case sym if sym.isModule && !considerAsTopLevelImplementationArtifact(sym) => // impl classes get the lateMODULE flag in mixin
      val r = exitingPickler(sym.moduleClass)
      assert(r != NoSymbol, sym.fullLocationString)
      r
  })(collection.breakOut)

  lazy val AnnotationRetentionPolicyModule       = AnnotationRetentionPolicyAttr.companionModule
  lazy val AnnotationRetentionPolicySourceValue  = AnnotationRetentionPolicyModule.tpe.member(TermName("SOURCE"))
  lazy val AnnotationRetentionPolicyClassValue   = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
  lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))

  /** Whether an annotation should be emitted as a Java annotation
    * .initialize: if 'annot' is read from pickle, atp might be un-initialized
    */
  def shouldEmitAnnotation(annot: AnnotationInfo) = {
    annot.symbol.initialize.isJavaDefined &&
      annot.matches(ClassfileAnnotationClass) &&
      retentionPolicyOf(annot) != AnnotationRetentionPolicySourceValue &&
      annot.args.isEmpty
  }

  def isRuntimeVisible(annot: AnnotationInfo): Boolean = {
    annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr) match {
      case Some(retentionAnnot) =>
        retentionAnnot.assocs.contains(nme.value -> LiteralAnnotArg(Constant(AnnotationRetentionPolicyRuntimeValue)))
      case _ =>
        // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the
        // annotation is emitted with visibility `RUNTIME`
        true
    }
  }

  private def retentionPolicyOf(annot: AnnotationInfo): Symbol =
    annot.atp.typeSymbol.getAnnotation(AnnotationRetentionAttr).map(_.assocs).map(assoc =>
      assoc.collectFirst {
        case (`nme`.value, LiteralAnnotArg(Constant(value: Symbol))) => value
      }).flatten.getOrElse(AnnotationRetentionPolicyClassValue)

  def implementedInterfaces(classSym: Symbol): List[Symbol] = {
    // Additional interface parents based on annotations and other cues
    def newParentForAnnotation(ann: AnnotationInfo): Option[Type] = ann.symbol match {
      case RemoteAttr => Some(RemoteInterfaceClass.tpe)
      case _          => None
    }

    def isInterfaceOrTrait(sym: Symbol) = sym.isInterface || sym.isTrait

    val allParents = classSym.info.parents ++ classSym.annotations.flatMap(newParentForAnnotation)

    // We keep the superClass when computing minimizeParents to eliminate more interfaces.
    // Example: T can be eliminated from D
    //   trait T
    //   class C extends T
    //   class D extends C with T
    val interfaces = erasure.minimizeParents(allParents) match {
      case superClass :: ifs if !isInterfaceOrTrait(superClass.typeSymbol) =>
        ifs
      case ifs =>
        // minimizeParents removes the superclass if it's redundant, for example:
        //  trait A
        //  class C extends Object with A  // minimizeParents removes Object
        ifs
    }
    interfaces.map(_.typeSymbol)
  }

  /**
   * This is a hack to work around SI-9111. The completer of `methodSym` may report type errors. We
   * cannot change the typer context of the completer at this point and make it silent: the context
   * captured when creating the completer in the namer. However, we can temporarily replace
   * global.reporter (it's a var) to store errors.
   */
  def completeSilentlyAndCheckErroneous(sym: Symbol): Boolean = {
    if (sym.hasCompleteInfo) false
    else {
      val originalReporter = global.reporter
      val storeReporter = new reporters.StoreReporter()
      global.reporter = storeReporter
      try {
        sym.info
      } finally {
        global.reporter = originalReporter
      }
      sym.isErroneous
    }
  }

  /**
   * Build the [[InlineInfo]] for a class symbol.
   */
  def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = {
    val traitSelfType = if (classSym.isTrait && !classSym.isImplClass) {
      // The mixin phase uses typeOfThis for the self parameter in implementation class methods.
      val selfSym = classSym.typeOfThis.typeSymbol
      if (selfSym != classSym) Some(classSymToInternalName(selfSym)) else None
    } else {
      None
    }

    val isEffectivelyFinal = classSym.isEffectivelyFinal

    var warning = Option.empty[ClassSymbolInfoFailureSI9111]

    // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some
    // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]].
    val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({
      case methodSym =>
        if (completeSilentlyAndCheckErroneous(methodSym)) {
          // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler.
          if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes")
          warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName))
          None
        } else {
          val name      = methodSym.javaSimpleName.toString // same as in genDefDef
          val signature = name + methodSymToDescriptor(methodSym)

          // Some detours are required here because of changing flags (lateDEFERRED, lateMODULE):
          // 1. Why the phase travel? Concrete trait methods obtain the lateDEFERRED flag in Mixin.
          //    This makes isEffectivelyFinalOrNotOverridden false, which would prevent non-final
          //    but non-overridden methods of sealed traits from being inlined.
          // 2. Why the special case for `classSym.isImplClass`? Impl class symbols obtain the
          //    lateMODULE flag during Mixin. During the phase travel to exitingPickler, the late
          //    flag is ignored. The members are therefore not isEffectivelyFinal (their owner
          //    is not a module). Since we know that all impl class members are static, we can
          //    just take the shortcut.
          val effectivelyFinal = classSym.isImplClass || exitingPickler(methodSym.isEffectivelyFinalOrNotOverridden)

          // Identify trait interface methods that have a static implementation in the implementation
          // class. Invocations of these methods can be re-wrired directly to the static implementation
          // if they are final or the receiver is known.
          //
          // Using `erasure.needsImplMethod` is not enough: it keeps field accessors, module getters
          // and super accessors. When AddInterfaces creates the impl class, these methods are
          // initially added to it.
          //
          // The mixin phase later on filters out most of these members from the impl class (see
          // Mixin.isImplementedStatically). However, accessors for concrete lazy vals remain in the
          // impl class after mixin. So the filter in mixin is not exactly what we need here (we
          // want to identify concrete trait methods, not any accessors). So we check some symbol
          // properties manually.
          val traitMethodWithStaticImplementation = {
            import symtab.Flags._
            classSym.isTrait && !classSym.isImplClass &&
              erasure.needsImplMethod(methodSym) &&
              !methodSym.isModule &&
              !(methodSym hasFlag (ACCESSOR | SUPERACCESSOR))
          }

          val info = MethodInlineInfo(
            effectivelyFinal                    = effectivelyFinal,
            traitMethodWithStaticImplementation = traitMethodWithStaticImplementation,
            annotatedInline                     = methodSym.hasAnnotation(ScalaInlineClass),
            annotatedNoInline                   = methodSym.hasAnnotation(ScalaNoInlineClass)
          )
          Some((signature, info))
        }
    }).toMap

    InlineInfo(traitSelfType, isEffectivelyFinal, methodInlineInfos, warning)
  }
}

object BCodeAsmCommon {
  /**
   * Valid flags for InnerClass attribute entry.
   * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6
   */
  val INNER_CLASSES_FLAGS = {
    asm.Opcodes.ACC_PUBLIC   | asm.Opcodes.ACC_PRIVATE   | asm.Opcodes.ACC_PROTECTED  |
      asm.Opcodes.ACC_STATIC   | asm.Opcodes.ACC_FINAL     | asm.Opcodes.ACC_INTERFACE  |
      asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION |
      asm.Opcodes.ACC_ENUM
  }
}
