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

dotty.tools.dotc.transform.CheckReentrant.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools.dotc
package transform

import core._
import dotty.tools.dotc.transform.MegaPhase._
import Flags._
import Contexts.Context
import Symbols._
import Decorators._


/** A no-op transform that checks whether the compiled sources are re-entrant.
 *  If -Ycheck:reentrant is set, the phase makes sure that there are no variables
 *  that are accessible from a global object. It excludes from checking paths that
 *  are labeled with one of the annotations
 *
 *      @sharable   Indicating a class or val can be safely shared
 *      @unshared   Indicating an object will not be accessed from multiple threads
 *
 *  Currently the analysis is only intended to check the dotty compiler itself. To make
 *  it generally useful we'd need to add at least the following:
 *
 *   - Handle polymorphic instantiation: We might instantiate a generic class
 *     with a type that contains vars. If the class contains fields of the generic
 *     type, this may constitute a path to a shared var, which currently goes undetected.
 *   - Handle arrays: Array elements are currently ignored because they are often used
 *     in an immutable way anyway. To do better, it would be helpful to have a type
 *     for immutable array.
 */
class CheckReentrant extends MiniPhase {
  import ast.tpd._

  override def phaseName: String = "checkReentrant"

  private[this] var shared: Set[Symbol] = Set()
  private[this] var seen: Set[ClassSymbol] = Set()
  private[this] var indent: Int = 0

  private val sharableAnnot = new CtxLazy(implicit ctx =>
    ctx.requiredClass("scala.annotation.internal.sharable"))
  private val unsharedAnnot = new CtxLazy(implicit ctx =>
    ctx.requiredClass("scala.annotation.internal.unshared"))

  def isIgnored(sym: Symbol)(implicit ctx: Context): Boolean =
    sym.hasAnnotation(sharableAnnot()) ||
    sym.hasAnnotation(unsharedAnnot()) ||
    sym.owner == defn.EnumValuesClass
      // enum values are initialized eagerly before use
      // in the long run, we should make them vals

  def scanning(sym: Symbol)(op: => Unit)(implicit ctx: Context): Unit = {
    ctx.log(i"${"  " * indent}scanning $sym")
    indent += 1
    try op
    finally indent -= 1
  }

  def addVars(cls: ClassSymbol)(implicit ctx: Context): Unit = {
    if (!seen.contains(cls) && !isIgnored(cls)) {
      seen += cls
      scanning(cls) {
        for (sym <- cls.classInfo.decls)
          if (sym.isTerm && !sym.isSetter && !isIgnored(sym))
            if (sym.is(Mutable)) {
              ctx.error(
                i"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info}
                   |  use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""")
              shared += sym
            } else if (!sym.is(Method) || sym.isOneOf(Accessor | ParamAccessor)) {
              scanning(sym) {
                sym.info.widenExpr.classSymbols.foreach(addVars)
              }
            }
        for (parent <- cls.classInfo.classParents)
          addVars(parent.classSymbol.asClass)
      }
    }
  }

  override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = {
    if (ctx.settings.YcheckReentrant.value && tree.symbol.owner.isStaticOwner)
      addVars(tree.symbol.owner.asClass)
    tree
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy