All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.scala-fortify_2.13.15.1.1.4.source-code.TranslatorHelpers.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2016-2024 Lightbend, Inc. All rights reserved.
 * No information contained herein may be reproduced or transmitted in any form
 * or by any means without the express written permission of Lightbend, Inc.
 */

package com.lightbend.tools.fortify.plugin

import scala.reflect.internal.Flags
import scala.tools.nsc

import com.fortify.frontend.nst

import nst._
import nodes._

// This is where we put pieces of Translator that are stateless
// and context-independent, yet Scala-version-specific.

trait TranslatorHelpers[T <: nsc.ast.Trees with nsc.symtab.SymbolTable]
    extends TranslatorBase
    with VersionSpecificHelpers[T]
    with Positions {

  val global: T
  import global._
  type Symbol = global.Symbol
  type Tree = global.Tree

  def seen(symbol: Symbol): Unit

  def toClassDecl(symbol: Symbol): STClassDecl = {
    seen(symbol)
    val result = new STClassDecl(symbol.pos)
    result.setSimpleName(symbol.name.decoded)
    result.setName(className(symbol))
    if (!symbol.owner.isEmptyPackageClass)
      result.setNamespace(symbol.owner.fullName)
    if (symbol.isInterface)
      result.addModifiers(NSTModifiers.Interface)
    else if (isAbstract(symbol))
      result.addModifiers(NSTModifiers.Abstract)
    if (isSyntheticClass(symbol))
      result.addModifiers(NSTModifiers.Synthetic)
    if (hasJavaEnumFlag(symbol)) {
      // emit same modifiers Java translator emits
      result.addModifiers(NSTModifiers.Enum)
      result.addModifiers(NSTModifiers.Final)
      result.addModifiers(NSTModifiers.Static)
    }
    result.addModifiers(NSTModifiers.Public)
    for (parent <- symbol.parentSymbols if parent != definitions.AnyClass)
      result.addExtends(typeForSymbol(parent, ref = false))
    result
  }

  def toFunDecl(symbol: Symbol, params: List[Symbol]): STFunDecl = {
    seen(symbol)
    val result = new STFunDecl(symbol.pos)
    result.setName(methodName(symbol))
    result.setSimpleName(toSimpleName(symbol))
    if (!symbol.isStaticMember) {
      val xthis =
        new STVarDecl(symbol.owner.pos, "this~", typeForSymbol(symbol.owner))
      result.addParameter(xthis)
    }
    for (param <- params)
      result.addParameter(
        new STVarDecl(param.pos, variableName(param, uniquify = false), typeForType(param.tpe)))
    val returnTypeSymbol = symbol.tpe.resultType.typeSymbol
    // in NST constructors have void return type
    val isVoid =
      symbol.isConstructor ||
        returnTypeSymbol == definitions.UnitClass
    result.setReturnType(
      if (isVoid)
        VoidType
      else if (symbol == definitions.Object_synchronized)
        new STType.STAnyType
      else if (symbol == definitions.Object_asInstanceOf)
        new STType.STAnyType
      else if (symbol == definitions.Object_isInstanceOf)
        STType.makePrimitiveBoolean(new SourceInfo)
      else
        typeForType(symbol.tpe.resultType))
    result.setModifiers(NSTModifiers.Public)
    if (isAbstract(symbol) && !isLateDeferred(symbol)) // latter via VersionSpecificHelpers
      result.setModifiers(NSTModifiers.Abstract)
    if (symbol.isStaticMember)
      result.setModifiers(NSTModifiers.Static)
    if (isSyntheticMethod(symbol))
      result.setModifiers(NSTModifiers.Synthetic)
    for (over <- overrides(symbol))
      result.addOverride(methodName(over))
    result
  }

  def paramNames(dd: DefDef): List[Symbol] =
    dd.vparamss.flatten.map(_.symbol)

  def overrides(symbol: Symbol): List[Symbol] =
    if (isImplClass(symbol.owner)) // via VersionSpecificHelpers
      Nil
    else if (symbol == definitions.Object_equals)
      // special case: don't let a `scala.Any` parent get in,
      // we generally try to keep `Any` out of the closure, especially
      // when it's clearly Java stuff like this
      Nil
    else
      global
        // gross typecast, but pulling in all of Global above just
        // for this one thing would be gross too.. :-\
        .asInstanceOf[nsc.Global]
        // we need to time travel here, because eventually this information gets lost. we had
        // `exitingTyper` but that turned out to be too far back in some cases, apparently because
        // when multiple parameter lists get flattened by uncurry, it makes new symbols? so let's
        // not time travel quite so far. see lightbend/scala-fortify#297
        .exitingUncurry(symbol.overrides)
        // further keeping `Any` out of the closure
        .filterNot(_ == definitions.Any_equals)

  def addModuleInit(classDef: ClassDef, clazz: STClassDecl): Unit = {
    clazz.addField {
      val field = new STFieldDecl(classDef.pos)
      field.setName("MODULE$")
      field.setType(typeForSymbol(classDef.symbol, ref = true))
      field.setModifiers(NSTModifiers.Public)
      field.setModifiers(NSTModifiers.Static)
      field.setModifiers(NSTModifiers.Synthetic)
      field
    }
    val staticInit = new STFunDecl(classDef.pos)
    staticInit.setModifiers(NSTModifiers.Public)
    staticInit.setModifiers(NSTModifiers.Static)
    staticInit.setReturnType(VoidType)
    val squig = squiggles(className(classDef.symbol))
    staticInit.setName(s"$squig~~~S~")
    staticInit.setSimpleName("")
    val body = new STBlock(classDef.pos)
    val assign = new STAssignmentStmt(classDef.pos)
    val access =
      new STStaticFieldAccess(classDef.pos, "MODULE$", typeForSymbol(classDef.symbol, ref = false))
    assign.setLeft(access)
    assign.setRight {
      val rhs = new STAllocation(classDef.pos)
      rhs.setType(typeForSymbol(classDef.symbol, ref = false))
      rhs
    }
    body.add(assign)
    body.add {
      val call = new STFunCall(classDef.pos)
      call.setName(s"$squig~~init^~L$squig^")
      call.addArgument(access)
      call
    }
    staticInit.setBody(body)
    clazz.addFunction(staticInit)
  }

  def toSimpleName(symbol: Symbol): String =
    if (symbol.isConstructor)
      symbol.owner.name.decoded
    else if (symbol.isLifted && !isSyntheticMethod(symbol) && !symbol.simpleName
        .startsWith(nme.ANON_FUN_NAME))
      symbol.name.decoded
        .replaceFirst("\\$\\d+$", "") // undo LambdaLift.renameSym for local defs
    else
      symbol.name.decoded

  def isSyntheticClass(symbol: Symbol): Boolean =
    symbol.isSynthetic &&
      // there's user code in these!
      !symbol.isAnonymousFunction

  def isSyntheticMethod(symbol: Symbol): Boolean =
    (symbol.isSynthetic &&
      !symbol.name.startsWith("delayedEndpoint$")) || // these contain actual user code
      symbol.isSpecialized ||
      symbol.isAccessor ||
      symbol.isParamAccessor ||
      (symbol.isConstructor && symbol.owner.isSynthetic) ||
      symbol.name.endsWith("$extension")

  def toFieldDecl(symbol: Symbol): STFieldDecl = {
    val result = new STFieldDecl(symbol.pos)
    result.setName(variableName(symbol, uniquify = false))
    result.setType(typeForType(symbol.tpe, ref = true))
    // we don't want to mark `private[this]` fields as synthetic, since they
    // have no accessors, there is nothing but the field.  so that's the reason
    // we call getterIn here
    if (!symbol.isJavaDefined && (symbol.isImplementationArtifact || symbol
        .getterIn(symbol.owner) != NoSymbol))
      result.setModifiers(NSTModifiers.Synthetic)
    result.setModifiers(
      if (symbol.isPublic)
        NSTModifiers.Public
      else
        NSTModifiers.Private)
    if (symbol.isStaticMember)
      result.setModifiers(NSTModifiers.Static)
    result
  }

  def methodName(symbol: Symbol): String =
    // special case because 2.13 gives the expected name
    // but 2.11 and 2.12 have `scala~Any` as the third type.
    // the 2.13 one seems right to me, and idk if SCA has special handling
    // of `equals` but if it does, matching Java here should help it
    if (symbol eq definitions.Object_equals)
      "java~lang~Object~~equals~Ljava~lang~Object^Ljava~lang~Object^"
    else {
      seen(symbol)
      seen(symbol.owner)
      val clazz = squiggles(className(symbol.owner))
      val name =
        if (symbol.isConstructor) "init^"
        else unDollar(symbol.name.toString)
      val extra =
        if (symbol.isStaticMember)
          "S~"
        else
          s"L$clazz^"
      val argTypes =
        if (symbol.name.toString.matches(""".*\$extension\d*"""))
          // for reasons that utterly baffle me, `paramTypes` was giving me different
          // results when running the actual plugin, than when running EndToEndTests
          // here in-repo. (I have never experienced that before in all my years
          // of working on this project!) so let's get the types a different way.
          // it's an open question what the right way is to translate
          // calls to `implicit class` based extension methods, and the way we are
          // currently doing it isn't necessarily right. but this is the
          // narrowest fix I could come up with to eliminate the behavior difference
          // between the two repos without altering existing test outputs
          // in the test repo. if it proves difficult to align the Scala 3 behavior
          // with this I could rethink, otherwise :shrug: I guess. as usual, if
          // it seems unlikely to affect vuln detection it isn't worth pouring too
          // much time into.
          global.asInstanceOf[nsc.Global]
            .exitingErasure(symbol.info.paramss.flatten.map(_.tpe))
        else
          symbol.info.paramTypes
      val argTypeString =
        argTypes
          .map(typeString)
          .map(unDollar)
          .mkString
      s"$clazz~~$name~$extra$argTypeString"
    }

  private val uniqueNames = collection.mutable.Map[Symbol, String]()
  private val usedNames = collection.mutable.Set[String]()

  def resetLocalNames(): Unit = {
    uniqueNames.clear()
    usedNames.clear()
  }

  def uniquifyVariable(sym: Symbol): String =
    uniqueNames.getOrElse(
      sym, {
        val originalName = sym.name.toString
        val newName =
          if (usedNames.contains(originalName)) {
            var index = 1
            while (usedNames.contains(s"$originalName~$index")) index += 1
            s"$originalName~$index"
          }
          else originalName
        usedNames += newName
        uniqueNames(sym) = newName
        newName
      }
    )

  def variableName(symbol: Symbol, uniquify: Boolean = false): String = {
    // for field names scalac is somehow coming up with a name
    // with a space the end, I guess to distinguish it from the
    // same-named accessor?
    def stripSpaceFromFieldName(s: String) =
      s.replaceFirst(" $", "")
    def baseName =
      if (isOuterParam(symbol))
        "@outer" // otherwise we end up with "arg@outer"
      else if (uniquify)
        uniquifyVariable(symbol)
      else
        symbol.name.toString
    val result = stripSpaceFromFieldName(unDollar(baseName))
    if (symbol.isSynthetic || symbol.isArtifact)
      s"~t~$result"
    else
      result
  }

  // copied from Scala 2.12 sources
  def isOuterParam(symbol: Symbol): Boolean =
    symbol.isParameter && symbol.owner.isConstructor && (symbol.name == TermName(
      "arg" + nme.OUTER) || symbol.name == nme.OUTER)

  def typeForSymbol(symbol: Symbol, ref: Boolean = true): STType = {
    seen(symbol)
    symbol match {
      case definitions.UnitClass =>
        STType.makePrimitiveVoid(new SourceInfo)
      case definitions.IntClass =>
        STType.makePrimitiveInt(new SourceInfo)
      case definitions.BooleanClass =>
        STType.makePrimitiveBoolean(new SourceInfo)
      case definitions.FloatClass =>
        STType.makePrimitiveFloat(new SourceInfo)
      case definitions.DoubleClass =>
        STType.makePrimitiveDouble(new SourceInfo)
      case definitions.CharClass =>
        STType.makePrimitiveChar(new SourceInfo)
      case definitions.ByteClass =>
        STType.makePrimitiveByte(new SourceInfo)
      case definitions.ShortClass =>
        STType.makePrimitiveShort(new SourceInfo)
      case definitions.LongClass =>
        STType.makePrimitiveLong(new SourceInfo)
      case _ =>
        val classType = new STType.STClassType(symbol.pos, className(symbol))
        if (ref)
          new STType.STPointerType(symbol.pos, classType)
        else
          classType
    }
  }

  // use this instead of typeForSymbol in more places, maybe eliminate
  // typeForSymbol entirely? be careful about "seen"
  def typeForType(tpe: Type, ref: Boolean = true): STType = {
    seen(tpe.typeSymbol)
    tpe.typeSymbol match {
      case definitions.ArrayClass =>
        val arr =
          new STType.STArrayType(tpe.typeSymbol.pos, typeForType(tpe.typeArgs.head, ref = true))
        if (ref)
          new STType.STPointerType(tpe.typeSymbol.pos, arr)
        else
          arr
      case sym =>
        typeForSymbol(sym, ref)
    }
  }

  def typeString(tpe: Type): String = {
    seen(tpe.typeSymbol)
    tpe match {
      case ErasedValueType(_, underlying) =>
        typeString(underlying)
      case _ =>
        tpe.typeSymbol match {
          case definitions.IntClass =>
            "I^"
          case definitions.BooleanClass =>
            "Z^"
          case definitions.CharClass =>
            "C^"
          case definitions.LongClass =>
            "L^"
          case definitions.FloatClass =>
            "F^"
          case definitions.DoubleClass =>
            "D^"
          case definitions.ShortClass =>
            "S^"
          case definitions.ByteClass =>
            "B^"
          case definitions.ArrayClass =>
            s"?${typeString(tpe.typeArgs.head)}"
          case definitions.AnyClass =>
            s"Ljava~lang~Object^"
          case _ =>
            s"L${squiggles(className(tpe.typeSymbol))}^"
        }
    }
  }

  def className(symbol: Symbol): String =
    symbol.fullName +
      (if (symbol.isModuleClass && !symbol.isJavaDefined) "$" else "")

  /// other helpers

  def isPatternMatch(block: Block): Boolean = {
    val Block(stats, _) = block
    stats
      .collectFirst { case label: LabelDef => label }
      .exists(treeInfo.hasSynthCaseSymbol)
  }

  private lazy val numericTypes =
    Set[Symbol](
      definitions.IntClass, definitions.DoubleClass, definitions.FloatClass, definitions.ShortClass,
      definitions.ByteClass, definitions.LongClass, definitions.CharClass
    )

  def isNumericType(tpe: Type): Boolean =
    numericTypes(tpe.typeSymbol)

  def isReferenceType(tpe: Type): Boolean =
    tpe <:< definitions.AnyRefTpe

  def isBooleanType(tpe: Type): Boolean =
    tpe.typeSymbol match {
      case definitions.BooleanClass =>
        true
      case _ =>
        false
    }

  def isArrayType(tpe: Type): Boolean =
    tpe.typeSymbol match {
      case definitions.ArrayClass =>
        true
      case _ =>
        false
    }

  def isAbstract(sym: Symbol): Boolean =
    sym.isAbstract ||
      sym.hasFlag(Flags.SUPERACCESSOR) ||
      // via VersionSpecificHelpers
      hasSynthesizeImplInSubclassFlag(sym)

  // 2.12 has this, 2.11 doesn't, so inline the 2.11 definition
  def isField(s: Symbol): Boolean = {
    import s._
    isTerm && !isModule && (!isMethod || owner.isTrait && isAccessor)
  }

  def shouldTranslate(dd: DefDef): Boolean =
    dd.symbol.isOuterAccessor ||
      dd.symbol.isSetter ||
      definitions.isJavaMainMethod(dd.symbol) ||
      (!isDelambdafyTarget(dd.symbol) &&
        !dd.symbol.hasFlag(Flags.BRIDGE))

  def synthesizeMain(classDef: ClassDef): Option[STClassDecl] = {
    val mainOption =
      classDef.symbol.tpe
        .member(nme.main)
        .alternatives
        .find(definitions.isJavaMainMethod)
    mainOption.map { main =>
      val clazz = toClassDecl(classDef.symbol.companionSymbol)
      clazz.addFunction {
        val fun = new STFunDecl
        fun.setName {
          val clazzName = squiggles(className(classDef.symbol.companionSymbol))
          s"$clazzName~~main~S~~?Ljava~lang~String^"
        }
        fun.addParameter(
          new STVarDecl(
            main.pos,
            "args",
            new STType.STPointerType(
              main.pos,
              new STType.STArrayType(
                main.pos,
                new STType.STPointerType(
                  main.pos,
                  new STType.STClassType(className(definitions.StringClass)))))
          ))
        fun.setSimpleName("main")
        fun.setReturnType(VoidType)
        fun.setModifiers(NSTModifiers.Public, NSTModifiers.Static, NSTModifiers.Synthetic)
        fun.setBody {
          val block = new STBlock(main.pos)
          block.add {
            val call = new STFunCall(main.pos)
            call.setVirtual()
            call.setName(methodName(main))
            seen(classDef.symbol)
            call.addArgument(
              new STStaticFieldAccess(
                main.pos,
                "MODULE$",
                new STType.STClassType(main.pos, classDef.symbol.fullName + "$")))
            call.addArgument(new STVarAccess(main.pos, "args"))
            call
          }
          block
        }
        fun
      }
      clazz
    }
  }

  // cribbed from Scala.js
  object WrapArray {
    import definitions.{getMemberMethod, PredefModule}
    lazy val isWrapArray: Set[Symbol] = Seq(
      nme.wrapRefArray, nme.wrapByteArray, nme.wrapShortArray, nme.wrapCharArray, nme.wrapIntArray,
      nme.wrapLongArray, nme.wrapFloatArray, nme.wrapDoubleArray, nme.wrapBooleanArray,
      nme.wrapUnitArray, nme.genericWrapArray
    ).map(getMemberMethod(PredefModule, _)).toSet
    def unapply(tree: Apply): Option[Tree] = tree match {
      case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) =>
        Some(wrapped)
      case _ =>
        None
    }
  }

  /// operators

  val referenceOps: Map[String, NSTOperators] =
    Map(
      "eq" -> NSTOperators.Equal,
      "neq" -> NSTOperators.NotEqual
    )

  val arithmeticOps: Map[String, NSTOperators] =
    Map(
      "$plus" -> NSTOperators.Add,
      "$minus" -> NSTOperators.Subtract,
      "$times" -> NSTOperators.Multiply,
      "$div" -> NSTOperators.Divide,
      "$percent" -> NSTOperators.Mod,
      "$amp" -> NSTOperators.And,
      "$bar" -> NSTOperators.Or,
      "$up" -> NSTOperators.Xor,
      "$eq$eq" -> NSTOperators.Equal,
      "$bang$eq" -> NSTOperators.NotEqual,
      "$less" -> NSTOperators.LessThan,
      "$less$eq" -> NSTOperators.LessThanEqual,
      "$greater" -> NSTOperators.GreaterThan,
      "$greater$eq" -> NSTOperators.GreaterThanEqual,
      "$less$less" -> NSTOperators.Lshift,
      "$greater$greater" -> NSTOperators.Rshift,
      "$greater$greater$greater" -> NSTOperators.Bwrshift
    )

  def conversionOps: Set[String] =
    Set("toDouble", "toLong", "toShort", "toByte", "toFloat", "toChar", "toInt")

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy