Skip to content

Change backend name handling #2322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 20 additions & 31 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Decorators._
import tpd._

import scala.tools.asm
import StdNames.nme
import StdNames.{nme, str}
import NameOps._
import NameKinds.DefaultGetterName
import dotty.tools.dotc.core
Expand Down Expand Up @@ -161,7 +161,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def boxMethods: Map[Symbol, Symbol] = defn.ScalaValueClasses().map{x => // @darkdimius Are you sure this should be a def?
(x, Erasure.Boxing.boxMethod(x.asClass))
}.toMap
def unboxMethods: Map[Symbol, Symbol] = defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap
def unboxMethods: Map[Symbol, Symbol] =
defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap

override def isSyntheticArrayConstructor(s: Symbol) = {
s eq defn.newArrayMethod
Expand Down Expand Up @@ -244,15 +245,15 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
case ClazzTag => av.visit(name, const.typeValue.toTypeKind(bcodeStore)(innerClasesStore).toASMType)
case EnumTag =>
val edesc = innerClasesStore.typeDescriptor(const.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class.
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
val evalue = const.symbolValue.name.mangledString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
}
case t: TypeApply if (t.fun.symbol == Predef_classOf) =>
av.visit(name, t.args.head.tpe.classSymbol.denot.info.toTypeKind(bcodeStore)(innerClasesStore).toASMType)
case t: tpd.Select =>
if (t.symbol.denot.is(Flags.Enum)) {
val edesc = innerClasesStore.typeDescriptor(t.tpe.asInstanceOf[bcodeStore.int.Type]) // the class descriptor of the enumeration class.
val evalue = t.symbol.name.toString // value the actual enumeration value.
val evalue = t.symbol.name.mangledString // value the actual enumeration value.
av.visitEnum(name, edesc, evalue)
} else {
assert(toDenot(t.symbol).name.is(DefaultGetterName)) // this should be default getter. do not emmit.
Expand All @@ -262,8 +263,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
for(arg <- t.elems) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) }
arrAnnotV.visitEnd()

case Apply(fun, args) if (fun.symbol == defn.ArrayClass.primaryConstructor ||
(toDenot(fun.symbol).owner == defn.ArrayClass.linkedClass && fun.symbol.name == nme_apply)) =>
case Apply(fun, args) if fun.symbol == defn.ArrayClass.primaryConstructor ||
toDenot(fun.symbol).owner == defn.ArrayClass.linkedClass && fun.symbol.name == nme_apply =>
val arrAnnotV: AnnotationVisitor = av.visitArray(name)

var actualArgs = if (fun.tpe.isInstanceOf[ImplicitMethodType]) {
Expand Down Expand Up @@ -311,7 +312,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)], bcodeStore: BCodeHelpers)
(innerClasesStore: bcodeStore.BCInnerClassGen) = {
for ((name, value) <- assocs)
emitArgument(av, name.toString, value.asInstanceOf[Tree], bcodeStore)(innerClasesStore)
emitArgument(av, name.mangledString, value.asInstanceOf[Tree], bcodeStore)(innerClasesStore)
av.visitEnd()
}

Expand Down Expand Up @@ -360,9 +361,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
else clazz.getName
}

def requiredClass[T](implicit evidence: ClassTag[T]): Symbol = {
def requiredClass[T](implicit evidence: ClassTag[T]): Symbol =
ctx.requiredClass(erasureString(evidence.runtimeClass).toTermName)
}

def requiredModule[T](implicit evidence: ClassTag[T]): Symbol = {
val moduleName = erasureString(evidence.runtimeClass)
Expand Down Expand Up @@ -404,16 +404,15 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def newAnyRefMap[K <: AnyRef, V](): mutable.AnyRefMap[K, V] = new mutable.AnyRefMap[K, V]()
def newWeakMap[K, V](): mutable.WeakHashMap[K, V] = new mutable.WeakHashMap[K, V]()
def recordCache[T <: Clearable](cache: T): T = cache
def newWeakSet[K <: AnyRef](): WeakHashSet[K] = new WeakHashSet[K]()
def newWeakSet[K >: Null <: AnyRef](): WeakHashSet[K] = new WeakHashSet[K]()
def newMap[K, V](): mutable.HashMap[K, V] = new mutable.HashMap[K, V]()
def newSet[K](): mutable.Set[K] = new mutable.HashSet[K]
}

val MODULE_INSTANCE_FIELD: String = str.MODULE_INSTANCE_FIELD


