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

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

There is a newer version: 3.6.4-RC1-bin-20241220-0bfa1af-NIGHTLY
Show newest version
package dotty.tools.dotc
package transform

import core.*
import dotty.tools.dotc.transform.MegaPhase.*
import Flags.*
import Contexts.*
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.name

  override def description: String = CheckReentrant.description

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

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

  private val scalaJSIRPackageClass = new CtxLazy(
    getPackageClassIfDefined("org.scalajs.ir"))

  def isIgnored(sym: Symbol)(using Context): Boolean =
    sym.hasAnnotation(sharableAnnot()) ||
    sym.hasAnnotation(unsharedAnnot()) ||
    sym.topLevelClass.owner == scalaJSIRPackageClass()
      // We would add @sharable annotations on ScalaJSVersions and
      // VersionChecks but we do not have control over that code

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

  def addVars(cls: ClassSymbol)(using 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)) {
              report.error(
                em"""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.parentSyms)
          addVars(parent.asClass)
      }
    }

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

object CheckReentrant:
  val name: String = "checkReentrant"
  val description: String = "check no data races involving global vars"




© 2015 - 2025 Weber Informatics LLC | Privacy Policy