val MODULE_INSTANCE_FIELD: String = nme.MODULE_INSTANCE_FIELD.toString

def internalNameString(offset: Int, length: Int): String = new String(Names.chrs, offset, length)
def dropModule(str: String) =
if (!str.isEmpty && str.last == '$') str.take(str.length - 1) else str

def newTermName(prefix: String): Name = prefix.toTermName

Expand All @@ -424,7 +423,6 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
Flags.Bridge | Flags.VBridge | Flags.Private | Flags.Macro
}.bits


def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual)
def desugarIdent(i: Ident): Option[tpd.Select] = {
i.tpe match {
Expand Down Expand Up @@ -537,32 +535,25 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}
}


implicit def nameHelper(n: Name): NameHelper = new NameHelper {
def toTypeName: Name = n.toTypeName
def isTypeName: Boolean = n.isTypeName
def toTermName: Name = n.toTermName
def dropModule: Name = n.stripModuleClassSuffix

def len: Int = n.toSimpleName.length
def offset: Int = n.toSimpleName.start
def isTermName: Boolean = n.isTermName
def startsWith(s: String): Boolean = n.startsWith(s)
def mangledString: String = n.mangledString
}


implicit def symHelper(sym: Symbol): SymbolHelper = new SymbolHelper {
// names
def fullName(sep: Char): String = sym.showFullName
def fullName: String = sym.showFullName
def simpleName: Name = sym.name
def javaSimpleName: Name = toDenot(sym).name // addModuleSuffix(simpleName.dropLocal)
def javaBinaryName: Name = javaClassName.replace('.', '/').toTypeName // TODO: can we make this a string? addModuleSuffix(fullNameInternal('/'))
def javaClassName: String = toDenot(sym).fullName.toString// addModuleSuffix(fullNameInternal('.')).toString
def javaSimpleName: String = toDenot(sym).name.mangledString // addModuleSuffix(simpleName.dropLocal)
def javaBinaryName: String = javaClassName.replace('.', '/') // TODO: can we make this a string? addModuleSuffix(fullNameInternal('/'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO is no longer necessary.

def javaClassName: String = toDenot(sym).fullName.mangledString // addModuleSuffix(fullNameInternal('.')).toString
def name: Name = sym.name
def rawname: Name = {
def rawname: String = {
val original = toDenot(sym).initial
sym.name(ctx.withPhase(original.validFor.phaseId))
sym.name(ctx.withPhase(original.validFor.phaseId)).mangledString
}

// types
Expand Down Expand Up @@ -675,9 +666,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}
def enclClass: Symbol = toDenot(sym).enclosingClass
def linkedClassOfClass: Symbol = linkedClass
def linkedClass: Symbol = {
toDenot(sym)(ctx).linkedClass(ctx)
} //exitingPickler(sym.linkedClassOfClass)
def linkedClass: Symbol = toDenot(sym)(ctx).linkedClass(ctx) //exitingPickler(sym.linkedClassOfClass)
def companionClass: Symbol = toDenot(sym).companionClass
def companionModule: Symbol = toDenot(sym).companionModule
def companionSymbol: Symbol = if (sym is Flags.Module) companionClass else companionModule
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/backend/jvm/GenBCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter

if (claszSymbol.isClass) // @DarkDimius is this test needed here?
for (binary <- ctx.compilationUnit.pickled.get(claszSymbol.asClass)) {
val dataAttr = new CustomAttr(nme.TASTYATTR.toString, binary)
val dataAttr = new CustomAttr(nme.TASTYATTR.mangledString, binary)
val store = if (mirrorC ne null) mirrorC else plainC
store.visitAttribute(dataAttr)
if (ctx.settings.emitTasty.value) {
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ object Config {
*/
final val checkNoSkolemsInInfo = false

/** Check that Name#toString is not called directly from backend by analyzing
* the stack trace of each toString call on names. This is very expensive,
* so not suitable for continuous testing. But it can be used to find a problem
* when running a specific test.
*/
final val checkBackendNames = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose to make this configurable as a flag. So that we can ask users to enable checked mode in case something looks very strange in their reports.

Copy link
Contributor Author

@odersky odersky Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that's a bit paranoid? So far, errors resulting from producing symbolic strings were very easy to diagnose.


/** Type comparer will fail with an assert if the upper bound
* of a constrained parameter becomes Nothing. This should be turned
* on only for specific debugging as normally instantiation to Nothing
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ object Denotations {
def hasUniqueSym: Boolean
protected def newLikeThis(symbol: Symbol, info: Type): SingleDenotation

final def signature(implicit ctx: Context): Signature = {
final def signature(implicit ctx: Context): Signature =
if (isType) Signature.NotAMethod // don't force info if this is a type SymDenotation
else info match {
case info: MethodicType =>
Expand All @@ -594,7 +594,6 @@ object Denotations {
}
case _ => Signature.NotAMethod
}
}

def derivedSingleDenotation(symbol: Symbol, info: Type)(implicit ctx: Context): SingleDenotation =
if ((symbol eq this.symbol) && (info eq this.info)) this
Expand Down
37 changes: 36 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import collection.mutable.{ Builder, StringBuilder, AnyRefMap }
import collection.immutable.WrappedString
import collection.generic.CanBuildFrom
import util.{DotClass, SimpleMap}
import config.Config
import java.util.HashMap

//import annotation.volatile
Expand Down Expand Up @@ -67,6 +68,7 @@ object Names {
def asSimpleName: SimpleTermName
def toSimpleName: SimpleTermName
def mangled: Name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to add some documentation on the meaning/use of mangled.

def mangledString: String = mangled.toString

def rewrite(f: PartialFunction[Name, Name]): ThisName
def collect[T](f: PartialFunction[Name, T]): Option[T]
Expand Down Expand Up @@ -287,7 +289,40 @@ object Names {
override def hashCode: Int = start

override def toString =
if (length == 0) "" else new String(chrs, start, length)
if (length == 0) ""
else {
if (Config.checkBackendNames) {
if (!toStringOK) {
// We print the stacktrace instead of doing an assert directly,
// because asserts are caught in exception handlers which might
// cause other failures. In that case the first, important failure
// is lost.
println("Backend should not call Name#toString, Name#mangledString should be used instead.")
new Error().printStackTrace()
assert(false)
}
}
new String(chrs, start, length)
}

/** It's OK to take a toString if the stacktrace does not occur a method
* in GenBCode or it also contains one of the whitelisted methods below.
*/
private def toStringOK = {
val trace = Thread.currentThread.getStackTrace
!trace.exists(_.getClassName.endsWith("GenBCode")) ||
trace.exists(elem =>
List(
"mangledString",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative to defining a whitelist in this way would be to define a new method named something like unmangledString that does what toString currently do, and have toString forward to it. Then we can just change all the code calling toString to use unmangledString instead, but keep the stacktrace check in toString

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would affect too many things. Note that most of the whitelist does not call toString directly, sometimes there are 10-20 calls in between.

"toSimpleName",
"decode",
"unmangle",
"dotty$tools$dotc$core$NameOps$NameDecorator$$functionArityFor$extension",
"dotty$tools$dotc$typer$Checking$CheckNonCyclicMap$$apply",
"$plus$plus",
"readConstant")
.contains(elem.getMethodName))
}

def debugString: String = toString
}
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ object StdNames {
final val INTERPRETER_LINE_PREFIX = "line"
final val INTERPRETER_VAR_PREFIX = "res"
final val INTERPRETER_WRAPPER_SUFFIX = "$object"
final val MODULE_INSTANCE_FIELD = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$"

final val Function = "Function"
final val ImplicitFunction = "ImplicitFunction"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class SymbolLoaders {
if (!sourceModule.isCompleted)
sourceModule.completer.complete(sourceModule)

val packageName = if (root.isEffectiveRoot) "" else root.fullName.toString
val packageName = if (root.isEffectiveRoot) "" else root.fullName.mangledString

enterFlatClasses = Some { ctx =>
enterFlatClasses = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ class ClassfileParser(
for (entry <- innerClasses.values) {
// create a new class member for immediate inner classes
if (entry.outerName == currentClassName) {
val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse {
val file = ctx.platform.classPath.findClassFile(entry.externalName.mangledString) getOrElse {
throw new AssertionError(entry.externalName)
}
enterClassAndModule(entry, file, entry.jflags)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class TreeChecker extends Phase with SymTransformer {
val NoSuperClass = Trait | Package

def testDuplicate(sym: Symbol, registry: mutable.Map[String, Symbol], typ: String)(implicit ctx: Context) = {
val name = sym.fullName.toString
val name = sym.fullName.mangledString
if (this.flatClasses && registry.contains(name))
printError(s"$typ defined twice $sym ${sym.id} ${registry(name).id}")
registry(name) = sym
Expand Down