diff options
112 files changed, 8268 insertions, 1093 deletions
diff --git a/src/asm/scala/tools/asm/tree/MethodNode.java b/src/asm/scala/tools/asm/tree/MethodNode.java index 5f9c778e0c..a161600edb 100644 --- a/src/asm/scala/tools/asm/tree/MethodNode.java +++ b/src/asm/scala/tools/asm/tree/MethodNode.java @@ -458,7 +458,7 @@ public class MethodNode extends MethodVisitor { */ protected LabelNode getLabelNode(final Label l) { if (!(l.info instanceof LabelNode)) { - l.info = new LabelNode(); + l.info = new LabelNode(l); } return (LabelNode) l.info; } diff --git a/src/compiler/scala/tools/cmd/CommandLine.scala b/src/compiler/scala/tools/cmd/CommandLine.scala index e8ac882ee6..e44752eb6e 100644 --- a/src/compiler/scala/tools/cmd/CommandLine.scala +++ b/src/compiler/scala/tools/cmd/CommandLine.scala @@ -16,7 +16,7 @@ trait CommandLineConfig { /** An instance of a command line, parsed according to a Spec. */ class CommandLine(val spec: Reference, val originalArgs: List[String]) extends CommandLineConfig { - def this(spec: Reference, line: String) = this(spec, Parser tokenize line) + def this(spec: Reference, line: String) = this(spec, CommandLineParser tokenize line) def this(spec: Reference, args: Array[String]) = this(spec, args.toList) import spec.{ isUnaryOption, isBinaryOption, isExpandOption } diff --git a/src/compiler/scala/tools/cmd/CommandLineParser.scala b/src/compiler/scala/tools/cmd/CommandLineParser.scala new file mode 100644 index 0000000000..ef55178594 --- /dev/null +++ b/src/compiler/scala/tools/cmd/CommandLineParser.scala @@ -0,0 +1,72 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import scala.annotation.tailrec + +/** A simple (overly so) command line parser. + * !!! This needs a thorough test suite to make sure quoting is + * done correctly and portably. + */ +object CommandLineParser { + // splits a string into a quoted prefix and the rest of the string, + // taking escaping into account (using \) + // `"abc"def` will match as `DoubleQuoted(abc, def)` + private class QuotedExtractor(quote: Char) { + def unapply(in: String): Option[(String, String)] = { + val del = quote.toString + if (in startsWith del) { + var escaped = false + val (quoted, next) = (in substring 1) span { + case `quote` if !escaped => false + case '\\' if !escaped => escaped = true; true + case _ => escaped = false; true + } + // the only way to get out of the above loop is with an empty next or !escaped + // require(next.isEmpty || !escaped) + if (next startsWith del) Some((quoted, next substring 1)) + else None + } else None + } + } + private object DoubleQuoted extends QuotedExtractor('"') + private object SingleQuoted extends QuotedExtractor('\'') + private val Word = """(\S+)(.*)""".r + + // parse `in` for an argument, return it and the remainder of the input (or an error message) + // (argument may be in single/double quotes, taking escaping into account, quotes are stripped) + private def argument(in: String): Either[String, (String, String)] = in match { + case DoubleQuoted(arg, rest) => Right(arg, rest) + case SingleQuoted(arg, rest) => Right(arg, rest) + case Word(arg, rest) => Right(arg, rest) + case _ => Left("Illegal argument: "+ in) + } + + // parse a list of whitespace-separated arguments (ignoring whitespace in quoted arguments) + @tailrec private def commandLine(in: String, accum: List[String] = Nil): Either[String, (List[String], String)] = { + val trimmed = in.trim + if (trimmed.isEmpty) Right(accum.reverse, "") + else argument(trimmed) match { + case Right((arg, next)) => + (next span Character.isWhitespace) match { + case("", rest) if rest.nonEmpty => Left("Arguments should be separated by whitespace.") // TODO: can this happen? + case(ws, rest) => commandLine(rest, arg :: accum) + } + case Left(msg) => Left(msg) + } + } + + class ParseException(msg: String) extends RuntimeException(msg) + + def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) + def tokenize(line: String, errorFn: String => Unit): List[String] = { + commandLine(line) match { + case Right((args, _)) => args + case Left(msg) => errorFn(msg) ; Nil + } + } +} diff --git a/src/compiler/scala/tools/cmd/Parser.scala b/src/compiler/scala/tools/cmd/Parser.scala deleted file mode 100644 index 6e2afa41c4..0000000000 --- a/src/compiler/scala/tools/cmd/Parser.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package cmd - -import scala.util.parsing.combinator._ -import scala.util.parsing.input.CharArrayReader.EofCh - -/** A simple (overly so) command line parser. - * !!! This needs a thorough test suite to make sure quoting is - * done correctly and portably. - */ -trait ParserUtil extends Parsers { - class ParserPlus[+T](underlying: Parser[T]) { - def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } - def <~![U](p: => Parser[U]): Parser[T] = (underlying ~! p) ^^ { case a~b => a } - } - protected implicit def parser2parserPlus[T](p: Parser[T]): ParserPlus[T] = new ParserPlus(p) -} - -object Parser extends RegexParsers with ParserUtil { - override def skipWhitespace = false - - def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) - def elemOf(xs: Elem*): Parser[Elem] = elem("elemOf", xs contains _) - def escaped(ch: Char): Parser[String] = "\\" + ch - def mkQuoted(ch: Char): Parser[String] = ( - elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) - | failure("Unmatched %s in input." format ch) - ) - - /** Apparently windows can't deal with the quotes sticking around. */ - lazy val squoted: Parser[String] = mkQuoted('\'') // ^^ (x => "'%s'" format x) - lazy val dquoted: Parser[String] = mkQuoted('"') // ^^ (x => "\"" + x + "\"") - lazy val token: Parser[String] = """\S+""".r - - lazy val argument: Parser[String] = squoted | dquoted | token - lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) - - class ParseException(msg: String) extends RuntimeException(msg) - - def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) - def tokenize(line: String, errorFn: String => Unit): List[String] = { - parse(commandLine, line.trim) match { - case Success(args, _) => args - case NoSuccess(msg, rest) => errorFn(msg) ; Nil - } - } -} diff --git a/src/compiler/scala/tools/cmd/Spec.scala b/src/compiler/scala/tools/cmd/Spec.scala index b761601167..a1cb31f911 100644 --- a/src/compiler/scala/tools/cmd/Spec.scala +++ b/src/compiler/scala/tools/cmd/Spec.scala @@ -15,7 +15,7 @@ trait Spec { def programInfo: Spec.Info protected def help(str: => String): Unit - protected def heading(str: => String): Unit = help("\n " + str) + protected def heading(str: => String): Unit = help(s"\n $str") type OptionMagic <: Opt.Implicit protected implicit def optionMagicAdditions(s: String): OptionMagic diff --git a/src/compiler/scala/tools/cmd/package.scala b/src/compiler/scala/tools/cmd/package.scala index c62a977950..7d67fa738b 100644 --- a/src/compiler/scala/tools/cmd/package.scala +++ b/src/compiler/scala/tools/cmd/package.scala @@ -22,7 +22,7 @@ package object cmd { def toOpt(s: String) = if (s startsWith "--") s else "--" + s def fromOpt(s: String) = s stripPrefix "--" - def toArgs(line: String) = Parser tokenize line + def toArgs(line: String) = CommandLineParser tokenize line def fromArgs(args: List[String]) = args mkString " " def stripQuotes(s: String) = { diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index 15d365ab8c..b52e6fdf57 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -34,6 +34,20 @@ trait CompilationUnits { self: Global => /** the content of the compilation unit in tree form */ var body: Tree = EmptyTree + /** The position of the first xml literal encountered while parsing this compilation unit. + * NoPosition if there were none. Write-once. + */ + private[this] var _firstXmlPos: Position = NoPosition + + /** Record that we encountered XML. Should only be called once. */ + protected[nsc] def encounteredXml(pos: Position) = _firstXmlPos = pos + + /** Does this unit contain XML? */ + def hasXml = _firstXmlPos ne NoPosition + + /** Position of first XML literal in this unit. */ + def firstXmlPos = _firstXmlPos + def exists = source != NoSourceFile && source != null /** Note: depends now contains toplevel classes. diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index eafe03d5cd..603f9af1b4 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -28,6 +28,7 @@ import transform.patmat.PatternMatching import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, Platform, JavaPlatform } +import backend.jvm.GenBCode import backend.jvm.GenASM import backend.opt.{ Inliners, InlineExceptionHandlers, ConstantOptimization, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ @@ -619,6 +620,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with GenASM + // phaseName = "bcode" + object genBCode extends { + val global: Global.this.type = Global.this + val runsAfter = List("dce") + val runsRightAfter = None + } with GenBCode + // phaseName = "terminal" object terminal extends { val global: Global.this.type = Global.this @@ -1057,6 +1065,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op) @inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op) @inline final def enteringRefchecks[T](op: => T): T = enteringPhase(currentRun.refchecksPhase)(op) + @inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op) @inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op) @inline final def enteringUncurry[T](op: => T): T = enteringPhase(currentRun.uncurryPhase)(op) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 692afbac66..c28a6ba337 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -19,11 +19,20 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { import global._ import definitions._ - /** Builds a fully attributed wildcard import node. + /** Builds a fully attributed, synthetic wildcard import node. */ - def mkWildcardImport(pkg: Symbol): Import = { - assert(pkg ne null, this) - val qual = gen.mkAttributedStableRef(pkg) + def mkWildcardImport(pkg: Symbol): Import = + mkImportFromSelector(pkg, ImportSelector.wildList) + + /** Builds a fully attributed, synthetic import node. + * import `qualSym`.{`name` => `toName`} + */ + def mkImport(qualSym: Symbol, name: Name, toName: Name): Import = + mkImportFromSelector(qualSym, ImportSelector(name, 0, toName, 0) :: Nil) + + private def mkImportFromSelector(qualSym: Symbol, selector: List[ImportSelector]): Import = { + assert(qualSym ne null, this) + val qual = gen.mkAttributedStableRef(qualSym) val importSym = ( NoSymbol newImport NoPosition @@ -31,7 +40,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { setInfo analyzer.ImportType(qual) ) val importTree = ( - Import(qual, ImportSelector.wildList) + Import(qual, selector) setSymbol importSym setType NoType ) diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 832a9bf63e..d3f495f280 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -10,8 +10,7 @@ import scala.collection.mutable import mutable.{ Buffer, ArrayBuffer, ListBuffer } import scala.util.control.ControlThrowable import scala.tools.nsc.util.CharArrayReader -import scala.xml.TextBuffer -import scala.xml.parsing.MarkupParserCommon +import scala.tools.nsc.ast.parser.xml.{MarkupParserCommon, Utility} import scala.reflect.internal.Chars.{ SU, LF } // XXX/Note: many/most of the functions in here are almost direct cut and pastes @@ -42,7 +41,7 @@ trait MarkupParsers { import global._ class MarkupParser(parser: SourceFileParser, final val preserveWS: Boolean) extends MarkupParserCommon { - + import Utility.{ isNameStart, isSpace } import Tokens.{ LBRACE, RBRACE } type PositionType = Position @@ -172,12 +171,21 @@ trait MarkupParsers { xTakeUntil(handle.comment, () => r2p(start, start, curOffset), "-->") } - def appendText(pos: Position, ts: Buffer[Tree], txt: String) { - val toAppend = - if (preserveWS) Seq(txt) - else TextBuffer.fromString(txt).toText map (_.text) + def appendText(pos: Position, ts: Buffer[Tree], txt: String): Unit = { + def append(t: String) = ts append handle.text(pos, t) + + if (preserveWS) append(txt) + else { + val sb = new StringBuilder() - toAppend foreach (t => ts append handle.text(pos, t)) + txt foreach { c => + if (!isSpace(c)) sb append c + else if (sb.isEmpty || !isSpace(sb.last)) sb append ' ' + } + + val trimmed = sb.toString.trim + if (!trimmed.isEmpty) append(trimmed) + } } /** adds entity/character to ts as side-effect diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index fc532f5d44..ef5872986c 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -166,13 +166,20 @@ self => def syntaxError(offset: Int, msg: String): Unit = throw new MalformedInput(offset, msg) def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg) - /** the markup parser */ - lazy val xmlp = new MarkupParser(this, preserveWS = true) - object symbXMLBuilder extends SymbolicXMLBuilder(this, preserveWS = true) { // DEBUG choices val global: self.global.type = self.global } + /** the markup parser + * The first time this lazy val is accessed, we assume we were trying to parse an xml literal. + * The current position is recorded for later error reporting if it turns out + * that we don't have the xml library on the compilation classpath. + */ + private[this] lazy val xmlp = { + currentUnit.encounteredXml(o2p(in.offset)) + new MarkupParser(this, preserveWS = true) + } + def xmlLiteral() : Tree = xmlp.xLiteral def xmlLiteralPattern() : Tree = xmlp.xLiteralPattern } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 80b6ad3cc7..2dca39f7a3 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -12,7 +12,7 @@ import Tokens._ import scala.annotation.{ switch, tailrec } import scala.collection.{ mutable, immutable } import mutable.{ ListBuffer, ArrayBuffer } -import scala.xml.Utility.{ isNameStart } +import scala.tools.nsc.ast.parser.xml.Utility.isNameStart import scala.language.postfixOps /** See Parsers.scala / ParsersCommon for some explanation of ScannersCommon. diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index f326212d5b..1abc0c860c 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -7,8 +7,6 @@ package scala.tools.nsc package ast.parser import scala.collection.{ mutable, immutable } -import scala.xml.{ EntityRef, Text } -import scala.xml.XML.{ xmlns } import symtab.Flags.MUTABLE import scala.reflect.internal.util.StringOps.splitWhere @@ -143,14 +141,12 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { (buf map convertToTextPat).toList def parseAttribute(pos: Position, s: String): Tree = { - val ts = scala.xml.Utility.parseAttributeValue(s) map { - case Text(s) => text(pos, s) - case EntityRef(s) => entityRef(pos, s) - } - ts.length match { - case 0 => gen.mkNil - case 1 => ts.head - case _ => makeXMLseq(pos, ts.toList) + import xml.Utility.parseAttributeValue + + parseAttributeValue(s, text(pos, _), entityRef(pos, _)) match { + case Nil => gen.mkNil + case t :: Nil => t + case ts => makeXMLseq(pos, ts.toList) } } @@ -198,7 +194,7 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) { /* Extract all the namespaces from the attribute map. */ val namespaces: List[Tree] = - for (z <- attrMap.keys.toList ; if z startsWith xmlns) yield { + for (z <- attrMap.keys.toList ; if z startsWith "xmlns") yield { val ns = splitPrefix(z) match { case (Some(_), rest) => rest case _ => null diff --git a/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala b/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala new file mode 100644 index 0000000000..f6cfb64ed8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/xml/MarkupParserCommon.scala @@ -0,0 +1,255 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools.nsc.ast.parser.xml + +/** This is not a public trait - it contains common code shared + * between the library level XML parser and the compiler's. + * All members should be accessed through those. + */ +private[scala] trait MarkupParserCommon { + import Utility._ + import scala.reflect.internal.Chars.SU + + protected def unreachable = scala.sys.error("Cannot be reached.") + + // type HandleType // MarkupHandler, SymbolicXMLBuilder + type InputType // Source, CharArrayReader + type PositionType // Int, Position + type ElementType // NodeSeq, Tree + type NamespaceType // NamespaceBinding, Any + type AttributesType // (MetaData, NamespaceBinding), mutable.Map[String, Tree] + + def mkAttributes(name: String, pscope: NamespaceType): AttributesType + def mkProcInstr(position: PositionType, name: String, text: String): ElementType + + /** parse a start or empty tag. + * [40] STag ::= '<' Name { S Attribute } [S] + * [44] EmptyElemTag ::= '<' Name { S Attribute } [S] + */ + protected def xTag(pscope: NamespaceType): (String, AttributesType) = { + val name = xName + xSpaceOpt() + + (name, mkAttributes(name, pscope)) + } + + /** '<?' ProcInstr ::= Name [S ({Char} - ({Char}'>?' {Char})]'?>' + * + * see [15] + */ + def xProcInstr: ElementType = { + val n = xName + xSpaceOpt() + xTakeUntil(mkProcInstr(_, n, _), () => tmppos, "?>") + } + + /** attribute value, terminated by either `'` or `"`. value may not contain `<`. + @param endCh either `'` or `"` + */ + def xAttributeValue(endCh: Char): String = { + val buf = new StringBuilder + while (ch != endCh) { + // well-formedness constraint + if (ch == '<') return errorAndResult("'<' not allowed in attrib value", "") + else if (ch == SU) truncatedError("") + else buf append ch_returning_nextch + } + ch_returning_nextch + // @todo: normalize attribute value + buf.toString + } + + def xAttributeValue(): String = { + val str = xAttributeValue(ch_returning_nextch) + // well-formedness constraint + normalizeAttributeValue(str) + } + + private def takeUntilChar(it: Iterator[Char], end: Char): String = { + val buf = new StringBuilder + while (it.hasNext) it.next() match { + case `end` => return buf.toString + case ch => buf append ch + } + scala.sys.error("Expected '%s'".format(end)) + } + + /** [42] '<' xmlEndTag ::= '<' '/' Name S? '>' + */ + def xEndTag(startName: String) { + xToken('/') + if (xName != startName) + errorNoEnd(startName) + + xSpaceOpt() + xToken('>') + } + + /** actually, Name ::= (Letter | '_' | ':') (NameChar)* but starting with ':' cannot happen + * Name ::= (Letter | '_') (NameChar)* + * + * see [5] of XML 1.0 specification + * + * pre-condition: ch != ':' // assured by definition of XMLSTART token + * post-condition: name does neither start, nor end in ':' + */ + def xName: String = { + if (ch == SU) + truncatedError("") + else if (!isNameStart(ch)) + return errorAndResult("name expected, but char '%s' cannot start a name" format ch, "") + + val buf = new StringBuilder + + do buf append ch_returning_nextch + while (isNameChar(ch)) + + if (buf.last == ':') { + reportSyntaxError( "name cannot end in ':'" ) + buf.toString dropRight 1 + } + else buf.toString + } + + private def attr_unescape(s: String) = s match { + case "lt" => "<" + case "gt" => ">" + case "amp" => "&" + case "apos" => "'" + case "quot" => "\"" + case "quote" => "\"" + case _ => "&" + s + ";" + } + + /** Replaces only character references right now. + * see spec 3.3.3 + */ + private def normalizeAttributeValue(attval: String): String = { + val buf = new StringBuilder + val it = attval.iterator.buffered + + while (it.hasNext) buf append (it.next() match { + case ' ' | '\t' | '\n' | '\r' => " " + case '&' if it.head == '#' => it.next() ; xCharRef(it) + case '&' => attr_unescape(takeUntilChar(it, ';')) + case c => c + }) + + buf.toString + } + + /** CharRef ::= "&#" '0'..'9' {'0'..'9'} ";" + * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" + * + * see [66] + */ + def xCharRef(ch: () => Char, nextch: () => Unit): String = + Utility.parseCharRef(ch, nextch, reportSyntaxError _, truncatedError _) + + def xCharRef(it: Iterator[Char]): String = { + var c = it.next() + Utility.parseCharRef(() => c, () => { c = it.next() }, reportSyntaxError _, truncatedError _) + } + + def xCharRef: String = xCharRef(() => ch, () => nextch()) + + /** Create a lookahead reader which does not influence the input */ + def lookahead(): BufferedIterator[Char] + + /** The library and compiler parsers had the interesting distinction of + * different behavior for nextch (a function for which there are a total + * of two plausible behaviors, so we know the design space was fully + * explored.) One of them returned the value of nextch before the increment + * and one of them the new value. So to unify code we have to at least + * temporarily abstract over the nextchs. + */ + def ch: Char + def nextch(): Unit + protected def ch_returning_nextch: Char + def eof: Boolean + + // def handle: HandleType + var tmppos: PositionType + + def xHandleError(that: Char, msg: String): Unit + def reportSyntaxError(str: String): Unit + def reportSyntaxError(pos: Int, str: String): Unit + + def truncatedError(msg: String): Nothing + def errorNoEnd(tag: String): Nothing + + protected def errorAndResult[T](msg: String, x: T): T = { + reportSyntaxError(msg) + x + } + + def xToken(that: Char) { + if (ch == that) nextch() + else xHandleError(that, "'%s' expected instead of '%s'".format(that, ch)) + } + def xToken(that: Seq[Char]) { that foreach xToken } + + /** scan [S] '=' [S]*/ + def xEQ() = { xSpaceOpt(); xToken('='); xSpaceOpt() } + + /** skip optional space S? */ + def xSpaceOpt() = while (isSpace(ch) && !eof) nextch() + + /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ + def xSpace() = + if (isSpace(ch)) { nextch(); xSpaceOpt() } + else xHandleError(ch, "whitespace expected") + + /** Apply a function and return the passed value */ + def returning[T](x: T)(f: T => Unit): T = { f(x); x } + + /** Execute body with a variable saved and restored after execution */ + def saving[A, B](getter: A, setter: A => Unit)(body: => B): B = { + val saved = getter + try body + finally setter(saved) + } + + /** Take characters from input stream until given String "until" + * is seen. Once seen, the accumulated characters are passed + * along with the current Position to the supplied handler function. + */ + protected def xTakeUntil[T]( + handler: (PositionType, String) => T, + positioner: () => PositionType, + until: String): T = + { + val sb = new StringBuilder + val head = until.head + val rest = until.tail + + while (true) { + if (ch == head && peek(rest)) + return handler(positioner(), sb.toString) + else if (ch == SU) + truncatedError("") // throws TruncatedXMLControl in compiler + + sb append ch + nextch() + } + unreachable + } + + /** Create a non-destructive lookahead reader and see if the head + * of the input would match the given String. If yes, return true + * and drop the entire String from input; if no, return false + * and leave input unchanged. + */ + private def peek(lookingFor: String): Boolean = + (lookahead() take lookingFor.length sameElements lookingFor.iterator) && { + // drop the chars from the real reader (all lookahead + orig) + (0 to lookingFor.length) foreach (_ => nextch()) + true + } +} diff --git a/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala b/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala new file mode 100755 index 0000000000..39e4831af2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/xml/Utility.scala @@ -0,0 +1,176 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.tools.nsc.ast.parser.xml + +import scala.collection.mutable + + +/** + * The `Utility` object provides utility functions for processing instances + * of bound and not bound XML classes, as well as escaping text nodes. + * + * @author Burak Emir + */ +object Utility { + import scala.reflect.internal.Chars.SU + + private val unescMap = Map( + "lt" -> '<', + "gt" -> '>', + "amp" -> '&', + "quot" -> '"', + "apos" -> '\'' + ) + + /** + * Appends unescaped string to `s`, `amp` becomes `&`, + * `lt` becomes `<` etc.. + * + * @return `'''null'''` if `ref` was not a predefined entity. + */ + private final def unescape(ref: String, s: StringBuilder): StringBuilder = + ((unescMap get ref) map (s append _)).orNull + + def parseAttributeValue[T](value: String, text: String => T, entityRef: String => T): List[T] = { + val sb = new StringBuilder + var rfb: StringBuilder = null + val nb = new mutable.ListBuffer[T]() + + val it = value.iterator + while (it.hasNext) { + var c = it.next() + // entity! flush buffer into text node + if (c == '&') { + c = it.next() + if (c == '#') { + c = it.next() + val theChar = parseCharRef ({ ()=> c },{ () => c = it.next() },{s => throw new RuntimeException(s)}, {s => throw new RuntimeException(s)}) + sb.append(theChar) + } + else { + if (rfb eq null) rfb = new StringBuilder() + rfb append c + c = it.next() + while (c != ';') { + rfb.append(c) + c = it.next() + } + val ref = rfb.toString() + rfb.clear() + unescape(ref,sb) match { + case null => + if (!sb.isEmpty) { // flush buffer + nb += text(sb.toString()) + sb.clear() + } + nb += entityRef(ref) // add entityref + case _ => + } + } + } + else sb append c + } + + if(!sb.isEmpty) // flush buffer + nb += text(sb.toString()) + + nb.toList + } + + /** + * {{{ + * CharRef ::= "&#" '0'..'9' {'0'..'9'} ";" + * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" + * }}} + * See [66] + */ + def parseCharRef(ch: () => Char, nextch: () => Unit, reportSyntaxError: String => Unit, reportTruncatedError: String => Unit): String = { + val hex = (ch() == 'x') && { nextch(); true } + val base = if (hex) 16 else 10 + var i = 0 + while (ch() != ';') { + ch() match { + case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => + i = i * base + ch().asDigit + case 'a' | 'b' | 'c' | 'd' | 'e' | 'f' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' => + if (! hex) + reportSyntaxError("hex char not allowed in decimal char ref\n" + + "Did you mean to write &#x ?") + else + i = i * base + ch().asDigit + case SU => + reportTruncatedError("") + case _ => + reportSyntaxError("character '" + ch() + "' not allowed in char ref\n") + } + nextch() + } + new String(Array(i), 0, 1) + } + + /** {{{ + * (#x20 | #x9 | #xD | #xA) + * }}} */ + final def isSpace(ch: Char): Boolean = ch match { + case '\u0009' | '\u000A' | '\u000D' | '\u0020' => true + case _ => false + } + /** {{{ + * (#x20 | #x9 | #xD | #xA)+ + * }}} */ + final def isSpace(cs: Seq[Char]): Boolean = cs.nonEmpty && (cs forall isSpace) + + /** {{{ + * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' + * | CombiningChar | Extender + * }}} + * See [4] and Appendix B of XML 1.0 specification. + */ + def isNameChar(ch: Char) = { + import java.lang.Character._ + // The constants represent groups Mc, Me, Mn, Lm, and Nd. + + isNameStart(ch) || (getType(ch).toByte match { + case COMBINING_SPACING_MARK | + ENCLOSING_MARK | NON_SPACING_MARK | + MODIFIER_LETTER | DECIMAL_DIGIT_NUMBER => true + case _ => ".-:" contains ch + }) + } + + /** {{{ + * NameStart ::= ( Letter | '_' ) + * }}} + * where Letter means in one of the Unicode general + * categories `{ Ll, Lu, Lo, Lt, Nl }`. + * + * We do not allow a name to start with `:`. + * See [3] and Appendix B of XML 1.0 specification + */ + def isNameStart(ch: Char) = { + import java.lang.Character._ + + getType(ch).toByte match { + case LOWERCASE_LETTER | + UPPERCASE_LETTER | OTHER_LETTER | + TITLECASE_LETTER | LETTER_NUMBER => true + case _ => ch == '_' + } + } + + /** {{{ + * Name ::= ( Letter | '_' ) (NameChar)* + * }}} + * See [5] of XML 1.0 specification. + */ + def isName(s: String) = + s.nonEmpty && isNameStart(s.head) && (s.tail forall isNameChar) + +} diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 00f2933fab..c5fc12e3ec 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -38,9 +38,13 @@ trait JavaPlatform extends Platform { // replaces the tighter abstract definition here. If we had DOT typing rules, the two // types would be conjoined and everything would work out. Yet another reason to push for DOT. + private def classEmitPhase = + if (settings.isBCodeActive) genBCode + else genASM + def platformPhases = List( flatten, // get rid of inner classes - genASM // generate .class files + classEmitPhase // generate .class files ) lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_) diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 72516dd9d9..e6f21fc1e3 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -52,6 +52,7 @@ abstract class GenICode extends SubComponent { } override def apply(unit: CompilationUnit): Unit = { + if (settings.isBCodeActive) { return } this.unit = unit unit.icode.clear() informProgress("Generating icode for " + unit) diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index 57a768d9cb..076f84ce7a 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -725,6 +725,8 @@ trait Opcodes { self: ICodes => /** Is this a static method call? */ def isStatic: Boolean = false + def isSuper: Boolean = false + /** Is this an instance method call? */ def hasInstance: Boolean = true @@ -758,6 +760,7 @@ trait Opcodes { self: ICodes => * On JVM, translated to `invokespecial`. */ case class SuperCall(mix: Name) extends InvokeStyle { + override def isSuper = true override def toString(): String = { "super(" + mix + ")" } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala new file mode 100644 index 0000000000..a7f43eefed --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -0,0 +1,1256 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeBodyBuilder extends BCodeSkelBuilder { + import global._ + import definitions._ + + /* + * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. + */ + abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) { + + import icodes.TestOp + import icodes.opcodes.InvokeStyle + + /* If the selector type has a member with the right name, + * it is the host class; otherwise the symbol's owner. + */ + def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match { + case NoSymbol => log(s"Rejecting $selector as host class for $sym") ; sym.owner + case _ => selector.typeSymbol + } + + /* ---------------- helper utils for generating methods and code ---------------- */ + + def emit(opc: Int) { mnode.visitInsn(opc) } + def emit(i: asm.tree.AbstractInsnNode) { mnode.instructions.add(i) } + def emit(is: List[asm.tree.AbstractInsnNode]) { for(i <- is) { mnode.instructions.add(i) } } + + def emitZeroOf(tk: BType) { + (tk.sort: @switch) match { + case asm.Type.BOOLEAN => bc.boolconst(false) + case asm.Type.BYTE | + asm.Type.SHORT | + asm.Type.CHAR | + asm.Type.INT => bc.iconst(0) + case asm.Type.LONG => bc.lconst(0) + case asm.Type.FLOAT => bc.fconst(0) + case asm.Type.DOUBLE => bc.dconst(0) + case asm.Type.VOID => () + case _ => emit(asm.Opcodes.ACONST_NULL) + } + } + + /* + * Emits code that adds nothing to the operand stack. + * Two main cases: `tree` is an assignment, + * otherwise an `adapt()` to UNIT is performed if needed. + */ + def genStat(tree: Tree) { + lineNumber(tree) + tree match { + case Assign(lhs @ Select(_, _), rhs) => + val isStatic = lhs.symbol.isStaticMember + if (!isStatic) { genLoadQualifier(lhs) } + genLoad(rhs, symInfoTK(lhs.symbol)) + lineNumber(tree) + fieldStore(lhs.symbol) + + case Assign(lhs, rhs) => + val s = lhs.symbol + val Local(tk, _, idx, _) = locals.getOrMakeLocal(s) + genLoad(rhs, tk) + lineNumber(tree) + bc.store(idx, tk) + + case _ => + genLoad(tree, UNIT) + } + } + + def genThrow(expr: Tree): BType = { + val thrownKind = tpeTK(expr) + assert(exemplars.get(thrownKind).isSubtypeOf(ThrowableReference)) + genLoad(expr, thrownKind) + lineNumber(expr) + emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. + + RT_NOTHING // always returns the same, the invoker should know :) + } + + /* Generate code for primitive arithmetic operations. */ + def genArithmeticOp(tree: Tree, code: Int): BType = { + val Apply(fun @ Select(larg, _), args) = tree + var resKind = tpeTK(larg) + + assert(resKind.isNumericType || (resKind == BOOL), + s"$resKind is not a numeric or boolean type [operation: ${fun.symbol}]") + + import scalaPrimitives._ + + args match { + // unary operation + case Nil => + genLoad(larg, resKind) + code match { + case POS => () // nothing + case NEG => bc.neg(resKind) + case NOT => bc.genPrimitiveArithmetic(icodes.NOT, resKind) + case _ => abort(s"Unknown unary operation: ${fun.symbol.fullName} code: $code") + } + + // binary operation + case rarg :: Nil => + resKind = maxType(tpeTK(larg), tpeTK(rarg)) + if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) { + assert(resKind.isIntegralType || (resKind == BOOL), + s"$resKind incompatible with arithmetic modulo operation.") + } + + genLoad(larg, resKind) + genLoad(rarg, // check .NET size of shift arguments! + if (scalaPrimitives.isShiftOp(code)) INT else resKind) + + (code: @switch) match { + case ADD => bc add resKind + case SUB => bc sub resKind + case MUL => bc mul resKind + case DIV => bc div resKind + case MOD => bc rem resKind + + case OR | XOR | AND => bc.genPrimitiveLogical(code, resKind) + + case LSL | LSR | ASR => bc.genPrimitiveShift(code, resKind) + + case _ => abort(s"Unknown primitive: ${fun.symbol}[$code]") + } + + case _ => + abort(s"Too many arguments for primitive function: $tree") + } + lineNumber(tree) + resKind + } + + /* Generate primitive array operations. */ + def genArrayOp(tree: Tree, code: Int, expectedType: BType): BType = { + val Apply(Select(arrayObj, _), args) = tree + val k = tpeTK(arrayObj) + genLoad(arrayObj, k) + val elementType = typeOfArrayOp.getOrElse(code, abort(s"Unknown operation on arrays: $tree code: $code")) + + var generatedType = expectedType + + if (scalaPrimitives.isArrayGet(code)) { + // load argument on stack + assert(args.length == 1, s"Too many arguments for array get operation: $tree"); + genLoad(args.head, INT) + generatedType = k.getComponentType + bc.aload(elementType) + } + else if (scalaPrimitives.isArraySet(code)) { + args match { + case a1 :: a2 :: Nil => + genLoad(a1, INT) + genLoad(a2) + // the following line should really be here, but because of bugs in erasure + // we pretend we generate whatever type is expected from us. + //generatedType = UNIT + bc.astore(elementType) + case _ => + abort(s"Too many arguments for array set operation: $tree") + } + } + else { + generatedType = INT + emit(asm.Opcodes.ARRAYLENGTH) + } + lineNumber(tree) + + generatedType + } + + def genLoadIf(tree: If, expectedType: BType): BType = { + val If(condp, thenp, elsep) = tree + + val success = new asm.Label + val failure = new asm.Label + + val hasElse = !elsep.isEmpty + val postIf = if (hasElse) new asm.Label else failure + + genCond(condp, success, failure) + + val thenKind = tpeTK(thenp) + val elseKind = if (!hasElse) UNIT else tpeTK(elsep) + def hasUnitBranch = (thenKind == UNIT || elseKind == UNIT) + val resKind = if (hasUnitBranch) UNIT else tpeTK(tree) + + markProgramPoint(success) + genLoad(thenp, resKind) + if (hasElse) { bc goTo postIf } + markProgramPoint(failure) + if (hasElse) { + genLoad(elsep, resKind) + markProgramPoint(postIf) + } + + resKind + } + + def genPrimitiveOp(tree: Apply, expectedType: BType): BType = { + val sym = tree.symbol + val Apply(fun @ Select(receiver, _), _) = tree + val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) + + import scalaPrimitives.{isArithmeticOp, isArrayOp, isLogicalOp, isComparisonOp} + + if (isArithmeticOp(code)) genArithmeticOp(tree, code) + else if (code == scalaPrimitives.CONCAT) genStringConcat(tree) + else if (code == scalaPrimitives.HASH) genScalaHash(receiver) + else if (isArrayOp(code)) genArrayOp(tree, code, expectedType) + else if (isLogicalOp(code) || isComparisonOp(code)) { + val success, failure, after = new asm.Label + genCond(tree, success, failure) + // success block + markProgramPoint(success) + bc boolconst true + bc goTo after + // failure block + markProgramPoint(failure) + bc boolconst false + // after + markProgramPoint(after) + + BOOL + } + else if (code == scalaPrimitives.SYNCHRONIZED) + genSynchronized(tree, expectedType) + else if (scalaPrimitives.isCoercion(code)) { + genLoad(receiver) + lineNumber(tree) + genCoercion(code) + coercionTo(code) + } + else abort( + s"Primitive operation not handled yet: ${sym.fullName}(${fun.symbol.simpleName}) at: ${tree.pos}" + ) + } + + def genLoad(tree: Tree) { + genLoad(tree, tpeTK(tree)) + } + + /* Generate code for trees that produce values on the stack */ + def genLoad(tree: Tree, expectedType: BType) { + var generatedType = expectedType + + lineNumber(tree) + + tree match { + case lblDf : LabelDef => genLabelDef(lblDf, expectedType) + + case ValDef(_, nme.THIS, _, _) => + debuglog("skipping trivial assign to _$this: " + tree) + + case ValDef(_, _, _, rhs) => + val sym = tree.symbol + /* most of the time, !locals.contains(sym), unless the current activation of genLoad() is being called + while duplicating a finalizer that contains this ValDef. */ + val Local(tk, _, idx, isSynth) = locals.getOrMakeLocal(sym) + if (rhs == EmptyTree) { emitZeroOf(tk) } + else { genLoad(rhs, tk) } + bc.store(idx, tk) + if (!isSynth) { // there are case <synthetic> ValDef's emitted by patmat + varsInScope ::= (sym -> currProgramPoint()) + } + generatedType = UNIT + + case t : If => + generatedType = genLoadIf(t, expectedType) + + case r : Return => + genReturn(r) + generatedType = expectedType + + case t : Try => + generatedType = genLoadTry(t) + + case Throw(expr) => + generatedType = genThrow(expr) + + case New(tpt) => + abort(s"Unexpected New(${tpt.summaryString}/$tpt) reached GenBCode.\n" + + " Call was genLoad" + ((tree, expectedType))) + + case app : Apply => + generatedType = genApply(app, expectedType) + + case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.") + + case This(qual) => + val symIsModuleClass = tree.symbol.isModuleClass + assert(tree.symbol == claszSymbol || symIsModuleClass, + s"Trying to access the this of another class: tree.symbol = ${tree.symbol}, class symbol = $claszSymbol compilation unit: $cunit") + if (symIsModuleClass && tree.symbol != claszSymbol) { + generatedType = genLoadModule(tree) + } + else { + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + generatedType = + if (tree.symbol == ArrayClass) ObjectReference + else brefType(thisName) // inner class (if any) for claszSymbol already tracked. + } + + case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => + assert(tree.symbol.isModule, s"Selection of non-module from empty package: $tree sym: ${tree.symbol} at: ${tree.pos}") + genLoadModule(tree) + + case Select(qualifier, selector) => + val sym = tree.symbol + generatedType = symInfoTK(sym) + val hostClass = findHostClass(qualifier.tpe, sym) + log(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass") + val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier + + def genLoadQualUnlessElidable() { if (!qualSafeToElide) { genLoadQualifier(tree) } } + + if (sym.isModule) { + genLoadQualUnlessElidable() + genLoadModule(tree) + } + else if (sym.isStaticMember) { + genLoadQualUnlessElidable() + fieldLoad(sym, hostClass) + } + else { + genLoadQualifier(tree) + fieldLoad(sym, hostClass) + } + + case Ident(name) => + val sym = tree.symbol + if (!sym.isPackage) { + val tk = symInfoTK(sym) + if (sym.isModule) { genLoadModule(tree) } + else { locals.load(sym) } + generatedType = tk + } + + case Literal(value) => + if (value.tag != UnitTag) (value.tag, expectedType) match { + case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG + case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL + case _ => genConstant(value); generatedType = tpeTK(tree) + } + + case blck : Block => genBlock(blck, expectedType) + + case Typed(Super(_, _), _) => genLoad(This(claszSymbol), expectedType) + + case Typed(expr, _) => genLoad(expr, expectedType) + + case Assign(_, _) => + generatedType = UNIT + genStat(tree) + + case av : ArrayValue => + generatedType = genArrayValue(av) + + case mtch : Match => + generatedType = genMatch(mtch) + + case EmptyTree => if (expectedType != UNIT) { emitZeroOf(expectedType) } + + case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.pos}") + } + + // emit conversion + if (generatedType != expectedType) { + adapt(generatedType, expectedType) + } + + } // end of GenBCode.genLoad() + + // ---------------- field load and store ---------------- + + /* + * must-single-thread + */ + def fieldLoad( field: Symbol, hostClass: Symbol = null) { + fieldOp(field, isLoad = true, hostClass) + } + /* + * must-single-thread + */ + def fieldStore(field: Symbol, hostClass: Symbol = null) { + fieldOp(field, isLoad = false, hostClass) + } + + /* + * must-single-thread + */ + private def fieldOp(field: Symbol, isLoad: Boolean, hostClass: Symbol = null) { + // LOAD_FIELD.hostClass , CALL_METHOD.hostClass , and #4283 + val owner = + if (hostClass == null) internalName(field.owner) + else internalName(hostClass) + val fieldJName = field.javaSimpleName.toString + val fieldDescr = symInfoTK(field).getDescriptor + val isStatic = field.isStaticMember + val opc = + if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD } + else { if (isStatic) asm.Opcodes.PUTSTATIC else asm.Opcodes.PUTFIELD } + mnode.visitFieldInsn(opc, owner, fieldJName, fieldDescr) + + } + + // ---------------- emitting constant values ---------------- + + /* + * For const.tag in {ClazzTag, EnumTag} + * must-single-thread + * Otherwise it's safe to call from multiple threads. + */ + def genConstant(const: Constant) { + (const.tag: @switch) match { + + case BooleanTag => bc.boolconst(const.booleanValue) + + case ByteTag => bc.iconst(const.byteValue) + case ShortTag => bc.iconst(const.shortValue) + case CharTag => bc.iconst(const.charValue) + case IntTag => bc.iconst(const.intValue) + + case LongTag => bc.lconst(const.longValue) + case FloatTag => bc.fconst(const.floatValue) + case DoubleTag => bc.dconst(const.doubleValue) + + case UnitTag => () + + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + mnode.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag + + case NullTag => emit(asm.Opcodes.ACONST_NULL) + + case ClazzTag => + val toPush: BType = { + val kind = toTypeKind(const.typeValue) + if (kind.isValueType) classLiteral(kind) + else kind + } + mnode.visitLdcInsn(toPush.toASMType) + + case EnumTag => + val sym = const.symbolValue + val ownerName = internalName(sym.owner) + val fieldName = sym.javaSimpleName.toString + val fieldDesc = toTypeKind(sym.tpe.underlying).getDescriptor + mnode.visitFieldInsn( + asm.Opcodes.GETSTATIC, + ownerName, + fieldName, + fieldDesc + ) + + case _ => abort(s"Unknown constant value: $const") + } + } + + private def genLabelDef(lblDf: LabelDef, expectedType: BType) { + // duplication of LabelDefs contained in `finally`-clauses is handled when emitting RETURN. No bookkeeping for that required here. + // no need to call index() over lblDf.params, on first access that magic happens (moreover, no LocalVariableTable entries needed for them). + markProgramPoint(programPoint(lblDf.symbol)) + lineNumber(lblDf) + genLoad(lblDf.rhs, expectedType) + } + + private def genReturn(r: Return) { + val Return(expr) = r + val returnedKind = tpeTK(expr) + genLoad(expr, returnedKind) + adapt(returnedKind, returnType) + val saveReturnValue = (returnType != UNIT) + lineNumber(r) + + cleanups match { + case Nil => + // not an assertion: !shouldEmitCleanup (at least not yet, pendingCleanups() may still have to run, and reset `shouldEmitCleanup`. + bc emitRETURN returnType + case nextCleanup :: rest => + if (saveReturnValue) { + if (insideCleanupBlock) { + cunit.warning(r.pos, "Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.") + bc drop returnType + } else { + // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted. + if (earlyReturnVar == null) { + earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar") + } + locals.store(earlyReturnVar) + } + } + bc goTo nextCleanup + shouldEmitCleanup = true + } + + } // end of genReturn() + + private def genApply(app: Apply, expectedType: BType): BType = { + var generatedType = expectedType + lineNumber(app) + app match { + + case Apply(TypeApply(fun, targs), _) => + + val sym = fun.symbol + val cast = sym match { + case Object_isInstanceOf => false + case Object_asInstanceOf => true + case _ => abort(s"Unexpected type application $fun[sym: ${sym.fullName}] in: $app") + } + + val Select(obj, _) = fun + val l = tpeTK(obj) + val r = tpeTK(targs.head) + + def genTypeApply(): BType = { + genLoadQualifier(fun) + + if (l.isValueType && r.isValueType) + genConversion(l, r, cast) + else if (l.isValueType) { + bc drop l + if (cast) { + mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.getInternalName) + bc dup ObjectReference + emit(asm.Opcodes.ATHROW) + } else { + bc boolconst false + } + } + else if (r.isValueType && cast) { + abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $app") + } + else if (r.isValueType) { + bc isInstance classLiteral(r) + } + else { + genCast(r, cast) + } + + if (cast) r else BOOL + } // end of genTypeApply() + + generatedType = genTypeApply() + + // 'super' call: Note: since constructors are supposed to + // return an instance of what they construct, we have to take + // special care. On JVM they are 'void', and Scala forbids (syntactically) + // to call super constructors explicitly and/or use their 'returned' value. + // therefore, we can ignore this fact, and generate code that leaves nothing + // on the stack (contrary to what the type in the AST says). + case Apply(fun @ Select(Super(_, mix), _), args) => + val invokeStyle = icodes.opcodes.SuperCall(mix) + // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + genLoadArguments(args, paramTKs(app)) + genCallMethod(fun.symbol, invokeStyle, pos = app.pos) + generatedType = asmMethodType(fun.symbol).getReturnType + + // 'new' constructor call: Note: since constructors are + // thought to return an instance of what they construct, + // we have to 'simulate' it by DUPlicating the freshly created + // instance (on JVM, <init> methods return VOID). + case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) => + val ctor = fun.symbol + assert(ctor.isClassConstructor, s"'new' call to non-constructor: ${ctor.name}") + + generatedType = tpeTK(tpt) + assert(generatedType.isRefOrArrayType, s"Non reference type cannot be instantiated: $generatedType") + + generatedType match { + case arr if generatedType.isArray => + genLoadArguments(args, paramTKs(app)) + val dims = arr.getDimensions + var elemKind = arr.getElementType + val argsSize = args.length + if (argsSize > dims) { + cunit.error(app.pos, s"too many arguments for array constructor: found ${args.length} but array has only $dims dimension(s)") + } + if (argsSize < dims) { + /* In one step: + * elemKind = new BType(BType.ARRAY, arr.off + argsSize, arr.len - argsSize) + * however the above does not enter a TypeName for each nested arrays in chrs. + */ + for (i <- args.length until dims) elemKind = arrayOf(elemKind) + } + (argsSize : @switch) match { + case 1 => bc newarray elemKind + case _ => + val descr = ('[' * argsSize) + elemKind.getDescriptor // denotes the same as: arrayN(elemKind, argsSize).getDescriptor + mnode.visitMultiANewArrayInsn(descr, argsSize) + } + + case rt if generatedType.hasObjectSort => + assert(exemplar(ctor.owner).c == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") + mnode.visitTypeInsn(asm.Opcodes.NEW, rt.getInternalName) + bc dup generatedType + genLoadArguments(args, paramTKs(app)) + genCallMethod(ctor, icodes.opcodes.Static(onInstance = true)) + + case _ => + abort(s"Cannot instantiate $tpt of kind: $generatedType") + } + + case Apply(fun @ _, List(expr)) if definitions.isBox(fun.symbol) => + val nativeKind = tpeTK(expr) + genLoad(expr, nativeKind) + val MethodNameAndType(mname, mdesc) = asmBoxTo(nativeKind) + bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + + case Apply(fun @ _, List(expr)) if definitions.isUnbox(fun.symbol) => + genLoad(expr) + val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + generatedType = boxType + val MethodNameAndType(mname, mdesc) = asmUnboxTo(boxType) + bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + + case app @ Apply(fun, args) => + val sym = fun.symbol + + if (sym.isLabel) { // jump to a label + genLoadLabelArguments(args, labelDef(sym), app.pos) + bc goTo programPoint(sym) + } else if (isPrimitive(sym)) { // primitive method call + generatedType = genPrimitiveOp(app, expectedType) + } else { // normal method call + + def genNormalMethodCall() { + + val invokeStyle = + if (sym.isStaticMember) icodes.opcodes.Static(onInstance = false) + else if (sym.isPrivate || sym.isClassConstructor) icodes.opcodes.Static(onInstance = true) + else icodes.opcodes.Dynamic; + + if (invokeStyle.hasInstance) { + genLoadQualifier(fun) + } + + genLoadArguments(args, paramTKs(app)) + + // In "a couple cases", squirrel away a extra information (hostClass, targetTypeKind). TODO Document what "in a couple cases" refers to. + var hostClass: Symbol = null + var targetTypeKind: BType = null + fun match { + case Select(qual, _) => + val qualSym = findHostClass(qual.tpe, sym) + if (qualSym == ArrayClass) { + targetTypeKind = tpeTK(qual) + log(s"Stored target type kind for ${sym.fullName} as $targetTypeKind") + } + else { + hostClass = qualSym + if (qual.tpe.typeSymbol != qualSym) { + log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}") + } + } + + case _ => + } + if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) { + val target: String = targetTypeKind.getInternalName + bc.invokevirtual(target, "clone", "()Ljava/lang/Object;") + } + else { + genCallMethod(sym, invokeStyle, hostClass, app.pos) + } + + } // end of genNormalMethodCall() + + genNormalMethodCall() + + generatedType = asmMethodType(sym).getReturnType + } + + } + + generatedType + } // end of genApply() + + private def genArrayValue(av: ArrayValue): BType = { + val ArrayValue(tpt @ TypeTree(), elems) = av + + val elmKind = tpeTK(tpt) + val generatedType = arrayOf(elmKind) + + lineNumber(av) + bc iconst elems.length + bc newarray elmKind + + var i = 0 + var rest = elems + while (!rest.isEmpty) { + bc dup generatedType + bc iconst i + genLoad(rest.head, elmKind) + bc astore elmKind + rest = rest.tail + i = i + 1 + } + + generatedType + } + + /* + * A Match node contains one or more case clauses, + * each case clause lists one or more Int values to use as keys, and a code block. + * Except the "default" case clause which (if it exists) doesn't list any Int key. + * + * On a first pass over the case clauses, we flatten the keys and their targets (the latter represented with asm.Labels). + * That representation allows JCodeMethodV to emit a lookupswitch or a tableswitch. + * + * On a second pass, we emit the switch blocks, one for each different target. + */ + private def genMatch(tree: Match): BType = { + lineNumber(tree) + genLoad(tree.selector, INT) + val generatedType = tpeTK(tree) + + var flatKeys: List[Int] = Nil + var targets: List[asm.Label] = Nil + var default: asm.Label = null + var switchBlocks: List[Pair[asm.Label, Tree]] = Nil + + // collect switch blocks and their keys, but don't emit yet any switch-block. + for (caze @ CaseDef(pat, guard, body) <- tree.cases) { + assert(guard == EmptyTree, guard) + val switchBlockPoint = new asm.Label + switchBlocks ::= Pair(switchBlockPoint, body) + pat match { + case Literal(value) => + flatKeys ::= value.intValue + targets ::= switchBlockPoint + case Ident(nme.WILDCARD) => + assert(default == null, s"multiple default targets in a Match node, at ${tree.pos}") + default = switchBlockPoint + case Alternative(alts) => + alts foreach { + case Literal(value) => + flatKeys ::= value.intValue + targets ::= switchBlockPoint + case _ => + abort(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.pos}") + } + case _ => + abort(s"Invalid pattern in Match node: $tree at: ${tree.pos}") + } + } + bc.emitSWITCH(mkArrayReverse(flatKeys), mkArray(targets.reverse), default, MIN_SWITCH_DENSITY) + + // emit switch-blocks. + val postMatch = new asm.Label + for (sb <- switchBlocks.reverse) { + val Pair(caseLabel, caseBody) = sb + markProgramPoint(caseLabel) + genLoad(caseBody, generatedType) + bc goTo postMatch + } + + markProgramPoint(postMatch) + generatedType + } + + def genBlock(tree: Block, expectedType: BType) { + val Block(stats, expr) = tree + val savedScope = varsInScope + varsInScope = Nil + stats foreach genStat + genLoad(expr, expectedType) + val end = currProgramPoint() + if (emitVars) { // add entries to LocalVariableTable JVM attribute + for (Pair(sym, start) <- varsInScope.reverse) { emitLocalVarScope(sym, start, end) } + } + varsInScope = savedScope + } + + def adapt(from: BType, to: BType) { + if (!conforms(from, to)) { + to match { + case UNIT => bc drop from + case _ => bc.emitT2T(from, to) + } + } else if (from.isNothingType) { + emit(asm.Opcodes.ATHROW) // ICode enters here into enterIgnoreMode, we'll rely instead on DCE at ClassNode level. + } else if (from.isNullType) { + bc drop from + mnode.visitInsn(asm.Opcodes.ACONST_NULL) + } + else (from, to) match { + case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG) + case _ => () + } + } + + /* Emit code to Load the qualifier of `tree` on top of the stack. */ + def genLoadQualifier(tree: Tree) { + lineNumber(tree) + tree match { + case Select(qualifier, _) => genLoad(qualifier) + case _ => abort(s"Unknown qualifier $tree") + } + } + + /* Generate code that loads args into label parameters. */ + def genLoadLabelArguments(args: List[Tree], lblDef: LabelDef, gotoPos: Position) { + assert(args forall { a => !a.hasSymbolField || a.hasSymbolWhich( s => !s.isLabel) }, s"SI-6089 at: $gotoPos") // SI-6089 + + val aps = { + val params: List[Symbol] = lblDef.params.map(_.symbol) + assert(args.length == params.length, s"Wrong number of arguments in call to label at: $gotoPos") + + def isTrivial(kv: (Tree, Symbol)) = kv match { + case (This(_), p) if p.name == nme.THIS => true + case (arg @ Ident(_), p) if arg.symbol == p => true + case _ => false + } + + (args zip params) filterNot isTrivial + } + + // first push *all* arguments. This makes sure muliple uses of the same labelDef-var will all denote the (previous) value. + aps foreach { case (arg, param) => genLoad(arg, locals(param).tk) } // `locals` is known to contain `param` because `genDefDef()` visited `labelDefsAtOrUnder` + + // second assign one by one to the LabelDef's variables. + aps.reverse foreach { + case (_, param) => + // TODO FIXME a "this" param results from tail-call xform. If so, the `else` branch seems perfectly fine. And the `then` branch must be wrong. + if (param.name == nme.THIS) mnode.visitVarInsn(asm.Opcodes.ASTORE, 0) + else locals.store(param) + } + + } + + def genLoadArguments(args: List[Tree], btpes: List[BType]) { + (args zip btpes) foreach { case (arg, btpe) => genLoad(arg, btpe) } + } + + def genLoadModule(tree: Tree): BType = { + // Working around SI-5604. Rather than failing the compile when we see a package here, check if there's a package object. + val module = ( + if (!tree.symbol.isPackageClass) tree.symbol + else tree.symbol.info.member(nme.PACKAGE) match { + case NoSymbol => abort(s"Cannot use package as value: $tree") ; NoSymbol + case s => devWarning("Bug: found package class where package object expected. Converting.") ; s.moduleClass + } + ) + lineNumber(tree) + genLoadModule(module) + symInfoTK(module) + } + + def genLoadModule(module: Symbol) { + if (claszSymbol == module.moduleClass && jMethodName != "readResolve") { + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + } else { + val mbt = symInfoTK(module) + mnode.visitFieldInsn( + asm.Opcodes.GETSTATIC, + mbt.getInternalName /* + "$" */ , + strMODULE_INSTANCE_FIELD, + mbt.getDescriptor // for nostalgics: toTypeKind(module.tpe).getDescriptor + ) + } + } + + def genConversion(from: BType, to: BType, cast: Boolean) { + if (cast) { bc.emitT2T(from, to) } + else { + bc drop from + bc boolconst (from == to) + } + } + + def genCast(to: BType, cast: Boolean) { + if (cast) { bc checkCast to } + else { bc isInstance to } + } + + /* Is the given symbol a primitive operation? */ + def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun) + + /* Generate coercion denoted by "code" */ + def genCoercion(code: Int) { + import scalaPrimitives._ + (code: @switch) match { + case B2B | S2S | C2C | I2I | L2L | F2F | D2D => () + case _ => + val from = coercionFrom(code) + val to = coercionTo(code) + bc.emitT2T(from, to) + } + } + + def genStringConcat(tree: Tree): BType = { + lineNumber(tree) + liftStringConcat(tree) match { + + // Optimization for expressions of the form "" + x. We can avoid the StringBuilder. + case List(Literal(Constant("")), arg) => + genLoad(arg, ObjectReference) + genCallMethod(String_valueOf, icodes.opcodes.Static(onInstance = false)) + + case concatenations => + bc.genStartConcat + for (elem <- concatenations) { + val kind = tpeTK(elem) + genLoad(elem, kind) + bc.genStringConcat(kind) + } + bc.genEndConcat + + } + + StringReference + } + + def genCallMethod(method: Symbol, style: InvokeStyle, hostClass0: Symbol = null, pos: Position = NoPosition) { + + val siteSymbol = claszSymbol + val hostSymbol = if (hostClass0 == null) method.owner else hostClass0; + val methodOwner = method.owner + // info calls so that types are up to date; erasure may add lateINTERFACE to traits + hostSymbol.info ; methodOwner.info + + def needsInterfaceCall(sym: Symbol) = ( + sym.isInterface + || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) + ) + + def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = { + target.isPublic || target.isProtected && { + (site.enclClass isSubClass target.enclClass) || + (site.enclosingPackage == target.privateWithin) + } + } + + // whether to reference the type of the receiver or + // the type of the method owner + val useMethodOwner = ( + style != icodes.opcodes.Dynamic + || hostSymbol.isBottomClass + || methodOwner == definitions.ObjectClass + ) + val receiver = if (useMethodOwner) methodOwner else hostSymbol + val bmOwner = asmClassType(receiver) + val jowner = bmOwner.getInternalName + val jname = method.javaSimpleName.toString + val bmType = asmMethodType(method) + val mdescr = bmType.getDescriptor + + def initModule() { + // we initialize the MODULE$ field immediately after the super ctor + if (!isModuleInitialized && + jMethodName == INSTANCE_CONSTRUCTOR_NAME && + jname == INSTANCE_CONSTRUCTOR_NAME && + isStaticModule(siteSymbol)) { + isModuleInitialized = true + mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + mnode.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + strMODULE_INSTANCE_FIELD, + "L" + thisName + ";" + ) + } + } + + if (style.isStatic) { + if (style.hasInstance) { bc.invokespecial (jowner, jname, mdescr) } + else { bc.invokestatic (jowner, jname, mdescr) } + } + else if (style.isDynamic) { + if (needsInterfaceCall(receiver)) { bc.invokeinterface(jowner, jname, mdescr) } + else { bc.invokevirtual (jowner, jname, mdescr) } + } + else { + assert(style.isSuper, s"An unknown InvokeStyle: $style") + bc.invokespecial(jowner, jname, mdescr) + initModule() + } + + } // end of genCallMethod() + + /* Generate the scala ## method. */ + def genScalaHash(tree: Tree): BType = { + genLoadModule(ScalaRunTimeModule) // TODO why load ScalaRunTimeModule if ## has InvokeStyle of Static(false) ? + genLoad(tree, ObjectReference) + genCallMethod(hashMethodSym, icodes.opcodes.Static(onInstance = false)) + + INT + } + + /* + * Returns a list of trees that each should be concatenated, from left to right. + * It turns a chained call like "a".+("b").+("c") into a list of arguments. + */ + def liftStringConcat(tree: Tree): List[Tree] = tree match { + case Apply(fun @ Select(larg, method), rarg) => + if (isPrimitive(fun.symbol) && + scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT) + liftStringConcat(larg) ::: rarg + else + tree :: Nil + case _ => + tree :: Nil + } + + /* Some useful equality helpers. */ + def isNull(t: Tree) = { + t match { + case Literal(Constant(null)) => true + case _ => false + } + } + + /* If l or r is constant null, returns the other ; otherwise null */ + def ifOneIsNull(l: Tree, r: Tree) = if (isNull(l)) r else if (isNull(r)) l else null + + /* Emit code to compare the two top-most stack values using the 'op' operator. */ + private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF_ICMP(op, success) + } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + bc.emitIF_ACMP(op, success) + } else { + (tk: @unchecked) match { + case LONG => emit(asm.Opcodes.LCMP) + case FLOAT => + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + else emit(asm.Opcodes.FCMPL) + case DOUBLE => + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) + } + bc goTo failure + } + + /* Emits code to compare (and consume) stack-top and zero using the 'op' operator */ + private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { + if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT + bc.emitIF(op, success) + } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + // @unchecked because references aren't compared with GT, GE, LT, LE. + (op : @unchecked) match { + case icodes.EQ => bc emitIFNULL success + case icodes.NE => bc emitIFNONNULL success + } + } else { + (tk: @unchecked) match { + case LONG => + emit(asm.Opcodes.LCONST_0) + emit(asm.Opcodes.LCMP) + case FLOAT => + emit(asm.Opcodes.FCONST_0) + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.FCMPG) + else emit(asm.Opcodes.FCMPL) + case DOUBLE => + emit(asm.Opcodes.DCONST_0) + if (op == icodes.LT || op == icodes.LE) emit(asm.Opcodes.DCMPG) + else emit(asm.Opcodes.DCMPL) + } + bc.emitIF(op, success) + } + bc goTo failure + } + + val testOpForPrimitive: Array[TestOp] = Array( + icodes.EQ, icodes.NE, icodes.EQ, icodes.NE, icodes.LT, icodes.LE, icodes.GE, icodes.GT + ) + + /* + * Generate code for conditional expressions. + * The jump targets success/failure of the test are `then-target` and `else-target` resp. + */ + private def genCond(tree: Tree, success: asm.Label, failure: asm.Label) { + + def genComparisonOp(l: Tree, r: Tree, code: Int) { + val op: TestOp = testOpForPrimitive(code - scalaPrimitives.ID) + // special-case reference (in)equality test for null (null eq x, x eq null) + var nonNullSide: Tree = null + if (scalaPrimitives.isReferenceEqualityOp(code) && + { nonNullSide = ifOneIsNull(l, r); nonNullSide != null } + ) { + genLoad(nonNullSide, ObjectReference) + genCZJUMP(success, failure, op, ObjectReference) + } + else { + val tk = maxType(tpeTK(l), tpeTK(r)) + genLoad(l, tk) + genLoad(r, tk) + genCJUMP(success, failure, op, tk) + } + } + + def default() = { + genLoad(tree, BOOL) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + + lineNumber(tree) + tree match { + + case Apply(fun, args) if isPrimitive(fun.symbol) => + import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive } + + // lhs and rhs of test + lazy val Select(lhs, _) = fun + val rhs = if (args.isEmpty) EmptyTree else args.head; // args.isEmpty only for ZNOT + + def genZandOrZor(and: Boolean) { // TODO WRONG + // reaching "keepGoing" indicates the rhs should be evaluated too (ie not short-circuited). + val keepGoing = new asm.Label + + if (and) genCond(lhs, keepGoing, failure) + else genCond(lhs, success, keepGoing) + + markProgramPoint(keepGoing) + genCond(rhs, success, failure) + } + + getPrimitive(fun.symbol) match { + case ZNOT => genCond(lhs, failure, success) + case ZAND => genZandOrZor(and = true) + case ZOR => genZandOrZor(and = false) + case code => + // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) + if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).hasObjectSort) { + // `lhs` has reference type + if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure) + else genEqEqPrimitive(lhs, rhs, failure, success) + } + else if (scalaPrimitives.isComparisonOp(code)) + genComparisonOp(lhs, rhs, code) + else + default + } + + case _ => default + } + + } // end of genCond() + + /* + * Generate the "==" code for object references. It is equivalent of + * if (l eq null) r eq null else l.equals(r); + * + * @param l left-hand-side of the '==' + * @param r right-hand-side of the '==' + */ + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label) { + + /* True if the equality comparison is between values that require the use of the rich equality + * comparator (scala.runtime.Comparator.equals). This is the case when either side of the + * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character. + * When it is statically known that both sides are equal and subtypes of Number of Character, + * not using the rich equality is possible (their own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = { + val areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) + + !areSameFinals && platform.isMaybeBoxed(l.tpe.typeSymbol) && platform.isMaybeBoxed(r.tpe.typeSymbol) + } + + if (mustUseAnyComparator) { + val equalsMethod = { + + def default = platform.externalEquals + + platform match { + case x: JavaPlatform => + import x._ + if (l.tpe <:< BoxedNumberClass.tpe) { + if (r.tpe <:< BoxedNumberClass.tpe) externalEqualsNumNum + else if (r.tpe <:< BoxedCharacterClass.tpe) externalEqualsNumChar + else externalEqualsNumObject + } + else default + + case _ => default + } + } + genLoad(l, ObjectReference) + genLoad(r, ObjectReference) + genCallMethod(equalsMethod, icodes.opcodes.Static(onInstance = false)) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + else { + if (isNull(l)) { + // null == expr -> expr eq null + genLoad(r, ObjectReference) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + } else if (isNull(r)) { + // expr == null -> expr eq null + genLoad(l, ObjectReference) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + } else { + // l == r -> if (l eq null) r eq null else l.equals(r) + val eqEqTempLocal = locals.makeLocal(AnyRefReference, nme.EQEQ_LOCAL_VAR.toString) + val lNull = new asm.Label + val lNonNull = new asm.Label + + genLoad(l, ObjectReference) + genLoad(r, ObjectReference) + locals.store(eqEqTempLocal) + bc dup ObjectReference + genCZJUMP(lNull, lNonNull, icodes.EQ, ObjectReference) + + markProgramPoint(lNull) + bc drop ObjectReference + locals.load(eqEqTempLocal) + genCZJUMP(success, failure, icodes.EQ, ObjectReference) + + markProgramPoint(lNonNull) + locals.load(eqEqTempLocal) + genCallMethod(Object_equals, icodes.opcodes.Dynamic) + genCZJUMP(success, failure, icodes.NE, BOOL) + } + } + } + + /* can-multi-thread */ + def getMaxType(ts: List[Type]): BType = { + ts map toTypeKind reduceLeft maxType + } + + def genSynchronized(tree: Apply, expectedType: BType): BType + def genLoadTry(tree: Try): BType + + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala new file mode 100644 index 0000000000..f95ceef678 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeGlue.scala @@ -0,0 +1,881 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } + +/* + * Immutable representations of bytecode-level types. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeGlue extends SubComponent { + + import global._ + + object BType { + + import global.chrs + + // ------------- sorts ------------- + + val VOID : Int = 0 + val BOOLEAN: Int = 1 + val CHAR : Int = 2 + val BYTE : Int = 3 + val SHORT : Int = 4 + val INT : Int = 5 + val FLOAT : Int = 6 + val LONG : Int = 7 + val DOUBLE : Int = 8 + val ARRAY : Int = 9 + val OBJECT : Int = 10 + val METHOD : Int = 11 + + // ------------- primitive types ------------- + + val VOID_TYPE = new BType(VOID, ('V' << 24) | (5 << 16) | (0 << 8) | 0, 1) + val BOOLEAN_TYPE = new BType(BOOLEAN, ('Z' << 24) | (0 << 16) | (5 << 8) | 1, 1) + val CHAR_TYPE = new BType(CHAR, ('C' << 24) | (0 << 16) | (6 << 8) | 1, 1) + val BYTE_TYPE = new BType(BYTE, ('B' << 24) | (0 << 16) | (5 << 8) | 1, 1) + val SHORT_TYPE = new BType(SHORT, ('S' << 24) | (0 << 16) | (7 << 8) | 1, 1) + val INT_TYPE = new BType(INT, ('I' << 24) | (0 << 16) | (0 << 8) | 1, 1) + val FLOAT_TYPE = new BType(FLOAT, ('F' << 24) | (2 << 16) | (2 << 8) | 1, 1) + val LONG_TYPE = new BType(LONG, ('J' << 24) | (1 << 16) | (1 << 8) | 2, 1) + val DOUBLE_TYPE = new BType(DOUBLE, ('D' << 24) | (3 << 16) | (3 << 8) | 2, 1) + + /* + * Returns the Java type corresponding to the given type descriptor. + * + * @param off the offset of this descriptor in the chrs buffer. + * @return the Java type corresponding to the given type descriptor. + * + * can-multi-thread + */ + def getType(off: Int): BType = { + var len = 0 + chrs(off) match { + case 'V' => VOID_TYPE + case 'Z' => BOOLEAN_TYPE + case 'C' => CHAR_TYPE + case 'B' => BYTE_TYPE + case 'S' => SHORT_TYPE + case 'I' => INT_TYPE + case 'F' => FLOAT_TYPE + case 'J' => LONG_TYPE + case 'D' => DOUBLE_TYPE + case '[' => + len = 1 + while (chrs(off + len) == '[') { + len += 1 + } + if (chrs(off + len) == 'L') { + len += 1 + while (chrs(off + len) != ';') { + len += 1 + } + } + new BType(ARRAY, off, len + 1) + case 'L' => + len = 1 + while (chrs(off + len) != ';') { + len += 1 + } + new BType(OBJECT, off + 1, len - 1) + // case '(': + case _ => + assert(chrs(off) == '(') + var resPos = off + 1 + while (chrs(resPos) != ')') { resPos += 1 } + val resType = getType(resPos + 1) + val len = resPos - off + 1 + resType.len; + new BType( + METHOD, + off, + if (resType.hasObjectSort) { + len + 2 // "+ 2" accounts for the "L ... ;" in a descriptor for a non-array reference. + } else { + len + } + ) + } + } + + /* Params denote an internal name. + * can-multi-thread + */ + def getObjectType(index: Int, length: Int): BType = { + val sort = if (chrs(index) == '[') ARRAY else OBJECT; + new BType(sort, index, length) + } + + /* + * @param typeDescriptor a field or method type descriptor. + * + * must-single-thread + */ + def getType(typeDescriptor: String): BType = { + val n = global.newTypeName(typeDescriptor) + getType(n.start) + } + + /* + * @param methodDescriptor a method descriptor. + * + * must-single-thread + */ + def getMethodType(methodDescriptor: String): BType = { + val n = global.newTypeName(methodDescriptor) + new BType(BType.METHOD, n.start, n.length) // TODO assert isValidMethodDescriptor + } + + /* + * Returns the Java method type corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the Java type corresponding to the given argument and return types. + * + * must-single-thread + */ + def getMethodType(returnType: BType, argumentTypes: Array[BType]): BType = { + val n = global.newTypeName(getMethodDescriptor(returnType, argumentTypes)) + new BType(BType.METHOD, n.start, n.length) + } + + /* + * Returns the Java types corresponding to the argument types of method descriptor whose first argument starts at idx0. + * + * @param idx0 index into chrs of the first argument. + * @return the Java types corresponding to the argument types of the given method descriptor. + * + * can-multi-thread + */ + private def getArgumentTypes(idx0: Int): Array[BType] = { + assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") + val args = new Array[BType](getArgumentCount(idx0)) + var off = idx0 + var size = 0 + while (chrs(off) != ')') { + args(size) = getType(off) + off += args(size).len + if (args(size).sort == OBJECT) { off += 2 } + // debug: assert("LVZBSCIJFD[)".contains(chrs(off))) + size += 1 + } + // debug: var check = 0; while (check < args.length) { assert(args(check) != null); check += 1 } + args + } + + /* + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java types corresponding to the argument types of the given method descriptor. + * + * must-single-thread + */ + def getArgumentTypes(methodDescriptor: String): Array[BType] = { + val n = global.newTypeName(methodDescriptor) + getArgumentTypes(n.start + 1) + } + + /* + * Returns the number of argument types of this method type, whose first argument starts at idx0. + * + * @param idx0 index into chrs of the first argument. + * @return the number of argument types of this method type. + * + * can-multi-thread + */ + private def getArgumentCount(idx0: Int): Int = { + assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") + var off = idx0 + var size = 0 + var keepGoing = true + while (keepGoing) { + val car = chrs(off) + off += 1 + if (car == ')') { + keepGoing = false + } else if (car == 'L') { + while (chrs(off) != ';') { off += 1 } + off += 1 + size += 1 + } else if (car != '[') { + size += 1 + } + } + + size + } + + /* + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java type corresponding to the return type of the given method descriptor. + * + * must-single-thread + */ + def getReturnType(methodDescriptor: String): BType = { + val n = global.newTypeName(methodDescriptor) + val delta = n.pos(')') // `delta` is relative to the Name's zero-based start position, not a valid index into chrs. + assert(delta < n.length, s"not a valid method descriptor: $methodDescriptor") + getType(n.start + delta + 1) + } + + /* + * Returns the descriptor corresponding to the given argument and return types. + * Note: no BType is created here for the resulting method descriptor, + * if that's desired the invoker is responsible for that. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + * + * can-multi-thread + */ + def getMethodDescriptor( + returnType: BType, + argumentTypes: Array[BType]): String = + { + val buf = new StringBuffer() + buf.append('(') + var i = 0 + while (i < argumentTypes.length) { + argumentTypes(i).getDescriptor(buf) + i += 1 + } + buf.append(')') + returnType.getDescriptor(buf) + buf.toString() + } + + } // end of object BType + + /* + * Based on ASM's Type class. Namer's chrs is used in this class for the same purposes as the `buf` char array in asm.Type. + * + * All methods of this classs can-multi-thread + */ + final class BType(val sort: Int, val off: Int, val len: Int) { + + import global.chrs + + /* + * can-multi-thread + */ + def toASMType: scala.tools.asm.Type = { + import scala.tools.asm + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (sort: @switch) match { + case asm.Type.VOID => asm.Type.VOID_TYPE + case asm.Type.BOOLEAN => asm.Type.BOOLEAN_TYPE + case asm.Type.CHAR => asm.Type.CHAR_TYPE + case asm.Type.BYTE => asm.Type.BYTE_TYPE + case asm.Type.SHORT => asm.Type.SHORT_TYPE + case asm.Type.INT => asm.Type.INT_TYPE + case asm.Type.FLOAT => asm.Type.FLOAT_TYPE + case asm.Type.LONG => asm.Type.LONG_TYPE + case asm.Type.DOUBLE => asm.Type.DOUBLE_TYPE + case asm.Type.ARRAY | + asm.Type.OBJECT => asm.Type.getObjectType(getInternalName) + case asm.Type.METHOD => asm.Type.getMethodType(getDescriptor) + } + } + + /* + * Unlike for ICode's REFERENCE, isBoxedType(t) implies isReferenceType(t) + * Also, `isReferenceType(RT_NOTHING) == true` , similarly for RT_NULL. + * Use isNullType() , isNothingType() to detect Nothing and Null. + * + * can-multi-thread + */ + def hasObjectSort = (sort == BType.OBJECT) + + /* + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + * + * can-multi-thread + */ + def getDimensions: Int = { + var i = 1 + while (chrs(off + i) == '[') { + i += 1 + } + i + } + + /* + * Returns the (ultimate) element type of this array type. + * This method should only be used for an array type. + * + * @return Returns the type of the elements of this array type. + * + * can-multi-thread + */ + def getElementType: BType = { + assert(isArray, s"Asked for the element type of a non-array type: $this") + BType.getType(off + getDimensions) + } + + /* + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + * + * can-multi-thread + */ + def getInternalName: String = { + new String(chrs, off, len) + } + + /* + * @return the prefix of the internal name until the last '/' (if '/' present), empty string otherwise. + * + * can-multi-thread + */ + def getRuntimePackage: String = { + assert(hasObjectSort, s"not of object sort: $toString") + val iname = getInternalName + val idx = iname.lastIndexOf('/') + if (idx == -1) "" + else iname.substring(0, idx) + } + + /* + * @return the suffix of the internal name until the last '/' (if '/' present), internal name otherwise. + * + * can-multi-thread + */ + def getSimpleName: String = { + assert(hasObjectSort, s"not of object sort: $toString") + val iname = getInternalName + val idx = iname.lastIndexOf('/') + if (idx == -1) iname + else iname.substring(idx + 1) + } + + /* + * Returns the argument types of methods of this type. + * This method should only be used for method types. + * + * @return the argument types of methods of this type. + * + * can-multi-thread + */ + def getArgumentTypes: Array[BType] = { + BType.getArgumentTypes(off + 1) + } + + /* + * Returns the number of arguments of methods of this type. + * This method should only be used for method types. + * + * @return the number of arguments of methods of this type. + * + * can-multi-thread + */ + def getArgumentCount: Int = { + BType.getArgumentCount(off + 1) + } + + /* + * Returns the return type of methods of this type. + * This method should only be used for method types. + * + * @return the return type of methods of this type. + * + * can-multi-thread + */ + def getReturnType: BType = { + assert(chrs(off) == '(', s"doesn't look like a method descriptor: $toString") + var resPos = off + 1 + while (chrs(resPos) != ')') { resPos += 1 } + BType.getType(resPos + 1) + } + + /* + * Given a zero-based formal-param-position, return its corresponding local-var-index, + * taking into account the JVM-type-sizes of preceding formal params. + */ + def convertFormalParamPosToLocalVarIdx(paramPos: Int, isInstanceMethod: Boolean): Int = { + assert(sort == asm.Type.METHOD) + val paramTypes = getArgumentTypes + var local = 0 + (0 until paramPos) foreach { argPos => local += paramTypes(argPos).getSize } + + local + (if (isInstanceMethod) 1 else 0) + } + + /* + * Given a local-var-index, return its corresponding zero-based formal-param-position, + * taking into account the JVM-type-sizes of preceding formal params. + */ + def convertLocalVarIdxToFormalParamPos(localIdx: Int, isInstanceMethod: Boolean): Int = { + assert(sort == asm.Type.METHOD) + val paramTypes = getArgumentTypes + var remaining = (if (isInstanceMethod) (localIdx - 1) else localIdx) + assert(remaining >= 0) + var result = 0 + while (remaining > 0) { + remaining -= paramTypes(result).getSize + result += 1 + } + assert(remaining == 0) + + result + } + + // ------------------------------------------------------------------------ + // Inspector methods + // ------------------------------------------------------------------------ + + def isPrimitiveOrVoid = (sort < BType.ARRAY) // can-multi-thread + def isValueType = (sort < BType.ARRAY) // can-multi-thread + def isArray = (sort == BType.ARRAY) // can-multi-thread + def isUnitType = (sort == BType.VOID) // can-multi-thread + + def isRefOrArrayType = { hasObjectSort || isArray } // can-multi-thread + def isNonUnitValueType = { isValueType && !isUnitType } // can-multi-thread + + def isNonSpecial = { !isValueType && !isArray && !isPhantomType } // can-multi-thread + def isNothingType = { (this == RT_NOTHING) || (this == CT_NOTHING) } // can-multi-thread + def isNullType = { (this == RT_NULL) || (this == CT_NULL) } // can-multi-thread + def isPhantomType = { isNothingType || isNullType } // can-multi-thread + + /* + * can-multi-thread + */ + def isBoxed = { + this match { + case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | + BOXED_BYTE | BOXED_SHORT | BOXED_INT | + BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE + => true + case _ + => false + } + } + + /* On the JVM, + * BOOL, BYTE, CHAR, SHORT, and INT + * are like Ints for the purpose of lub calculation. + * + * can-multi-thread + */ + def isIntSizedType = { + (sort : @switch) match { + case BType.BOOLEAN | BType.CHAR | + BType.BYTE | BType.SHORT | BType.INT + => true + case _ + => false + } + } + + /* On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is. + * + * can-multi-thread + */ + def isIntegralType = { + (sort : @switch) match { + case BType.CHAR | + BType.BYTE | BType.SHORT | BType.INT | + BType.LONG + => true + case _ + => false + } + } + + /* On the JVM, FLOAT and DOUBLE. + * + * can-multi-thread + */ + def isRealType = { (sort == BType.FLOAT ) || (sort == BType.DOUBLE) } + + def isNumericType = (isIntegralType || isRealType) // can-multi-thread + + /* Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?) + * + * can-multi-thread + */ + def isWideType = (getSize == 2) + + def isCapturedCellRef: Boolean = { + this == srBooleanRef || this == srByteRef || + this == srCharRef || + this == srIntRef || + this == srLongRef || + this == srFloatRef || this == srDoubleRef + } + + /* + * Element vs. Component type of an array: + * Quoting from the JVMS, Sec. 2.4 "Reference Types and Values" + * + * An array type consists of a component type with a single dimension (whose + * length is not given by the type). The component type of an array type may itself be + * an array type. If, starting from any array type, one considers its component type, + * and then (if that is also an array type) the component type of that type, and so on, + * eventually one must reach a component type that is not an array type; this is called + * the element type of the array type. The element type of an array type is necessarily + * either a primitive type, or a class type, or an interface type. + * + */ + + /* The type of items this array holds. + * + * can-multi-thread + */ + def getComponentType: BType = { + assert(isArray, s"Asked for the component type of a non-array type: $this") + BType.getType(off + 1) + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /* + * @return the descriptor corresponding to this Java type. + * + * can-multi-thread + */ + def getDescriptor: String = { + val buf = new StringBuffer() + getDescriptor(buf) + buf.toString() + } + + /* + * Appends the descriptor corresponding to this Java type to the given string buffer. + * + * @param buf the string buffer to which the descriptor must be appended. + * + * can-multi-thread + */ + private def getDescriptor(buf: StringBuffer) { + if (isPrimitiveOrVoid) { + // descriptor is in byte 3 of 'off' for primitive types (buf == null) + buf.append(((off & 0xFF000000) >>> 24).asInstanceOf[Char]) + } else if (sort == BType.OBJECT) { + buf.append('L') + buf.append(chrs, off, len) + buf.append(';') + } else { // sort == ARRAY || sort == METHOD + buf.append(chrs, off, len) + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /* + * Returns the size of values of this type. + * This method must not be used for method types. + * + * @return the size of values of this type, i.e., 2 for <tt>long</tt> and + * <tt>double</tt>, 0 for <tt>void</tt> and 1 otherwise. + * + * can-multi-thread + */ + def getSize: Int = { + // the size is in byte 0 of 'off' for primitive types (buf == null) + if (isPrimitiveOrVoid) (off & 0xFF) else 1 + } + + /* + * Returns a JVM instruction opcode adapted to this Java type. This method + * must not be used for method types. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, + * ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is <tt>float</tt> and + * <tt>opcode</tt> is IRETURN, this method returns FRETURN. + * + * can-multi-thread + */ + def getOpcode(opcode: Int): Int = { + import scala.tools.asm.Opcodes + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + opcode + (if (isPrimitiveOrVoid) (off & 0xFF00) >> 8 else 4) + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + opcode + (if (isPrimitiveOrVoid) (off & 0xFF0000) >> 16 else 4) + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /* + * Tests if the given object is equal to this type. + * + * @param o the object to be compared to this type. + * @return <tt>true</tt> if the given object is equal to this type. + * + * can-multi-thread + */ + override def equals(o: Any): Boolean = { + if (!(o.isInstanceOf[BType])) { + return false + } + val t = o.asInstanceOf[BType] + if (this eq t) { + return true + } + if (sort != t.sort) { + return false + } + if (sort >= BType.ARRAY) { + if (len != t.len) { + return false + } + // sort checked already + if (off == t.off) { + return true + } + var i = 0 + while (i < len) { + if (chrs(off + i) != chrs(t.off + i)) { + return false + } + i += 1 + } + // If we reach here, we could update the largest of (this.off, t.off) to match the other, so as to simplify future == comparisons. + // But that would require a var rather than val. + } + true + } + + /* + * @return a hash code value for this type. + * + * can-multi-thread + */ + override def hashCode(): Int = { + var hc = 13 * sort; + if (sort >= BType.ARRAY) { + var i = off + val end = i + len + while (i < end) { + hc = 17 * (hc + chrs(i)) + i += 1 + } + } + hc + } + + /* + * @return the descriptor of this type. + * + * can-multi-thread + */ + override def toString: String = { getDescriptor } + + } + + /* + * Creates a TypeName and the BType token for it. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * + * must-single-thread + */ + def brefType(iname: String): BType = { brefType(newTypeName(iname.toCharArray(), 0, iname.length())) } + + /* + * Creates a BType token for the TypeName received as argument. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * + * can-multi-thread + */ + def brefType(iname: TypeName): BType = { BType.getObjectType(iname.start, iname.length) } + + // due to keyboard economy only + val UNIT = BType.VOID_TYPE + val BOOL = BType.BOOLEAN_TYPE + val CHAR = BType.CHAR_TYPE + val BYTE = BType.BYTE_TYPE + val SHORT = BType.SHORT_TYPE + val INT = BType.INT_TYPE + val LONG = BType.LONG_TYPE + val FLOAT = BType.FLOAT_TYPE + val DOUBLE = BType.DOUBLE_TYPE + + val BOXED_UNIT = brefType("java/lang/Void") + val BOXED_BOOLEAN = brefType("java/lang/Boolean") + val BOXED_BYTE = brefType("java/lang/Byte") + val BOXED_SHORT = brefType("java/lang/Short") + val BOXED_CHAR = brefType("java/lang/Character") + val BOXED_INT = brefType("java/lang/Integer") + val BOXED_LONG = brefType("java/lang/Long") + val BOXED_FLOAT = brefType("java/lang/Float") + val BOXED_DOUBLE = brefType("java/lang/Double") + + /* + * RT_NOTHING and RT_NULL exist at run-time only. + * They are the bytecode-level manifestation (in method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * Therefore, when RT_NOTHING or RT_NULL are to be emitted, + * a mapping is needed: the internal names of NothingClass and NullClass can't be emitted as-is. + */ + val RT_NOTHING = brefType("scala/runtime/Nothing$") + val RT_NULL = brefType("scala/runtime/Null$") + val CT_NOTHING = brefType("scala/Nothing") // TODO needed? + val CT_NULL = brefType("scala/Null") // TODO needed? + + val srBooleanRef = brefType("scala/runtime/BooleanRef") + val srByteRef = brefType("scala/runtime/ByteRef") + val srCharRef = brefType("scala/runtime/CharRef") + val srIntRef = brefType("scala/runtime/IntRef") + val srLongRef = brefType("scala/runtime/LongRef") + val srFloatRef = brefType("scala/runtime/FloatRef") + val srDoubleRef = brefType("scala/runtime/DoubleRef") + + /* Map from type kinds to the Java reference types. + * Useful when pushing class literals onto the operand stack (ldc instruction taking a class literal). + * @see Predef.classOf + * @see genConstant() + */ + val classLiteral = immutable.Map[BType, BType]( + UNIT -> BOXED_UNIT, + BOOL -> BOXED_BOOLEAN, + BYTE -> BOXED_BYTE, + SHORT -> BOXED_SHORT, + CHAR -> BOXED_CHAR, + INT -> BOXED_INT, + LONG -> BOXED_LONG, + FLOAT -> BOXED_FLOAT, + DOUBLE -> BOXED_DOUBLE + ) + + case class MethodNameAndType(mname: String, mdesc: String) + + val asmBoxTo: Map[BType, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("boxToBoolean", "(Z)Ljava/lang/Boolean;" ) , + BYTE -> MethodNameAndType("boxToByte", "(B)Ljava/lang/Byte;" ) , + CHAR -> MethodNameAndType("boxToCharacter", "(C)Ljava/lang/Character;") , + SHORT -> MethodNameAndType("boxToShort", "(S)Ljava/lang/Short;" ) , + INT -> MethodNameAndType("boxToInteger", "(I)Ljava/lang/Integer;" ) , + LONG -> MethodNameAndType("boxToLong", "(J)Ljava/lang/Long;" ) , + FLOAT -> MethodNameAndType("boxToFloat", "(F)Ljava/lang/Float;" ) , + DOUBLE -> MethodNameAndType("boxToDouble", "(D)Ljava/lang/Double;" ) + ) + } + + val asmUnboxTo: Map[BType, MethodNameAndType] = { + Map( + BOOL -> MethodNameAndType("unboxToBoolean", "(Ljava/lang/Object;)Z") , + BYTE -> MethodNameAndType("unboxToByte", "(Ljava/lang/Object;)B") , + CHAR -> MethodNameAndType("unboxToChar", "(Ljava/lang/Object;)C") , + SHORT -> MethodNameAndType("unboxToShort", "(Ljava/lang/Object;)S") , + INT -> MethodNameAndType("unboxToInt", "(Ljava/lang/Object;)I") , + LONG -> MethodNameAndType("unboxToLong", "(Ljava/lang/Object;)J") , + FLOAT -> MethodNameAndType("unboxToFloat", "(Ljava/lang/Object;)F") , + DOUBLE -> MethodNameAndType("unboxToDouble", "(Ljava/lang/Object;)D") + ) + } + + /* + * can-multi-thread + */ + def toBType(t: asm.Type): BType = { + (t.getSort: @switch) match { + case asm.Type.VOID => BType.VOID_TYPE + case asm.Type.BOOLEAN => BType.BOOLEAN_TYPE + case asm.Type.CHAR => BType.CHAR_TYPE + case asm.Type.BYTE => BType.BYTE_TYPE + case asm.Type.SHORT => BType.SHORT_TYPE + case asm.Type.INT => BType.INT_TYPE + case asm.Type.FLOAT => BType.FLOAT_TYPE + case asm.Type.LONG => BType.LONG_TYPE + case asm.Type.DOUBLE => BType.DOUBLE_TYPE + case asm.Type.ARRAY | + asm.Type.OBJECT | + asm.Type.METHOD => + // TODO confirm whether this also takes care of the phantom types. + val key = + if (t.getSort == asm.Type.METHOD) t.getDescriptor + else t.getInternalName + + val n = global.lookupTypeName(key.toCharArray) + new BType(t.getSort, n.start, n.length) + } + } + + /* + * ASM trees represent types as strings (internal names, descriptors). + * Given that we operate instead on BTypes, conversion is needed when visiting MethodNodes outside GenBCode. + * + * can-multi-thread + */ + def descrToBType(typeDescriptor: String): BType = { + val c: Char = typeDescriptor(0) + c match { + case 'V' => BType.VOID_TYPE + case 'Z' => BType.BOOLEAN_TYPE + case 'C' => BType.CHAR_TYPE + case 'B' => BType.BYTE_TYPE + case 'S' => BType.SHORT_TYPE + case 'I' => BType.INT_TYPE + case 'F' => BType.FLOAT_TYPE + case 'J' => BType.LONG_TYPE + case 'D' => BType.DOUBLE_TYPE + case 'L' => + val iname = typeDescriptor.substring(1, typeDescriptor.length() - 1) + val n = global.lookupTypeName(iname.toCharArray) + new BType(asm.Type.OBJECT, n.start, n.length) + case _ => + val n = global.lookupTypeName(typeDescriptor.toCharArray) + BType.getType(n.start) + } + } + + /* + * Use only to lookup reference types, otherwise use `descrToBType()` + * + * can-multi-thread + */ + def lookupRefBType(iname: String): BType = { + import global.chrs + val n = global.lookupTypeName(iname.toCharArray) + val sort = if (chrs(n.start) == '[') BType.ARRAY else BType.OBJECT; + new BType(sort, n.start, n.length) + } + + def lookupRefBTypeIfExisting(iname: String): BType = { + import global.chrs + val n = global.lookupTypeNameIfExisting(iname.toCharArray, false) + if (n == null) { return null } + val sort = if (chrs(n.start) == '[') BType.ARRAY else BType.OBJECT; + new BType(sort, n.start, n.length) + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala new file mode 100644 index 0000000000..62270b7c0a --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -0,0 +1,1329 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } +import scala.tools.nsc.io.AbstractFile + +/* + * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { + + import global._ + + /* + * must-single-thread + */ + def getFileForClassfile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + getFile(base, clsName, suffix) + } + + /* + * must-single-thread + */ + def getOutFolder(csym: Symbol, cName: String, cunit: CompilationUnit): _root_.scala.tools.nsc.io.AbstractFile = { + try { + outputDirectory(csym) + } catch { + case ex: Throwable => + cunit.error(cunit.body.pos, s"Couldn't create file for class $cName\n${ex.getMessage}") + null + } + } + + var pickledBytes = 0 // statistics + + // ----------------------------------------------------------------------------------------- + // finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM) + // Background: + // http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + // http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + // https://issues.scala-lang.org/browse/SI-3872 + // ----------------------------------------------------------------------------------------- + + /* + * can-multi-thread + */ + def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): BType = { + var chainA = as + var chainB = bs + var fcs: Tracked = null + do { + if (chainB contains chainA.head) fcs = chainA.head + else if (chainA contains chainB.head) fcs = chainB.head + else { + chainA = chainA.tail + chainB = chainB.tail + } + } while (fcs == null) + fcs.c + } + + /* An `asm.ClassWriter` that uses `jvmWiseLUB()` + * The internal name of the least common ancestor of the types given by inameA and inameB. + * It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow + */ + final class CClassWriter(flags: Int) extends asm.ClassWriter(flags) { + + /* + * This method is thread re-entrant because chrs never grows during its operation (that's because all TypeNames being looked up have already been entered). + * To stress this point, rather than using `newTypeName()` we use `lookupTypeName()` + * + * can-multi-thread + */ + override def getCommonSuperClass(inameA: String, inameB: String): String = { + val a = brefType(lookupTypeName(inameA.toCharArray)) + val b = brefType(lookupTypeName(inameB.toCharArray)) + val lca = jvmWiseLUB(a, b) + val lcaName = lca.getInternalName // don't call javaName because that side-effects innerClassBuffer. + assert(lcaName != "scala/Any") + + lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. + } + + } + + /* + * Finding the least upper bound in agreement with the bytecode verifier (given two internal names handed out by ASM) + * Background: + * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + * https://issues.scala-lang.org/browse/SI-3872 + * + * can-multi-thread + */ + def jvmWiseLUB(a: BType, b: BType): BType = { + + assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a") + assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b") + + val ta = exemplars.get(a) + val tb = exemplars.get(b) + + val res = Pair(ta.isInterface, tb.isInterface) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (tb.isSubtypeOf(ta.c)) ta.c + else if (ta.isSubtypeOf(tb.c)) tb.c + else ObjectReference + case (true, false) => + if (tb.isSubtypeOf(a)) a else ObjectReference + case (false, true) => + if (ta.isSubtypeOf(b)) b else ObjectReference + case _ => + firstCommonSuffix(ta :: ta.superClasses, tb :: tb.superClasses) + } + assert(res.isNonSpecial, "jvmWiseLUB() returned a non-plain-class.") + res + } + + /* + * must-single-thread + */ + object isJavaEntryPoint { + + /* + * must-single-thread + */ + def apply(sym: Symbol, csymCompUnit: CompilationUnit): Boolean = { + def fail(msg: String, pos: Position = sym.pos) = { + csymCompUnit.warning(sym.pos, + sym.name + + s" has a main method with parameter type Array[String], but ${sym.fullName('.')} will not be a runnable program.\n Reason: $msg" + // TODO: make this next claim true, if possible + // by generating valid main methods as static in module classes + // not sure what the jvm allows here + // + " You can still run the program by calling it as " + sym.javaSimpleName + " instead." + ) + false + } + def failNoForwarder(msg: String) = { + fail(s"$msg, which means no static forwarder can be generated.\n") + } + val possibles = if (sym.hasModuleFlag) (sym.tpe nonPrivateMember nme.main).alternatives else Nil + val hasApproximate = possibles exists { m => + m.info match { + case MethodType(p :: Nil, _) => p.tpe.typeSymbol == definitions.ArrayClass + case _ => false + } + } + // At this point it's a module with a main-looking method, so either succeed or warn that it isn't. + hasApproximate && { + // Before erasure so we can identify generic mains. + enteringErasure { + val companion = sym.linkedClassOfClass + + if (definitions.hasJavaMainMethod(companion)) + failNoForwarder("companion contains its own main method") + else if (companion.tpe.member(nme.main) != NoSymbol) + // this is only because forwarders aren't smart enough yet + failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)") + else if (companion.isTrait) + failNoForwarder("companion is a trait") + // Now either succeeed, or issue some additional warnings for things which look like + // attempts to be java main methods. + else (possibles exists definitions.isJavaMainMethod) || { + possibles exists { m => + m.info match { + case PolyType(_, _) => + fail("main methods cannot be generic.") + case MethodType(params, res) => + if (res.typeSymbol :: params exists (_.isAbstractType)) + fail("main methods cannot refer to type parameters or abstract types.", m.pos) + else + definitions.isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos) + case tp => + fail(s"don't know what this is: $tp", m.pos) + } + } + } + } + } + } + + } + + /* + * must-single-thread + */ + def initBytecodeWriter(entryPoints: List[Symbol]): BytecodeWriter = { + settings.outputDirs.getSingleOutput match { + case Some(f) if f hasExtension "jar" => + // If no main class was specified, see if there's only one + // entry point among the classes going into the jar. + if (settings.mainClass.isDefault) { + entryPoints map (_.fullName('.')) match { + case Nil => + log("No Main-Class designated or discovered.") + case name :: Nil => + log(s"Unique entry point: setting Main-Class to $name") + settings.mainClass.value = name + case names => + log(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}") + } + } + else log(s"Main-Class was specified: ${settings.mainClass.value}") + + new DirectToJarfileWriter(f.file) + + case _ => factoryNonJarBytecodeWriter() + } + } + + /* + * must-single-thread + */ + def fieldSymbols(cls: Symbol): List[Symbol] = { + for (f <- cls.info.decls.toList ; + if !f.isMethod && f.isTerm && !f.isModule + ) yield f; + } + + /* + * can-multi-thread + */ + def methodSymbols(cd: ClassDef): List[Symbol] = { + cd.impl.body collect { case dd: DefDef => dd.symbol } + } + + /* + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. + * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) + * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` + * but otherwise not mentioned in `jclass`. + * + * `refedInnerClasses` may contain duplicates, + * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * + * This method serializes in the InnerClasses JVM attribute in an appropriate order, + * not necessarily that given by `refedInnerClasses`. + * + * can-multi-thread + */ + final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: Iterable[BType]) { + // used to detect duplicates. + val seen = mutable.Map.empty[String, String] + // result without duplicates, not yet sorted. + val result = mutable.Set.empty[InnerClassEntry] + + for(s: BType <- refedInnerClasses; + e: InnerClassEntry <- exemplars.get(s).innersChain) { + + assert(e.name != null, "saveInnerClassesFor() is broken.") // documentation + val doAdd = seen.get(e.name) match { + // TODO is it ok for prevOName to be null? (Someone should really document the invariants of the InnerClasses bytecode attribute) + case Some(prevOName) => + // this occurs e.g. when innerClassBuffer contains both class Thread$State, object Thread$State, + // i.e. for them it must be the case that oname == java/lang/Thread + assert(prevOName == e.outerName, "duplicate") + false + case None => true + } + + if (doAdd) { + seen += (e.name -> e.outerName) + result += e + } + + } + // sorting ensures inner classes are listed after their enclosing class thus satisfying the Eclipse Java compiler + for(e <- result.toList sortBy (_.name.toString)) { + jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.access) + } + + } // end of method addInnerClassesASM() + + /* + * Custom attribute (JVMS 4.7.1) "ScalaSig" used as marker only + * i.e., the pickle is contained in a custom annotation, see: + * (1) `addAnnotations()`, + * (2) SID # 10 (draft) - Storage of pickled Scala signatures in class files, http://www.scala-lang.org/sid/10 + * (3) SID # 5 - Internals of Scala Annotations, http://www.scala-lang.org/sid/5 + * That annotation in turn is not related to the "java-generic-signature" (JVMS 4.7.9) + * other than both ending up encoded as attributes (JVMS 4.7) + * (with the caveat that the "ScalaSig" attribute is associated to some classes, + * while the "Signature" attribute can be associated to classes, methods, and fields.) + * + */ + trait BCPickles { + + import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } + + val versionPickle = { + val vp = new PickleBuffer(new Array[Byte](16), -1, 0) + assert(vp.writeIndex == 0, vp) + vp writeNat PickleFormat.MajorVersion + vp writeNat PickleFormat.MinorVersion + vp writeNat 0 + vp + } + + /* + * can-multi-thread + */ + def createJAttribute(name: String, b: Array[Byte], offset: Int, len: Int): asm.Attribute = { + val dest = new Array[Byte](len); + System.arraycopy(b, offset, dest, 0, len); + new asm.CustomAttr(name, dest) + } + + /* + * can-multi-thread + */ + def pickleMarkerLocal = { + createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) + } + + /* + * can-multi-thread + */ + def pickleMarkerForeign = { + createJAttribute(tpnme.ScalaATTR.toString, new Array[Byte](0), 0, 0) + } + + /* Returns a ScalaSignature annotation if it must be added to this class, none otherwise. + * This annotation must be added to the class' annotations list when generating them. + * + * Depending on whether the returned option is defined, it adds to `jclass` one of: + * (a) the ScalaSig marker attribute + * (indicating that a scala-signature-annotation aka pickle is present in this class); or + * (b) the Scala marker attribute + * (indicating that a scala-signature-annotation aka pickle is to be found in another file). + * + * + * @param jclassName The class file that is being readied. + * @param sym The symbol for which the signature has been entered in the symData map. + * This is different than the symbol + * that is being generated in the case of a mirror class. + * @return An option that is: + * - defined and contains an AnnotationInfo of the ScalaSignature type, + * instantiated with the pickle signature for sym. + * - empty if the jclass/sym pair must not contain a pickle. + * + * must-single-thread + */ + def getAnnotPickle(jclassName: String, sym: Symbol): Option[AnnotationInfo] = { + currentRun.symData get sym match { + case Some(pickle) if !nme.isModuleName(newTermName(jclassName)) => + val scalaAnnot = { + val sigBytes = ScalaSigBytes(pickle.bytes.take(pickle.writeIndex)) + AnnotationInfo(sigBytes.sigAnnot, Nil, (nme.bytes, sigBytes) :: Nil) + } + pickledBytes += pickle.writeIndex + currentRun.symData -= sym + currentRun.symData -= sym.companionSymbol + Some(scalaAnnot) + case _ => + None + } + } + + } // end of trait BCPickles + + trait BCInnerClassGen { + + def debugLevel = settings.debuginfo.indexOfChoice + + val emitSource = debugLevel >= 1 + val emitLines = debugLevel >= 2 + val emitVars = debugLevel >= 3 + + /* + * Contains class-symbols that: + * (a) are known to denote inner classes + * (b) are mentioned somewhere in the class being generated. + * + * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". + */ + val innerClassBufferASM = mutable.Set.empty[BType] + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def internalName(sym: Symbol): String = { asmClassType(sym).getInternalName } + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def asmClassType(sym: Symbol): BType = { + assert( + hasInternalName(sym), + { + val msg0 = if (sym.isAbstractType) "An AbstractTypeSymbol (SI-7122) " else "A symbol "; + msg0 + s"has reached the bytecode emitter, for which no JVM-level internal name can be found: ${sym.fullName}" + } + ) + val phantOpt = phantomTypeMap.get(sym) + if (phantOpt.isDefined) { + return phantOpt.get + } + val tracked = exemplar(sym) + val tk = tracked.c + if (tracked.isInnerClass) { + innerClassBufferASM += tk + } + + tk + } + + /* + * Returns the BType for the given type. + * Tracks (if needed) the inner class given by `t`. + * + * must-single-thread + */ + final def toTypeKind(t: Type): BType = { + + /* Interfaces have to be handled delicately to avoid introducing spurious errors, + * but if we treat them all as AnyRef we lose too much information. + */ + def newReference(sym0: Symbol): BType = { + assert(!primitiveTypeMap.contains(sym0), "Use primitiveTypeMap instead.") + assert(sym0 != definitions.ArrayClass, "Use arrayOf() instead.") + + if (sym0 == definitions.NullClass) return RT_NULL; + if (sym0 == definitions.NothingClass) return RT_NOTHING; + + // Working around SI-5604. Rather than failing the compile when we see + // a package here, check if there's a package object. + val sym = ( + if (!sym0.isPackageClass) sym0 + else sym0.info.member(nme.PACKAGE) match { + case NoSymbol => abort(s"Cannot use package as value: ${sym0.fullName}") + case s => devWarning("Bug: found package class where package object expected. Converting.") ; s.moduleClass + } + ) + + // Can't call .toInterface (at this phase) or we trip an assertion. + // See PackratParser#grow for a method which fails with an apparent mismatch + // between "object PackratParsers$class" and "trait PackratParsers" + if (sym.isImplClass) { + // pos/spec-List.scala is the sole failure if we don't check for NoSymbol + val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) + if (traitSym != NoSymbol) { + // this tracks the inner class in innerClassBufferASM, if needed. + return asmClassType(traitSym) + } + } + + assert(hasInternalName(sym), s"Invoked for a symbol lacking JVM internal name: ${sym.fullName}") + assert(!phantomTypeMap.contains(sym), "phantom types not supposed to reach here.") + + val tracked = exemplar(sym) + val tk = tracked.c + if (tracked.isInnerClass) { + innerClassBufferASM += tk + } + + tk + } + + def primitiveOrRefType(sym: Symbol): BType = { + assert(sym != definitions.ArrayClass, "Use primitiveOrArrayOrRefType() instead.") + + primitiveTypeMap.getOrElse(sym, newReference(sym)) + } + + def primitiveOrRefType2(sym: Symbol): BType = { + primitiveTypeMap.get(sym) match { + case Some(pt) => pt + case None => + sym match { + case definitions.NullClass => RT_NULL + case definitions.NothingClass => RT_NOTHING + case _ if sym.isClass => newReference(sym) + case _ => + assert(sym.isType, sym) // it must be compiling Array[a] + ObjectReference + } + } + } + + import definitions.ArrayClass + + // Call to .normalize fixes #3003 (follow type aliases). Otherwise, primitiveOrArrayOrRefType() would return ObjectReference. + t.normalize match { + + case ThisType(sym) => + if (sym == ArrayClass) ObjectReference + else phantomTypeMap.getOrElse(sym, exemplar(sym).c) + + case SingleType(_, sym) => primitiveOrRefType(sym) + + case _: ConstantType => toTypeKind(t.underlying) + + case TypeRef(_, sym, args) => + if (sym == ArrayClass) arrayOf(toTypeKind(args.head)) + else primitiveOrRefType2(sym) + + case ClassInfoType(_, _, sym) => + assert(sym != ArrayClass, "ClassInfoType to ArrayClass!") + primitiveOrRefType(sym) + + // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType. + case ExistentialType(_, t) => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala + case AnnotatedType(_, w, _) => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here. + case RefinedType(parents, _) => parents map toTypeKind reduceLeft jvmWiseLUB + + // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy. + // case WildcardType => REFERENCE(ObjectClass) + case norm => abort( + s"Unknown type: $t, $norm [${t.getClass}, ${norm.getClass}] TypeRef? ${t.isInstanceOf[TypeRef]}" + ) + } + + } // end of method toTypeKind() + + /* + * must-single-thread + */ + def asmMethodType(msym: Symbol): BType = { + assert(msym.isMethod, s"not a method-symbol: $msym") + val resT: BType = + if (msym.isClassConstructor || msym.isConstructor) BType.VOID_TYPE + else toTypeKind(msym.tpe.resultType); + BType.getMethodType( resT, mkArray(msym.tpe.paramTypes map toTypeKind) ) + } + + /* + * Returns all direct member inner classes of `csym`, + * thus making sure they get entries in the InnerClasses JVM attribute + * even if otherwise not mentioned in the class being built. + * + * must-single-thread + */ + final def trackMemberClasses(csym: Symbol, lateClosuresBTs: List[BType]): List[BType] = { + val lateInnerClasses = exitingErasure { + for (sym <- List(csym, csym.linkedClassOfClass); memberc <- sym.info.decls.map(innerClassSymbolFor) if memberc.isClass) + yield memberc + } + // as a precaution, do the following outside the above `exitingErasure` otherwise funny internal names might be computed. + val result = for(memberc <- lateInnerClasses) yield { + val tracked = exemplar(memberc) + val memberCTK = tracked.c + assert(tracked.isInnerClass, s"saveInnerClassesFor() says this was no inner-class after all: ${memberc.fullName}") + + memberCTK + } + + exemplar(csym).directMemberClasses = (result ::: lateClosuresBTs) + + result + } + + /* + * Tracks (if needed) the inner class given by `t`. + * + * must-single-thread + */ + final def descriptor(t: Type): String = { toTypeKind(t).getDescriptor } + + /* + * Tracks (if needed) the inner class given by `sym`. + * + * must-single-thread + */ + final def descriptor(sym: Symbol): String = { asmClassType(sym).getDescriptor } + + } // end of trait BCInnerClassGen + + trait BCAnnotGen extends BCInnerClassGen { + + /* + * can-multi-thread + */ + def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + val ca = new Array[Char](bytes.length) + var idx = 0 + while (idx < bytes.length) { + val b: Byte = bytes(idx) + assert((b & ~0x7f) == 0) + ca(idx) = b.asInstanceOf[Char] + idx += 1 + } + + ca + } + + /* + * can-multi-thread + */ + private def arrEncode(sb: ScalaSigBytes): Array[String] = { + var strs: List[String] = Nil + val bSeven: Array[Byte] = sb.sevenBitsMayBeZero + // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) + var prevOffset = 0 + var offset = 0 + var encLength = 0 + while (offset < bSeven.size) { + val deltaEncLength = (if (bSeven(offset) == 0) 2 else 1) + val newEncLength = encLength.toLong + deltaEncLength + if (newEncLength >= 65535) { + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + encLength = 0 + prevOffset = offset + } else { + encLength += deltaEncLength + offset += 1 + } + } + if (prevOffset < offset) { + assert(offset == bSeven.length) + val ba = bSeven.slice(prevOffset, offset) + strs ::= new java.lang.String(ubytesToCharArray(ba)) + } + assert(strs.size > 1, "encode instead as one String via strEncode()") // TODO too strict? + mkArrayReverse(strs) + } + + /* + * can-multi-thread + */ + private def strEncode(sb: ScalaSigBytes): String = { + val ca = ubytesToCharArray(sb.sevenBitsMayBeZero) + new java.lang.String(ca) + // debug val bvA = new asm.ByteVector; bvA.putUTF8(s) + // debug val enc: Array[Byte] = scala.reflect.internal.pickling.ByteCodecs.encode(bytes) + // debug assert(enc(idx) == bvA.getByte(idx + 2)) + // debug assert(bvA.getLength == enc.size + 2) + } + + /* + * For arg a LiteralAnnotArg(constt) with const.tag in {ClazzTag, EnumTag} + * as well as for arg a NestedAnnotArg + * must-single-thread + * Otherwise it's safe to call from multiple threads. + */ + def emitArgument(av: asm.AnnotationVisitor, + name: String, + arg: ClassfileAnnotArg) { + arg match { + + case LiteralAnnotArg(const) => + if (const.isNonUnitAnyVal) { av.visit(name, const.value) } + else { + const.tag match { + case StringTag => + assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` + av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag + case ClazzTag => av.visit(name, toTypeKind(const.typeValue).toASMType) + case EnumTag => + val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val evalue = const.symbolValue.name.toString // value the actual enumeration value. + av.visitEnum(name, edesc, evalue) + } + } + + case sb @ ScalaSigBytes(bytes) => + // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) + // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. + if (sb.fitsInOneString) { + av.visit(name, strEncode(sb)) + } else { + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- arrEncode(sb)) { arrAnnotV.visit(name, arg) } + arrAnnotV.visitEnd() + } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. + + case ArrayAnnotArg(args) => + val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) + for(arg <- args) { emitArgument(arrAnnotV, null, arg) } + arrAnnotV.visitEnd() + + case NestedAnnotArg(annInfo) => + val AnnotationInfo(typ, args, assocs) = annInfo + assert(args.isEmpty, args) + val desc = descriptor(typ) // the class descriptor of the nested annotation class + val nestedVisitor = av.visitAnnotation(name, desc) + emitAssocs(nestedVisitor, assocs) + } + } + + /* Whether an annotation should be emitted as a Java annotation + * .initialize: if 'annot' is read from pickle, atp might be un-initialized + * + * must-single-thread + */ + private def shouldEmitAnnotation(annot: AnnotationInfo) = + annot.symbol.initialize.isJavaDefined && + annot.matches(definitions.ClassfileAnnotationClass) && + annot.args.isEmpty && + !annot.matches(definitions.DeprecatedAttr) + + /* + * In general, + * must-single-thread + * but not necessarily always. + */ + def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) { + for ((name, value) <- assocs) { + emitArgument(av, name.toString(), value) + } + av.visitEnd() + } + + /* + * must-single-thread + */ + def emitAnnotations(cw: asm.ClassVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = cw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitAnnotations(mw: asm.MethodVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = mw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitAnnotations(fw: asm.FieldVisitor, annotations: List[AnnotationInfo]) { + for(annot <- annotations; if shouldEmitAnnotation(annot)) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val av = fw.visitAnnotation(descriptor(typ), true) + emitAssocs(av, assocs) + } + } + + /* + * must-single-thread + */ + def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[AnnotationInfo]]) { + val annotationss = pannotss map (_ filter shouldEmitAnnotation) + if (annotationss forall (_.isEmpty)) return + for (Pair(annots, idx) <- annotationss.zipWithIndex; + annot <- annots) { + val AnnotationInfo(typ, args, assocs) = annot + assert(args.isEmpty, args) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), true) + emitAssocs(pannVisitor, assocs) + } + } + + } // end of trait BCAnnotGen + + trait BCJGenSigGen { + + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + /* + * must-single-thread + */ + private def needsGenericSignature(sym: Symbol) = !( + // PP: This condition used to include sym.hasExpandedName, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + settings.Ynogenericsig + || sym.isArtifact + || sym.isLiftedMethod + || sym.isBridge + || (sym.ownerChain exists (_.isImplClass)) + ) + + def getCurrentCUnit(): CompilationUnit + + /* @return + * - `null` if no Java signature is to be added (`null` is what ASM expects in these cases). + * - otherwise the signature in question + * + * must-single-thread + */ + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + + if (!needsGenericSignature(sym)) { return null } + + val memberTpe = enteringErasure(owner.thisType.memberInfo(sym)) + + val jsOpt: Option[String] = erasure.javaSig(sym, memberTpe) + if (jsOpt.isEmpty) { return null } + + val sig = jsOpt.get + log(sig) // This seems useful enough in the general case. + + def wrap(op: => Unit) = { + try { op; true } + catch { case _: Throwable => false } + } + + if (settings.Xverify) { + // Run the signature parser to catch bogus signatures. + val isValidSignature = wrap { + // Alternative: scala.tools.reflect.SigParser (frontend to sun.reflect.generics.parser.SignatureParser) + import scala.tools.asm.util.CheckClassAdapter + if (sym.isMethod) { CheckClassAdapter checkMethodSignature sig } + else if (sym.isTerm) { CheckClassAdapter checkFieldSignature sig } + else { CheckClassAdapter checkClassSignature sig } + } + + if (!isValidSignature) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created invalid generic signature for %s in %s + |signature: %s + |if this is reproducible, please report bug at https://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig)) + return null + } + } + + if ((settings.check containsName phaseName)) { + val normalizedTpe = enteringErasure(erasure.prepareSigMap(memberTpe)) + val bytecodeTpe = owner.thisType.memberInfo(sym) + if (!sym.isType && !sym.isConstructor && !(erasure.erasure(sym)(normalizedTpe) =:= bytecodeTpe)) { + getCurrentCUnit().warning(sym.pos, + """|compiler bug: created generic signature for %s in %s that does not conform to its erasure + |signature: %s + |original type: %s + |normalized type: %s + |erasure type: %s + |if this is reproducible, please report bug at http://issues.scala-lang.org/ + """.trim.stripMargin.format(sym, sym.owner.skipPackageObject.fullName, sig, memberTpe, normalizedTpe, bytecodeTpe)) + return null + } + } + + sig + } + + } // end of trait BCJGenSigGen + + trait BCForwardersGen extends BCAnnotGen with BCJGenSigGen { + + // ----------------------------------------------------------------------------------------- + // Static forwarders (related to mirror classes but also present in + // a plain class lacking companion module, for details see `isCandidateForForwarders`). + // ----------------------------------------------------------------------------------------- + + val ExcludedForwarderFlags = { + import symtab.Flags._ + // Should include DEFERRED but this breaks findMember. + ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags | MACRO ) + } + + /* Adds a @remote annotation, actual use unknown. + * + * Invoked from genMethod() and addForwarder(). + * + * must-single-thread + */ + def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) { + val needsAnnotation = ( + ( isRemoteClass || + isRemote(meth) && isJMethodPublic + ) && !(meth.throwsAnnotations contains definitions.RemoteExceptionClass) + ) + if (needsAnnotation) { + val c = Constant(definitions.RemoteExceptionClass.tpe) + val arg = Literal(c) setType c.tpe + meth.addAnnotation(appliedType(definitions.ThrowsClass, c.tpe), arg) + } + } + + /* Add a forwarder for method m. Used only from addForwarders(). + * + * must-single-thread + */ + private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) { + val moduleName = internalName(module) + val methodInfo = module.thisType.memberInfo(m) + val paramJavaTypes: List[BType] = methodInfo.paramTypes map toTypeKind + // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) + + /* Forwarders must not be marked final, + * as the JVM will not allow redefinition of a final static method, + * and we don't know what classes might be subclassing the companion class. See SI-4827. + */ + // TODO: evaluate the other flags we might be dropping on the floor here. + // TODO: ACC_SYNTHETIC ? + val flags = PublicStatic | ( + if (m.isVarargsMethod) asm.Opcodes.ACC_VARARGS else 0 + ) + + // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } + val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) + val (throws, others) = m.annotations partition (_.symbol == definitions.ThrowsClass) + val thrownExceptions: List[String] = getExceptions(throws) + + val jReturnType = toTypeKind(methodInfo.resultType) + val mdesc = BType.getMethodType(jReturnType, mkArray(paramJavaTypes)).getDescriptor + val mirrorMethodName = m.javaSimpleName.toString + val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( + flags, + mirrorMethodName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ) + + emitAnnotations(mirrorMethod, others) + emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations)) + + mirrorMethod.visitCode() + + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + + var index = 0 + for(jparamType <- paramJavaTypes) { + mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) + assert(jparamType.sort != BType.METHOD, jparamType) + index += jparamType.getSize + } + + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor) + mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) + + mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments + mirrorMethod.visitEnd() + + } + + /* Add forwarders for all methods defined in `module` that don't conflict + * with methods in the companion class of `module`. A conflict arises when + * a method with the same name is defined both in a class and its companion object: + * method signature is not taken into account. + * + * must-single-thread + */ + def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) { + assert(moduleClass.isModuleClass, moduleClass) + debuglog(s"Dumping mirror class for object: $moduleClass") + + val linkedClass = moduleClass.companionClass + lazy val conflictingNames: Set[Name] = { + (linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name }).toSet + } + debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") + + for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD)) { + if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + else if (conflictingNames(m.name)) + log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") + else if (m.hasAccessBoundary) + log(s"No forwarder for non-public member $m") + else { + log(s"Adding static forwarder for '$m' from $jclassName to '$moduleClass'") + addForwarder(isRemoteClass, jclass, moduleClass, m) + } + } + } + + /* + * Quoting from JVMS 4.7.5 The Exceptions Attribute + * "The Exceptions attribute indicates which checked exceptions a method may throw. + * There may be at most one Exceptions attribute in each method_info structure." + * + * The contents of that attribute are determined by the `String[] exceptions` argument to ASM's ClassVisitor.visitMethod() + * This method returns such list of internal names. + * + * must-single-thread + */ + def getExceptions(excs: List[AnnotationInfo]): List[String] = { + for (ThrownException(exc) <- excs.distinct) + yield internalName(exc) + } + + } // end of trait BCForwardersGen + + trait BCClassGen extends BCInnerClassGen { + + // Used as threshold above which a tableswitch bytecode instruction is preferred over a lookupswitch. + // There's a space tradeoff between these multi-branch instructions (details in the JVM spec). + // The particular value in use for `MIN_SWITCH_DENSITY` reflects a heuristic. + val MIN_SWITCH_DENSITY = 0.7 + + /* + * must-single-thread + */ + def serialVUID(csym: Symbol): Option[Long] = csym getAnnotation definitions.SerialVersionUIDAttr collect { + case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue + } + + /* + * Add public static final field serialVersionUID with value `id` + * + * can-multi-thread + */ + def addSerialVUID(id: Long, jclass: asm.ClassVisitor) { + // add static serialVersionUID field if `clasz` annotated with `@SerialVersionUID(uid: Long)` + jclass.visitField( + PublicStaticFinal, + "serialVersionUID", + "J", + null, // no java-generic-signature + new java.lang.Long(id) + ).visitEnd() + } + + /* + * @param owner internal name of the enclosing class of the class. + * + * @param name the name of the method that contains the class. + + * @param methodType the method that contains the class. + */ + case class EnclMethodEntry(owner: String, name: String, methodType: BType) + + /* + * @return null if the current class is not internal to a method + * + * Quoting from JVMS 4.7.7 The EnclosingMethod Attribute + * A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. + * A class may have no more than one EnclosingMethod attribute. + * + * must-single-thread + */ + def getEnclosingMethodAttribute(clazz: Symbol): EnclMethodEntry = { // JVMS 4.7.7 + + def newEEE(eClass: Symbol, m: Symbol) = { + EnclMethodEntry( + internalName(eClass), + m.javaSimpleName.toString, + asmMethodType(m) + ) + } + + var res: EnclMethodEntry = null + val sym = clazz.originalEnclosingMethod + if (sym.isMethod) { + debuglog(s"enclosing method for $clazz is $sym (in ${sym.enclClass})") + res = newEEE(sym.enclClass, sym) + } else if (clazz.isAnonymousClass) { + val enclClass = clazz.rawowner + assert(enclClass.isClass, enclClass) + val sym = enclClass.primaryConstructor + if (sym == NoSymbol) { + log(s"Ran out of room looking for an enclosing method for $clazz: no constructor here: $enclClass.") + } else { + debuglog(s"enclosing method for $clazz is $sym (in $enclClass)") + res = newEEE(enclClass, sym) + } + } + + res + } + + } // end of trait BCClassGen + + /* basic functionality for class file building of plain, mirror, and beaninfo classes. */ + abstract class JBuilder extends BCInnerClassGen { + + } // end of class JBuilder + + /* functionality for building plain and mirror classes */ + abstract class JCommonBuilder + extends JBuilder + with BCAnnotGen + with BCForwardersGen + with BCPickles { } + + /* builder of mirror classes */ + class JMirrorBuilder extends JCommonBuilder { + + private var cunit: CompilationUnit = _ + def getCurrentCUnit(): CompilationUnit = cunit; + + /* Generate a mirror class for a top-level module. A mirror class is a class + * containing only static methods that forward to the corresponding method + * on the MODULE instance of the given Scala object. It will only be + * generated if there is no companion class: if there is, an attempt will + * instead be made to add the forwarder methods to the companion class. + * + * must-single-thread + */ + def genMirrorClass(modsym: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { + assert(modsym.companionClass == NoSymbol, modsym) + innerClassBufferASM.clear() + this.cunit = cunit + val moduleName = internalName(modsym) // + "$" + val mirrorName = moduleName.substring(0, moduleName.length() - 1) + + val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) + val mirrorClass = new asm.tree.ClassNode + mirrorClass.visit( + classfileVersion, + flags, + mirrorName, + null /* no java-generic-signature */, + JAVA_LANG_OBJECT.getInternalName, + EMPTY_STRING_ARRAY + ) + + if (emitSource) { + mirrorClass.visitSource("" + cunit.source, + null /* SourceDebugExtension */) + } + + val ssa = getAnnotPickle(mirrorName, modsym.companionSymbol) + mirrorClass.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(mirrorClass, modsym.annotations ++ ssa) + + addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym) + + innerClassBufferASM ++= trackMemberClasses(modsym, Nil /* TODO what about Late-Closure-Classes */ ) + addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) + + mirrorClass.visitEnd() + + ("" + modsym.name) // this side-effect is necessary, really. + + mirrorClass + } + + } // end of class JMirrorBuilder + + /* builder of bean info classes */ + class JBeanInfoBuilder extends JBuilder { + + /* + * Generate a bean info class that describes the given class. + * + * @author Ross Judson (ross.judson@soletta.com) + * + * must-single-thread + */ + def genBeanInfoClass(cls: Symbol, cunit: CompilationUnit, fieldSymbols: List[Symbol], methodSymbols: List[Symbol]): asm.tree.ClassNode = { + + def javaSimpleName(s: Symbol): String = { s.javaSimpleName.toString } + + innerClassBufferASM.clear() + + val flags = mkFlags( + javaFlags(cls), + if (isDeprecated(cls)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val beanInfoName = (internalName(cls) + "BeanInfo") + val beanInfoClass = new asm.tree.ClassNode + beanInfoClass.visit( + classfileVersion, + flags, + beanInfoName, + null, // no java-generic-signature + "scala/beans/ScalaBeanInfo", + EMPTY_STRING_ARRAY + ) + + beanInfoClass.visitSource( + cunit.source.toString, + null /* SourceDebugExtension */ + ) + + var fieldList = List[String]() + + for (f <- fieldSymbols if f.hasGetter; + g = f.getter(cls); + s = f.setter(cls); + if g.isPublic && !(f.name startsWith "$") + ) { + // inserting $outer breaks the bean + fieldList = javaSimpleName(f) :: javaSimpleName(g) :: (if (s != NoSymbol) javaSimpleName(s) else null) :: fieldList + } + + val methodList: List[String] = + for (m <- methodSymbols + if !m.isConstructor && + m.isPublic && + !(m.name startsWith "$") && + !m.isGetter && + !m.isSetter) + yield javaSimpleName(m) + + val constructor = beanInfoClass.visitMethod( + asm.Opcodes.ACC_PUBLIC, + INSTANCE_CONSTRUCTOR_NAME, + "()V", + null, // no java-generic-signature + EMPTY_STRING_ARRAY // no throwable exceptions + ) + + val stringArrayJType: BType = arrayOf(JAVA_LANG_STRING) + val conJType: BType = + BType.getMethodType( + BType.VOID_TYPE, + Array(exemplar(definitions.ClassClass).c, stringArrayJType, stringArrayJType) + ) + + def push(lst: List[String]) { + var fi = 0 + for (f <- lst) { + constructor.visitInsn(asm.Opcodes.DUP) + constructor.visitLdcInsn(new java.lang.Integer(fi)) + if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } + else { constructor.visitLdcInsn(f) } + constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) + fi += 1 + } + } + + constructor.visitCode() + + constructor.visitVarInsn(asm.Opcodes.ALOAD, 0) + // push the class + constructor.visitLdcInsn(exemplar(cls).c) + + // push the string array of field information + constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(fieldList) + + // push the string array of method information + constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + push(methodList) + + // invoke the superclass constructor, which will do the + // necessary java reflection and create Method objects. + constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor) + constructor.visitInsn(asm.Opcodes.RETURN) + + constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments + constructor.visitEnd() + + innerClassBufferASM ++= trackMemberClasses(cls, Nil /* TODO what about Late-Closure-Classes */ ) + addInnerClassesASM(beanInfoClass, innerClassBufferASM.toList) + + beanInfoClass.visitEnd() + + beanInfoClass + } + + } // end of class JBeanInfoBuilder + + trait JAndroidBuilder { + self: BCInnerClassGen => + + /* From the reference documentation of the Android SDK: + * The `Parcelable` interface identifies classes whose instances can be written to and restored from a `Parcel`. + * Classes implementing the `Parcelable` interface must also have a static field called `CREATOR`, + * which is an object implementing the `Parcelable.Creator` interface. + */ + val androidFieldName = newTermName("CREATOR") + + /* + * must-single-thread + */ + def isAndroidParcelableClass(sym: Symbol) = + (AndroidParcelableInterface != NoSymbol) && + (sym.parentSymbols contains AndroidParcelableInterface) + + /* + * must-single-thread + */ + def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { + // this tracks the inner class in innerClassBufferASM, if needed. + val androidCreatorType = asmClassType(AndroidCreatorClass) + val tdesc_creator = androidCreatorType.getDescriptor + + cnode.visitField( + PublicStaticFinal, + "CREATOR", + tdesc_creator, + null, // no java-generic-signature + null // no initial value + ).visitEnd() + + val moduleName = (thisName + "$") + + // GETSTATIC `moduleName`.MODULE$ : `moduleName`; + clinit.visitFieldInsn( + asm.Opcodes.GETSTATIC, + moduleName, + strMODULE_INSTANCE_FIELD, + "L" + moduleName + ";" + ) + + // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; + val bt = BType.getMethodType(androidCreatorType, Array.empty[BType]) + clinit.visitMethodInsn( + asm.Opcodes.INVOKEVIRTUAL, + moduleName, + "CREATOR", + bt.getDescriptor + ) + + // PUTSTATIC `thisName`.CREATOR; + clinit.visitFieldInsn( + asm.Opcodes.PUTSTATIC, + thisName, + "CREATOR", + tdesc_creator + ) + } + + } // end of trait JAndroidBuilder + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala new file mode 100644 index 0000000000..eda17c6e32 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -0,0 +1,844 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.annotation.switch +import scala.collection.{ immutable, mutable } +import collection.convert.Wrappers.JListWrapper + +/* + * A high-level facade to the ASM API for bytecode generation. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeIdiomatic extends BCodeGlue { + + import global._ + + val classfileVersion: Int = settings.target.value match { + case "jvm-1.5" => asm.Opcodes.V1_5 + case "jvm-1.6" => asm.Opcodes.V1_6 + case "jvm-1.7" => asm.Opcodes.V1_7 + } + + val majorVersion: Int = (classfileVersion & 0xFF) + val emitStackMapFrame = (majorVersion >= 50) + + def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + + val extraProc: Int = mkFlags( + asm.ClassWriter.COMPUTE_MAXS, + if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 + ) + + val StringBuilderClassName = "scala/collection/mutable/StringBuilder" + + val CLASS_CONSTRUCTOR_NAME = "<clinit>" + val INSTANCE_CONSTRUCTOR_NAME = "<init>" + + val ObjectReference = brefType("java/lang/Object") + val AnyRefReference = ObjectReference + val objArrayReference = arrayOf(ObjectReference) + + val JAVA_LANG_OBJECT = ObjectReference + val JAVA_LANG_STRING = brefType("java/lang/String") + + var StringBuilderReference: BType = null + + val EMPTY_STRING_ARRAY = Array.empty[String] + val EMPTY_INT_ARRAY = Array.empty[Int] + val EMPTY_LABEL_ARRAY = Array.empty[asm.Label] + val EMPTY_BTYPE_ARRAY = Array.empty[BType] + + /* can-multi-thread */ + final def mkArray(xs: List[BType]): Array[BType] = { + if (xs.isEmpty) { return EMPTY_BTYPE_ARRAY } + val a = new Array[BType](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[String]): Array[String] = { + if (xs.isEmpty) { return EMPTY_STRING_ARRAY } + val a = new Array[String](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[asm.Label]): Array[asm.Label] = { + if (xs.isEmpty) { return EMPTY_LABEL_ARRAY } + val a = new Array[asm.Label](xs.size); xs.copyToArray(a); a + } + /* can-multi-thread */ + final def mkArray(xs: List[Int]): Array[Int] = { + if (xs.isEmpty) { return EMPTY_INT_ARRAY } + val a = new Array[Int](xs.size); xs.copyToArray(a); a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[String]): Array[String] = { + val len = xs.size + if (len == 0) { return EMPTY_STRING_ARRAY } + val a = new Array[String](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[Int]): Array[Int] = { + val len = xs.size + if (len == 0) { return EMPTY_INT_ARRAY } + val a = new Array[Int](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * can-multi-thread + */ + final def mkArrayReverse(xs: List[asm.Label]): Array[asm.Label] = { + val len = xs.size + if (len == 0) { return EMPTY_LABEL_ARRAY } + val a = new Array[asm.Label](len) + var i = len - 1 + var rest = xs + while (!rest.isEmpty) { + a(i) = rest.head + rest = rest.tail + i -= 1 + } + a + } + + /* + * The type of 1-dimensional arrays of `elem` type. + * The invoker is responsible for tracking (if needed) the inner class given by the elem BType. + * + * must-single-thread + */ + final def arrayOf(elem: BType): BType = { + assert(!(elem.isUnitType), s"The element type of an array can't be: $elem") + brefType("[" + elem.getDescriptor) + } + + /* + * The type of N-dimensional arrays of `elem` type. + * The invoker is responsible for tracking (if needed) the inner class given by the elem BType. + * + * must-single-thread + */ + final def arrayN(elem: BType, dims: Int): BType = { + assert(dims > 0) + assert(!(elem.isUnitType) && !(elem.isPhantomType), + "The element type of an array type is necessarily either a primitive type, or a class type, or an interface type.") + val desc = ("[" * dims) + elem.getDescriptor + brefType(desc) + } + + /* Just a namespace for utilities that encapsulate MethodVisitor idioms. + * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, + * but the methods here allow choosing when to transition from ICode to ASM types + * (including not at all, e.g. for performance). + */ + abstract class JCodeMethodN { + + def jmethod: asm.MethodVisitor + + import asm.Opcodes; + import icodes.opcodes.{ InvokeStyle, Static, Dynamic, SuperCall } + + final def emit(opc: Int) { jmethod.visitInsn(opc) } + + /* + * can-multi-thread + */ + final def genPrimitiveArithmetic(op: icodes.ArithmeticOp, kind: BType) { + + import icodes.{ ADD, SUB, MUL, DIV, REM, NOT } + + op match { + + case ADD => add(kind) + case SUB => sub(kind) + case MUL => mul(kind) + case DIV => div(kind) + case REM => rem(kind) + + case NOT => + if (kind.isIntSizedType) { + emit(Opcodes.ICONST_M1) + emit(Opcodes.IXOR) + } else if (kind == LONG) { + jmethod.visitLdcInsn(new java.lang.Long(-1)) + jmethod.visitInsn(Opcodes.LXOR) + } else { + abort(s"Impossible to negate an $kind") + } + + case _ => + abort(s"Unknown arithmetic primitive $op") + } + + } // end of method genPrimitiveArithmetic() + + /* + * can-multi-thread + */ + final def genPrimitiveLogical(op: /* LogicalOp */ Int, kind: BType) { + + import scalaPrimitives.{ AND, OR, XOR } + + ((op, kind): @unchecked) match { + case (AND, LONG) => emit(Opcodes.LAND) + case (AND, INT) => emit(Opcodes.IAND) + case (AND, _) => + emit(Opcodes.IAND) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (OR, LONG) => emit(Opcodes.LOR) + case (OR, INT) => emit(Opcodes.IOR) + case (OR, _) => + emit(Opcodes.IOR) + if (kind != BOOL) { emitT2T(INT, kind) } + + case (XOR, LONG) => emit(Opcodes.LXOR) + case (XOR, INT) => emit(Opcodes.IXOR) + case (XOR, _) => + emit(Opcodes.IXOR) + if (kind != BOOL) { emitT2T(INT, kind) } + } + + } // end of method genPrimitiveLogical() + + /* + * can-multi-thread + */ + final def genPrimitiveShift(op: /* ShiftOp */ Int, kind: BType) { + + import scalaPrimitives.{ LSL, ASR, LSR } + + ((op, kind): @unchecked) match { + case (LSL, LONG) => emit(Opcodes.LSHL) + case (LSL, INT) => emit(Opcodes.ISHL) + case (LSL, _) => + emit(Opcodes.ISHL) + emitT2T(INT, kind) + + case (ASR, LONG) => emit(Opcodes.LSHR) + case (ASR, INT) => emit(Opcodes.ISHR) + case (ASR, _) => + emit(Opcodes.ISHR) + emitT2T(INT, kind) + + case (LSR, LONG) => emit(Opcodes.LUSHR) + case (LSR, INT) => emit(Opcodes.IUSHR) + case (LSR, _) => + emit(Opcodes.IUSHR) + emitT2T(INT, kind) + } + + } // end of method genPrimitiveShift() + + /* + * can-multi-thread + */ + final def genPrimitiveComparison(op: icodes.ComparisonOp, kind: BType) { + + import icodes.{ CMPL, CMP, CMPG } + + ((op, kind): @unchecked) match { + case (CMP, LONG) => emit(Opcodes.LCMP) + case (CMPL, FLOAT) => emit(Opcodes.FCMPL) + case (CMPG, FLOAT) => emit(Opcodes.FCMPG) + case (CMPL, DOUBLE) => emit(Opcodes.DCMPL) + case (CMPG, DOUBLE) => emit(Opcodes.DCMPL) // http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html + } + + } // end of method genPrimitiveComparison() + + /* + * can-multi-thread + */ + final def genStartConcat { + jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName) + jmethod.visitInsn(Opcodes.DUP) + invokespecial( + StringBuilderClassName, + INSTANCE_CONSTRUCTOR_NAME, + "()V" + ) + } + + /* + * can-multi-thread + */ + final def genStringConcat(el: BType) { + + val jtype = + if (el.isArray || el.hasObjectSort) JAVA_LANG_OBJECT + else el; + + val bt = BType.getMethodType(StringBuilderReference, Array(jtype)) + + invokevirtual(StringBuilderClassName, "append", bt.getDescriptor) + } + + /* + * can-multi-thread + */ + final def genEndConcat { + invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;") + } + + /* + * Emits one or more conversion instructions based on the types given as arguments. + * + * @param from The type of the value to be converted into another type. + * @param to The type the value will be converted into. + * + * can-multi-thread + */ + final def emitT2T(from: BType, to: BType) { + + assert( + from.isNonUnitValueType && to.isNonUnitValueType, + s"Cannot emit primitive conversion from $from to $to" + ) + + def pickOne(opcs: Array[Int]) { // TODO index on to.sort + val chosen = (to: @unchecked) match { + case BYTE => opcs(0) + case SHORT => opcs(1) + case CHAR => opcs(2) + case INT => opcs(3) + case LONG => opcs(4) + case FLOAT => opcs(5) + case DOUBLE => opcs(6) + } + if (chosen != -1) { emit(chosen) } + } + + if (from == to) { return } + // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) + assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") + + // We're done with BOOL already + (from.sort: @switch) match { + + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + + case asm.Type.BYTE => pickOne(JCodeMethodN.fromByteT2T) + case asm.Type.SHORT => pickOne(JCodeMethodN.fromShortT2T) + case asm.Type.CHAR => pickOne(JCodeMethodN.fromCharT2T) + case asm.Type.INT => pickOne(JCodeMethodN.fromIntT2T) + + case asm.Type.FLOAT => + import asm.Opcodes.{ F2L, F2D, F2I } + (to.sort: @switch) match { + case asm.Type.LONG => emit(F2L) + case asm.Type.DOUBLE => emit(F2D) + case _ => emit(F2I); emitT2T(INT, to) + } + + case asm.Type.LONG => + import asm.Opcodes.{ L2F, L2D, L2I } + (to.sort: @switch) match { + case asm.Type.FLOAT => emit(L2F) + case asm.Type.DOUBLE => emit(L2D) + case _ => emit(L2I); emitT2T(INT, to) + } + + case asm.Type.DOUBLE => + import asm.Opcodes.{ D2L, D2F, D2I } + (to.sort: @switch) match { + case asm.Type.FLOAT => emit(D2F) + case asm.Type.LONG => emit(D2L) + case _ => emit(D2I); emitT2T(INT, to) + } + } + } // end of emitT2T() + + // can-multi-thread + final def aconst(cst: AnyRef) { + if (cst == null) { emit(Opcodes.ACONST_NULL) } + else { jmethod.visitLdcInsn(cst) } + } + + // can-multi-thread + final def boolconst(b: Boolean) { iconst(if (b) 1 else 0) } + + // can-multi-thread + final def iconst(cst: Int) { + if (cst >= -1 && cst <= 5) { + emit(Opcodes.ICONST_0 + cst) + } else if (cst >= java.lang.Byte.MIN_VALUE && cst <= java.lang.Byte.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.BIPUSH, cst) + } else if (cst >= java.lang.Short.MIN_VALUE && cst <= java.lang.Short.MAX_VALUE) { + jmethod.visitIntInsn(Opcodes.SIPUSH, cst) + } else { + jmethod.visitLdcInsn(new Integer(cst)) + } + } + + // can-multi-thread + final def lconst(cst: Long) { + if (cst == 0L || cst == 1L) { + emit(Opcodes.LCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Long(cst)) + } + } + + // can-multi-thread + final def fconst(cst: Float) { + val bits: Int = java.lang.Float.floatToIntBits(cst) + if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 + emit(Opcodes.FCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Float(cst)) + } + } + + // can-multi-thread + final def dconst(cst: Double) { + val bits: Long = java.lang.Double.doubleToLongBits(cst) + if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d + emit(Opcodes.DCONST_0 + cst.asInstanceOf[Int]) + } else { + jmethod.visitLdcInsn(new java.lang.Double(cst)) + } + } + + // can-multi-thread + final def newarray(elem: BType) { + if (elem.isRefOrArrayType || elem.isPhantomType ) { + /* phantom type at play in `Array(null)`, SI-1513. On the other hand, Array(()) has element type `scala.runtime.BoxedUnit` which hasObjectSort. */ + jmethod.visitTypeInsn(Opcodes.ANEWARRAY, elem.getInternalName) + } else { + val rand = { + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (elem.sort: @switch) match { + case asm.Type.BOOLEAN => Opcodes.T_BOOLEAN + case asm.Type.BYTE => Opcodes.T_BYTE + case asm.Type.SHORT => Opcodes.T_SHORT + case asm.Type.CHAR => Opcodes.T_CHAR + case asm.Type.INT => Opcodes.T_INT + case asm.Type.LONG => Opcodes.T_LONG + case asm.Type.FLOAT => Opcodes.T_FLOAT + case asm.Type.DOUBLE => Opcodes.T_DOUBLE + } + } + jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) + } + } + + + final def load( idx: Int, tk: BType) { emitVarInsn(Opcodes.ILOAD, idx, tk) } // can-multi-thread + final def store(idx: Int, tk: BType) { emitVarInsn(Opcodes.ISTORE, idx, tk) } // can-multi-thread + + final def aload( tk: BType) { emitTypeBased(JCodeMethodN.aloadOpcodes, tk) } // can-multi-thread + final def astore(tk: BType) { emitTypeBased(JCodeMethodN.astoreOpcodes, tk) } // can-multi-thread + + final def neg(tk: BType) { emitPrimitive(JCodeMethodN.negOpcodes, tk) } // can-multi-thread + final def add(tk: BType) { emitPrimitive(JCodeMethodN.addOpcodes, tk) } // can-multi-thread + final def sub(tk: BType) { emitPrimitive(JCodeMethodN.subOpcodes, tk) } // can-multi-thread + final def mul(tk: BType) { emitPrimitive(JCodeMethodN.mulOpcodes, tk) } // can-multi-thread + final def div(tk: BType) { emitPrimitive(JCodeMethodN.divOpcodes, tk) } // can-multi-thread + final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread + + // can-multi-thread + final def invokespecial(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc) + } + // can-multi-thread + final def invokestatic(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc) + } + // can-multi-thread + final def invokeinterface(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc) + } + // can-multi-thread + final def invokevirtual(owner: String, name: String, desc: String) { + jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc) + } + + // can-multi-thread + final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } + // can-multi-thread + final def emitIF(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) } + // can-multi-thread + final def emitIF_ICMP(cond: icodes.TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) } + // can-multi-thread + final def emitIF_ACMP(cond: icodes.TestOp, label: asm.Label) { + assert((cond == icodes.EQ) || (cond == icodes.NE), cond) + val opc = (if (cond == icodes.EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) + jmethod.visitJumpInsn(opc, label) + } + // can-multi-thread + final def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) } + // can-multi-thread + final def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) } + + // can-multi-thread + final def emitRETURN(tk: BType) { + if (tk == UNIT) { emit(Opcodes.RETURN) } + else { emitTypeBased(JCodeMethodN.returnOpcodes, tk) } + } + + /* Emits one of tableswitch or lookoupswitch. + * + * can-multi-thread + */ + final def emitSWITCH(keys: Array[Int], branches: Array[asm.Label], defaultBranch: asm.Label, minDensity: Double) { + assert(keys.length == branches.length) + + // For empty keys, it makes sense emitting LOOKUPSWITCH with defaultBranch only. + // Similar to what javac emits for a switch statement consisting only of a default case. + if (keys.length == 0) { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + return + } + + // sort `keys` by increasing key, keeping `branches` in sync. TODO FIXME use quicksort + var i = 1 + while (i < keys.length) { + var j = 1 + while (j <= keys.length - i) { + if (keys(j) < keys(j - 1)) { + val tmp = keys(j) + keys(j) = keys(j - 1) + keys(j - 1) = tmp + val tmpL = branches(j) + branches(j) = branches(j - 1) + branches(j - 1) = tmpL + } + j += 1 + } + i += 1 + } + + // check for duplicate keys to avoid "VerifyError: unsorted lookupswitch" (SI-6011) + i = 1 + while (i < keys.length) { + if (keys(i-1) == keys(i)) { + abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") + } + i += 1 + } + + val keyMin = keys(0) + val keyMax = keys(keys.length - 1) + + val isDenseEnough: Boolean = { + /* Calculate in long to guard against overflow. TODO what overflow? */ + val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] + val klenD: Double = keys.length + val kdensity: Double = (klenD / keyRangeD) + + kdensity >= minDensity + } + + if (isDenseEnough) { + // use a table in which holes are filled with defaultBranch. + val keyRange = (keyMax - keyMin + 1) + val newBranches = new Array[asm.Label](keyRange) + var oldPos = 0 + var i = 0 + while (i < keyRange) { + val key = keyMin + i; + if (keys(oldPos) == key) { + newBranches(i) = branches(oldPos) + oldPos += 1 + } else { + newBranches(i) = defaultBranch + } + i += 1 + } + assert(oldPos == keys.length, "emitSWITCH") + jmethod.visitTableSwitchInsn(keyMin, keyMax, defaultBranch, newBranches: _*) + } else { + jmethod.visitLookupSwitchInsn(defaultBranch, keys, branches) + } + } + + // internal helpers -- not part of the public API of `jcode` + // don't make private otherwise inlining will suffer + + // can-multi-thread + final def emitVarInsn(opc: Int, idx: Int, tk: BType) { + assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) + jmethod.visitVarInsn(tk.getOpcode(opc), idx) + } + + // ---------------- array load and store ---------------- + + // can-multi-thread + final def emitTypeBased(opcs: Array[Int], tk: BType) { + assert(tk != UNIT, tk) + val opc = { + if (tk.isRefOrArrayType) { opcs(0) } + else if (tk.isIntSizedType) { + (tk: @unchecked) match { + case BOOL | BYTE => opcs(1) + case SHORT => opcs(2) + case CHAR => opcs(3) + case INT => opcs(4) + } + } else { + (tk: @unchecked) match { + case LONG => opcs(5) + case FLOAT => opcs(6) + case DOUBLE => opcs(7) + } + } + } + emit(opc) + } + + // ---------------- primitive operations ---------------- + + // can-multi-thread + final def emitPrimitive(opcs: Array[Int], tk: BType) { + val opc = { + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + (tk.sort: @switch) match { + case asm.Type.LONG => opcs(1) + case asm.Type.FLOAT => opcs(2) + case asm.Type.DOUBLE => opcs(3) + case _ => opcs(0) + } + } + emit(opc) + } + + // can-multi-thread + final def drop(tk: BType) { emit(if (tk.isWideType) Opcodes.POP2 else Opcodes.POP) } + + // can-multi-thread + final def dup(tk: BType) { emit(if (tk.isWideType) Opcodes.DUP2 else Opcodes.DUP) } + + // ---------------- type checks and casts ---------------- + + // can-multi-thread + final def isInstance(tk: BType) { + jmethod.visitTypeInsn(Opcodes.INSTANCEOF, tk.getInternalName) + } + + // can-multi-thread + final def checkCast(tk: BType) { + assert(tk.isRefOrArrayType, s"checkcast on primitive type: $tk") + // TODO ICode also requires: but that's too much, right? assert(!isBoxedType(tk), "checkcast on boxed type: " + tk) + jmethod.visitTypeInsn(Opcodes.CHECKCAST, tk.getInternalName) + } + + } // end of class JCodeMethodN + + /* Constant-valued val-members of JCodeMethodN at the companion object, so as to avoid re-initializing them multiple times. */ + object JCodeMethodN { + + import asm.Opcodes._ + + // ---------------- conversions ---------------- + + val fromByteT2T = { Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT) + val fromCharT2T = { Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing + val fromShortT2T = { Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing + val fromIntT2T = { Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) } + + // ---------------- array load and store ---------------- + + val aloadOpcodes = { Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) } + val astoreOpcodes = { Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) } + val returnOpcodes = { Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) } + + // ---------------- primitive operations ---------------- + + val negOpcodes: Array[Int] = { Array(INEG, LNEG, FNEG, DNEG) } + val addOpcodes: Array[Int] = { Array(IADD, LADD, FADD, DADD) } + val subOpcodes: Array[Int] = { Array(ISUB, LSUB, FSUB, DSUB) } + val mulOpcodes: Array[Int] = { Array(IMUL, LMUL, FMUL, DMUL) } + val divOpcodes: Array[Int] = { Array(IDIV, LDIV, FDIV, DDIV) } + val remOpcodes: Array[Int] = { Array(IREM, LREM, FREM, DREM) } + + } // end of object JCodeMethodN + + // ---------------- adapted from scalaPrimitives ---------------- + + /* Given `code` reports the src TypeKind of the coercion indicated by `code`. + * To find the dst TypeKind, `ScalaPrimitives.generatedKind(code)` can be used. + * + * can-multi-thread + */ + final def coercionFrom(code: Int): BType = { + import scalaPrimitives._ + (code: @switch) match { + case B2B | B2C | B2S | B2I | B2L | B2F | B2D => BYTE + case S2B | S2S | S2C | S2I | S2L | S2F | S2D => SHORT + case C2B | C2S | C2C | C2I | C2L | C2F | C2D => CHAR + case I2B | I2S | I2C | I2I | I2L | I2F | I2D => INT + case L2B | L2S | L2C | L2I | L2L | L2F | L2D => LONG + case F2B | F2S | F2C | F2I | F2L | F2F | F2D => FLOAT + case D2B | D2S | D2C | D2I | D2L | D2F | D2D => DOUBLE + } + } + + /* If code is a coercion primitive, the result type. + * + * can-multi-thread + */ + final def coercionTo(code: Int): BType = { + import scalaPrimitives._ + (code: @scala.annotation.switch) match { + case B2B | C2B | S2B | I2B | L2B | F2B | D2B => BYTE + case B2C | C2C | S2C | I2C | L2C | F2C | D2C => CHAR + case B2S | C2S | S2S | I2S | L2S | F2S | D2S => SHORT + case B2I | C2I | S2I | I2I | L2I | F2I | D2I => INT + case B2L | C2L | S2L | I2L | L2L | F2L | D2L => LONG + case B2F | C2F | S2F | I2F | L2F | F2F | D2F => FLOAT + case B2D | C2D | S2D | I2D | L2D | F2D | D2D => DOUBLE + } + } + + final val typeOfArrayOp: Map[Int, BType] = { + import scalaPrimitives._ + Map( + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ + (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ + (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ + (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ + (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ + (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ + (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + ) + } + + /* + * Collects (in `result`) all LabelDef nodes enclosed (directly or not) by each node it visits. + * + * In other words, this traverser prepares a map giving + * all labelDefs (the entry-value) having a Tree node (the entry-key) as ancestor. + * The entry-value for a LabelDef entry-key always contains the entry-key. + * + */ + class LabelDefsFinder extends Traverser { + val result = mutable.Map.empty[Tree, List[LabelDef]] + var acc: List[LabelDef] = Nil + + /* + * can-multi-thread + */ + override def traverse(tree: Tree) { + val saved = acc + acc = Nil + super.traverse(tree) + // acc contains all LabelDefs found under (but not at) `tree` + tree match { + case lblDf: LabelDef => acc ::= lblDf + case _ => () + } + if (acc.isEmpty) { + acc = saved + } else { + result += (tree -> acc) + acc = acc ::: saved + } + } + } + + implicit class MethodIterClassNode(cnode: asm.tree.ClassNode) { + + @inline final def foreachMethod(f: (asm.tree.MethodNode) => Unit) { toMethodList.foreach(f) } + + @inline final def toMethodList: List[asm.tree.MethodNode] = { JListWrapper(cnode.methods).toList } + + @inline final def toFieldList: List[asm.tree.FieldNode] = { JListWrapper(cnode.fields).toList } + + } + + implicit class InsnIterMethodNode(mnode: asm.tree.MethodNode) { + + @inline final def foreachInsn(f: (asm.tree.AbstractInsnNode) => Unit) { mnode.instructions.foreachInsn(f) } + + @inline final def toList: List[asm.tree.AbstractInsnNode] = { mnode.instructions.toList } + + } + + implicit class InsnIterInsnList(lst: asm.tree.InsnList) { + + @inline final def foreachInsn(f: (asm.tree.AbstractInsnNode) => Unit) { + val insnIter = lst.iterator() + while (insnIter.hasNext) { + f(insnIter.next()) + } + } + + @inline final def toList: List[asm.tree.AbstractInsnNode] = { + var result: List[asm.tree.AbstractInsnNode] = Nil + lst foreachInsn { insn => if (insn != null) { result ::= insn } } + result.reverse + } + + } + + /* + * Upon finding a name already seen among previous List elements, adds a numeric postfix to make it unique. + */ + def uniquify(names: List[String]): List[String] = { + val seen = mutable.Set.empty[String] + + @scala.annotation.tailrec def uniquified(current: String, attempt: Int): String = { + if (seen contains current) { + val currentBis = (current + "$" + attempt.toString) + if (seen contains currentBis) { + uniquified(current, attempt + 1) + } else currentBis + } else current + } + + var rest = names + var result: List[String] = Nil + while (rest.nonEmpty) { + val u = uniquified(rest.head.trim, 1) + seen += u + result ::= u + rest = rest.tail + } + + result.reverse + } + + def allDifferent[ElemType](xs: Iterable[ElemType]): Boolean = { + val seen = mutable.Set.empty[ElemType] + val iter = xs.iterator + while (iter.hasNext) { + val nxt = iter.next() + if (seen contains nxt) { return false } + seen += nxt + } + true + } + +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala new file mode 100644 index 0000000000..8b6b4ab9ce --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -0,0 +1,727 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.tools.nsc.symtab._ +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeSkelBuilder extends BCodeHelpers { + import global._ + import definitions._ + + /* + * There's a dedicated PlainClassBuilder for each CompilationUnit, + * which simplifies the initialization of per-class data structures in `genPlainClass()` which in turn delegates to `initJClass()` + * + * The entry-point to emitting bytecode instructions is `genDefDef()` where the per-method data structures are initialized, + * including `resetMethodBookkeeping()` and `initJMethod()`. + * Once that's been done, and assuming the method being visited isn't abstract, `emitNormalMethodBody()` populates + * the ASM MethodNode instance with ASM AbstractInsnNodes. + * + * Given that CleanUp delivers trees that produce values on the stack, + * the entry-point to all-things instruction-emit is `genLoad()`. + * There, an operation taking N arguments results in recursively emitting instructions to lead each of them, + * followed by emitting instructions to process those arguments (to be found at run-time on the operand-stack). + * + * In a few cases the above recipe deserves more details, as provided in the documentation for: + * - `genLoadTry()` + * - `genSynchronized() + * - `jumpDest` , `cleanups` , `labelDefsAtOrUnder` + */ + abstract class PlainSkelBuilder(cunit: CompilationUnit) + extends BCClassGen + with BCAnnotGen + with BCInnerClassGen + with JAndroidBuilder + with BCForwardersGen + with BCPickles + with BCJGenSigGen { + + // Strangely I can't find this in the asm code 255, but reserving 1 for "this" + final val MaximumJvmParameters = 254 + + // current class + var cnode: asm.tree.ClassNode = null + var thisName: String = null // the internal name of the class being emitted + + var claszSymbol: Symbol = null + var isCZParcelable = false + var isCZStaticModule = false + var isCZRemote = false + + /* ---------------- idiomatic way to ask questions to typer ---------------- */ + + def paramTKs(app: Apply): List[BType] = { + val Apply(fun, _) = app + val funSym = fun.symbol + (funSym.info.paramTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + } + + def symInfoTK(sym: Symbol): BType = { + toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) + } + + def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) } + + def log(msg: => AnyRef) { + global synchronized { global.log(msg) } + } + + override def getCurrentCUnit(): CompilationUnit = { cunit } + + /* ---------------- helper utils for generating classes and fiels ---------------- */ + + def genPlainClass(cd: ClassDef) { + assert(cnode == null, "GenBCode detected nested methods.") + innerClassBufferASM.clear() + + claszSymbol = cd.symbol + isCZParcelable = isAndroidParcelableClass(claszSymbol) + isCZStaticModule = isStaticModule(claszSymbol) + isCZRemote = isRemote(claszSymbol) + thisName = internalName(claszSymbol) + + cnode = new asm.tree.ClassNode() + + initJClass(cnode) + + val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor) + if (!hasStaticCtor) { + // but needs one ... + if (isCZStaticModule || isCZParcelable) { + fabricateStaticInit() + } + } + + val optSerial: Option[Long] = serialVUID(claszSymbol) + if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)} + + addClassFields() + + innerClassBufferASM ++= trackMemberClasses(claszSymbol, Nil) + + gen(cd.impl) + + assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().") + + } // end of method genPlainClass() + + /* + * must-single-thread + */ + private def initJClass(jclass: asm.ClassVisitor) { + + val ps = claszSymbol.info.parents + val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else internalName(ps.head.typeSymbol); + val ifaces: Array[String] = { + val arrIfacesTr: Array[Tracked] = exemplar(claszSymbol).ifaces + val arrIfaces = new Array[String](arrIfacesTr.length) + var i = 0 + while (i < arrIfacesTr.length) { + val ifaceTr = arrIfacesTr(i) + val bt = ifaceTr.c + if (ifaceTr.isInnerClass) { innerClassBufferASM += bt } + arrIfaces(i) = bt.getInternalName + i += 1 + } + arrIfaces + } + // `internalName()` tracks inner classes. + + val flags = mkFlags( + javaFlags(claszSymbol), + if (isDeprecated(claszSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) + cnode.visit(classfileVersion, flags, + thisName, thisSignature, + superClass, ifaces) + + if (emitSource) { + cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) + } + + val enclM = getEnclosingMethodAttribute(claszSymbol) + if (enclM != null) { + val EnclMethodEntry(className, methodName, methodType) = enclM + cnode.visitOuterClass(className, methodName, methodType.getDescriptor) + } + + val ssa = getAnnotPickle(thisName, claszSymbol) + cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) + emitAnnotations(cnode, claszSymbol.annotations ++ ssa) + + if (isCZStaticModule || isCZParcelable) { + + if (isCZStaticModule) { addModuleInstanceField() } + + } else { + + val skipStaticForwarders = (claszSymbol.isInterface || settings.noForwarders) + if (!skipStaticForwarders) { + val lmoc = claszSymbol.companionModule + // add static forwarders if there are no name conflicts; see bugs #363 and #1735 + if (lmoc != NoSymbol) { + // it must be a top level class (name contains no $s) + val isCandidateForForwarders = { + exitingPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass } + } + if (isCandidateForForwarders) { + log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'") + addForwarders(isRemote(claszSymbol), cnode, thisName, lmoc.moduleClass) + } + } + } + + } + + // the invoker is responsible for adding a class-static constructor. + + } // end of method initJClass + + /* + * can-multi-thread + */ + private def addModuleInstanceField() { + val fv = + cnode.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + strMODULE_INSTANCE_FIELD, + "L" + thisName + ";", + null, // no java-generic-signature + null // no initial value + ) + + fv.visitEnd() + } + + /* + * must-single-thread + */ + private def fabricateStaticInit() { + + val clinit: asm.MethodVisitor = cnode.visitMethod( + PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + CLASS_CONSTRUCTOR_NAME, + "()V", + null, // no java-generic-signature + null // no throwable exceptions + ) + clinit.visitCode() + + /* "legacy static initialization" */ + if (isCZStaticModule) { + clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) + clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, + thisName, INSTANCE_CONSTRUCTOR_NAME, "()V") + } + if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) } + clinit.visitInsn(asm.Opcodes.RETURN) + + clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments + clinit.visitEnd() + } + + def addClassFields() { + /* Non-method term members are fields, except for module members. Module + * members can only happen on .NET (no flatten) for inner traits. There, + * a module symbol is generated (transformInfo in mixin) which is used + * as owner for the members of the implementation class (so that the + * backend emits them as static). + * No code is needed for this module symbol. + */ + for (f <- fieldSymbols(claszSymbol)) { + val javagensig = getGenericSignature(f, claszSymbol) + val flags = mkFlags( + javaFieldFlags(f), + if (isDeprecated(f)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val jfield = new asm.tree.FieldNode( + flags, + f.javaSimpleName.toString, + symInfoTK(f).getDescriptor, + javagensig, + null // no initial value + ) + cnode.fields.add(jfield) + emitAnnotations(jfield, f.annotations) + } + + } // end of method addClassFields() + + // current method + var mnode: asm.tree.MethodNode = null + var jMethodName: String = null + var isMethSymStaticCtor = false + var isMethSymBridge = false + var returnType: BType = null + var methSymbol: Symbol = null + // in GenASM this is local to genCode(), ie should get false whenever a new method is emitted (including fabricated ones eg addStaticInit()) + var isModuleInitialized = false + // used by genLoadTry() and genSynchronized() + var earlyReturnVar: Symbol = null + var shouldEmitCleanup = false + var insideCleanupBlock = false + // line numbers + var lastEmittedLineNr = -1 + + object bc extends JCodeMethodN { + override def jmethod = PlainSkelBuilder.this.mnode + } + + /* ---------------- Part 1 of program points, ie Labels in the ASM world ---------------- */ + + /* + * A jump is represented as an Apply node whose symbol denotes a LabelDef, the target of the jump. + * The `jumpDest` map is used to: + * (a) find the asm.Label for the target, given an Apply node's symbol; + * (b) anchor an asm.Label in the instruction stream, given a LabelDef node. + * In other words, (a) is necessary when visiting a jump-source, and (b) when visiting a jump-target. + * A related map is `labelDef`: it has the same keys as `jumpDest` but its values are LabelDef nodes not asm.Labels. + * + */ + var jumpDest: immutable.Map[ /* LabelDef */ Symbol, asm.Label ] = null + def programPoint(labelSym: Symbol): asm.Label = { + assert(labelSym.isLabel, s"trying to map a non-label symbol to an asm.Label, at: ${labelSym.pos}") + jumpDest.getOrElse(labelSym, { + val pp = new asm.Label + jumpDest += (labelSym -> pp) + pp + }) + } + + /* + * A program point may be lexically nested (at some depth) + * (a) in the try-clause of a try-with-finally expression + * (b) in a synchronized block. + * Each of the constructs above establishes a "cleanup block" to execute upon + * both normal-exit, early-return, and abrupt-termination of the instructions it encloses. + * + * The `cleanups` LIFO queue represents the nesting of active (for the current program point) + * pending cleanups. For each such cleanup an asm.Label indicates the start of its cleanup-block. + * At any given time during traversal of the method body, + * the head of `cleanups` denotes the cleanup-block for the closest enclosing try-with-finally or synchronized-expression. + * + * `cleanups` is used: + * + * (1) upon visiting a Return statement. + * In case of pending cleanups, we can't just emit a RETURN instruction, but must instead: + * - store the result (if any) in `earlyReturnVar`, and + * - jump to the next pending cleanup. + * See `genReturn()` + * + * (2) upon emitting a try-with-finally or a synchronized-expr, + * In these cases, the targets of the above jumps are emitted, + * provided an early exit was actually encountered somewhere in the protected clauses. + * See `genLoadTry()` and `genSynchronized()` + * + * The code thus emitted for jumps and targets covers the early-return case. + * The case of abrupt (ie exceptional) termination is covered by exception handlers + * emitted for that purpose as described in `genLoadTry()` and `genSynchronized()`. + */ + var cleanups: List[asm.Label] = Nil + def registerCleanup(finCleanup: asm.Label) { + if (finCleanup != null) { cleanups = finCleanup :: cleanups } + } + def unregisterCleanup(finCleanup: asm.Label) { + if (finCleanup != null) { + assert(cleanups.head eq finCleanup, + s"Bad nesting of cleanup operations: $cleanups trying to unregister: $finCleanup") + cleanups = cleanups.tail + } + } + + /* ---------------- local variables and params ---------------- */ + + case class Local(tk: BType, name: String, idx: Int, isSynth: Boolean) + + /* + * Bookkeeping for method-local vars and method-params. + */ + object locals { + + private val slots = mutable.Map.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth)) + + private var nxtIdx = -1 // next available index for local-var + + def reset(isStaticMethod: Boolean) { + slots.clear() + nxtIdx = if (isStaticMethod) 0 else 1 + } + + def contains(locSym: Symbol): Boolean = { slots.contains(locSym) } + + def apply(locSym: Symbol): Local = { slots.apply(locSym) } + + /* Make a fresh local variable, ensuring a unique name. + * The invoker must make sure inner classes are tracked for the sym's tpe. + */ + def makeLocal(tk: BType, name: String): Symbol = { + val locSym = methSymbol.newVariable(cunit.freshTermName(name), NoPosition, Flags.SYNTHETIC) // setInfo tpe + makeLocal(locSym, tk) + locSym + } + + def makeLocal(locSym: Symbol): Local = { + makeLocal(locSym, symInfoTK(locSym)) + } + + def getOrMakeLocal(locSym: Symbol): Local = { + // `getOrElse` below has the same effect as `getOrElseUpdate` because `makeLocal()` adds an entry to the `locals` map. + slots.getOrElse(locSym, makeLocal(locSym)) + } + + private def makeLocal(sym: Symbol, tk: BType): Local = { + assert(!slots.contains(sym), "attempt to create duplicate local var.") + assert(nxtIdx != -1, "not a valid start index") + val loc = Local(tk, sym.javaSimpleName.toString, nxtIdx, sym.isSynthetic) + slots += (sym -> loc) + assert(tk.getSize > 0, "makeLocal called for a symbol whose type is Unit.") + nxtIdx += tk.getSize + loc + } + + // not to be confused with `fieldStore` and `fieldLoad` which also take a symbol but a field-symbol. + def store(locSym: Symbol) { + val Local(tk, _, idx, _) = slots(locSym) + bc.store(idx, tk) + } + + def load(locSym: Symbol) { + val Local(tk, _, idx, _) = slots(locSym) + bc.load(idx, tk) + } + + } + + /* ---------------- Part 2 of program points, ie Labels in the ASM world ---------------- */ + + /* + * The semantics of try-with-finally and synchronized-expr require their cleanup code + * to be present in three forms in the emitted bytecode: + * (a) as normal-exit code, reached via fall-through from the last program point being protected, + * (b) as code reached upon early-return from an enclosed return statement. + * The only difference between (a) and (b) is their next program-point: + * the former must continue with fall-through while + * the latter must continue to the next early-return cleanup (if any, otherwise return from the method). + * Otherwise they are identical. + * (c) as exception-handler, reached via exceptional control flow, + * which rethrows the caught exception once it's done with the cleanup code. + * + * A particular cleanup may in general contain LabelDefs. Care is needed when duplicating such jump-targets, + * so as to preserve agreement wit the (also duplicated) jump-sources. + * This is achieved based on the bookkeeping provided by two maps: + * - `labelDefsAtOrUnder` lists all LabelDefs enclosed by a given Tree node (the key) + * - `labelDef` provides the LabelDef node whose symbol is used as key. + * As a sidenote, a related map is `jumpDest`: it has the same keys as `labelDef` but its values are asm.Labels not LabelDef nodes. + * + * Details in `emitFinalizer()`, which is invoked from `genLoadTry()` and `genSynchronized()`. + */ + var labelDefsAtOrUnder: scala.collection.Map[Tree, List[LabelDef]] = null + var labelDef: scala.collection.Map[Symbol, LabelDef] = null// (LabelDef-sym -> LabelDef) + + // bookkeeping the scopes of non-synthetic local vars, to emit debug info (`emitVars`). + var varsInScope: List[Pair[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope) + + // helpers around program-points. + def lastInsn: asm.tree.AbstractInsnNode = { + mnode.instructions.getLast + } + def currProgramPoint(): asm.Label = { + lastInsn match { + case labnode: asm.tree.LabelNode => labnode.getLabel + case _ => + val pp = new asm.Label + mnode visitLabel pp + pp + } + } + def markProgramPoint(lbl: asm.Label) { + val skip = (lbl == null) || isAtProgramPoint(lbl) + if (!skip) { mnode visitLabel lbl } + } + def isAtProgramPoint(lbl: asm.Label): Boolean = { + (lastInsn match { case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl); case _ => false } ) + } + def lineNumber(tree: Tree) { + if (!emitLines || !tree.pos.isDefined) return; + val nr = tree.pos.line + if (nr != lastEmittedLineNr) { + lastEmittedLineNr = nr + lastInsn match { + case lnn: asm.tree.LineNumberNode => + // overwrite previous landmark as no instructions have been emitted for it + lnn.line = nr + case _ => + mnode.visitLineNumber(nr, currProgramPoint()) + } + } + } + + // on entering a method + def resetMethodBookkeeping(dd: DefDef) { + locals.reset(isStaticMethod = methSymbol.isStaticMember) + jumpDest = immutable.Map.empty[ /* LabelDef */ Symbol, asm.Label ] + // populate labelDefsAtOrUnder + val ldf = new LabelDefsFinder + ldf.traverse(dd.rhs) + labelDefsAtOrUnder = ldf.result.withDefaultValue(Nil) + labelDef = labelDefsAtOrUnder(dd.rhs).map(ld => (ld.symbol -> ld)).toMap + // check previous invocation of genDefDef exited as many varsInScope as it entered. + assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().") + // check previous invocation of genDefDef unregistered as many cleanups as it registered. + assert(cleanups == Nil, "Previous invocation of genDefDef didn't unregister as many cleanups as it registered.") + isModuleInitialized = false + earlyReturnVar = null + shouldEmitCleanup = false + + lastEmittedLineNr = -1 + } + + /* ---------------- top-down traversal invoking ASM Tree API along the way ---------------- */ + + def gen(tree: Tree) { + tree match { + case EmptyTree => () + + case _: ModuleDef => abort(s"Modules should have been eliminated by refchecks: $tree") + + case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()` + + case dd : DefDef => genDefDef(dd) + + case Template(_, _, body) => body foreach gen + + case _ => abort(s"Illegal tree in gen: $tree") + } + } + + /* + * must-single-thread + */ + def initJMethod(flags: Int, paramAnnotations: List[List[AnnotationInfo]]) { + + val jgensig = getGenericSignature(methSymbol, claszSymbol) + addRemoteExceptionAnnot(isCZRemote, hasPublicBitSet(flags), methSymbol) + val (excs, others) = methSymbol.annotations partition (_.symbol == definitions.ThrowsClass) + val thrownExceptions: List[String] = getExceptions(excs) + + val bytecodeName = + if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME + else jMethodName + + val mdesc = asmMethodType(methSymbol).getDescriptor + mnode = cnode.visitMethod( + flags, + bytecodeName, + mdesc, + jgensig, + mkArray(thrownExceptions) + ).asInstanceOf[asm.tree.MethodNode] + + // TODO param names: (m.params map (p => javaName(p.sym))) + + emitAnnotations(mnode, others) + emitParamAnnotations(mnode, paramAnnotations) + + } // end of method initJMethod + + + def genDefDef(dd: DefDef) { + // the only method whose implementation is not emitted: getClass() + if (definitions.isGetClass(dd.symbol)) { return } + assert(mnode == null, "GenBCode detected nested method.") + + methSymbol = dd.symbol + jMethodName = methSymbol.javaSimpleName.toString + returnType = asmMethodType(dd.symbol).getReturnType + isMethSymStaticCtor = methSymbol.isStaticConstructor + isMethSymBridge = methSymbol.isBridge + + resetMethodBookkeeping(dd) + + // add method-local vars for params + val DefDef(_, _, _, vparamss, _, rhs) = dd + assert(vparamss.isEmpty || vparamss.tail.isEmpty, s"Malformed parameter list: $vparamss") + val params = if (vparamss.isEmpty) Nil else vparamss.head + for (p <- params) { locals.makeLocal(p.symbol) } + // debug assert((params.map(p => locals(p.symbol).tk)) == asmMethodType(methSymbol).getArgumentTypes.toList, "debug") + + if (params.size > MaximumJvmParameters) { + // SI-7324 + cunit.error(methSymbol.pos, s"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters.") + return + } + + val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) + val isAbstractMethod = (methSymbol.isDeferred || methSymbol.owner.isInterface) + val flags = mkFlags( + javaFlags(methSymbol), + if (claszSymbol.isInterface) asm.Opcodes.ACC_ABSTRACT else 0, + if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, + if (isNative) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes + if (isDeprecated(methSymbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } + initJMethod(flags, params.map(p => p.symbol.annotations)) + + /* Add method-local vars for LabelDef-params. + * + * This makes sure that: + * (1) upon visiting any "forward-jumping" Apply (ie visited before its target LabelDef), and after + * (2) grabbing the corresponding param symbols, + * those param-symbols can be used to access method-local vars. + * + * When duplicating a finally-contained LabelDef, another program-point is needed for the copy (each such copy has its own asm.Label), + * but the same vars (given by the LabelDef's params) can be reused, + * because no LabelDef ends up nested within itself after such duplication. + */ + for(ld <- labelDefsAtOrUnder(dd.rhs); ldp <- ld.params; if !locals.contains(ldp.symbol)) { + // the tail-calls xform results in symbols shared btw method-params and labelDef-params, thus the guard above. + locals.makeLocal(ldp.symbol) + } + + if (!isAbstractMethod && !isNative) { + + def emitNormalMethodBody() { + val veryFirstProgramPoint = currProgramPoint() + genLoad(rhs, returnType) + + rhs match { + case Block(_, Return(_)) => () + case Return(_) => () + case EmptyTree => + globalError("Concrete method has no definition: " + dd + ( + if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")" + else "") + ) + case _ => + bc emitRETURN returnType + } + if (emitVars) { + // add entries to LocalVariableTable JVM attribute + val onePastLastProgramPoint = currProgramPoint() + val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0) + if (!hasStaticBitSet) { + mnode.visitLocalVariable( + "this", + "L" + thisName + ";", + null, + veryFirstProgramPoint, + onePastLastProgramPoint, + 0 + ) + } + for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) } + } + + if (isMethSymStaticCtor) { appendToStaticCtor(dd) } + } // end of emitNormalMethodBody() + + lineNumber(rhs) + emitNormalMethodBody() + + // Note we don't invoke visitMax, thus there are no FrameNode among mnode.instructions. + // The only non-instruction nodes to be found are LabelNode and LineNumberNode. + } + mnode = null + } // end of method genDefDef() + + /* + * must-single-thread + * + * TODO document, explain interplay with `fabricateStaticInit()` + */ + private def appendToStaticCtor(dd: DefDef) { + + def insertBefore( + location: asm.tree.AbstractInsnNode, + i0: asm.tree.AbstractInsnNode, + i1: asm.tree.AbstractInsnNode) { + if (i0 != null) { + mnode.instructions.insertBefore(location, i0.clone(null)) + mnode.instructions.insertBefore(location, i1.clone(null)) + } + } + + // collect all return instructions + var rets: List[asm.tree.AbstractInsnNode] = Nil + mnode foreachInsn { i => if (i.getOpcode() == asm.Opcodes.RETURN) { rets ::= i } } + if (rets.isEmpty) { return } + + var insnModA: asm.tree.AbstractInsnNode = null + var insnModB: asm.tree.AbstractInsnNode = null + // call object's private ctor from static ctor + if (isCZStaticModule) { + // NEW `moduleName` + val className = internalName(methSymbol.enclClass) + insnModA = new asm.tree.TypeInsnNode(asm.Opcodes.NEW, className) + // INVOKESPECIAL <init> + val callee = methSymbol.enclClass.primaryConstructor + val jname = callee.javaSimpleName.toString + val jowner = internalName(callee.owner) + val jtype = asmMethodType(callee).getDescriptor + insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype) + } + + var insnParcA: asm.tree.AbstractInsnNode = null + var insnParcB: asm.tree.AbstractInsnNode = null + // android creator code + if (isCZParcelable) { + // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator + val andrFieldDescr = asmClassType(AndroidCreatorClass).getDescriptor + cnode.visitField( + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, + "CREATOR", + andrFieldDescr, + null, + null + ) + // INVOKESTATIC CREATOR(): android.os.Parcelable$Creator; -- TODO where does this Android method come from? + val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName) + val jowner = internalName(callee.owner) + val jname = callee.javaSimpleName.toString + val jtype = asmMethodType(callee).getDescriptor + insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype) + // PUTSTATIC `thisName`.CREATOR; + insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) + } + + // insert a few instructions for initialization before each return instruction + for(r <- rets) { + insertBefore(r, insnModA, insnModB) + insertBefore(r, insnParcA, insnParcB) + } + + } + + def emitLocalVarScope(sym: Symbol, start: asm.Label, end: asm.Label, force: Boolean = false) { + val Local(tk, name, idx, isSynth) = locals(sym) + if (force || !isSynth) { + mnode.visitLocalVariable(name, tk.getDescriptor, null, start, end, idx) + } + } + + def genLoad(tree: Tree, expectedType: BType) + + } // end of class PlainSkelBuilder + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala new file mode 100644 index 0000000000..439be77b31 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -0,0 +1,401 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class BCodeSyncAndTry extends BCodeBodyBuilder { + import global._ + + + /* + * Functionality to lower `synchronized` and `try` expressions. + */ + abstract class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { + + def genSynchronized(tree: Apply, expectedType: BType): BType = { + val Apply(fun, args) = tree + val monitor = locals.makeLocal(ObjectReference, "monitor") + val monCleanup = new asm.Label + + // if the synchronized block returns a result, store it in a local variable. + // Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks). + val hasResult = (expectedType != UNIT) + val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult") else null; + + /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ + genLoadQualifier(fun) + bc dup ObjectReference + locals.store(monitor) + emit(asm.Opcodes.MONITORENTER) + + /* ------ (2) Synchronized block. + * Reached by fall-through from (1). + * Protected by: + * (2.a) the EH-version of the monitor-exit, and + * (2.b) whatever protects the whole synchronized expression. + * ------ + */ + val startProtected = currProgramPoint() + registerCleanup(monCleanup) + genLoad(args.head, expectedType /* toTypeKind(tree.tpe.resultType) */) + unregisterCleanup(monCleanup) + if (hasResult) { locals.store(monitorResult) } + nopIfNeeded(startProtected) + val endProtected = currProgramPoint() + + /* ------ (3) monitor-exit after normal, non-early-return, termination of (2). + * Reached by fall-through from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + if (hasResult) { locals.load(monitorResult) } + val postHandler = new asm.Label + bc goTo postHandler + + /* ------ (4) exception-handler version of monitor-exit code. + * Reached upon abrupt termination of (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + protect(startProtected, endProtected, currProgramPoint(), ThrowableReference) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + emit(asm.Opcodes.ATHROW) + + /* ------ (5) cleanup version of monitor-exit code. + * Reached upon early-return from (2). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + if (shouldEmitCleanup) { + markProgramPoint(monCleanup) + locals.load(monitor) + emit(asm.Opcodes.MONITOREXIT) + pendingCleanups() + } + + /* ------ (6) normal exit of the synchronized expression. + * Reached after normal, non-early-return, termination of (3). + * Protected by whatever protects the whole synchronized expression. + * ------ + */ + mnode visitLabel postHandler + + lineNumber(tree) + + expectedType + } + + /* + * Detects whether no instructions have been emitted since label `lbl` and if so emits a NOP. + * Useful to avoid emitting an empty try-block being protected by exception handlers, + * which results in "java.lang.ClassFormatError: Illegal exception table range". See SI-6102. + */ + def nopIfNeeded(lbl: asm.Label) { + val noInstructionEmitted = isAtProgramPoint(lbl) + if (noInstructionEmitted) { emit(asm.Opcodes.NOP) } + } + + /* + * Emitting try-catch is easy, emitting try-catch-finally not quite so. + * A finally-block (which always has type Unit, thus leaving the operand stack unchanged) + * affects control-transfer from protected regions, as follows: + * + * (a) `return` statement: + * + * First, the value to return (if any) is evaluated. + * Afterwards, all enclosing finally-blocks are run, from innermost to outermost. + * Only then is the return value (if any) returned. + * + * Some terminology: + * (a.1) Executing a return statement that is protected + * by one or more finally-blocks is called "early return" + * (a.2) the chain of code sections (a code section for each enclosing finally-block) + * to run upon early returns is called "cleanup chain" + * + * As an additional spin, consider a return statement in a finally-block. + * In this case, the value to return depends on how control arrived at that statement: + * in case it arrived via a previous return, the previous return enjoys priority: + * the value to return is given by that statement. + * + * (b) A finally-block protects both the try-clause and the catch-clauses. + * + * Sidenote: + * A try-clause may contain an empty block. On CLR, a finally-block has special semantics + * regarding Abort interruptions; but on the JVM it's safe to elide an exception-handler + * that protects an "empty" range ("empty" as in "containing NOPs only", + * see `asm.optimiz.DanglingExcHandlers` and SI-6720). + * + * This means a finally-block indicates instructions that can be reached: + * (b.1) Upon normal (non-early-returning) completion of the try-clause or a catch-clause + * In this case, the next-program-point is that following the try-catch-finally expression. + * (b.2) Upon early-return initiated in the try-clause or a catch-clause + * In this case, the next-program-point is the enclosing cleanup section (if any), otherwise return. + * (b.3) Upon abrupt termination (due to unhandled exception) of the try-clause or a catch-clause + * In this case, the unhandled exception must be re-thrown after running the finally-block. + * + * (c) finally-blocks are implicit to `synchronized` (a finally-block is added to just release the lock) + * that's why `genSynchronized()` too emits cleanup-sections. + * + * A number of code patterns can be emitted to realize the intended semantics. + * + * A popular alternative (GenICode, javac) consists in duplicating the cleanup-chain at each early-return position. + * The principle at work being that once control is transferred to a cleanup-section, + * control will always stay within the cleanup-chain. + * That is, barring an exception being thrown in a cleanup-section, in which case the enclosing try-block + * (reached via abrupt termination) takes over. + * + * The observations above hint at another code layout, less verbose, for the cleanup-chain. + * + * The code layout that GenBCode emits takes into account that once a cleanup section has been reached, + * jumping to the next cleanup-section (and so on, until the outermost one) realizes the correct semantics. + * + * There is still code duplication in that two cleanup-chains are needed (but this is unavoidable, anyway): + * one for normal control flow and another chain consisting of exception handlers. + * The in-line comments below refer to them as + * - "early-return-cleanups" and + * - "exception-handler-version-of-finally-block" respectively. + * + */ + def genLoadTry(tree: Try): BType = { + + val Try(block, catches, finalizer) = tree + val kind = tpeTK(tree) + + val caseHandlers: List[EHClause] = + for (CaseDef(pat, _, caseBody) <- catches) yield { + pat match { + case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt), caseBody) + case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Bind(_, _) => BoundEH (pat.symbol, caseBody) + } + } + + // ------ (0) locals used later ------ + + /* + * `postHandlers` is a program point denoting: + * (a) the finally-clause conceptually reached via fall-through from try-catch-finally + * (in case a finally-block is present); or + * (b) the program point right after the try-catch + * (in case there's no finally-block). + * The name choice emphasizes that the code section lies "after all exception handlers", + * where "all exception handlers" includes those derived from catch-clauses as well as from finally-blocks. + */ + val postHandlers = new asm.Label + + val hasFinally = (finalizer != EmptyTree) + + /* + * used in the finally-clause reached via fall-through from try-catch, if any. + */ + val guardResult = hasFinally && (kind != UNIT) && mayCleanStack(finalizer) + + /* + * please notice `tmp` has type tree.tpe, while `earlyReturnVar` has the method return type. + * Because those two types can be different, dedicated vars are needed. + */ + val tmp = if (guardResult) locals.makeLocal(tpeTK(tree), "tmp") else null; + + /* + * upon early return from the try-body or one of its EHs (but not the EH-version of the finally-clause) + * AND hasFinally, a cleanup is needed. + */ + val finCleanup = if (hasFinally) new asm.Label else null + + /* ------ (1) try-block, protected by: + * (1.a) the EHs due to case-clauses, emitted in (2), + * (1.b) the EH due to finally-clause, emitted in (3.A) + * (1.c) whatever protects the whole try-catch-finally expression. + * ------ + */ + + val startTryBody = currProgramPoint() + registerCleanup(finCleanup) + genLoad(block, kind) + unregisterCleanup(finCleanup) + nopIfNeeded(startTryBody) + val endTryBody = currProgramPoint() + bc goTo postHandlers + + /* ------ (2) One EH for each case-clause (this does not include the EH-version of the finally-clause) + * An EH in (2) is reached upon abrupt termination of (1). + * An EH in (2) is protected by: + * (2.a) the EH-version of the finally-clause, if any. + * (2.b) whatever protects the whole try-catch-finally expression. + * ------ + */ + + for (ch <- caseHandlers) { + + // (2.a) emit case clause proper + val startHandler = currProgramPoint() + var endHandler: asm.Label = null + var excType: BType = null + registerCleanup(finCleanup) + ch match { + case NamelessEH(typeToDrop, caseBody) => + bc drop typeToDrop + genLoad(caseBody, kind) // adapts caseBody to `kind`, thus it can be stored, if `guardResult`, in `tmp`. + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + excType = typeToDrop + + case BoundEH (patSymbol, caseBody) => + // test/files/run/contrib674.scala , a local-var already exists for patSymbol. + // rather than creating on first-access, we do it right away to emit debug-info for the created local var. + val Local(patTK, _, patIdx, _) = locals.getOrMakeLocal(patSymbol) + bc.store(patIdx, patTK) + genLoad(caseBody, kind) + nopIfNeeded(startHandler) + endHandler = currProgramPoint() + emitLocalVarScope(patSymbol, startHandler, endHandler) + excType = patTK + } + unregisterCleanup(finCleanup) + // (2.b) mark the try-body as protected by this case clause. + protect(startTryBody, endTryBody, startHandler, excType) + // (2.c) emit jump to the program point where the finally-clause-for-normal-exit starts, or in effect `after` if no finally-clause was given. + bc goTo postHandlers + + } + + /* ------ (3.A) The exception-handler-version of the finally-clause. + * Reached upon abrupt termination of (1) or one of the EHs in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * ------ + */ + + // a note on terminology: this is not "postHandlers", despite appearences. + // "postHandlers" as in the source-code view. And from that perspective, both (3.A) and (3.B) are invisible implementation artifacts. + if (hasFinally) { + nopIfNeeded(startTryBody) + val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. + protect(startTryBody, finalHandler, finalHandler, null) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc")) + bc.store(eIdx, eTK) + emitFinalizer(finalizer, null, isDuplicate = true) + bc.load(eIdx, eTK) + emit(asm.Opcodes.ATHROW) + } + + /* ------ (3.B) Cleanup-version of the finally-clause. + * Reached upon early RETURN from (1) or upon early RETURN from one of the EHs in (2) + * (and only from there, ie reached only upon early RETURN from + * program regions bracketed by registerCleanup/unregisterCleanup). + * Protected only by whatever protects the whole try-catch-finally expression. + * + * Given that control arrives to a cleanup section only upon early RETURN, + * the value to return (if any) is always available. Therefore, a further RETURN + * found in a cleanup section is always ignored (a warning is displayed, @see `genReturn()`). + * In order for `genReturn()` to know whether the return statement is enclosed in a cleanup section, + * the variable `insideCleanupBlock` is used. + * ------ + */ + + // this is not "postHandlers" either. + // `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause. + // In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid. + if (hasFinally && shouldEmitCleanup) { + val savedInsideCleanup = insideCleanupBlock + insideCleanupBlock = true + markProgramPoint(finCleanup) + // regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted. + emitFinalizer(finalizer, null, isDuplicate = true) + pendingCleanups() + insideCleanupBlock = savedInsideCleanup + } + + /* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit + * Reached upon normal, non-early-return termination of (1) or of an EH in (2). + * Protected only by whatever protects the whole try-catch-finally expression. + * TODO explain what happens upon RETURN contained in (4) + * ------ + */ + + markProgramPoint(postHandlers) + if (hasFinally) { + emitFinalizer(finalizer, tmp, isDuplicate = false) // the only invocation of emitFinalizer with `isDuplicate == false` + } + + kind + } // end of genLoadTry() + + /* if no more pending cleanups, all that remains to do is return. Otherwise jump to the next (outer) pending cleanup. */ + private def pendingCleanups() { + cleanups match { + case Nil => + if (earlyReturnVar != null) { + locals.load(earlyReturnVar) + bc.emitRETURN(locals(earlyReturnVar).tk) + } else { + bc emitRETURN UNIT + } + shouldEmitCleanup = false + + case nextCleanup :: _ => + bc goTo nextCleanup + } + } + + def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: BType) { + val excInternalName: String = + if (excType == null) null + else excType.getInternalName + assert(start != end, "protecting a range of zero instructions leads to illegal class format. Solution: add a NOP to that range.") + mnode.visitTryCatchBlock(start, end, handler, excInternalName) + } + + /* `tmp` (if non-null) is the symbol of the local-var used to preserve the result of the try-body, see `guardResult` */ + def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean) { + var saved: immutable.Map[ /* LabelDef */ Symbol, asm.Label ] = null + if (isDuplicate) { + saved = jumpDest + for(ldef <- labelDefsAtOrUnder(finalizer)) { + jumpDest -= ldef.symbol + } + } + // when duplicating, the above guarantees new asm.Labels are used for LabelDefs contained in the finalizer (their vars are reused, that's ok) + if (tmp != null) { locals.store(tmp) } + genLoad(finalizer, UNIT) + if (tmp != null) { locals.load(tmp) } + if (isDuplicate) { + jumpDest = saved + } + } + + /* Does this tree have a try-catch block? */ + def mayCleanStack(tree: Tree): Boolean = tree exists { t => t.isInstanceOf[Try] } + + abstract class Cleanup(val value: AnyRef) { + def contains(x: AnyRef) = value == x + } + case class MonitorRelease(v: Symbol) extends Cleanup(v) { } + case class Finalizer(f: Tree) extends Cleanup (f) { } + + trait EHClause + case class NamelessEH(typeToDrop: BType, caseBody: Tree) extends EHClause + case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause + + } + +} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala new file mode 100644 index 0000000000..542a90fa85 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala @@ -0,0 +1,991 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools.nsc +package backend.jvm + +import scala.tools.asm +import scala.collection.{ immutable, mutable } + +/* + * Utilities to mediate between types as represented in Scala ASTs and ASM trees. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded + * @version 1.0 + * + */ +abstract class BCodeTypes extends BCodeIdiomatic { + + import global._ + + // when compiling the Scala library, some assertions don't hold (e.g., scala.Boolean has null superClass although it's not an interface) + val isCompilingStdLib = !(settings.sourcepath.isDefault) + + val srBoxedUnit = brefType("scala/runtime/BoxedUnit") + + // special names + var StringReference : BType = null + var ThrowableReference : BType = null + var jlCloneableReference : BType = null // java/lang/Cloneable + var jlNPEReference : BType = null // java/lang/NullPointerException + var jioSerializableReference : BType = null // java/io/Serializable + var scalaSerializableReference : BType = null // scala/Serializable + var classCastExceptionReference : BType = null // java/lang/ClassCastException + + var lateClosureInterfaces: Array[Tracked] = null // the only interface a Late-Closure-Class implements is scala.Serializable + + /* A map from scala primitive type-symbols to BTypes */ + var primitiveTypeMap: Map[Symbol, BType] = null + /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */ + var phantomTypeMap: Map[Symbol, BType] = null + /* Maps the method symbol for a box method to the boxed type of the result. + * For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */ + var boxResultType: Map[Symbol, BType] = null + /* Maps the method symbol for an unbox method to the primitive type of the result. + * For example, the method symbol for `Byte.unbox()`) is mapped to the BType BYTE. */ + var unboxResultType: Map[Symbol, BType] = null + + var hashMethodSym: Symbol = null // scala.runtime.ScalaRunTime.hash + + var AndroidParcelableInterface: Symbol = null + var AndroidCreatorClass : Symbol = null // this is an inner class, use asmType() to get hold of its BType while tracking in innerClassBufferASM + var androidCreatorType : BType = null + + var BeanInfoAttr: Symbol = null + + /* The Object => String overload. */ + var String_valueOf: Symbol = null + + var ArrayInterfaces: Set[Tracked] = null + + // scala.FunctionX and scala.runtim.AbstractFunctionX + val FunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) + val AbstractFunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) + val abstractFunctionArityMap = mutable.Map.empty[BType, Int] + + var PartialFunctionReference: BType = null // scala.PartialFunction + var AbstractPartialFunctionReference: BType = null // scala.runtime.AbstractPartialFunction + + var BoxesRunTime: BType = null + + /* + * must-single-thread + */ + def initBCodeTypes() { + + import definitions._ + + primitiveTypeMap = + Map( + UnitClass -> UNIT, + BooleanClass -> BOOL, + CharClass -> CHAR, + ByteClass -> BYTE, + ShortClass -> SHORT, + IntClass -> INT, + LongClass -> LONG, + FloatClass -> FLOAT, + DoubleClass -> DOUBLE + ) + + phantomTypeMap = + Map( + NothingClass -> RT_NOTHING, + NullClass -> RT_NULL, + NothingClass -> RT_NOTHING, // we map on purpose to RT_NOTHING, getting rid of the distinction compile-time vs. runtime for NullClass. + NullClass -> RT_NULL // ditto. + ) + + boxResultType = + for(Pair(csym, msym) <- definitions.boxMethod) + yield (msym -> classLiteral(primitiveTypeMap(csym))) + + unboxResultType = + for(Pair(csym, msym) <- definitions.unboxMethod) + yield (msym -> primitiveTypeMap(csym)) + + // boxed classes are looked up in the `exemplars` map by jvmWiseLUB(). + // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others). + val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) + for(csym <- boxedClasses) { + val key = brefType(csym.javaBinaryName.toTypeName) + val tr = buildExemplar(key, csym) + symExemplars.put(csym, tr) + exemplars.put(tr.c, tr) + } + + // reversePrimitiveMap = (primitiveTypeMap map { case (s, pt) => (s.tpe, pt) } map (_.swap)).toMap + + hashMethodSym = getMember(ScalaRunTimeModule, nme.hash_) + + // TODO avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540 + AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable") + AndroidCreatorClass = rootMirror.getClassIfDefined("android.os.Parcelable$Creator") + + // the following couldn't be an eager vals in Phase constructors: + // that might cause cycles before Global has finished initialization. + BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo") + + String_valueOf = { + getMember(StringModule, nme.valueOf) filter (sym => + sym.info.paramTypes match { + case List(pt) => pt.typeSymbol == ObjectClass + case _ => false + } + ) + } + + ArrayInterfaces = Set(JavaCloneableClass, JavaSerializableClass) map exemplar + + StringReference = exemplar(StringClass).c + StringBuilderReference = exemplar(StringBuilderClass).c + ThrowableReference = exemplar(ThrowableClass).c + jlCloneableReference = exemplar(JavaCloneableClass).c + jlNPEReference = exemplar(NullPointerExceptionClass).c + jioSerializableReference = exemplar(JavaSerializableClass).c + scalaSerializableReference = exemplar(SerializableClass).c + classCastExceptionReference = exemplar(ClassCastExceptionClass).c + + lateClosureInterfaces = Array(exemplar(SerializableClass)) + + /* + * The bytecode emitter special-cases String concatenation, in that three methods of `JCodeMethodN` + * ( `genStartConcat()` , `genStringConcat()` , and `genEndConcat()` ) + * don't obtain the method descriptor of the callee via `asmMethodType()` (as normally done) + * but directly emit callsites on StringBuilder using literal constant for method descriptors. + * In order to make sure those method descriptors are available as BTypes, they are initialized here. + */ + BType.getMethodType("()V") // necessary for JCodeMethodN.genStartConcat + BType.getMethodType("()Ljava/lang/String;") // necessary for JCodeMethodN.genEndConcat + + PartialFunctionReference = exemplar(PartialFunctionClass).c + for(idx <- 0 to definitions.MaxFunctionArity) { + FunctionReference(idx) = exemplar(FunctionClass(idx)) + AbstractFunctionReference(idx) = exemplar(AbstractFunctionClass(idx)) + abstractFunctionArityMap += (AbstractFunctionReference(idx).c -> idx) + AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c + } + + // later a few analyses (e.g. refreshInnerClasses) will look up BTypes based on descriptors in instructions + // we make sure those BTypes can be found via lookup as opposed to creating them on the fly. + BoxesRunTime = brefType("scala/runtime/BoxesRunTime") + asmBoxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } + asmUnboxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } + + } + + /* + * must-single-thread + */ + def clearBCodeTypes() { + symExemplars.clear() + exemplars.clear() + } + + val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC + val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL + + val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString + + // ------------------------------------------------ + // accessory maps tracking the isInterface, innerClasses, superClass, and supportedInterfaces relations, + // allowing answering `conforms()` without resorting to typer. + // ------------------------------------------------ + + val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, Tracked] + val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] + + /* + * Typically, a question about a BType can be answered only by using the BType as lookup key in one or more maps. + * A `Tracked` object saves time by holding together information required to answer those questions: + * + * - `sc` denotes the bytecode-level superclass if any, null otherwise + * + * - `ifaces` denotes the interfaces explicitly declared. + * Not included are those transitively supported, but the utility method `allLeafIfaces()` can be used for that. + * + * - `innersChain` denotes the containing classes for a non-package-level class `c`, null otherwise. + * Note: the optimizer may inline anonymous closures, thus eliding those inner classes + * (no physical class file is emitted for elided classes). + * Before committing `innersChain` to bytecode, cross-check with the list of elided classes (SI-6546). + * + * All methods of this class can-multi-thread + */ + case class Tracked(c: BType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { + + // not a case-field because we initialize it only for JVM classes we emit. + private var _directMemberClasses: List[BType] = null + + def directMemberClasses: List[BType] = { + assert(_directMemberClasses != null, s"getter directMemberClasses() invoked too early for $c") + _directMemberClasses + } + + def directMemberClasses_=(bs: List[BType]) { + if (_directMemberClasses != null) { + // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol. + assert(_directMemberClasses == bs.sortBy(_.off)) + } + _directMemberClasses = bs.sortBy(_.off) + } + + /* `isCompilingStdLib` saves the day when compiling: + * (1) scala.Nothing (the test `c.isNonSpecial` fails for it) + * (2) scala.Boolean (it has null superClass and is not an interface) + */ + assert(c.isNonSpecial || isCompilingStdLib /*(1)*/, s"non well-formed plain-type: $this") + assert( + if (sc == null) { (c == ObjectReference) || isInterface || isCompilingStdLib /*(2)*/ } + else { (c != ObjectReference) && !sc.isInterface } + , "non well-formed plain-type: " + this + ) + assert(ifaces.forall(i => i.c.isNonSpecial && i.isInterface), s"non well-formed plain-type: $this") + + import asm.Opcodes._ + def hasFlags(mask: Int) = (flags & mask) != 0 + def isPrivate = hasFlags(ACC_PRIVATE) + def isPublic = hasFlags(ACC_PUBLIC) + def isAbstract = hasFlags(ACC_ABSTRACT) + def isInterface = hasFlags(ACC_INTERFACE) + def isFinal = hasFlags(ACC_FINAL) + def isSynthetic = hasFlags(ACC_SYNTHETIC) + def isSuper = hasFlags(ACC_SUPER) + def isDeprecated = hasFlags(ACC_DEPRECATED) + def isInnerClass = { innersChain != null } + def isTraditionalClosureClass = { + isInnerClass && isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) + } + def isLambda = { + // ie isLCC || isTraditionalClosureClass + isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) + } + def isSerializable = { isSubtypeOf(jioSerializableReference) } + + /* can-multi-thread */ + def superClasses: List[Tracked] = { + if (sc == null) Nil else sc :: sc.superClasses + } + + /* can-multi-thread */ + def isSubtypeOf(other: BType): Boolean = { + assert(other.isNonSpecial, "so called special cases have to be handled in BCodeTypes.conforms()") + + if (c == other) return true; + + val otherIsIface = exemplars.get(other).isInterface + + if (this.isInterface) { + if (other == ObjectReference) return true; + if (!otherIsIface) return false; + } + else { + if (sc != null && sc.isSubtypeOf(other)) return true; + if (!otherIsIface) return false; + } + + var idx = 0 + while (idx < ifaces.length) { + if (ifaces(idx).isSubtypeOf(other)) return true; + idx += 1 + } + + false + } + + /* + * The `ifaces` field lists only those interfaces declared by `c` + * From the set of all supported interfaces, this method discards those which are supertypes of others in the set. + */ + def allLeafIfaces: Set[Tracked] = { + if (sc == null) { ifaces.toSet } + else { minimizeInterfaces(ifaces.toSet ++ sc.allLeafIfaces) } + } + + /* + * This type may not support in its entirety the interface given by the argument, however it may support some of its super-interfaces. + * We visualize each such supported subset of the argument's functionality as a "branch". This method returns all such branches. + * + * In other words, let Ri be a branch supported by `ib`, + * this method returns all Ri such that this <:< Ri, where each Ri is maximally deep. + */ + def supportedBranches(ib: Tracked): Set[Tracked] = { + assert(ib.isInterface, s"Non-interface argument: $ib") + + val result: Set[Tracked] = + if (this.isSubtypeOf(ib.c)) { Set(ib) } + else { ib.ifaces.toSet[Tracked].flatMap( bi => supportedBranches(bi) ) } + + checkAllInterfaces(result) + + result + } + + override def toString = { c.toString } + + } + + /* must-single-thread */ + final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) } + + /* must-single-thread */ + final def hasInternalName(sym: Symbol) = { sym.isClass || (sym.isModule && !sym.isMethod) } + + /* must-single-thread */ + def getSuperInterfaces(csym: Symbol): List[Symbol] = { + + // Additional interface parents based on annotations and other cues + def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match { + case definitions.RemoteAttr => definitions.RemoteInterfaceClass + case _ => NoSymbol + } + + /* Drop redundant interfaces (which are implemented by some other parent) from the immediate parents. + * In other words, no two interfaces in the result are related by subtyping. + * This method works on Symbols, a similar one (not duplicate) works on Tracked instances. + */ + def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = { + var rest = lstIfaces + var leaves = List.empty[Symbol] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { lsym => lsym isSubClass candidate } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym }) + } + rest = rest.tail + } + + leaves + } + + val superInterfaces0: List[Symbol] = csym.mixinClasses + val superInterfaces = existingSymbols(superInterfaces0 ++ csym.annotations.map(newParentForAttr)).distinct + + assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString}") + assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString}") + + minimizeInterfaces(superInterfaces) + } + + final def exemplarIfExisting(iname: String): Tracked = { + val bt = lookupRefBTypeIfExisting(iname) + if (bt != null) exemplars.get(bt) + else null + } + + final def lookupExemplar(iname: String) = { + exemplars.get(lookupRefBType(iname)) + } + + /* + * Records the superClass and supportedInterfaces relations, + * so that afterwards queries can be answered without resorting to typer. + * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that. + * On the other hand, this method does record the inner-class status of the argument, via `buildExemplar()`. + * + * must-single-thread + */ + final def exemplar(csym0: Symbol): Tracked = { + assert(csym0 != NoSymbol, "NoSymbol can't be tracked") + + val csym = { + if (csym0.isJavaDefined && csym0.isModuleClass) csym0.linkedClassOfClass + else if (csym0.isModule) csym0.moduleClass + else csym0 // we track only module-classes and plain-classes + } + + assert(!primitiveTypeMap.contains(csym) || isCompilingStdLib, s"primitive types not tracked here: ${csym.fullName}") + assert(!phantomTypeMap.contains(csym), s"phantom types not tracked here: ${csym.fullName}") + + val opt = symExemplars.get(csym) + if (opt != null) { + return opt + } + + val key = brefType(csym.javaBinaryName.toTypeName) + assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}") + + // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877 + // TODO Weaken this assertion? buildExemplar() needs to be updated, too. In the meantime, pos/t5031_3 has been moved to test/disabled/pos. + val whatWasInExemplars = exemplars.get(key) + assert(whatWasInExemplars == null, "Maps `symExemplars` and `exemplars` got out of synch.") + val tr = buildExemplar(key, csym) + symExemplars.put(csym, tr) + if (csym != csym0) { symExemplars.put(csym0, tr) } + exemplars.put(tr.c, tr) // tr.c is the hash-consed, internalized, canonical representative for csym's key. + tr + } + + val EMPTY_TRACKED_SET = Set.empty[Tracked] + + val EMPTY_TRACKED_ARRAY = Array.empty[Tracked] + val EMPTY_InnerClassEntry_ARRAY = Array.empty[InnerClassEntry] + + /* + * must-single-thread + */ + private def buildExemplar(key: BType, csym: Symbol): Tracked = { + val sc = + if (csym.isImplClass) definitions.ObjectClass + else csym.superClass + assert( + if (csym == definitions.ObjectClass) + sc == NoSymbol + else if (csym.isInterface) + sc == definitions.ObjectClass + else + ((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib, + "superClass out of order" + ) + val ifaces = getSuperInterfaces(csym) map exemplar; + val ifacesArr = + if (ifaces.isEmpty) EMPTY_TRACKED_ARRAY + else { + val arr = new Array[Tracked](ifaces.size) + ifaces.copyToArray(arr) + arr + } + + val flags = mkFlags( + javaFlags(csym), + if (isDeprecated(csym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag + ) + + val tsc = if (sc == NoSymbol) null else exemplar(sc) + + val innersChain = saveInnerClassesFor(csym, key) + + Tracked(key, flags, tsc, ifacesArr, innersChain) + } + + /* can-multi-thread */ + final def mkArray(xs: List[Tracked]): Array[Tracked] = { + if (xs.isEmpty) { return EMPTY_TRACKED_ARRAY } + val a = new Array[Tracked](xs.size); xs.copyToArray(a); a + } + + // ---------------- utilities around interfaces represented by Tracked instances. ---------------- + + /* Drop redundant interfaces (those which are implemented by some other). + * In other words, no two interfaces in the result are related by subtyping. + * This method works on Tracked elements, a similar one (not duplicate) works on Symbols. + */ + def minimizeInterfaces(lstIfaces: Set[Tracked]): Set[Tracked] = { + checkAllInterfaces(lstIfaces) + var rest = lstIfaces.toList + var leaves = List.empty[Tracked] + while (!rest.isEmpty) { + val candidate = rest.head + val nonLeaf = leaves exists { leaf => leaf.isSubtypeOf(candidate.c) } + if (!nonLeaf) { + leaves = candidate :: (leaves filterNot { leaf => candidate.isSubtypeOf(leaf.c) }) + } + rest = rest.tail + } + + leaves.toSet + } + + def allInterfaces(is: Iterable[Tracked]): Boolean = { is forall { i => i.isInterface } } + def nonInterfaces(is: Iterable[Tracked]): Iterable[Tracked] = { is filterNot { i => i.isInterface } } + + def checkAllInterfaces(ifaces: Iterable[Tracked]) { + assert(allInterfaces(ifaces), s"Non-interfaces: ${nonInterfaces(ifaces).mkString}") + } + + /* + * Returns the intersection of two sets of interfaces. + */ + def intersection(ifacesA: Set[Tracked], ifacesB: Set[Tracked]): Set[Tracked] = { + var acc: Set[Tracked] = Set() + for(ia <- ifacesA; ib <- ifacesB) { + val ab = ia.supportedBranches(ib) + val ba = ib.supportedBranches(ia) + acc = minimizeInterfaces(acc ++ ab ++ ba) + } + checkAllInterfaces(acc) + + acc + } + + /* + * Subtype check `a <:< b` on BTypes that takes into account the JVM built-in numeric promotions (e.g. BYTE to INT). + * Its operation can be visualized more easily in terms of the Java bytecode type hierarchy. + * This method used to be called, in the ICode world, TypeKind.<:<() + * + * can-multi-thread + */ + final def conforms(a: BType, b: BType): Boolean = { + if (a.isArray) { // may be null + /* Array subtyping is covariant here, as in Java bytecode. Also necessary for Java interop. */ + if ((b == jlCloneableReference) || + (b == jioSerializableReference) || + (b == AnyRefReference)) { true } + else if (b.isArray) { conforms(a.getComponentType, b.getComponentType) } + else { false } + } + else if (a.isBoxed) { // may be null + if (b.isBoxed) { a == b } + else if (b == AnyRefReference) { true } + else if (!(b.hasObjectSort)) { false } + else { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number + } + else if (a.isNullType) { // known to be null + if (b.isNothingType) { false } + else if (b.isValueType) { false } + else { true } + } + else if (a.isNothingType) { // known to be Nothing + true + } + else if (a.isUnitType) { + b.isUnitType + } + else if (a.hasObjectSort) { // may be null + if (a.isNothingType) { true } + else if (b.hasObjectSort) { exemplars.get(a).isSubtypeOf(b) } + else if (b.isArray) { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already. + else { false } + } + else { + + def msg = s"(a: $a, b: $b)" + + assert(a.isNonUnitValueType, s"a isn't a non-Unit value type. $msg") + assert(b.isValueType, s"b isn't a value type. $msg") + + (a eq b) || (a match { + case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). + case _ => a == b + }) + } + } + + /* The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative values of Byte and Short. See ticket #2087. + * + * can-multi-thread + */ + def maxValueType(a: BType, other: BType): BType = { + assert(a.isValueType, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") + + def uncomparable: Nothing = { + abort(s"Uncomparable BTypes: $a with $other") + } + + if (a.isNothingType) return other; + if (other.isNothingType) return a; + if (a == other) return a; + + a match { + + case UNIT => uncomparable + case BOOL => uncomparable + + case BYTE => + if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable + + case SHORT => + other match { + case BYTE => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case CHAR => + other match { + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case INT => + other match { + case BYTE | SHORT | CHAR => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case LONG => + if (other.isIntegralType) LONG + else if (other.isRealType) DOUBLE + else uncomparable + + case FLOAT => + if (other == DOUBLE) DOUBLE + else if (other.isNumericType) FLOAT + else uncomparable + + case DOUBLE => + if (other.isNumericType) DOUBLE + else uncomparable + + case _ => uncomparable + } + } + + /* Takes promotions of numeric primitives into account. + * + * can-multi-thread + */ + final def maxType(a: BType, other: BType): BType = { + if (a.isValueType) { maxValueType(a, other) } + else { + if (a.isNothingType) return other; + if (other.isNothingType) return a; + if (a == other) return a; + // Approximate `lub`. The common type of two references is always AnyRef. + // For 'real' least upper bound wrt to subclassing use method 'lub'. + assert(a.isArray || a.isBoxed || a.hasObjectSort, s"This is not a valuetype and it's not something else, what is it? $a") + // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference. + // BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType() + if (other.isRefOrArrayType) { AnyRefReference } + else { abort(s"Uncomparable BTypes: $a with $other") } + } + } + + /* + * Whether the argument (the signature of a method) takes as argument + * one ore more Function or PartialFunction (in particular an anonymous closure). + * + * can-multi-thread + */ + final def isHigherOrderMethod(mtype: BType): Boolean = { + assert(mtype.sort == BType.METHOD) + + val ats = mtype.getArgumentTypes + var idx = 0 + while (idx < ats.length) { + val t = ats(idx) + if (isFunctionType(t) || isPartialFunctionType(t)) { + return true + } + idx += 1 + } + false + } + + /* + * Whether the argument is a subtype of + * scala.PartialFunction[-A, +B] extends (A => B) + * N.B.: this method returns true for a scala.runtime.AbstractPartialFunction + * + * can-multi-thread + */ + def isPartialFunctionType(t: BType): Boolean = { + (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) + } + + /* + * Whether the argument is a subtype of + * scala.runtime.AbstractPartialFunction[-T1, +R] extends Function1[T1, R] with PartialFunction[T1, R] + * + * can-multi-thread + */ + def isAbstractPartialFunctionType(t: BType): Boolean = { + (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(AbstractPartialFunctionReference) + } + + /* + * Whether the argument is a subtype of scala.FunctionX where 0 <= X <= definitions.MaxFunctionArity + * + * can-multi-thread + */ + def isFunctionType(t: BType): Boolean = { + if (!t.hasObjectSort) return false + var idx = 0 + val et: Tracked = exemplars.get(t) + while (idx <= definitions.MaxFunctionArity) { + if (et.isSubtypeOf(FunctionReference(idx).c)) { + return true + } + idx += 1 + } + false + } + + def isClosureClass(bt: BType): Boolean = { + val tr = exemplars.get(bt); (tr != null && tr.isLambda) + } + + /* + * Whether the argument is a subtype of scala.runtime.AbstractFunctionX where 0 <= X <= definitions.MaxFunctionArity + * + * can-multi-thread + */ + def isAbstractFunctionType(t: BType): Boolean = { + if (!t.hasObjectSort) return false + var idx = 0 + val et: Tracked = exemplars.get(t) + while (idx <= definitions.MaxFunctionArity) { + if (et.isSubtypeOf(AbstractFunctionReference(idx).c)) { + return true + } + idx += 1 + } + false + } + + /* + * For an argument of exactly one of the types + * scala.runtime.AbstractFunctionX where 0 <= X <= definitions.MaxFunctionArity + * returns the function arity, -1 otherwise. + * + * can-multi-thread + */ + def abstractFunctionArity(t: BType): Int = { + abstractFunctionArityMap.getOrElse(t, -1) + } + + /* + * must-single-thread + */ + def isTopLevelModule(sym: Symbol): Boolean = { + exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass } + } + + /* + * must-single-thread + */ + def isStaticModule(sym: Symbol): Boolean = { + sym.isModuleClass && !sym.isImplClass && !sym.isLifted + } + + // --------------------------------------------------------------------- + // ---------------- InnerClasses attribute (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_INTERFACE | asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_FINAL) + + /* + * @param name the internal name of an inner class. + * @param outerName the internal name of the class to which the inner class belongs. + * May be `null` for non-member inner classes (ie for a Java local class or a Java anonymous class). + * @param innerName the (simple) name of the inner class inside its enclosing class. It's `null` for anonymous inner classes. + * @param access the access flags of the inner class as originally declared in the enclosing class. + */ + case class InnerClassEntry(name: String, outerName: String, innerName: String, access: Int) { + assert(name != null, "Null isn't good as class name in an InnerClassEntry.") + } + + /* For given symbol return a symbol corresponding to a class that should be declared as inner class. + * + * For example: + * class A { + * class B + * object C + * } + * + * then method will return: + * NoSymbol for A, + * the same symbol for A.B (corresponding to A$B class), and + * A$C$ symbol for A.C. + * + * must-single-thread + */ + def innerClassSymbolFor(s: Symbol): Symbol = + if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol + + /* + * Computes the chain of inner-class (over the is-member-of relation) for the given argument. + * The resulting chain will be cached in `exemplars`. + * + * The chain thus cached is valid during this compiler run, see in contrast + * `innerClassBufferASM` for a cache that is valid only for the class being emitted. + * + * The argument can be any symbol, but given that this method is invoked only from `buildExemplar()`, + * in practice it has been vetted to be a class-symbol. + * + * Returns: + * + * - a non-empty array of entries for an inner-class argument. + * The array's first element is the outermost top-level class, + * the array's last element corresponds to csym. + * + * - null otherwise. + * + * This method does not add to `innerClassBufferASM`, use instead `exemplar()` for that. + * + * must-single-thread + */ + final def saveInnerClassesFor(csym: Symbol, csymTK: BType): Array[InnerClassEntry] = { + + val ics = innerClassSymbolFor(csym) + if (ics == NoSymbol) { + return null + } + assert(ics == csym, s"Disagreement between innerClassSymbolFor() and exemplar()'s tracked symbol for the same input: ${csym.fullName}") + + var chain: List[Symbol] = Nil + var x = ics + while (x ne NoSymbol) { + assert(x.isClass, s"not a class symbol: ${x.fullName}") + val isInner = !x.rawowner.isPackageClass + if (isInner) { + chain ::= x + x = innerClassSymbolFor(x.rawowner) + } else { + x = NoSymbol + } + } + + // now that we have all of `ics` , `csym` , and soon the inner-classes-chain, it's too tempting not to cache. + if (chain.isEmpty) { null } + else { + val arr = new Array[InnerClassEntry](chain.size) + (chain map toInnerClassEntry).copyToArray(arr) + + arr + } + } + + /* + * must-single-thread + */ + private def toInnerClassEntry(innerSym: Symbol): InnerClassEntry = { + + /* The outer name for this inner class. Note that it returns null + * when the inner class should not get an index in the constant pool. + * That means non-member classes (anonymous). See Section 4.7.5 in the JVMS. + */ + def outerName(innerSym: Symbol): Name = { + if (innerSym.originalEnclosingMethod != NoSymbol) + null + else { + val outerName = innerSym.rawowner.javaBinaryName + if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName) + else outerName + } + } + + def innerName(innerSym: Symbol): String = { + if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction) + null + else + innerSym.rawname + innerSym.moduleSuffix + } + + val flagsWithFinal: Int = mkFlags( + if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0, + javaFlags(innerSym), + if (isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag + ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED) + val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding. + + val jname = innerSym.javaBinaryName.toString // never null + val oname = { // null when method-enclosed + val on = outerName(innerSym) + if (on == null) null else on.toString + } + val iname = { // null for anonymous inner class + val in = innerName(innerSym) + if (in == null) null else in.toString + } + + InnerClassEntry(jname, oname, iname, flags) + } + + // -------------------------------------------- + // ---------------- Java flags ---------------- + // -------------------------------------------- + + /* + * can-multi-thread + */ + final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0) + + /* + * must-single-thread + */ + final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr) + + /* + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + * + * must-single-thread + */ + def javaFlags(sym: Symbol): Int = { + // constructors of module classes should be private + // PP: why are they only being marked private at this stage and not earlier? + val privateFlag = + sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) + + // Final: the only fields which can receive ACC_FINAL are eager vals. + // Neither vars nor lazy vals can, because: + // + // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3 + // "Another problem is that the specification allows aggressive + // optimization of final fields. Within a thread, it is permissible to + // reorder reads of a final field with those modifications of a final + // field that do not take place in the constructor." + // + // A var or lazy val which is marked final still has meaning to the + // scala compiler. The word final is heavily overloaded unfortunately; + // for us it means "not overridable". At present you can't override + // vars regardless; this may change. + // + // The logic does not check .isFinal (which checks flags for the FINAL flag, + // and includes symbols marked lateFINAL) instead inspecting rawflags so + // we can exclude lateFINAL. Such symbols are eligible for inlining, but to + // avoid breaking proxy software which depends on subclassing, we do not + // emit ACC_FINAL. + // Nested objects won't receive ACC_FINAL in order to allow for their overriding. + + val finalFlag = ( + (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym)) + && !sym.enclClass.isInterface + && !sym.isClassConstructor + && !sym.isMutable // lazy vals and vars both + ) + + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + import asm.Opcodes._ + mkFlags( + if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, + if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0, + if (sym.isInterface) ACC_INTERFACE else 0, + if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0, + if (sym.isStaticMember) ACC_STATIC else 0, + if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0, + if (sym.isArtifact) ACC_SYNTHETIC else 0, + if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, + if (sym.isVarargsMethod) ACC_VARARGS else 0, + if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0 + ) + } + + /* + * must-single-thread + */ + def javaFieldFlags(sym: Symbol) = { + javaFlags(sym) | mkFlags( + if (sym hasAnnotation definitions.TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0, + if (sym hasAnnotation definitions.VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0, + if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL + ) + } + +} // end of class BCodeTypes diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 0df828393d..8e6c09213f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -22,13 +22,13 @@ trait BytecodeWriters { val global: Global import global._ - private def outputDirectory(sym: Symbol): AbstractFile = + def outputDirectory(sym: Symbol): AbstractFile = settings.outputDirs outputDirFor enteringFlatten(sym.sourceFile) /** * @param clsName cls.getName */ - private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { + def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = { def ensureDirectory(dir: AbstractFile): AbstractFile = if (dir.isDirectory) dir else throw new FileConflictException(s"${base.path}/$clsName$suffix: ${dir.path} is not a directory", dir) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala new file mode 100644 index 0000000000..e55a3baed0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -0,0 +1,203 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + + +package scala +package tools.nsc +package backend +package jvm + +import scala.collection.{ mutable, immutable } +import scala.annotation.switch + +import scala.tools.asm + +/* + * Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk. + * + * `BCodePhase.apply(CompilationUnit)` is invoked by some external force and that sets in motion: + * - visiting each ClassDef contained in that CompilationUnit + * - lowering the ClassDef into: + * (a) an optional mirror class, + * (b) a plain class, and + * (c) an optional bean class. + * - each of the ClassNodes above is lowered into a byte-array (ie into a classfile) and serialized. + * + * Plain, mirror, and bean classes are built respectively by PlainClassBuilder, JMirrorBuilder, and JBeanInfoBuilder. + * + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ + * @version 1.0 + * + */ +abstract class GenBCode extends BCodeSyncAndTry { + import global._ + + val phaseName = "jvm" + + override def newPhase(prev: Phase) = new BCodePhase(prev) + + final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit) + + class BCodePhase(prev: Phase) extends StdPhase(prev) { + + override def name = phaseName + override def description = "Generate bytecode from ASTs using the ASM library" + override def erasedTypes = true + + private var bytecodeWriter : BytecodeWriter = null + private var mirrorCodeGen : JMirrorBuilder = null + private var beanInfoCodeGen : JBeanInfoBuilder = null + + private var needsOutFolder = false // whether getOutFolder(claszSymbol) should be invoked for each claszSymbol + + val caseInsensitively = mutable.Map.empty[String, Symbol] + + /* + * Checks for duplicate internal names case-insensitively, + * builds ASM ClassNodes for mirror, plain, and bean classes. + * + */ + def visit(arrivalPos: Int, cd: ClassDef, cunit: CompilationUnit) { + val claszSymbol = cd.symbol + + // GenASM checks this before classfiles are emitted, https://github.com/scala/scala/commit/e4d1d930693ac75d8eb64c2c3c69f2fc22bec739 + val lowercaseJavaClassName = claszSymbol.javaClassName.toLowerCase + caseInsensitively.get(lowercaseJavaClassName) match { + case None => + caseInsensitively.put(lowercaseJavaClassName, claszSymbol) + case Some(dupClassSym) => + cunit.warning( + claszSymbol.pos, + s"Class ${claszSymbol.javaClassName} differs only in case from ${dupClassSym.javaClassName}. " + + "Such classes will overwrite one another on case-insensitive filesystems." + ) + } + + // -------------- mirror class, if needed -------------- + val mirrorC = + if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) { + if (claszSymbol.companionClass == NoSymbol) { + mirrorCodeGen.genMirrorClass(claszSymbol, cunit) + } else { + log(s"No mirror class for module with linked class: ${claszSymbol.fullName}"); + null + } + } else null + + // -------------- "plain" class -------------- + val pcb = new PlainClassBuilder(cunit) + pcb.genPlainClass(cd) + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null; + val plainC = pcb.cnode + + // -------------- bean info class, if needed -------------- + val beanC = + if (claszSymbol hasAnnotation BeanInfoAttr) { + beanInfoCodeGen.genBeanInfoClass( + claszSymbol, cunit, + fieldSymbols(claszSymbol), + methodSymbols(cd) + ) + } else null + + // ----------- serialize classfiles to disk + + def getByteArray(cn: asm.tree.ClassNode): Array[Byte] = { + val cw = new CClassWriter(extraProc) + cn.accept(cw) + cw.toByteArray + } + + if (mirrorC != null) { + sendToDisk(mirrorC.name, getByteArray(mirrorC), outF) + } + sendToDisk(plainC.name, getByteArray(plainC), outF) + if (beanC != null) { + sendToDisk(beanC.name, getByteArray(beanC), outF) + } + + } // end of method visit() + + var arrivalPos = 0 + + /* + * A run of the BCodePhase phase comprises: + * + * (a) set-up steps (most notably supporting maps in `BCodeTypes`, + * but also "the" writer where class files in byte-array form go) + * + * (b) building of ASM ClassNodes, their optimization and serialization. + * + * (c) tear down (closing the classfile-writer and clearing maps) + * + */ + override def run() { + + arrivalPos = 0 // just in case + scalaPrimitives.init + initBCodeTypes() + + // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. + bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints) + mirrorCodeGen = new JMirrorBuilder + beanInfoCodeGen = new JBeanInfoBuilder + + needsOutFolder = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + + super.run() + + // closing output files. + bytecodeWriter.close() + + caseInsensitively.clear() + + /* TODO Bytecode can be verified (now that all classfiles have been written to disk) + * + * (1) asm.util.CheckAdapter.verify() + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) + * passing a custom ClassLoader to verify inter-dependent classes. + * Alternatively, + * - an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). + * - -Xverify:all + * + * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` + * + */ + + // clearing maps + clearBCodeTypes() + } + + def sendToDisk(jclassName: String, jclassBytes: Array[Byte], outFolder: _root_.scala.tools.nsc.io.AbstractFile) { + try { + val outFile = + if (outFolder == null) null + else getFileForClassfile(outFolder, jclassName, ".class") + bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile) + } + catch { + case e: FileConflictException => + error(s"error writing $jclassName: ${e.getMessage}") + } + } + + override def apply(cunit: CompilationUnit): Unit = { + + def gen(tree: Tree) { + tree match { + case EmptyTree => () + case PackageDef(_, stats) => stats foreach gen + case cd: ClassDef => + visit(arrivalPos, cd, cunit) + arrivalPos += 1 + } + } + + gen(cunit.body) + } + + } // end of class BCodePhase + +} // end of class GenBCode diff --git a/src/compiler/scala/tools/nsc/plugins/Plugin.scala b/src/compiler/scala/tools/nsc/plugins/Plugin.scala index a584a4ed5d..4fd6ba7d9d 100644 --- a/src/compiler/scala/tools/nsc/plugins/Plugin.scala +++ b/src/compiler/scala/tools/nsc/plugins/Plugin.scala @@ -14,7 +14,6 @@ import java.util.zip.ZipException import scala.collection.mutable.ListBuffer import scala.util.{ Try, Success, Failure } -import scala.xml.XML /** Information about a plugin loaded from a jar file. * @@ -81,14 +80,14 @@ object Plugin { private def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { // XXX Return to this once we have more ARM support def read(is: Option[InputStream]) = is match { - case None => throw new RuntimeException(s"Missing $PluginXML in $jarp") - case _ => PluginDescription fromXML (XML load is.get) + case None => throw new RuntimeException(s"Missing $PluginXML in $jarp") + case Some(is) => PluginDescription.fromXML(is) } Try(new Jar(jarp.jfile).withEntryStream(PluginXML)(read)) } private def loadDescriptionFromFile(f: Path): Try[PluginDescription] = - Try(XML loadFile f.jfile) map (PluginDescription fromXML _) + Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jfile))) type AnyClass = Class[_] diff --git a/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala index 27693d1a45..bf78c93fcc 100644 --- a/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala +++ b/src/compiler/scala/tools/nsc/plugins/PluginDescription.scala @@ -6,57 +6,50 @@ package scala.tools.nsc package plugins -import scala.xml.Node +import scala.reflect.internal.util.StringContextStripMarginOps /** A description of a compiler plugin, suitable for serialization * to XML for inclusion in the plugin's .jar file. * * @author Lex Spoon * @version 1.0, 2007-5-21 - * @param name A short name of the plugin, used to identify it in - * various contexts. The phase defined by the plugin - * should have the same name. - * @param classname The name of the main Plugin class. + * @author Adriaan Moors + * @version 2.0, 2013 + * @param name A short name of the plugin, used to identify it in + * various contexts. The phase defined by the plugin + * should have the same name. + * @param classname The name of the main Plugin class. */ case class PluginDescription(name: String, classname: String) { - - /** An XML representation of this description. It can be - * read back using `PluginDescription.fromXML`. + /** An XML representation of this description. * It should be stored inside the jar archive file. */ - def toXML: Node = { - <plugin> - <name>{name}</name> - <classname>{classname}</classname> - </plugin> - } + def toXML: String = + sm"""<plugin> + | <name>${name}</name> + | <classname>${classname}</classname> + |</plugin>""" } /** Utilities for the PluginDescription class. * - * @author Lex Spoon - * @version 1.0, 2007-5-21 + * @author Lex Spoon + * @version 1.0, 2007-5-21 + * @author Adriaan Moors + * @version 2.0, 2013 */ object PluginDescription { + private def text(ns: org.w3c.dom.NodeList): String = + if (ns.getLength == 1) ns.item(0).getTextContent.trim + else throw new RuntimeException("Bad plugin descriptor.") + + def fromXML(xml: java.io.InputStream): PluginDescription = { + import javax.xml.parsers.DocumentBuilderFactory + val root = DocumentBuilderFactory.newInstance.newDocumentBuilder.parse(xml).getDocumentElement + root.normalize() + if (root.getNodeName != "plugin") + throw new RuntimeException("Plugin descriptor root element must be <plugin>.") - def fromXML(xml: Node): PluginDescription = { - // extract one field - def getField(field: String): Option[String] = { - val text = (xml \\ field).text.trim - if (text == "") None else Some(text) - } - def extracted = { - val name = "name" - val claas = "classname" - val vs = Map(name -> getField(name), claas -> getField(claas)) - if (vs.values exists (_.isEmpty)) fail() - else PluginDescription(name = vs(name).get, classname = vs(claas).get) - } - def fail() = throw new RuntimeException("Bad plugin descriptor.") - // check the top-level tag - xml match { - case <plugin>{_*}</plugin> => extracted - case _ => fail() - } + PluginDescription(text(root.getElementsByTagName("name")), text(root.getElementsByTagName("classname"))) } } diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index cd23ad74e4..b5cc89c0c8 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -107,7 +107,7 @@ class MutableSettings(val errorFn: String => Unit) /** Split the given line into parameters. */ - def splitParams(line: String) = cmd.Parser.tokenize(line, errorFn) + def splitParams(line: String) = cmd.CommandLineParser.tokenize(line, errorFn) /** Returns any unprocessed arguments. */ diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index fe9165203f..321baba562 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -39,7 +39,7 @@ trait ScalaSettings extends AbsScalaSettings protected def futureSettings = List[BooleanSetting]() /** Enabled under -optimise. */ - protected def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization) + def optimiseSettings = List[BooleanSetting](inline, inlineHandlers, Xcloselim, Xdce, YconstOptimization) /** Internal use - syntax enhancements. */ private class EnableSettings[T <: BooleanSetting](val s: T) { @@ -198,6 +198,12 @@ trait ScalaSettings extends AbsScalaSettings val nooptimise = BooleanSetting("-Ynooptimise", "Clears all the flags set by -optimise. Useful for testing optimizations in isolation.") withAbbreviation "-Ynooptimize" disabling optimise::optimiseSettings val Xexperimental = BooleanSetting("-Xexperimental", "Enable experimental extensions.") enabling experimentalSettings + /** + * Settings motivated by GenBCode + */ + val Ybackend = ChoiceSetting ("-Ybackend", "choice of bytecode emitter", "Choice of bytecode emitter.", + List("GenASM", "GenBCode"), + "GenASM") // Feature extensions val XmacroSettings = MultiStringSetting("-Xmacro-settings", "option", "Custom settings for macros.") @@ -220,4 +226,12 @@ trait ScalaSettings extends AbsScalaSettings /** Test whether this is scaladoc we're looking at */ def isScaladoc = false + + /** + * Helper utilities for use by checkConflictingSettings() + */ + def isBCodeActive = !isICodeAskedFor + def isBCodeAskedFor = (Ybackend.value != "GenASM") + def isICodeAskedFor = ((Ybackend.value == "GenASM") || optimiseSettings.exists(_.value) || writeICode.isSetByUser) + } diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index a37ef29355..b16ba91916 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -20,6 +20,18 @@ abstract class CleanUp extends Transform with ast.TreeDSL { /** the following two members override abstract members in Transform */ val phaseName: String = "cleanup" + /* used in GenBCode: collects ClassDef symbols owning a main(Array[String]) method */ + private var entryPoints: List[Symbol] = null + def getEntryPoints: List[Symbol] = { + assert(settings.isBCodeActive, "Candidate Java entry points are collected here only when GenBCode in use.") + entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages. + } + + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { + entryPoints = if (settings.isBCodeActive) Nil else null; + super.newPhase(prev) + } + protected def newTransformer(unit: CompilationUnit): Transformer = new CleanUpTransformer(unit) @@ -390,6 +402,15 @@ abstract class CleanUp extends Transform with ast.TreeDSL { override def transform(tree: Tree): Tree = tree match { + case _: ClassDef + if (entryPoints != null) && + genBCode.isJavaEntryPoint(tree.symbol, currentUnit) + => + // collecting symbols for entry points here (as opposed to GenBCode where they are used) + // has the advantage of saving an additional pass over all ClassDefs. + entryPoints ::= tree.symbol + super.transform(tree) + /* Transforms dynamic calls (i.e. calls to methods that are undefined * in the erased type space) to -- dynamically -- unsafe calls using * reflection. This is used for structural sub-typing of refinement diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index f43e42c027..4bc4e06fa7 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -852,7 +852,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { debuglog("%s expands to %s in %s".format(sym, specMember.name.decode, pp(env))) info(specMember) = NormalizedMember(sym) newOverload(sym, specMember, env) - owner.info.decls.enter(specMember) + // if this is a class, we insert the normalized member in scope, + // if this is a method, there's no attached scope for it (EmptyScope) + val decls = owner.info.decls + if (decls != EmptyScope) + decls.enter(specMember) specMember } } @@ -1263,7 +1267,35 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } protected override def newBodyDuplicator(context: Context) = new BodyDuplicator(context) + } + /** Introduced to fix SI-7343: Phase ordering problem between Duplicators and Specialization. + * brief explanation: specialization rewires class parents during info transformation, and + * the new info then guides the tree changes. But if a symbol is created during duplication, + * which runs after specialization, its info is not visited and thus the corresponding tree + * is not specialized. One manifestation is the following: + * ``` + * object Test { + * class Parent[@specialized(Int) T] + * + * def spec_method[@specialized(Int) T](t: T, expectedXSuper: String) = { + * class X extends Parent[T]() + * // even in the specialized variant, the local X class + * // doesn't extend Parent$mcI$sp, since its symbol has + * // been created after specialization and was not seen + * // by specialzation's info transformer. + * ... + * } + * } + * ``` + * We fix this by forcing duplication to take place before specialization. + * + * Note: The constructors phase (which also uses duplication) comes after erasure and uses the + * post-erasure typer => we must protect it from the beforeSpecialization phase shifting. + */ + class SpecializationDuplicator(casts: Map[Symbol, Type]) extends Duplicator(casts) { + override def retyped(context: Context, tree: Tree, oldThis: Symbol, newThis: Symbol, env: scala.collection.Map[Symbol, Type]): Tree = + enteringSpecialize(super.retyped(context, tree, oldThis, newThis, env)) } /** A tree symbol substituter that substitutes on type skolems. @@ -1445,6 +1477,32 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } + /** Computes residual type parameters after rewiring, like "String" in the following example: + * ``` + * def specMe[@specialized T, U](t: T, u: U) = ??? + * specMe[Int, String](1, "2") => specMe$mIc$sp[String](1, "2") + * ``` + */ + def computeResidualTypeVars(baseTree: Tree, specMember: Symbol, specTree: Tree, baseTargs: List[Tree], env: TypeEnv): Tree = { + val residualTargs = symbol.info.typeParams zip baseTargs collect { + case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ + } + // See SI-5583. Don't know why it happens now if it didn't before. + if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) { + devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs))) + baseTree + } + else { + ifDebug(assert(residualTargs.length == specMember.info.typeParams.length, + "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env)) + ) + + val tree1 = gen.mkTypeApply(specTree, residualTargs) + debuglog("rewrote " + tree + " to " + tree1) + localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method + } + } + curTree = tree tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => @@ -1470,12 +1528,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } transformSuperApply + // This rewires calls to specialized methods defined in a class (which have a receiver) + // class C { + // def foo[@specialized T](t: T): T = t + // C.this.foo(3) // TypeApply(Select(This(C), foo), List(Int)) => C.this.foo$mIc$sp(3) + // } case TypeApply(sel @ Select(qual, name), targs) - if (!specializedTypeVars(symbol.info).isEmpty && name != nme.CONSTRUCTOR) => - def transformTypeApply = { + if (specializedTypeVars(symbol.info).nonEmpty && name != nme.CONSTRUCTOR) => debuglog("checking typeapp for rerouting: " + tree + " with sym.tpe: " + symbol.tpe + " tree.tpe: " + tree.tpe) val qual1 = transform(qual) - // log(">>> TypeApply: " + tree + ", qual1: " + qual1) + log(">>> TypeApply: " + tree + ", qual1: " + qual1) specSym(qual1) match { case NoSymbol => // See pos/exponential-spec.scala - can't call transform on the whole tree again. @@ -1485,26 +1547,23 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { ifDebug(assert(symbol.info.typeParams.length == targs.length, symbol.info.typeParams + " / " + targs)) val env = typeEnv(specMember) - val residualTargs = symbol.info.typeParams zip targs collect { - case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ - } - // See SI-5583. Don't know why it happens now if it didn't before. - if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) { - devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs))) - localTyper.typed(sel) - } - else { - ifDebug(assert(residualTargs.length == specMember.info.typeParams.length, - "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env)) - ) - - val tree1 = gen.mkTypeApply(Select(qual1, specMember), residualTargs) - debuglog("rewrote " + tree + " to " + tree1) - localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method - } + computeResidualTypeVars(tree, specMember, gen.mkAttributedSelect(qual1, specMember), targs, env) } + + // This rewires calls to specialized methods defined in the local scope. For example: + // def outerMethod = { + // def foo[@specialized T](t: T): T = t + // foo(3) // TypeApply(Ident(foo), List(Int)) => foo$mIc$sp(3) + // } + case TypeApply(sel @ Ident(name), targs) if name != nme.CONSTRUCTOR => + val env = unify(symbol.tpe, tree.tpe, emptyEnv, false) + if (env.isEmpty) super.transform(tree) + else { + overloads(symbol) find (_ matchesEnv env) match { + case Some(Overload(specMember, _)) => computeResidualTypeVars(tree, specMember, Ident(specMember), targs, env) + case _ => super.transform(tree) + } } - transformTypeApply case Select(Super(_, _), _) if illegalSpecializedInheritance(currentClass) => val pos = tree.pos @@ -1621,7 +1680,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { localTyper.typed(deriveDefDef(tree)(rhs => rhs)) } } - transformDefDef + expandInnerNormalizedMembers(transformDefDef) + + case ddef @ DefDef(_, _, _, _, _, _) => + val tree1 = expandInnerNormalizedMembers(tree) + super.transform(tree1) case ValDef(_, _, _, _) if symbol.hasFlag(SPECIALIZED) && !symbol.isParamAccessor => def transformValDef = { @@ -1629,7 +1692,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val tree1 = deriveValDef(tree)(_ => body(symbol.alias).duplicate) debuglog("now typing: " + tree1 + " in " + tree.symbol.owner.fullName) - val d = new Duplicator(emptyEnv) + val d = new SpecializationDuplicator(emptyEnv) val newValDef = d.retyped( localTyper.context1.asInstanceOf[d.Context], tree1, @@ -1645,6 +1708,39 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } } + /** + * This performs method specialization inside a scope other than a {class, trait, object}: could be another method + * or a value. This specialization is much simpler, since there is no need to record the new members in the class + * signature, their signatures are only visible locally. It works according to the usual logic: + * - we use normalizeMember to create the specialized symbols + * - we leave DefDef stubs in the tree that are later filled in by tree duplication and adaptation + * @see duplicateBody + */ + private def expandInnerNormalizedMembers(tree: Tree) = tree match { + case ddef @ DefDef(_, _, _, vparams :: Nil, _, rhs) + if ddef.symbol.owner.isMethod && + specializedTypeVars(ddef.symbol.info).nonEmpty && + !ddef.symbol.hasFlag(SPECIALIZED) => + + val sym = ddef.symbol + val owner = sym.owner + val norm = normalizeMember(owner, sym, emptyEnv) + + if (norm.length > 1) { + // record the body for duplication + body(sym) = rhs + parameters(sym) = vparams.map(_.symbol) + // to avoid revisiting the member, we can set the SPECIALIZED + // flag. nobody has to see this anyway :) + sym.setFlag(SPECIALIZED) + // create empty bodies for specializations + localTyper.typed(Block(norm.tail.map(sym => DefDef(sym, { vparamss => EmptyTree })), ddef)) + } else + tree + case _ => + tree + } + /** Duplicate the body of the given method `tree` to the new symbol `source`. * * Knowing that the method can be invoked only in the `castmap` type environment, @@ -1655,7 +1751,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { val symbol = tree.symbol val meth = addBody(tree, source) - val d = new Duplicator(castmap) + val d = new SpecializationDuplicator(castmap) debuglog("-->d DUPLICATING: " + meth) d.retyped( localTyper.context1.asInstanceOf[d.Context], diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 61967d4cee..1f4ff7cc2d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -16,7 +16,7 @@ import scala.reflect.internal.util.shortClassOfInstance */ trait Contexts { self: Analyzer => import global._ - import definitions.{ JavaLangPackage, ScalaPackage, PredefModule } + import definitions.{ JavaLangPackage, ScalaPackage, PredefModule, ScalaXmlTopScope, ScalaXmlPackage } import ContextMode._ object NoContext @@ -93,9 +93,31 @@ trait Contexts { self: Analyzer => else RootImports.completeList } + def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { val rootImportsContext = (startContext /: rootImports(unit))((c, sym) => c.make(gen.mkWildcardImport(sym))) - val c = rootImportsContext.make(tree, unit = unit) + + // there must be a scala.xml package when xml literals were parsed in this unit + if (unit.hasXml && ScalaXmlPackage == NoSymbol) + unit.error(unit.firstXmlPos, "XML literals may only be used if the package scala.xml is present in the compilation classpath.") + + // TODO: remove the def below and drop `|| predefDefinesDollarScope` in the condition for `contextWithXML` + // as soon as 2.11.0-M4 is released and used as STARR (and $scope is no longer defined in Predef) + // Until then, to allow compiling quick with pre-2.11.0-M4 STARR, + // which relied on Predef defining `val $scope`, we've left it in place. + // Since the new scheme also imports $scope (as an alias for scala.xml.TopScope), + // we must check whether it is still there and not import the alias to avoid ambiguity. + // (All of this is only necessary to compile the full quick stage with STARR. + // if using locker, Predef.$scope is no longer needed.) + def predefDefinesDollarScope = definitions.getMemberIfDefined(PredefModule, nme.dollarScope) != NoSymbol + + // hack for the old xml library (detected by looking for scala.xml.TopScope, which needs to be in scope as $scope) + // import scala.xml.{TopScope => $scope} + val contextWithXML = + if (!unit.hasXml || ScalaXmlTopScope == NoSymbol || predefDefinesDollarScope) rootImportsContext + else rootImportsContext.make(gen.mkImport(ScalaXmlPackage, nme.TopScope, nme.dollarScope)) + + val c = contextWithXML.make(tree, unit = unit) if (erasedTypes) c.setThrowErrors() else c.setReportErrors() c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes c diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 95b771a8a5..0a2628b482 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -32,6 +32,7 @@ abstract class Duplicators extends Analyzer { envSubstitution = new SubstSkolemsTypeMap(env.keysIterator.toList, env.valuesIterator.toList) debuglog("retyped with env: " + env) + newBodyDuplicator(context).typed(tree) } @@ -365,7 +366,8 @@ abstract class Duplicators extends Analyzer { tree.symbol = NoSymbol // maybe we can find a more specific member in a subclass of Any (see AnyVal members, like ==) } val ntree = castType(tree, pt) - super.typed(ntree, mode, pt) + val res = super.typed(ntree, mode, pt) + res } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 0305aab844..1282cfb416 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -1408,11 +1408,20 @@ trait Namers extends MethodSynthesis { if (!annotated.isInitialized) tree match { case defn: MemberDef => val ainfos = defn.mods.annotations filterNot (_ eq null) map { ann => + val ctx = typer.context + val annCtx = ctx.make(ann) + annCtx.setReportErrors() // need to be lazy, #1782. beforeTyper to allow inferView in annotation args, SI-5892. AnnotationInfo lazily { - val context1 = typer.context.make(ann) - context1.setReportErrors() - enteringTyper(newTyper(context1) typedAnnotation ann) + if (typer.context ne ctx) + log(sm"""|The var `typer.context` in ${Namer.this} was mutated before the annotation ${ann} was forced. + | + |current value = ${typer.context} + |original value = $ctx + | + |This confirms the hypothesis for the cause of SI-7603. If you see this message, please comment on that ticket.""") + + enteringTyper(newTyper(annCtx) typedAnnotation ann) } } if (ainfos.nonEmpty) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index cb3a12b60d..353e8e4810 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1431,8 +1431,8 @@ trait Typers extends Adaptations with Tags { implRestriction(tree, "nested object") //see https://issues.scala-lang.org/browse/SI-6444 //see https://issues.scala-lang.org/browse/SI-6463 - case _: ClassDef => - implRestriction(tree, "nested class") + case cd: ClassDef if !cd.symbol.isAnonymousClass => // Don't warn about partial functions, etc. SI-7571 + implRestriction(tree, "nested class") // avoiding Type Tests that might check the $outer pointer. case Select(sup @ Super(qual, mix), selector) if selector != nme.CONSTRUCTOR && qual.symbol == clazz && mix != tpnme.EMPTY => //see https://issues.scala-lang.org/browse/SI-6483 implRestriction(sup, "qualified super reference") diff --git a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala deleted file mode 100644 index e8f962a9e2..0000000000 --- a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala +++ /dev/null @@ -1,139 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2013 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import scala.util.parsing.combinator._ -import scala.util.parsing.input.CharArrayReader.EofCh -import scala.collection.mutable.ListBuffer - -/** A simple command line parser to replace the several different - * simple ones spread around trunk. - * - * XXX Note this has been completely obsolesced by scala.tools.cmd. - * I checked it back in as part of rolling partest back a month - * rather than go down the rabbit hole of unravelling dependencies. - */ - -trait ParserUtil extends Parsers { - protected implicit class ParserPlus[+T](underlying: Parser[T]) { - def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } - } -} - -case class CommandLine( - args: List[String], - unaryArguments: List[String], - binaryArguments: List[String] -) { - def this(args: List[String]) = this(args, Nil, Nil) - def this(args: Array[String]) = this(args.toList, Nil, Nil) - def this(line: String) = this(CommandLineParser tokenize line, Nil, Nil) - - def withUnaryArgs(xs: List[String]) = copy(unaryArguments = xs) - def withBinaryArgs(xs: List[String]) = copy(binaryArguments = xs) - - def assumeBinary = true - def enforceArity = true - def onlyKnownOptions = false - - val Terminator = "--" - val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true - - def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption) - def errorFn(msg: String) = println(msg) - - /** argMap is option -> argument (or "" if it is a unary argument) - * residualArgs are what is left after removing the options and their args. - */ - lazy val (argMap, residualArgs) = { - val residualBuffer = new ListBuffer[String] - - def stripQuotes(s: String) = { - def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c - if (List('"', '\'') exists isQuotedBy) s.tail.init else s - } - - def isValidOption(s: String) = !onlyKnownOptions || (unaryArguments contains s) || (binaryArguments contains s) - def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false }) - def isUnary(s: String) = isOption(s) && (unaryArguments contains s) - def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryArguments contains s)) - - def unknownOption(opt: String) = - errorFn("Option '%s' not recognized.".format(opt)) - def missingArg(opt: String, what: String) = - errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) - - def loop(args: List[String]): Map[String, String] = { - def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } - if (args.isEmpty) return Map() - val hd :: rest = args - if (rest.isEmpty) { - if (isBinary(hd) && enforceArity) - missingArg(hd, "EOF") - - if (isOption(hd)) mapForUnary(hd) else residual(args) - } - else - if (hd == Terminator) residual(rest) - else { - val hd1 :: hd2 :: rest = args - - if (hd2 == Terminator) mapForUnary(hd1) ++ residual(rest) - else if (isUnary(hd1)) mapForUnary(hd1) ++ loop(hd2 :: rest) - else if (isBinary(hd1)) { - // Disabling this check so - // --scalacopts "-verbose" works. We can't tell if it's quoted, - // the shell does us in. - // - // if (isOption(hd2) && enforceArity) - // missingArg(hd1, hd2) - - Map(hd1 -> hd2) ++ loop(rest) - } - else { residual(List(hd1)) ++ loop(hd2 :: rest) } - } - } - - (loop(args), residualBuffer map stripQuotes toList) - } - - def isSet(arg: String) = args contains arg - def get(arg: String) = argMap get arg - def apply(arg: String) = argMap(arg) - - override def toString() = "CommandLine(\n%s)\n" format (args map (" " + _ + "\n") mkString) -} - -object CommandLineParser extends RegexParsers with ParserUtil { - override def skipWhitespace = false - - def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) - def escaped(ch: Char): Parser[String] = "\\" + ch - def mkQuoted(ch: Char): Parser[String] = ( - elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) - | failure("Unmatched %s in input." format ch) - ) - - /** Apparently windows can't deal with the quotes sticking around. */ - lazy val squoted: Parser[String] = mkQuoted('\'') // ^^ (x => "'%s'" format x) - lazy val dquoted: Parser[String] = mkQuoted('"') // ^^ (x => "\"" + x + "\"") - lazy val token: Parser[String] = """\S+""".r - - lazy val argument: Parser[String] = squoted | dquoted | token - lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) - - class ParseException(msg: String) extends RuntimeException(msg) - - def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) - def tokenize(line: String, errorFn: String => Unit): List[String] = { - parse(commandLine, line.trim) match { - case Success(args, _) => args - case NoSuccess(msg, rest) => errorFn(msg) ; Nil - } - } - def apply(line: String) = new CommandLine(tokenize(line)) -} diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 5ba38600b6..a188602543 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -134,7 +134,13 @@ object Predef extends LowPriorityImplicits with DeprecatedPredef { @inline def implicitly[T](implicit e: T) = e // for summoning implicit values from the nether world -- TODO: when dependent method types are on by default, give this result type `e.type`, so that inliner has better chance of knowing which method to inline in calls like `implicitly[MatchingStrategy[Option]].zero` @inline def locally[T](x: T): T = x // to communicate intent and avoid unmoored statements - // Apparently needed for the xml library + // TODO: remove `val $scope = ...` as soon as 2.11.0-M4 is released and used as STARR + // As it has a '$' in its name, we don't have to deprecate first. + // The compiler now aliases `scala.xml.TopScope` to `$scope` (unless Predef.$scope is still there). + // This definition left in place for older compilers and to compile quick with pre-2.11.0-M4 STARR. + // In principle we don't need it to compile library/reflect/compiler (there's no xml left there), + // so a new locker can be built without this definition, and locker can build quick + // (partest, scaladoc still require xml). val $scope = scala.xml.TopScope // errors and asserts ------------------------------------------------- diff --git a/src/library/scala/collection/GenTraversableLike.scala b/src/library/scala/collection/GenTraversableLike.scala index f4aa063d8a..a0c519884c 100644 --- a/src/library/scala/collection/GenTraversableLike.scala +++ b/src/library/scala/collection/GenTraversableLike.scala @@ -324,7 +324,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * @tparam K the type of keys returned by the discriminator function. * @return A map from keys to ${coll}s such that the following invariant holds: * {{{ - * (xs partition f)(k) = xs filter (x => f(x) == k) + * (xs groupBy f)(k) = xs filter (x => f(x) == k) * }}} * That is, every key `k` is bound to a $coll of those elements `x` * for which `f(x)` equals `k`. diff --git a/src/library/scala/collection/immutable/TreeSet.scala b/src/library/scala/collection/immutable/TreeSet.scala index dfe1a833ef..4a15cb6d66 100644 --- a/src/library/scala/collection/immutable/TreeSet.scala +++ b/src/library/scala/collection/immutable/TreeSet.scala @@ -52,6 +52,9 @@ object TreeSet extends ImmutableSortedSetFactory[TreeSet] { class TreeSet[A] private (tree: RB.Tree[A, Unit])(implicit val ordering: Ordering[A]) extends SortedSet[A] with SortedSetLike[A, TreeSet[A]] with Serializable { + if (ordering eq null) + throw new NullPointerException("ordering must not be null") + override def stringPrefix = "TreeSet" override def size = RB.count(tree) diff --git a/src/library/scala/collection/mutable/TreeSet.scala b/src/library/scala/collection/mutable/TreeSet.scala index ea5b859367..d364eb1276 100644 --- a/src/library/scala/collection/mutable/TreeSet.scala +++ b/src/library/scala/collection/mutable/TreeSet.scala @@ -37,10 +37,13 @@ object TreeSet extends MutableSortedSetFactory[TreeSet] { * @author Lucien Pereira * */ -class TreeSet[A] private (treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], until: Option[A])(implicit val ordering: Ordering[A]) +class TreeSet[A] private (treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], until: Option[A])(implicit val ordering: Ordering[A]) extends SortedSet[A] with SetLike[A, TreeSet[A]] with SortedSetLike[A, TreeSet[A]] with Set[A] with Serializable { + if (ordering eq null) + throw new NullPointerException("ordering must not be null") + def this()(implicit ordering: Ordering[A]) = this(new ObjectRef(null), None, None) override def size: Int = RB.countInRange(treeRef.elem, from, until) @@ -53,13 +56,13 @@ class TreeSet[A] private (treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], case (Some(newB), Some(oldB)) => Some(comparison(newB, oldB)) case (None, _) => oldBound case _ => newBound - } - + } + override def rangeImpl(fromArg: Option[A], untilArg: Option[A]): TreeSet[A] = { val newFrom = pickBound(ordering.max, fromArg, from) val newUntil = pickBound(ordering.min, untilArg, until) - - new TreeSet(treeRef, newFrom, newUntil) + + new TreeSet(treeRef, newFrom, newUntil) } override def -=(elem: A): this.type = { @@ -78,9 +81,9 @@ class TreeSet[A] private (treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], * the clone. So clone complexity in time is O(1). * */ - override def clone(): TreeSet[A] = + override def clone(): TreeSet[A] = new TreeSet[A](new ObjectRef(treeRef.elem), from, until) - + private val notProjection = !(from.isDefined || until.isDefined) override def contains(elem: A): Boolean = { @@ -92,16 +95,16 @@ class TreeSet[A] private (treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], def rightAcceptable: Boolean = until match { case Some(ub) => ordering.lt(elem, ub) case _ => true - } - + } + (notProjection || (leftAcceptable && rightAcceptable)) && RB.contains(treeRef.elem, elem) } override def iterator: Iterator[A] = iteratorFrom(None) - + override def keysIteratorFrom(start: A) = iteratorFrom(Some(start)) - + private def iteratorFrom(start: Option[A]) = { val it = RB.keysIterator(treeRef.elem, pickBound(ordering.max, from, start)) until match { diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 2e4c72cd71..b072cd653b 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -14,7 +14,7 @@ import java.util.concurrent.{ ConcurrentLinkedQueue, TimeUnit, Callable } import java.util.concurrent.TimeUnit.{ NANOSECONDS => NANOS, MILLISECONDS ⇒ MILLIS } import java.lang.{ Iterable => JIterable } import java.util.{ LinkedList => JLinkedList } -import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicBoolean } +import java.util.concurrent.atomic.{ AtomicReferenceFieldUpdater, AtomicInteger, AtomicLong, AtomicBoolean } import scala.util.control.NonFatal import scala.Option @@ -101,7 +101,7 @@ trait Future[+T] extends Awaitable[T] { // that also have an executor parameter, which // keeps us from accidentally forgetting to use // the executor parameter. - private implicit def internalExecutor: ExecutionContext = Future.InternalCallbackExecutor + private def internalExecutor = Future.InternalCallbackExecutor /* Callbacks */ @@ -116,9 +116,10 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Success(v) if pf isDefinedAt v => pf(v) + case Success(v) => + pf.applyOrElse[T, Any](v, Predef.conforms[T]) // Exploiting the cached function to avoid MatchError case _ => - }(executor) + } /** When this future is completed with a failure (i.e. with a throwable), * apply the provided callback to the throwable. @@ -134,9 +135,10 @@ trait Future[+T] extends Awaitable[T] { * $callbackInContext */ def onFailure[U](@deprecatedName('callback) pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { - case Failure(t) if NonFatal(t) && pf.isDefinedAt(t) => pf(t) + case Failure(t) => + pf.applyOrElse[Throwable, Any](t, Predef.conforms[Throwable]) // Exploiting the cached function to avoid MatchError case _ => - }(executor) + } /** When this future is completed, either through an exception, or a value, * apply the provided function. @@ -186,13 +188,12 @@ trait Future[+T] extends Awaitable[T] { * and throws a corresponding exception if the original future fails. */ def failed: Future[Throwable] = { + implicit val ec = internalExecutor val p = Promise[Throwable]() - onComplete { case Failure(t) => p success t case Success(v) => p failure (new NoSuchElementException("Future.failed not completed with a throwable.")) } - p.future } @@ -203,10 +204,7 @@ trait Future[+T] extends Awaitable[T] { * * Will not be called if the future fails. */ - def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { - case Success(r) => f(r) - case _ => // do nothing - }(executor) + def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { _ foreach f } /** Creates a new future by applying the 's' function to the successful result of * this future, or the 'f' function to the failed result. If there is any non-fatal @@ -221,19 +219,11 @@ trait Future[+T] extends Awaitable[T] { */ def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = { val p = Promise[S]() - + // transform on Try has the wrong shape for us here onComplete { - case result => - try { - result match { - case Failure(t) => p failure f(t) - case Success(r) => p success s(r) - } - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + case Success(r) => p complete Try(s(r)) + case Failure(t) => p complete Try(throw f(t)) // will throw fatal errors! + } p.future } @@ -245,19 +235,7 @@ trait Future[+T] extends Awaitable[T] { */ def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { // transform(f, identity) val p = Promise[S]() - - onComplete { - case result => - try { - result match { - case Success(r) => p success f(r) - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - } - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + onComplete { v => p complete (v map f) } p.future } @@ -270,20 +248,10 @@ trait Future[+T] extends Awaitable[T] { */ def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = { val p = Promise[S]() - onComplete { case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => - try { - f(v).onComplete({ - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => p success v - })(internalExecutor) - } catch { - case NonFatal(t) => p failure t - } - }(executor) - + case Success(v) => try f(v) onComplete p.complete catch { case NonFatal(t) => p failure t } + } p.future } @@ -303,34 +271,14 @@ trait Future[+T] extends Awaitable[T] { * Await.result(h, Duration.Zero) // throw a NoSuchElementException * }}} */ - def filter(@deprecatedName('pred) p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = { - val promise = Promise[T]() - - onComplete { - case f: Failure[_] => promise complete f.asInstanceOf[Failure[T]] - case Success(v) => - try { - if (p(v)) promise success v - else promise failure new NoSuchElementException("Future.filter predicate is not satisfied") - } catch { - case NonFatal(t) => promise failure t - } - }(executor) - - promise.future - } + def filter(@deprecatedName('pred) p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = + map { + r => if (p(r)) r else throw new NoSuchElementException("Future.filter predicate is not satisfied") + } /** Used by for-comprehensions. */ final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor) - // final def withFilter(p: T => Boolean) = new FutureWithFilter[T](this, p) - - // final class FutureWithFilter[+S](self: Future[S], p: S => Boolean) { - // def foreach(f: S => Unit): Unit = self filter p foreach f - // def map[R](f: S => R) = self filter p map f - // def flatMap[R](f: S => Future[R]) = self filter p flatMap f - // def withFilter(q: S => Boolean): FutureWithFilter[S] = new FutureWithFilter[S](self, x => p(x) && q(x)) - // } /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. * @@ -352,22 +300,10 @@ trait Future[+T] extends Awaitable[T] { * Await.result(h, Duration.Zero) // throw a NoSuchElementException * }}} */ - def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = { - val p = Promise[S]() - - onComplete { - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(v) => - try { - if (pf.isDefinedAt(v)) p success pf(v) - else p failure new NoSuchElementException("Future.collect partial function is not defined at: " + v) - } catch { - case NonFatal(t) => p failure t - } - }(executor) - - p.future - } + def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = + map { + r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t)) + } /** Creates a new future that will handle any matching throwable that this * future might contain. If there is no match, or if this future contains @@ -383,9 +319,7 @@ trait Future[+T] extends Awaitable[T] { */ def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = { val p = Promise[U]() - - onComplete { case tr => p.complete(tr recover pf) }(executor) - + onComplete { v => p complete (v recover pf) } p.future } @@ -404,17 +338,10 @@ trait Future[+T] extends Awaitable[T] { */ def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = { val p = Promise[U]() - onComplete { - case Failure(t) if pf isDefinedAt t => - try { - p completeWith pf(t) - } catch { - case NonFatal(t) => p failure t - } - case otherwise => p complete otherwise - }(executor) - + case Failure(t) => try pf.applyOrElse(t, (_: Throwable) => this) onComplete p.complete catch { case NonFatal(t) => p failure t } + case other => p complete other + } p.future } @@ -427,19 +354,12 @@ trait Future[+T] extends Awaitable[T] { * with the throwable stored in `that`. */ def zip[U](that: Future[U]): Future[(T, U)] = { + implicit val ec = internalExecutor val p = Promise[(T, U)]() - - this onComplete { + onComplete { case f: Failure[_] => p complete f.asInstanceOf[Failure[(T, U)]] - case Success(r) => - that onSuccess { - case r2 => p success ((r, r2)) - } - that onFailure { - case f => p failure f - } + case Success(s) => that onComplete { c => p.complete(c map { s2 => (s, s2) }) } } - p.future } @@ -458,6 +378,7 @@ trait Future[+T] extends Awaitable[T] { * }}} */ def fallbackTo[U >: T](that: Future[U]): Future[U] = { + implicit val ec = internalExecutor val p = Promise[U]() onComplete { case s @ Success(_) => p complete s @@ -470,23 +391,13 @@ trait Future[+T] extends Awaitable[T] { * that conforms to `S`'s erased type or a `ClassCastException` otherwise. */ def mapTo[S](implicit tag: ClassTag[S]): Future[S] = { - def boxedType(c: Class[_]): Class[_] = { + implicit val ec = internalExecutor + val boxedClass = { + val c = tag.runtimeClass if (c.isPrimitive) Future.toBoxed(c) else c } - - val p = Promise[S]() - - onComplete { - case f: Failure[_] => p complete f.asInstanceOf[Failure[S]] - case Success(t) => - p complete (try { - Success(boxedType(tag.runtimeClass).cast(t).asInstanceOf[S]) - } catch { - case e: ClassCastException => Failure(e) - }) - } - - p.future + require(boxedClass ne null) + map(s => boxedClass.cast(s).asInstanceOf[S]) } /** Applies the side-effecting function to the result of this future, and returns @@ -514,11 +425,9 @@ trait Future[+T] extends Awaitable[T] { */ def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = { val p = Promise[T]() - onComplete { - case r => try if (pf isDefinedAt r) pf(r) finally p complete r - }(executor) - + case r => try pf.applyOrElse[Try[T], Any](r, Predef.conforms[Try[T]]) finally p complete r + } p.future } @@ -579,14 +488,12 @@ object Future { } map (_.result()) } - /** Returns a `Future` to the result of the first future in the list that is completed. + /** Returns a new `Future` to the result of the first future in the list that is completed. */ def firstCompletedOf[T](futures: TraversableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = { val p = Promise[T]() - val completeFirst: Try[T] => Unit = p tryComplete _ - futures.foreach(_ onComplete completeFirst) - + futures foreach { _ onComplete completeFirst } p.future } diff --git a/src/library/scala/math/ScalaNumericConversions.scala b/src/library/scala/math/ScalaNumericConversions.scala index 336991781e..0006133b13 100644 --- a/src/library/scala/math/ScalaNumericConversions.scala +++ b/src/library/scala/math/ScalaNumericConversions.scala @@ -20,6 +20,7 @@ trait ScalaNumericConversions extends ScalaNumber with ScalaNumericAnyConversion * across all the numeric types, suitable for use in value classes. */ trait ScalaNumericAnyConversions extends Any { + /** @return `'''true'''` if this number has no decimal component, `'''false'''` otherwise. */ def isWhole(): Boolean def underlying(): Any diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java index 3504c57b48..82a3b00ac4 100644 --- a/src/library/scala/runtime/BoxesRunTime.java +++ b/src/library/scala/runtime/BoxesRunTime.java @@ -10,7 +10,6 @@ package scala.runtime; -import java.io.*; import scala.math.ScalaNumber; /** An object (static class) that defines methods used for creating, diff --git a/src/library/scala/runtime/ObjectRef.java b/src/library/scala/runtime/ObjectRef.java index c553c780a8..b34f81c9c8 100644 --- a/src/library/scala/runtime/ObjectRef.java +++ b/src/library/scala/runtime/ObjectRef.java @@ -16,8 +16,9 @@ public class ObjectRef<T> implements java.io.Serializable { public T elem; public ObjectRef(T elem) { this.elem = elem; } + @Override public String toString() { return String.valueOf(elem); } - public static <U> ObjectRef create(U e) { return new ObjectRef(e); } - public static ObjectRef zero() { return new ObjectRef(null); } + public static <U> ObjectRef<U> create(U e) { return new ObjectRef<U>(e); } + public static ObjectRef<Object> zero() { return new ObjectRef<Object>(null); } } diff --git a/src/library/scala/runtime/RichByte.scala b/src/library/scala/runtime/RichByte.scala index ea23cb8867..ce658d2277 100644 --- a/src/library/scala/runtime/RichByte.scala +++ b/src/library/scala/runtime/RichByte.scala @@ -13,4 +13,18 @@ package runtime final class RichByte(val self: Byte) extends AnyVal with ScalaWholeNumberProxy[Byte] { protected def num = scala.math.Numeric.ByteIsIntegral protected def ord = scala.math.Ordering.Byte + + override def doubleValue() = self.toDouble + override def floatValue() = self.toFloat + override def longValue() = self.toLong + override def intValue() = self.toInt + override def byteValue() = self + override def shortValue() = self.toShort + + override def isValidByte = true + + override def abs: Byte = math.abs(self).toByte + override def max(that: Byte): Byte = math.max(self, that).toByte + override def min(that: Byte): Byte = math.min(self, that).toByte + override def signum: Int = math.signum(self.toInt) } diff --git a/src/library/scala/runtime/RichChar.scala b/src/library/scala/runtime/RichChar.scala index c069fd1fef..71ea3a21e1 100644 --- a/src/library/scala/runtime/RichChar.scala +++ b/src/library/scala/runtime/RichChar.scala @@ -16,6 +16,20 @@ final class RichChar(val self: Char) extends AnyVal with IntegralProxy[Char] { protected def num = scala.math.Numeric.CharIsIntegral protected def ord = scala.math.Ordering.Char + override def doubleValue() = self.toDouble + override def floatValue() = self.toFloat + override def longValue() = self.toLong + override def intValue() = self.toInt + override def byteValue() = self.toByte + override def shortValue() = self.toShort + + override def isValidChar = true + + override def abs: Char = self + override def max(that: Char): Char = math.max(self.toInt, that.toInt).toChar + override def min(that: Char): Char = math.min(self.toInt, that.toInt).toChar + override def signum: Int = math.signum(self.toInt) + def asDigit: Int = Character.digit(self, Character.MAX_RADIX) def isControl: Boolean = Character.isISOControl(self) diff --git a/src/library/scala/runtime/RichDouble.scala b/src/library/scala/runtime/RichDouble.scala index 2f16a296b3..9d7a55d5cd 100644 --- a/src/library/scala/runtime/RichDouble.scala +++ b/src/library/scala/runtime/RichDouble.scala @@ -14,6 +14,35 @@ final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Dou protected def ord = scala.math.Ordering.Double protected def integralNum = scala.math.Numeric.DoubleAsIfIntegral + override def doubleValue() = self + override def floatValue() = self.toFloat + override def longValue() = self.toLong + override def intValue() = self.toInt + override def byteValue() = self.toByte + override def shortValue() = self.toShort + + override def isWhole = { + val l = self.toLong + l.toDouble == self || l == Long.MaxValue && self < Double.PositiveInfinity || l == Long.MinValue && self > Double.NegativeInfinity + } + override def isValidByte = self.toByte.toDouble == self + override def isValidShort = self.toShort.toDouble == self + override def isValidChar = self.toChar.toDouble == self + override def isValidInt = self.toInt.toDouble == self + // override def isValidLong = { val l = self.toLong; l.toDouble == self && l != Long.MaxValue } + // override def isValidFloat = self.toFloat.toDouble == self + // override def isValidDouble = !java.lang.Double.isNaN(self) + + def isNaN: Boolean = java.lang.Double.isNaN(self) + def isInfinity: Boolean = java.lang.Double.isInfinite(self) + def isPosInfinity: Boolean = Double.PositiveInfinity == self + def isNegInfinity: Boolean = Double.NegativeInfinity == self + + override def abs: Double = math.abs(self) + override def max(that: Double): Double = math.max(self, that) + override def min(that: Double): Double = math.min(self, that) + override def signum: Int = math.signum(self).toInt // !!! NaN + def round: Long = math.round(self) def ceil: Double = math.ceil(self) def floor: Double = math.floor(self) @@ -30,22 +59,4 @@ final class RichDouble(val self: Double) extends AnyVal with FractionalProxy[Dou * @return the measurement of the angle x in degrees. */ def toDegrees: Double = math.toDegrees(self) - - // isNaN is provided by the implicit conversion to java.lang.Double - // def isNaN: Boolean = java.lang.Double.isNaN(self) - def isInfinity: Boolean = java.lang.Double.isInfinite(self) - def isPosInfinity: Boolean = isInfinity && self > 0.0 - def isNegInfinity: Boolean = isInfinity && self < 0.0 - - override def isValidByte = self.toByte.toDouble == self - override def isValidShort = self.toShort.toDouble == self - override def isValidChar = self.toChar.toDouble == self - override def isValidInt = self.toInt.toDouble == self - // override def isValidLong = { val l = self.toLong; l.toDouble == self && l != Long.MaxValue } - // override def isValidFloat = self.toFloat.toDouble == self - // override def isValidDouble = !java.lang.Double.isNaN(self) - override def isWhole = { - val l = self.toLong - l.toDouble == self || l == Long.MaxValue && self < Double.PositiveInfinity || l == Long.MinValue && self > Double.NegativeInfinity - } } diff --git a/src/library/scala/runtime/RichFloat.scala b/src/library/scala/runtime/RichFloat.scala index 0bca033b7b..93777f2405 100644 --- a/src/library/scala/runtime/RichFloat.scala +++ b/src/library/scala/runtime/RichFloat.scala @@ -14,6 +14,35 @@ final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float protected def ord = scala.math.Ordering.Float protected def integralNum = scala.math.Numeric.FloatAsIfIntegral + override def doubleValue() = self.toDouble + override def floatValue() = self + override def longValue() = self.toLong + override def intValue() = self.toInt + override def byteValue() = self.toByte + override def shortValue() = self.toShort + + override def isWhole = { + val l = self.toLong + l.toFloat == self || l == Long.MaxValue && self < Float.PositiveInfinity || l == Long.MinValue && self > Float.NegativeInfinity + } + override def isValidByte = self.toByte.toFloat == self + override def isValidShort = self.toShort.toFloat == self + override def isValidChar = self.toChar.toFloat == self + override def isValidInt = { val i = self.toInt; i.toFloat == self && i != Int.MaxValue } + // override def isValidLong = { val l = self.toLong; l.toFloat == self && l != Long.MaxValue } + // override def isValidFloat = !java.lang.Float.isNaN(self) + // override def isValidDouble = !java.lang.Float.isNaN(self) + + def isNaN: Boolean = java.lang.Float.isNaN(self) + def isInfinity: Boolean = java.lang.Float.isInfinite(self) + def isPosInfinity: Boolean = Float.PositiveInfinity == self + def isNegInfinity: Boolean = Float.NegativeInfinity == self + + override def abs: Float = math.abs(self) + override def max(that: Float): Float = math.max(self, that) + override def min(that: Float): Float = math.min(self, that) + override def signum: Int = math.signum(self).toInt // !!! NaN + def round: Int = math.round(self) def ceil: Float = math.ceil(self.toDouble).toFloat def floor: Float = math.floor(self.toDouble).toFloat @@ -31,22 +60,4 @@ final class RichFloat(val self: Float) extends AnyVal with FractionalProxy[Float * @return the measurement of the angle `x` in degrees. */ def toDegrees: Float = math.toDegrees(self.toDouble).toFloat - - // isNaN is provided by the implicit conversion to java.lang.Float - // def isNaN: Boolean = java.lang.Float.isNaN(self) - def isInfinity: Boolean = java.lang.Float.isInfinite(self) - def isPosInfinity: Boolean = isInfinity && self > 0.0f - def isNegInfinity: Boolean = isInfinity && self < 0.0f - - override def isValidByte = self.toByte.toFloat == self - override def isValidShort = self.toShort.toFloat == self - override def isValidChar = self.toChar.toFloat == self - override def isValidInt = { val i = self.toInt; i.toFloat == self && i != Int.MaxValue } - // override def isValidLong = { val l = self.toLong; l.toFloat == self && l != Long.MaxValue } - // override def isValidFloat = !java.lang.Float.isNaN(self) - // override def isValidDouble = !java.lang.Float.isNaN(self) - override def isWhole = { - val l = self.toLong - l.toFloat == self || l == Long.MaxValue && self < Float.PositiveInfinity || l == Long.MinValue && self > Float.NegativeInfinity - } } diff --git a/src/library/scala/runtime/RichInt.scala b/src/library/scala/runtime/RichInt.scala index a39710b146..cc4e92dfa3 100644 --- a/src/library/scala/runtime/RichInt.scala +++ b/src/library/scala/runtime/RichInt.scala @@ -9,7 +9,6 @@ package scala package runtime - import scala.collection.immutable.Range // Note that this does not implement IntegralProxy[Int] so that it can return @@ -17,14 +16,33 @@ import scala.collection.immutable.Range final class RichInt(val self: Int) extends AnyVal with ScalaNumberProxy[Int] with RangedProxy[Int] { protected def num = scala.math.Numeric.IntIsIntegral protected def ord = scala.math.Ordering.Int - type ResultWithoutStep = Range - /** - * @return `'''true'''` if this number has no decimal component. - * Always returns `'''true'''` for `RichInt`. + override def doubleValue() = self.toDouble + override def floatValue() = self.toFloat + override def longValue() = self.toLong + override def intValue() = self + override def byteValue() = self.toByte + override def shortValue() = self.toShort + + /** Returns `'''true'''` if this number has no decimal component. + * Always `'''true'''` for `RichInt`. */ def isWhole() = true + override def isValidInt = true + def isValidLong = true + + override def abs: Int = math.abs(self) + override def max(that: Int): Int = math.max(self, that) + override def min(that: Int): Int = math.min(self, that) + override def signum: Int = math.signum(self) + + def toBinaryString: String = java.lang.Integer.toBinaryString(self) + def toHexString: String = java.lang.Integer.toHexString(self) + def toOctalString: String = java.lang.Integer.toOctalString(self) + + type ResultWithoutStep = Range + /** * @param end The final bound of the range to make. * @return A [[scala.collection.immutable.Range]] from `this` up to but @@ -55,23 +73,4 @@ final class RichInt(val self: Int) extends AnyVal with ScalaNumberProxy[Int] wit * and including `end`. */ def to(end: Int, step: Int): Range.Inclusive = Range.inclusive(self, end, step) - - /** - * @return `'''this'''` if `'''this''' < that` or `that` otherwise - */ - override def min(that: Int): Int = if (self < that) self else that - - /** - * @return `'''this'''` if `'''this''' > that` or `that` otherwise - */ - override def max(that: Int): Int = if (self > that) self else that - - /** - * Computes the absolute value of `'''this'''`. - */ - override def abs: Int = if (self < 0) -self else self - - def toBinaryString: String = java.lang.Integer.toBinaryString(self) - def toHexString: String = java.lang.Integer.toHexString(self) - def toOctalString: String = java.lang.Integer.toOctalString(self) } diff --git a/src/library/scala/runtime/RichLong.scala b/src/library/scala/runtime/RichLong.scala index e5b39b1c09..df0bbec047 100644 --- a/src/library/scala/runtime/RichLong.scala +++ b/src/library/scala/runtime/RichLong.scala @@ -9,20 +9,31 @@ package scala package runtime - final class RichLong(val self: Long) extends AnyVal with IntegralProxy[Long] { protected def num = scala.math.Numeric.LongIsIntegral protected def ord = scala.math.Ordering.Long - def toBinaryString: String = java.lang.Long.toBinaryString(self) - def toHexString: String = java.lang.Long.toHexString(self) - def toOctalString: String = java.lang.Long.toOctalString(self) + override def doubleValue() = self.toDouble + override def floatValue() = self.toFloat + override def longValue() = self + override def intValue() = self.toInt + override def byteValue() = self.toByte + override def shortValue() = self.toShort - override def isValidByte = self.toByte.toLong == self + override def isValidByte = self.toByte.toLong == self override def isValidShort = self.toShort.toLong == self - override def isValidChar = self.toChar.toLong == self - override def isValidInt = self.toInt.toLong == self - // override def isValidLong = true + override def isValidChar = self.toChar.toLong == self + override def isValidInt = self.toInt.toLong == self + def isValidLong = true // override def isValidFloat = self.toFloat.toLong == self && self != Long.MaxValue // override def isValidDouble = self.toDouble.toLong == self && self != Long.MaxValue + + override def abs: Long = math.abs(self) + override def max(that: Long): Long = math.max(self, that) + override def min(that: Long): Long = math.min(self, that) + override def signum: Int = math.signum(self).toInt + + def toBinaryString: String = java.lang.Long.toBinaryString(self) + def toHexString: String = java.lang.Long.toHexString(self) + def toOctalString: String = java.lang.Long.toOctalString(self) } diff --git a/src/library/scala/runtime/RichShort.scala b/src/library/scala/runtime/RichShort.scala index 3e5d8781ff..b35beff7eb 100644 --- a/src/library/scala/runtime/RichShort.scala +++ b/src/library/scala/runtime/RichShort.scala @@ -13,4 +13,18 @@ package runtime final class RichShort(val self: Short) extends AnyVal with ScalaWholeNumberProxy[Short] { protected def num = scala.math.Numeric.ShortIsIntegral protected def ord = scala.math.Ordering.Short + + override def doubleValue() = self.toDouble + override def floatValue() = self.toFloat + override def longValue() = self.toLong + override def intValue() = self.toInt + override def byteValue() = self.toByte + override def shortValue() = self + + override def isValidShort = true + + override def abs: Short = math.abs(self.toInt).toShort + override def max(that: Short): Short = math.max(self.toInt, that.toInt).toShort + override def min(that: Short): Short = math.min(self.toInt, that.toInt).toShort + override def signum: Int = math.signum(self.toInt) } diff --git a/src/library/scala/runtime/ScalaNumberProxy.scala b/src/library/scala/runtime/ScalaNumberProxy.scala index 6ea6448b1a..5e4da24c0d 100644 --- a/src/library/scala/runtime/ScalaNumberProxy.scala +++ b/src/library/scala/runtime/ScalaNumberProxy.scala @@ -32,9 +32,13 @@ trait ScalaNumberProxy[T] extends Any with ScalaNumericAnyConversions with Typed def byteValue() = intValue().toByte def shortValue() = intValue().toShort + /** Returns `'''this'''` if `'''this''' < that` or `that` otherwise. */ def min(that: T): T = num.min(self, that) + /** Returns `'''this'''` if `'''this''' > that` or `that` otherwise. */ def max(that: T): T = num.max(self, that) + /** Returns the absolute value of `'''this'''`. */ def abs = num.abs(self) + /** Returns the signum of `'''this'''`. */ def signum = num.signum(self) } trait ScalaWholeNumberProxy[T] extends Any with ScalaNumberProxy[T] { diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 3a85207235..c049de3a28 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -15,7 +15,6 @@ import scala.collection.immutable.{ StringLike, NumericRange, List, Stream, Nil, import scala.collection.generic.{ Sorted } import scala.reflect.{ ClassTag, classTag } import scala.util.control.ControlThrowable -import scala.xml.{ Node, MetaData } import java.lang.{ Class => jClass } import java.lang.Double.doubleToLongBits @@ -268,11 +267,10 @@ object ScalaRunTime { } def isScalaClass(x: AnyRef) = packageOf(x) startsWith "scala." def isScalaCompilerClass(x: AnyRef) = packageOf(x) startsWith "scala.tools.nsc." + def isXmlClass(x: AnyRef) = packageOf(x) startsWith "scala.xml." // When doing our own iteration is dangerous def useOwnToString(x: Any) = x match { - // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] - case _: Node | _: MetaData => true // Range/NumericRange have a custom toString to avoid walking a gazillion elements case _: Range | _: NumericRange[_] => true // Sorted collections to the wrong thing (for us) on iteration - ticket #3493 @@ -281,10 +279,11 @@ object ScalaRunTime { case _: StringLike[_] => true // Don't want to evaluate any elements in a view case _: TraversableView[_, _] => true + // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] -> catch those and more by isXmlClass(x) // Don't want to a) traverse infinity or b) be overly helpful with peoples' custom // collections which may have useful toString methods - ticket #3710 // or c) print AbstractFiles which are somehow also Iterable[AbstractFile]s. - case x: Traversable[_] => !x.hasDefiniteSize || !isScalaClass(x) || isScalaCompilerClass(x) + case x: Traversable[_] => !x.hasDefiniteSize || !isScalaClass(x) || isScalaCompilerClass(x) || isXmlClass(x) // Otherwise, nothing could possibly go wrong case _ => false } diff --git a/src/library/scala/runtime/VolatileObjectRef.java b/src/library/scala/runtime/VolatileObjectRef.java index 9f1f3ac0cf..6063501ffb 100755 --- a/src/library/scala/runtime/VolatileObjectRef.java +++ b/src/library/scala/runtime/VolatileObjectRef.java @@ -16,8 +16,9 @@ public class VolatileObjectRef<T> implements java.io.Serializable { volatile public T elem; public VolatileObjectRef(T elem) { this.elem = elem; } + @Override public String toString() { return String.valueOf(elem); } - public static <U> VolatileObjectRef create(U e) { return new VolatileObjectRef(e); } - public static VolatileObjectRef zero() { return new VolatileObjectRef(null); } + public static <U> VolatileObjectRef<U> create(U e) { return new VolatileObjectRef<U>(e); } + public static VolatileObjectRef<Object> zero() { return new VolatileObjectRef<Object>(null); } } diff --git a/src/library/scala/sys/process/Process.scala b/src/library/scala/sys/process/Process.scala index 402183a1f0..dcd06c89e9 100644 --- a/src/library/scala/sys/process/Process.scala +++ b/src/library/scala/sys/process/Process.scala @@ -127,15 +127,6 @@ trait ProcessCreation { */ def apply(url: URL): URLBuilder = new URLImpl(url) - /** Creates a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. - * This can be used as a way to template strings. - * - * @example {{{ - * apply(<x> {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}</x>) - * }}} - */ - def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim) - /** Creates a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be * to force an exit value. */ @@ -220,14 +211,6 @@ trait ProcessImplicits { */ implicit def urlToProcess(url: URL): URLBuilder = apply(url) - /** Implicitly convert a [[scala.xml.Elem]] into a - * [[scala.sys.process.ProcessBuilder]]. This is done by obtaining the text - * elements of the element, trimming spaces, and then converting the result - * from string to a process. Importantly, tags are completely ignored, so - * they cannot be used to separate parameters. - */ - implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = apply(command) - /** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]]. */ implicit def stringToProcess(command: String): ProcessBuilder = apply(command) diff --git a/src/library/scala/sys/process/package.scala b/src/library/scala/sys/process/package.scala index 902543665f..d68cd004f8 100644 --- a/src/library/scala/sys/process/package.scala +++ b/src/library/scala/sys/process/package.scala @@ -80,10 +80,7 @@ package scala.sys { * spaces -- no escaping of spaces is possible -- or out of a * [[scala.collection.Seq]], where the first element represents the command * name, and the remaining elements are arguments to it. In this latter case, - * arguments may contain spaces. One can also implicitly convert - * [[scala.xml.Elem]] and `java.lang.ProcessBuilder` into a `ProcessBuilder`. - * In the introductory example, the strings were converted into - * `ProcessBuilder` implicitly. + * arguments may contain spaces. * * To further control what how the process will be run, such as specifying * the directory in which it will be run, see the factories on diff --git a/src/library/scala/util/parsing/combinator/Parsers.scala b/src/library/scala/util/parsing/combinator/Parsers.scala index 4602c3cc53..16754646fd 100644 --- a/src/library/scala/util/parsing/combinator/Parsers.scala +++ b/src/library/scala/util/parsing/combinator/Parsers.scala @@ -531,10 +531,6 @@ trait Parsers { } } - /*trait ElemFun - case class EFCons(hd: Elem => ElemFun, tl: ElemFun) extends ElemFun - case class EFNil(res: Boolean) extends ElemFun*/ - /** A parser matching input elements that satisfy a given predicate. * * `elem(kind, p)` succeeds if the input starts with an element `e` for which `p(e)` is true. diff --git a/src/library/scala/xml/Elem.scala b/src/library/scala/xml/Elem.scala index 4200b7046a..484cf98744 100755 --- a/src/library/scala/xml/Elem.scala +++ b/src/library/scala/xml/Elem.scala @@ -35,8 +35,31 @@ object Elem { case _: SpecialNode | _: Group => None case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child)) } + + import scala.sys.process._ + /** Implicitly convert a [[scala.xml.Elem]] into a + * [[scala.sys.process.ProcessBuilder]]. This is done by obtaining the text + * elements of the element, trimming spaces, and then converting the result + * from string to a process. Importantly, tags are completely ignored, so + * they cannot be used to separate parameters. + */ + @deprecated("To create a scala.sys.process.Process from an xml.Elem, please use Process(elem.text.trim).", "2.11.0") + implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = Process(command.text.trim) + + @deprecated("To create a scala.sys.process.Process from an xml.Elem, please use Process(elem.text.trim).", "2.11.0") + implicit def processXml(p: Process.type) = new { + /** Creates a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. + * This can be used as a way to template strings. + * + * @example {{{ + * apply(<x> {dxPath.absolutePath} --dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}</x>) + * }}} + */ + def apply(command: Elem): ProcessBuilder = Process(command.text.trim) + } } + /** The case class `Elem` extends the `Node` class, * providing an immutable data object representing an XML element. * diff --git a/src/partest/scala/tools/partest/DirectTest.scala b/src/partest/scala/tools/partest/DirectTest.scala index 953b5e5535..2e6c3baa02 100644 --- a/src/partest/scala/tools/partest/DirectTest.scala +++ b/src/partest/scala/tools/partest/DirectTest.scala @@ -7,8 +7,9 @@ package scala.tools.partest import scala.tools.nsc._ import settings.ScalaVersion -import util.{ SourceFile, BatchSourceFile, CommandLineParser } +import util.{ SourceFile, BatchSourceFile } import reporters.{Reporter, ConsoleReporter} +import scala.tools.cmd.CommandLineParser /** A class for testing code which is embedded as a string. * It allows for more complete control over settings, compiler diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala index b5b09a753a..8b88021dbf 100644 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ b/src/partest/scala/tools/partest/PartestTask.scala @@ -15,6 +15,7 @@ import java.lang.reflect.Method import org.apache.tools.ant.Task import org.apache.tools.ant.types.{ Reference, FileSet} import org.apache.tools.ant.types.Commandline.Argument +import scala.tools.ant.ScalaTask /** An Ant task to execute the Scala test suite (NSC). * @@ -34,7 +35,7 @@ import org.apache.tools.ant.types.Commandline.Argument * * @author Philippe Haller */ -class PartestTask extends Task with CompilationPathProperty { +class PartestTask extends Task with CompilationPathProperty with ScalaTask { type Path = org.apache.tools.ant.types.Path private var kinds: List[String] = Nil @@ -178,7 +179,7 @@ class PartestTask extends Task with CompilationPathProperty { val allFailures = _results map (_._2) sum val allFailedPaths = _results flatMap (_._3) - def f = if (errorOnFailed && allFailures > 0) (sys error _) else log(_: String) + def f = if (errorOnFailed && allFailures > 0) buildError(_: String) else log(_: String) def s = if (allFailures > 1) "s" else "" val msg = if (allFailures > 0) diff --git a/src/partest/scala/tools/partest/TestKinds.scala b/src/partest/scala/tools/partest/TestKinds.scala index ec682690ca..b4e8afd0d2 100644 --- a/src/partest/scala/tools/partest/TestKinds.scala +++ b/src/partest/scala/tools/partest/TestKinds.scala @@ -4,8 +4,7 @@ package partest import nest.PathSettings.srcDir object TestKinds { - val standardKinds = "pos neg run jvm res buildmanager scalacheck scalap specialized instrumented presentation ant" split "\\s+" toList - val standardArgs = standardKinds map ("--" + _) + val standardKinds = ("pos neg run jvm res scalacheck scalap specialized instrumented presentation ant" split "\\s+").toList def denotesTestFile(p: Path) = p.isFile && p.hasExtension("scala", "res", "xml") def denotesTestDir(p: Path) = kindOf(p) match { diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala index 8161e53bf9..332131ca3a 100644 --- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala @@ -9,13 +9,15 @@ package nest import utils.Properties._ import scala.tools.nsc.Properties.{ versionMsg, setProp } -import scala.tools.nsc.util.CommandLineParser import scala.collection.{ mutable, immutable } import PathSettings.srcDir import TestKinds._ import scala.reflect.internal.util.Collections.distinctBy +import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance } -class ConsoleRunner extends DirectRunner { +class ConsoleRunner(argstr: String) extends { + val parsed = ConsoleRunnerSpec.creator(CommandLineParser tokenize argstr) +} with DirectRunner with ConsoleRunnerSpec with Instance { import NestUI._ import NestUI.color._ @@ -86,27 +88,15 @@ class ConsoleRunner extends DirectRunner { } } - private val unaryArgs = List( - "--pack", "--all", - "--terse", "--verbose", "--show-diff", "--show-log", "--self-test", - "--failed", "--update-check", "--version", "--ansi", "--debug", "--help" - ) ::: standardArgs - - private val binaryArgs = List( - "--grep", "--srcpath", "--buildpath", "--classpath", "--timeout" - ) - - def main(argstr: String) { - val parsed = CommandLineParser(argstr) withUnaryArgs unaryArgs withBinaryArgs binaryArgs - - if (parsed isSet "--debug") NestUI.setDebug() - if (parsed isSet "--verbose") NestUI.setVerbose() - if (parsed isSet "--terse") NestUI.setTerse() - if (parsed isSet "--show-diff") NestUI.setDiffOnFail() + def run(): Unit = { + if (optDebug) NestUI.setDebug() + if (optVerbose) NestUI.setVerbose() + if (optTerse) NestUI.setTerse() + if (optShowDiff) NestUI.setDiffOnFail() // Early return on no args, version, or invalid args - if (parsed isSet "--version") return echo(versionMsg) - if ((argstr == "") || (parsed isSet "--help")) return NestUI.usage() + if (optVersion) return echo(versionMsg) + if ((argstr == "") || optHelp) return NestUI.usage() val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath if (invalid.nonEmpty) { @@ -116,27 +106,26 @@ class ConsoleRunner extends DirectRunner { echoWarning(s"Discarding ${invalid.size} invalid test paths") } - parsed get "--srcpath" foreach (x => setProp("partest.srcdir", x)) - parsed get "--timeout" foreach (x => setProp("partest.timeout", x)) + optSourcePath foreach (x => setProp("partest.srcdir", x)) + optTimeout foreach (x => setProp("partest.timeout", x)) fileManager = - if (parsed isSet "--buildpath") new ConsoleFileManager(parsed("--buildpath")) - else if (parsed isSet "--classpath") new ConsoleFileManager(parsed("--classpath"), true) - else if (parsed isSet "--pack") new ConsoleFileManager("build/pack") + if (optBuildPath.isDefined) new ConsoleFileManager(optBuildPath.get) + else if (optClassPath.isDefined) new ConsoleFileManager(optClassPath.get, true) + else if (optPack) new ConsoleFileManager("build/pack") else new ConsoleFileManager // auto detection, see ConsoleFileManager.findLatest - fileManager.updateCheck = parsed isSet "--update-check" - fileManager.failed = parsed isSet "--failed" + fileManager.updateCheck = optUpdateCheck + fileManager.failed = optFailed val partestTests = ( - if (parsed isSet "--self-test") TestKinds.testsForPartest + if (optSelfTest) TestKinds.testsForPartest else Nil ) - val grepExpr = parsed get "--grep" getOrElse "" + val grepExpr = optGrep getOrElse "" // If --grep is given we suck in every file it matches. - var grepMessage = "" val greppedTests = if (grepExpr == "") Nil else { val paths = grepFor(grepExpr) if (paths.isEmpty) @@ -145,14 +134,14 @@ class ConsoleRunner extends DirectRunner { paths.sortBy(_.toString) } - val isRerun = parsed isSet "--failed" + val isRerun = optFailed val rerunTests = if (isRerun) TestKinds.failedTests else Nil def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests - val givenKinds = standardArgs filter parsed.isSet + val givenKinds = standardKinds filter parsed.isSet val kinds = ( - if (parsed isSet "--all") standardKinds - else if (givenKinds.nonEmpty) givenKinds map (_ stripPrefix "--") + if (optAll) standardKinds + else if (givenKinds.nonEmpty) givenKinds else if (invalid.isEmpty && miscTests.isEmpty && !isRerun) standardKinds // If no kinds, --grep, or individual tests were given, assume --all else Nil ) @@ -223,4 +212,13 @@ class ConsoleRunner extends DirectRunner { issueSummaryReport() System exit ( if (isSuccess) 0 else 1 ) } + + run() } + +object ConsoleRunner { + def main(args: Array[String]): Unit = { + new ConsoleRunner(args mkString " ") + } +} + diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala b/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala new file mode 100644 index 0000000000..f9143013e9 --- /dev/null +++ b/src/partest/scala/tools/partest/nest/ConsoleRunnerSpec.scala @@ -0,0 +1,54 @@ +package scala.tools.partest.nest + +import language.postfixOps + +import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec } + +trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation { + def referenceSpec = ConsoleRunnerSpec + def programInfo = Spec.Info( + "console-runner", + "Usage: NestRunner [options] [test test ...]", + "scala.tools.partest.nest.ConsoleRunner") + + heading("Test categories:") + val optAll = "all" / "run all tests" --? + val optPos = "pos" / "run compilation tests (success)" --? + val optNeg = "neg" / "run compilation tests (failure)" --? + val optRun = "run" / "run interpreter and backend tests" --? + val optJvm = "jvm" / "run JVM backend tests" --? + val optRes = "res" / "run resident compiler tests" --? + val optAnt = "ant" / "run Ant tests" --? + val optScalap = "scalap" / "run scalap tests" --? + val optSpecialized = "specialized" / "run specialization tests" --? + val optScalacheck = "scalacheck" / "run ScalaCheck tests" --? + val optInstrumented = "instrumented" / "run instrumented tests" --? + val optPresentation = "presentation" / "run presentation compiler tests" --? + + heading("Test runner options:") + val optFailed = "failed" / "run only those tests that failed during the last run" --? + val optTimeout = "timeout" / "aborts the test suite after the given amount of time" --| + val optPack = "pack" / "pick compiler/reflect/library in build/pack, and run all tests" --? + val optGrep = "grep" / "run all tests whose source file contains the expression given to grep" --| + val optUpdateCheck = "update-check" / "instead of failing tests with output change, update checkfile (use with care!)" --? + val optBuildPath = "buildpath" / "set (relative) path to build jars (ex.: --buildpath build/pack)" --| + val optClassPath = "classpath" / "set (absolute) path to build classes" --| + val optSourcePath = "srcpath" / "set (relative) path to test source files (ex.: --srcpath pending)" --| + + heading("Test output options:") + val optShowDiff = "show-diff" / "show diffs for failed tests" --? + val optVerbose = "verbose" / "show verbose progress information" --? + val optTerse = "terse" / "show terse progress information" --? + val optDebug = "debug" / "enable debugging output" --? + + heading("Other options:") + val optVersion = "version" / "show Scala version and exit" --? + val optSelfTest = "self-test" / "run tests for partest itself" --? + val optHelp = "help" / "show this page and exit" --? + +} + +object ConsoleRunnerSpec extends ConsoleRunnerSpec with Reference { + type ThisCommandLine = CommandLine + def creator(args: List[String]): ThisCommandLine = new CommandLine(ConsoleRunnerSpec, args) +} diff --git a/src/partest/scala/tools/partest/nest/NestUI.scala b/src/partest/scala/tools/partest/nest/NestUI.scala index 564270e531..5148115905 100644 --- a/src/partest/scala/tools/partest/nest/NestUI.scala +++ b/src/partest/scala/tools/partest/nest/NestUI.scala @@ -144,34 +144,8 @@ object NestUI { } def usage() { - println("Usage: NestRunner [options] [test test ...]") - println - println(" Test categories:") - println(" --all run all tests") - println(" --pos run compilation tests (success)") - println(" --neg run compilation tests (failure)") - println(" --run run interpreter and backend tests") - println(" --jvm run JVM backend tests") - println(" --res run resident compiler tests") - println(" --scalacheck run ScalaCheck tests") - println(" --instrumented run instrumented tests") - println(" --presentation run presentation compiler tests") - println - println(" Other options:") - println(" --pack pick compiler/reflect/library in build/pack, and run all tests") - println(" --grep <expr> run all tests whose source file contains <expr>") - println(" --failed run only those tests that failed during the last run") - println(" --update-check instead of failing tests with output change, update checkfile. (Use with care!)") - println(" --verbose show progress information") - println(" --buildpath set (relative) path to build jars") - println(" ex.: --buildpath build/pack") - println(" --classpath set (absolute) path to build classes") - println(" --srcpath set (relative) path to test source files") - println(" ex.: --srcpath pending") - println(" --debug enable debugging output") - println - println(utils.Properties.versionString) - println("maintained by Philipp Haller (EPFL)") + println(ConsoleRunnerSpec.programInfo.usage) + println(ConsoleRunnerSpec.helpMsg) sys.exit(1) } diff --git a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala index 734affa153..3c77a03f1e 100644 --- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala +++ b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala @@ -85,10 +85,9 @@ class ReflectiveRunner { try { val sepRunnerClass = sepLoader loadClass sepRunnerClassName - val sepRunner = sepRunnerClass.newInstance() - val sepMainMethod = sepRunnerClass.getMethod("main", Array(classOf[String]): _*) - val cargs: Array[AnyRef] = Array(args) - sepMainMethod.invoke(sepRunner, cargs: _*) + val sepMainMethod = sepRunnerClass.getMethod("main", classOf[Array[String]]) + val cargs: Array[AnyRef] = Array(Array(args)) + sepMainMethod.invoke(null, cargs: _*) } catch { case cnfe: ClassNotFoundException => diff --git a/src/partest/scala/tools/partest/nest/Runner.scala b/src/partest/scala/tools/partest/nest/Runner.scala index a53698eb77..d7d87bdcf5 100644 --- a/src/partest/scala/tools/partest/nest/Runner.scala +++ b/src/partest/scala/tools/partest/nest/Runner.scala @@ -772,15 +772,8 @@ trait DirectRunner { import PartestDefaults.{ numThreads, waitTime } - Thread.setDefaultUncaughtExceptionHandler( - new Thread.UncaughtExceptionHandler { - def uncaughtException(thread: Thread, t: Throwable) { - val t1 = Exceptional unwrap t - System.err.println(s"Uncaught exception on thread $thread: $t1") - t1.printStackTrace() - } - } - ) + setUncaughtHandler + def runTestsForFiles(kindFiles: List[File], kind: String): List[TestState] = { NestUI.resetTestNumber(kindFiles.size) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 4f2b7e2642..bf8ef79a63 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -16,7 +16,7 @@ import scala.reflect.api.{Universe => ApiUniverse} trait Definitions extends api.StandardDefinitions { self: SymbolTable => - import rootMirror.{getModule, getPackage, getClassByName, getRequiredClass, getRequiredModule, getClassIfDefined, getModuleIfDefined, getPackageObject, getPackageObjectIfDefined, requiredClass, requiredModule} + import rootMirror.{getModule, getPackage, getClassByName, getRequiredClass, getRequiredModule, getClassIfDefined, getModuleIfDefined, getPackageObject, getPackageIfDefined, getPackageObjectIfDefined, requiredClass, requiredModule} object definitions extends DefinitionsClass @@ -471,6 +471,10 @@ trait Definitions extends api.StandardDefinitions { def methodCache_find = getMemberMethod(MethodCacheClass, nme.find_) def methodCache_add = getMemberMethod(MethodCacheClass, nme.add_) + // XML + lazy val ScalaXmlTopScope = getModuleIfDefined("scala.xml.TopScope") + lazy val ScalaXmlPackage = getPackageIfDefined("scala.xml") + // scala.reflect lazy val ReflectPackage = requiredModule[scala.reflect.`package`.type] lazy val ReflectApiPackage = getPackageObjectIfDefined("scala.reflect.api") // defined in scala-reflect.jar, so we need to be careful @@ -866,6 +870,12 @@ trait Definitions extends api.StandardDefinitions { lazy val BoxedNumberClass = getClassByName(sn.BoxedNumber) lazy val BoxedCharacterClass = getClassByName(sn.BoxedCharacter) lazy val BoxedBooleanClass = getClassByName(sn.BoxedBoolean) + lazy val BoxedByteClass = requiredClass[java.lang.Byte] + lazy val BoxedShortClass = requiredClass[java.lang.Short] + lazy val BoxedIntClass = requiredClass[java.lang.Integer] + lazy val BoxedLongClass = requiredClass[java.lang.Long] + lazy val BoxedFloatClass = requiredClass[java.lang.Float] + lazy val BoxedDoubleClass = requiredClass[java.lang.Double] lazy val Boxes_isNumberOrBool = getDecl(BoxesRunTimeClass, nme.isBoxedNumberOrBoolean) lazy val Boxes_isNumber = getDecl(BoxesRunTimeClass, nme.isBoxedNumber) diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index bf38c3bf1e..6ed9de8e20 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -176,6 +176,9 @@ trait Mirrors extends api.Mirrors { def getPackage(fullname: TermName): ModuleSymbol = ensurePackageSymbol(fullname.toString, getModuleOrClass(fullname), allowModules = true) + def getPackageIfDefined(fullname: TermName): Symbol = + wrapMissing(getPackage(fullname)) + @deprecated("Use getPackage", "2.11.0") def getRequiredPackage(fullname: String): ModuleSymbol = getPackage(newTermNameCached(fullname)) diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index ed5b92565d..0d78e24548 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -121,6 +121,41 @@ trait Names extends api.Names { def newTypeName(bs: Array[Byte], offset: Int, len: Int): TypeName = newTermName(bs, offset, len).toTypeName + /** + * Used only by the GenBCode backend, to represent bytecode-level types in a way that makes equals() and hashCode() efficient. + * For bytecode-level types of OBJECT sort, its internal name (not its descriptor) is stored. + * For those of ARRAY sort, its descriptor is stored ie has a leading '[' + * For those of METHOD sort, its descriptor is stored ie has a leading '(' + * + * can-multi-thread + */ + final def lookupTypeName(cs: Array[Char]): TypeName = { lookupTypeNameIfExisting(cs, true) } + + final def lookupTypeNameIfExisting(cs: Array[Char], failOnNotFound: Boolean): TypeName = { + + val hterm = hashValue(cs, 0, cs.size) & HASH_MASK + var nterm = termHashtable(hterm) + while ((nterm ne null) && (nterm.length != cs.size || !equals(nterm.start, cs, 0, cs.size))) { + nterm = nterm.next + } + if (nterm eq null) { + if (failOnNotFound) { assert(false, "TermName not yet created: " + new String(cs)) } + return null + } + + val htype = hashValue(chrs, nterm.start, nterm.length) & HASH_MASK + var ntype = typeHashtable(htype) + while ((ntype ne null) && ntype.start != nterm.start) { + ntype = ntype.next + } + if (ntype eq null) { + if (failOnNotFound) { assert(false, "TypeName not yet created: " + new String(cs)) } + return null + } + + ntype + } + // Classes ---------------------------------------------------------------------- /** The name class. diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 81fffc833c..30aaaa1f3a 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -599,6 +599,7 @@ trait StdNames { val currentMirror: NameType = "currentMirror" val delayedInit: NameType = "delayedInit" val delayedInitArg: NameType = "delayedInit$body" + val dollarScope: NameType = "$scope" val drop: NameType = "drop" val elem: NameType = "elem" val emptyValDef: NameType = "emptyValDef" @@ -684,6 +685,7 @@ trait StdNames { val thisPrefix : NameType = "thisPrefix" val toArray: NameType = "toArray" val toObjectArray : NameType = "toObjectArray" + val TopScope: NameType = "TopScope" val toString_ : NameType = "toString" val toTypeConstructor: NameType = "toTypeConstructor" val tpe : NameType = "tpe" diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 30ec555bc5..5c92512193 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -98,7 +98,7 @@ abstract class TreeInfo { */ def isStableIdentifier(tree: Tree, allowVolatile: Boolean): Boolean = tree match { - case Ident(_) => symOk(tree.symbol) && tree.symbol.isStable && !tree.symbol.hasVolatileType // TODO SPEC: not required by spec + case i @ Ident(_) => isStableIdent(i) case Select(qual, _) => isStableMemberOf(tree.symbol, qual, allowVolatile) && isPath(qual, allowVolatile) case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX => // see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm` @@ -119,6 +119,13 @@ abstract class TreeInfo { typeOk(tree.tpe) && (allowVolatile || !hasVolatileType(tree)) && !definitions.isByNameParamType(tree.tpe) ) + private def isStableIdent(tree: Ident): Boolean = ( + symOk(tree.symbol) + && tree.symbol.isStable + && !definitions.isByNameParamType(tree.tpe) + && !tree.symbol.hasVolatileType // TODO SPEC: not required by spec + ) + /** Is `tree`'s type volatile? (Ignored if its symbol has the @uncheckedStable annotation.) */ def hasVolatileType(tree: Tree): Boolean = @@ -201,7 +208,7 @@ abstract class TreeInfo { } def isWarnableSymbol = { val sym = tree.symbol - (sym == null) || !(sym.isModule || sym.isLazy) || { + (sym == null) || !(sym.isModule || sym.isLazy || definitions.isByNameParamType(sym.tpe_*)) || { debuglog("'Pure' but side-effecting expression in statement position: " + tree) false } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 8781423a6d..ceb3b383d7 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -327,7 +327,7 @@ trait Trees extends api.Trees { self: SymbolTable => case class ImportSelector(name: Name, namePos: Int, rename: Name, renamePos: Int) extends ImportSelectorApi object ImportSelector extends ImportSelectorExtractor { val wild = ImportSelector(nme.WILDCARD, -1, null, -1) - val wildList = List(wild) + val wildList = List(wild) // OPT This list is shared for performance. } case class Import(expr: Tree, selectors: List[ImportSelector]) diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 8ec8b2ed5f..a84d076e76 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -22,6 +22,7 @@ import scala.reflect.classTag import StdReplTags._ import scala.concurrent.{ ExecutionContext, Await, Future, future } import ExecutionContext.Implicits._ +import scala.reflect.internal.util.BatchSourceFile /** The Scala interactive shell. It provides a read-eval-print loop * around the Interpreter class. @@ -530,8 +531,19 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def pasteCommand(): Result = { echo("// Entering paste mode (ctrl-D to finish)\n") val code = readWhile(_ => true) mkString "\n" - echo("\n// Exiting paste mode, now interpreting.\n") - intp interpret code + if (code.trim.isEmpty) { + echo("\n// Nothing pasted, nothing gained.\n") + } else { + echo("\n// Exiting paste mode, now interpreting.\n") + val res = intp interpret code + // if input is incomplete, let the compiler try to say why + if (res == IR.Incomplete) { + echo("The pasted code is incomplete!\n") + // Remembrance of Things Pasted in an object + val errless = intp compileSources new BatchSourceFile("<pastie>", s"object pastel {\n$code\n}") + if (errless) echo("...but compilation found no error? Good luck with that.") + } + } () } diff --git a/src/scaladoc/scala/tools/partest/ScaladocModelTest.scala b/src/scaladoc/scala/tools/partest/ScaladocModelTest.scala index f0a9caac15..70423cc7dc 100644 --- a/src/scaladoc/scala/tools/partest/ScaladocModelTest.scala +++ b/src/scaladoc/scala/tools/partest/ScaladocModelTest.scala @@ -7,7 +7,7 @@ package scala.tools.partest import scala.tools.nsc import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser +import scala.tools.cmd.CommandLineParser import scala.tools.nsc.doc.{ DocFactory, Universe } import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ diff --git a/test/files/jvm/bytecode-test-example/Foo_1.flags b/test/files/jvm/bytecode-test-example/Foo_1.flags new file mode 100644 index 0000000000..49f2d2c4c8 --- /dev/null +++ b/test/files/jvm/bytecode-test-example/Foo_1.flags @@ -0,0 +1 @@ +-Ybackend:GenASM diff --git a/test/files/jvm/nooptimise/Foo_1.flags b/test/files/jvm/nooptimise/Foo_1.flags index 9686c20775..f493cf9f3f 100644 --- a/test/files/jvm/nooptimise/Foo_1.flags +++ b/test/files/jvm/nooptimise/Foo_1.flags @@ -1 +1 @@ --optimise -Ynooptimise
\ No newline at end of file +-Ybackend:GenASM -optimise -Ynooptimise
\ No newline at end of file diff --git a/test/files/jvm/scala-concurrent-tck.check b/test/files/jvm/scala-concurrent-tck.check deleted file mode 100644 index 18fc456acb..0000000000 --- a/test/files/jvm/scala-concurrent-tck.check +++ /dev/null @@ -1,3 +0,0 @@ -scala-concurrent-tck.scala:429: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses - f - ^ diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index 438ee310e6..6e2b8ca033 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -15,21 +15,17 @@ import scala.reflect.{ classTag, ClassTag } import scala.tools.partest.TestUtil.intercept trait TestBase { - - def once(body: (() => Unit) => Unit) { - val sv = new SyncVar[Boolean] - body(() => sv put true) - sv.take(2000) + trait Done { def apply(proof: => Boolean): Unit } + def once(body: Done => Unit) { + import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit } + val q = new LinkedBlockingQueue[Try[Boolean]] + body(new Done { + def apply(proof: => Boolean): Unit = q offer Try(proof) + }) + assert(q.poll(2000, TimeUnit.MILLISECONDS).get) + // Check that we don't get more than one completion + assert(q.poll(50, TimeUnit.MILLISECONDS) eq null) } - - // def assert(cond: => Boolean) { - // try { - // Predef.assert(cond) - // } catch { - // case e => e.printStackTrace() - // } - // } - } @@ -39,99 +35,52 @@ trait FutureCallbacks extends TestBase { def testOnSuccess(): Unit = once { done => var x = 0 - val f = future { - x = 1 - } - f onSuccess { - case _ => - done() - assert(x == 1) - } + val f = future { x = 1 } + f onSuccess { case _ => done(x == 1) } } def testOnSuccessWhenCompleted(): Unit = once { done => var x = 0 - val f = future { - x = 1 - } + val f = future { x = 1 } f onSuccess { - case _ => - assert(x == 1) + case _ if x == 1 => x = 2 - f onSuccess { - case _ => - assert(x == 2) - done() - } + f onSuccess { case _ => done(x == 2) } } } def testOnSuccessWhenFailed(): Unit = once { done => - val f = future[Unit] { - done() - throw new Exception - } - f onSuccess { - case _ => assert(false) - } + val f = future[Unit] { throw new Exception } + f onSuccess { case _ => done(false) } + f onFailure { case _ => done(true) } } def testOnFailure(): Unit = once { done => - var x = 0 - val f = future[Unit] { - x = 1 - throw new Exception - } - f onSuccess { - case _ => - done() - assert(false) - } - f onFailure { - case _ => - done() - assert(x == 1) - } + val f = future[Unit] { throw new Exception } + f onSuccess { case _ => done(false) } + f onFailure { case _ => done(true) } } def testOnFailureWhenSpecialThrowable(num: Int, cause: Throwable): Unit = once { done => - val f = future[Unit] { - throw cause - } - f onSuccess { - case _ => - done() - assert(false) - } + val f = future[Unit] { throw cause } + f onSuccess { case _ => done(false) } f onFailure { - case e: ExecutionException if (e.getCause == cause) => - done() - case _ => - done() - assert(false) + case e: ExecutionException if e.getCause == cause => done(true) + case _ => done(false) } } def testOnFailureWhenTimeoutException(): Unit = once { done => - val f = future[Unit] { - throw new TimeoutException() - } - f onSuccess { - case _ => - done() - assert(false) - } + val f = future[Unit] { throw new TimeoutException() } + f onSuccess { case _ => done(false) } f onFailure { - case e: TimeoutException => - done() - case other => - done() - assert(false) + case e: TimeoutException => done(true) + case _ => done(false) } } @@ -151,7 +100,6 @@ trait FutureCallbacks extends TestBase { //testOnFailureWhenSpecialThrowable(7, new InterruptedException) testThatNestedCallbacksDoNotYieldStackOverflow() testOnFailureWhenTimeoutException() - } @@ -162,207 +110,120 @@ trait FutureCombinators extends TestBase { done => val f = future { 5 } val g = f map { x => "result: " + x } - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testMapFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("exception message") } val g = f map { x => "result: " + x } - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "exception message") } } def testMapSuccessPF(): Unit = once { done => val f = future { 5 } val g = f map { case r => "result: " + r } - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testTransformSuccess(): Unit = once { done => val f = future { 5 } val g = f.transform(r => "result: " + r, identity) - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } } def testTransformSuccessPF(): Unit = once { done => val f = future { 5 } val g = f.transform( { case r => "result: " + r }, identity) - g onSuccess { - case s => - done() - assert(s == "result: 5") - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case s => done(s == "result: 5") } + g onFailure { case _ => done(false) } + } + +def testTransformFailure(): Unit = once { + done => + val transformed = new Exception("transformed") + val f = future { throw new Exception("expected") } + val g = f.transform(identity, _ => transformed) + g onSuccess { case _ => done(false) } + g onFailure { case e => done(e eq transformed) } + } + + def testTransformFailurePF(): Unit = once { + done => + val e = new Exception("expected") + val transformed = new Exception("transformed") + val f = future[Unit] { throw e } + val g = f.transform(identity, { case `e` => transformed }) + g onSuccess { case _ => done(false) } + g onFailure { case e => done(e eq transformed) } } def testFoldFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("expected") } val g = f.transform(r => "result: " + r, identity) - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "expected") } } def testFlatMapSuccess(): Unit = once { done => val f = future { 5 } val g = f flatMap { _ => future { 10 } } - g onSuccess { - case x => - done() - assert(x == 10) - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case x => done(x == 10) } + g onFailure { case _ => done(false) } } def testFlatMapFailure(): Unit = once { done => - val f = future { - throw new Exception("exception message") - } + val f = future[Unit] { throw new Exception("expected") } val g = f flatMap { _ => future { 10 } } - g onSuccess { - case _ => - done() - assert(false) - } - g onFailure { - case t => - done() - assert(t.getMessage() == "exception message") - } + g onSuccess { case _ => done(false) } + g onFailure { case t => done(t.getMessage() == "expected") } } def testFilterSuccess(): Unit = once { done => val f = future { 4 } val g = f filter { _ % 2 == 0 } - g onSuccess { - case x: Int => - done() - assert(x == 4) - } - g onFailure { - case _ => - done() - assert(false) - } + g onSuccess { case x: Int => done(x == 4) } + g onFailure { case _ => done(false) } } def testFilterFailure(): Unit = once { done => val f = future { 4 } val g = f filter { _ % 2 == 1 } - g onSuccess { - case x: Int => - done() - assert(false) - } + g onSuccess { case x: Int => done(false) } g onFailure { - case e: NoSuchElementException => - done() - assert(true) - case _ => - done() - assert(false) + case e: NoSuchElementException => done(true) + case _ => done(false) } } def testCollectSuccess(): Unit = once { done => val f = future { -5 } - val g = f collect { - case x if x < 0 => -x - } - g onSuccess { - case x: Int => - done() - assert(x == 5) - } - g onFailure { - case _ => - done() - assert(false) - } + val g = f collect { case x if x < 0 => -x } + g onSuccess { case x: Int => done(x == 5) } + g onFailure { case _ => done(false) } } def testCollectFailure(): Unit = once { done => val f = future { -5 } - val g = f collect { - case x if x > 0 => x * 2 - } - g onSuccess { - case _ => - done() - assert(false) - } + val g = f collect { case x if x > 0 => x * 2 } + g onSuccess { case _ => done(false) } g onFailure { - case e: NoSuchElementException => - done() - assert(true) - case _ => - done() - assert(false) + case e: NoSuchElementException => done(true) + case _ => done(false) } } @@ -376,16 +237,8 @@ trait FutureCombinators extends TestBase { f foreach { x => p.success(x * 2) } val g = p.future - g.onSuccess { - case res: Int => - done() - assert(res == 10) - } - g.onFailure { - case _ => - done() - assert(false) - } + g.onSuccess { case res: Int => done(res == 10) } + g.onFailure { case _ => done(false) } } def testForeachFailure(): Unit = once { @@ -396,16 +249,8 @@ trait FutureCombinators extends TestBase { f onFailure { case _ => p.failure(new Exception) } val g = p.future - g.onSuccess { - case _ => - done() - assert(false) - } - g.onFailure { - case _ => - done() - assert(true) - } + g.onSuccess { case _ => done(false) } + g.onFailure { case _ => done(true) } } def testRecoverSuccess(): Unit = once { @@ -415,18 +260,9 @@ trait FutureCombinators extends TestBase { throw cause } recover { case re: RuntimeException => - "recovered" - } - f onSuccess { - case x => - done() - assert(x == "recovered") - } - f onFailure { case any => - done() - assert(false) - } - f + "recovered" } + f onSuccess { case x => done(x == "recovered") } + f onFailure { case any => done(false) } } def testRecoverFailure(): Unit = once { @@ -437,15 +273,8 @@ trait FutureCombinators extends TestBase { } recover { case te: TimeoutException => "timeout" } - f onSuccess { - case x => - done() - assert(false) - } - f onFailure { case any => - done() - assert(any == cause) - } + f onSuccess { case _ => done(false) } + f onFailure { case any => done(any == cause) } } def testRecoverWithSuccess(): Unit = once { @@ -457,15 +286,8 @@ trait FutureCombinators extends TestBase { case re: RuntimeException => future { "recovered" } } - f onSuccess { - case x => - done() - assert(x == "recovered") - } - f onFailure { case any => - done() - assert(false) - } + f onSuccess { case x => done(x == "recovered") } + f onFailure { case any => done(false) } } def testRecoverWithFailure(): Unit = once { @@ -477,15 +299,8 @@ trait FutureCombinators extends TestBase { case te: TimeoutException => future { "timeout" } } - f onSuccess { - case x => - done() - assert(false) - } - f onFailure { case any => - done() - assert(any == cause) - } + f onSuccess { case x => done(false) } + f onFailure { case any => done(any == cause) } } def testZipSuccess(): Unit = once { @@ -493,52 +308,28 @@ trait FutureCombinators extends TestBase { val f = future { 5 } val g = future { 6 } val h = f zip g - h onSuccess { - case (l: Int, r: Int) => - done() - assert(l+r == 11) - } - h onFailure { - case _ => - done() - assert(false) - } + h onSuccess { case (l: Int, r: Int) => done(l+r == 11) } + h onFailure { case _ => done(false) } } def testZipFailureLeft(): Unit = once { done => - val cause = new Exception("exception message") + val cause = new Exception("expected") val f = future { throw cause } val g = future { 6 } val h = f zip g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e.getMessage == "exception message") - } + h onSuccess { case _ => done(false) } + h onFailure { case e: Exception => done(e.getMessage == "expected") } } def testZipFailureRight(): Unit = once { done => - val cause = new Exception("exception message") + val cause = new Exception("expected") val f = future { 5 } val g = future { throw cause } val h = f zip g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e.getMessage == "exception message") - } + h onSuccess { case _ => done(false) } + h onFailure { case e: Exception => done(e.getMessage == "expected") } } def testFallbackTo(): Unit = once { @@ -546,17 +337,8 @@ trait FutureCombinators extends TestBase { val f = future { sys.error("failed") } val g = future { 5 } val h = f fallbackTo g - - h onSuccess { - case x: Int => - done() - assert(x == 5) - } - h onFailure { - case _ => - done() - assert(false) - } + h onSuccess { case x: Int => done(x == 5) } + h onFailure { case _ => done(false) } } def testFallbackToFailure(): Unit = once { @@ -566,16 +348,8 @@ trait FutureCombinators extends TestBase { val g = future { throw cause } val h = f fallbackTo g - h onSuccess { - case _ => - done() - assert(false) - } - h onFailure { - case e: Exception => - done() - assert(e == cause) - } + h onSuccess { case _ => done(false) } + h onFailure { case e => done(e eq cause) } } testMapSuccess() @@ -597,6 +371,8 @@ trait FutureCombinators extends TestBase { testZipFailureRight() testFallbackTo() testFallbackToFailure() + testTransformSuccess() + testTransformSuccessPF() } @@ -606,40 +382,26 @@ trait FutureProjections extends TestBase { def testFailedFailureOnComplete(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } + val f = future { throw cause } f.failed onComplete { - case Success(t) => - assert(t == cause) - done() - case Failure(t) => - assert(false) + case Success(t) => done(t == cause) + case Failure(t) => done(false) } } def testFailedFailureOnSuccess(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } - f.failed onSuccess { - case t => - assert(t == cause) - done() - } + val f = future { throw cause } + f.failed onSuccess { case t => done(t == cause) } } def testFailedSuccessOnComplete(): Unit = once { done => val f = future { 0 } f.failed onComplete { - case Success(t) => - assert(false) - case Failure(t) => - assert(t.isInstanceOf[NoSuchElementException]) - done() + case Failure(_: NoSuchElementException) => done(true) + case _ => done(false) } } @@ -647,19 +409,17 @@ trait FutureProjections extends TestBase { done => val f = future { 0 } f.failed onFailure { - case nsee: NoSuchElementException => - done() + case e: NoSuchElementException => done(true) + case _ => done(false) } + f.failed onSuccess { case _ => done(false) } } def testFailedFailureAwait(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } - assert(Await.result(f.failed, Duration(500, "ms")) == cause) - done() + val f = future { throw cause } + done(Await.result(f.failed, Duration(500, "ms")) == cause) } def testFailedSuccessAwait(): Unit = once { @@ -667,9 +427,10 @@ trait FutureProjections extends TestBase { val f = future { 0 } try { Await.result(f.failed, Duration(500, "ms")) - assert(false) + done(false) } catch { - case nsee: NoSuchElementException => done() + case nsee: NoSuchElementException => done(true) + case _: Throwable => done(false) } } @@ -682,8 +443,8 @@ trait FutureProjections extends TestBase { Await.ready(f, Duration.Zero) Await.ready(f, Duration(500, "ms")) Await.ready(f, Duration.Inf) - done() - } onFailure { case x => throw x } + done(true) + } onFailure { case x => done(throw x) } } def testAwaitNegativeDuration(): Unit = once { done => @@ -692,8 +453,8 @@ trait FutureProjections extends TestBase { intercept[TimeoutException] { Await.ready(f, Duration.Zero) } intercept[TimeoutException] { Await.ready(f, Duration.MinusInf) } intercept[TimeoutException] { Await.ready(f, Duration(-500, "ms")) } - done() - } onFailure { case x => throw x } + done(true) + } onFailure { case x => done(throw x) } } testFailedFailureOnComplete() @@ -704,7 +465,6 @@ trait FutureProjections extends TestBase { testFailedSuccessAwait() testAwaitPositiveDuration() testAwaitNegativeDuration() - } @@ -714,33 +474,25 @@ trait Blocking extends TestBase { def testAwaitSuccess(): Unit = once { done => val f = future { 0 } - Await.result(f, Duration(500, "ms")) - done() + done(Await.result(f, Duration(500, "ms")) == 0) } def testAwaitFailure(): Unit = once { done => val cause = new RuntimeException - val f = future { - throw cause - } + val f = future { throw cause } try { Await.result(f, Duration(500, "ms")) - assert(false) + done(false) } catch { - case t: Throwable => - assert(t == cause) - done() + case t: Throwable => done(t == cause) } } def testFQCNForAwaitAPI(): Unit = once { done => - - assert(classOf[CanAwait].getName == "scala.concurrent.CanAwait") - assert(Await.getClass.getName == "scala.concurrent.Await") - - done() + done(classOf[CanAwait].getName == "scala.concurrent.CanAwait" && + Await.getClass.getName == "scala.concurrent.Await") } testAwaitSuccess() @@ -813,22 +565,26 @@ trait Promises extends TestBase { val p = promise[Int]() val f = p.future - f onSuccess { - case x => - done() - assert(x == 5) - } - f onFailure { - case any => - done() - assert(false) - } + f onSuccess { case x => done(x == 5) } + f onFailure { case any => done(false) } p.success(5) } - testSuccess() + def testFailure(): Unit = once { + done => + val e = new Exception("expected") + val p = promise[Int]() + val f = p.future + + f onSuccess { case x => done(false) } + f onFailure { case any => done(any eq e) } + + p.failure(e) + } + testSuccess() + testFailure() } @@ -888,11 +644,11 @@ trait CustomExecutionContext extends TestBase { val count = countExecs { implicit ec => blocking { once { done => - val f = future({ assertNoEC() })(defaultEC) + val f = future(assertNoEC())(defaultEC) f onSuccess { case _ => assertEC() - done() + done(true) } assertNoEC() } @@ -911,7 +667,7 @@ trait CustomExecutionContext extends TestBase { f onSuccess { case _ => assertEC() - done() + done(true) } } } @@ -935,15 +691,10 @@ trait CustomExecutionContext extends TestBase { Promise.successful(x + 1).future.map(addOne).map(addOne) } onComplete { case Failure(t) => - try { - throw new AssertionError("error in test: " + t.getMessage, t) - } finally { - done() - } + done(throw new AssertionError("error in test: " + t.getMessage, t)) case Success(x) => assertEC() - assert(x == 14) - done() + done(x == 14) } assertNoEC() } @@ -999,21 +750,14 @@ trait ExecutionContextPrepare extends TestBase { done => theLocal.set("secret") val fut = future { 42 } - fut onComplete { - case _ => - assert(theLocal.get == "secret") - done() - } + fut onComplete { case _ => done(theLocal.get == "secret") } } def testMap(): Unit = once { done => theLocal.set("secret2") val fut = future { 42 } - fut map { x => - assert(theLocal.get == "secret2") - done() - } + fut map { x => done(theLocal.get == "secret2") } } testOnComplete() diff --git a/test/files/neg/case-collision.flags b/test/files/neg/case-collision.flags index 85d8eb2ba2..14c1069dee 100644 --- a/test/files/neg/case-collision.flags +++ b/test/files/neg/case-collision.flags @@ -1 +1 @@ --Xfatal-warnings +-Ybackend:GenASM -Xfatal-warnings diff --git a/test/files/neg/case-collision2.check b/test/files/neg/case-collision2.check new file mode 100644 index 0000000000..b8481f46bb --- /dev/null +++ b/test/files/neg/case-collision2.check @@ -0,0 +1,12 @@ +case-collision2.scala:5: warning: Class foo.BIPPY differs only in case from foo.Bippy. Such classes will overwrite one another on case-insensitive filesystems. +class BIPPY + ^ +case-collision2.scala:8: warning: Class foo.DINGO$ differs only in case from foo.Dingo$. Such classes will overwrite one another on case-insensitive filesystems. +object DINGO + ^ +case-collision2.scala:11: warning: Class foo.HyRaX$ differs only in case from foo.Hyrax$. Such classes will overwrite one another on case-insensitive filesystems. +object HyRaX + ^ +error: No warnings can be incurred under -Xfatal-warnings. +three warnings found +one error found diff --git a/test/files/neg/case-collision2.flags b/test/files/neg/case-collision2.flags new file mode 100644 index 0000000000..5bfa9da5c5 --- /dev/null +++ b/test/files/neg/case-collision2.flags @@ -0,0 +1 @@ +-Ynooptimize -Ybackend:GenBCode -Xfatal-warnings diff --git a/test/files/neg/case-collision2.scala b/test/files/neg/case-collision2.scala new file mode 100644 index 0000000000..924e33005a --- /dev/null +++ b/test/files/neg/case-collision2.scala @@ -0,0 +1,12 @@ +package foo + +class Bippy + +class BIPPY + +object Dingo +object DINGO + +case class Hyrax() +object HyRaX + diff --git a/test/files/neg/valueclasses-impl-restrictions.check b/test/files/neg/valueclasses-impl-restrictions.check index 63924493aa..0af9173f74 100644 --- a/test/files/neg/valueclasses-impl-restrictions.check +++ b/test/files/neg/valueclasses-impl-restrictions.check @@ -6,12 +6,8 @@ valueclasses-impl-restrictions.scala:9: error: implementation restriction: neste This restriction is planned to be removed in subsequent releases. trait I2 { ^ -valueclasses-impl-restrictions.scala:15: error: implementation restriction: nested class is not allowed in value class -This restriction is planned to be removed in subsequent releases. - val i2 = new I2 { val q = x.s } - ^ -valueclasses-impl-restrictions.scala:21: error: implementation restriction: nested class is not allowed in value class +valueclasses-impl-restrictions.scala:23: error: implementation restriction: nested class is not allowed in value class This restriction is planned to be removed in subsequent releases. private[this] class I2(val q: String) ^ -four errors found +three errors found diff --git a/test/files/neg/valueclasses-impl-restrictions.scala b/test/files/neg/valueclasses-impl-restrictions.scala index 137f3f854c..f0577a94aa 100644 --- a/test/files/neg/valueclasses-impl-restrictions.scala +++ b/test/files/neg/valueclasses-impl-restrictions.scala @@ -12,8 +12,10 @@ class X1(val s: String) extends AnyVal { } def y(x: X1) = { - val i2 = new I2 { val q = x.s } + val i2 = new I2 { val q = x.s } // allowed as of SI-7571 i2.z + + { case x => x } : PartialFunction[Int, Int] // allowed } } diff --git a/test/files/pos/t3936/BlockingQueue.java b/test/files/pos/t3936/BlockingQueue.java new file mode 100644 index 0000000000..b902d4528d --- /dev/null +++ b/test/files/pos/t3936/BlockingQueue.java @@ -0,0 +1,3 @@ +package pack; +import java.util.Queue; +public interface BlockingQueue<E> extends Queue<E> { } diff --git a/test/files/pos/t3936/Queue.java b/test/files/pos/t3936/Queue.java new file mode 100644 index 0000000000..25c9087601 --- /dev/null +++ b/test/files/pos/t3936/Queue.java @@ -0,0 +1,2 @@ +package pack; +public interface Queue { } diff --git a/test/files/pos/t3936/Test.scala b/test/files/pos/t3936/Test.scala new file mode 100644 index 0000000000..c867a05ec9 --- /dev/null +++ b/test/files/pos/t3936/Test.scala @@ -0,0 +1,4 @@ +package pack +trait Test { + val b: BlockingQueue[Nothing] +} diff --git a/test/files/pos/t7584.scala b/test/files/pos/t7584.scala new file mode 100644 index 0000000000..52d127ecb9 --- /dev/null +++ b/test/files/pos/t7584.scala @@ -0,0 +1,11 @@ +object Test { + def fold[A, B](f: (A, => B) => B) = ??? + def f[A, B](x: A, y: B): B = ??? + def bip[A, B] = fold[A, B]((x, y) => f(x, y)) + def bop[A, B] = fold[A, B](f) + + // these work: + fold[Int, Int]((x, y) => f(x, y)) + fold[Int, Int](f) +} + diff --git a/src/compiler/scala/tools/cmd/Demo.scala b/test/files/pos/t7591/Demo.scala index fc90140f8f..696d53585b 100644 --- a/src/compiler/scala/tools/cmd/Demo.scala +++ b/test/files/pos/t7591/Demo.scala @@ -3,9 +3,7 @@ * @author Paul Phillips */ -package scala -package tools -package cmd +import scala.tools.cmd._ /** A sample command specification for illustrative purposes. * First take advantage of the meta-options: diff --git a/test/files/run/stream_length.check b/test/files/run/stream_length.check index 9906de773c..d1068f3247 100644 --- a/test/files/run/stream_length.check +++ b/test/files/run/stream_length.check @@ -1 +1,6 @@ +#partest !avian Length: 970299 + +#partest avian +!!!TEST SKIPPED!!! +See SI-7600 for further information. diff --git a/test/files/run/stream_length.scala b/test/files/run/stream_length.scala index 2808fbc495..33929f4b57 100644 --- a/test/files/run/stream_length.scala +++ b/test/files/run/stream_length.scala @@ -10,6 +10,10 @@ object Test { } def main(args: Array[String]) { - println("Length: " + walk(3, "---").length) + if (scala.tools.partest.utils.Properties.isAvian) { + println("!!!TEST SKIPPED!!!") + println("See SI-7600 for further information.") + } else + println("Length: " + walk(3, "---").length) } } diff --git a/test/files/run/t4294.scala b/test/files/run/t4294.scala index fafaf1d8ef..e15c716047 100644 --- a/test/files/run/t4294.scala +++ b/test/files/run/t4294.scala @@ -1,7 +1,12 @@ object Test { def main(args: Array[String]) { + // Skip test on Avian, see SI-7600 for further information + if (!scala.tools.partest.utils.Properties.isAvian) + run() + } + + def run(): Unit = { (Stream.from(1).collect{case x if x > 5000000 => x}: Stream[Int]) - assert((Stream from 1 take 10 collect { case x if x <= 3 => x*x }).sum == 14) } } diff --git a/test/files/run/t5603.scala b/test/files/run/t5603.scala index 8c8038a602..77c2775cc3 100644 --- a/test/files/run/t5603.scala +++ b/test/files/run/t5603.scala @@ -1,7 +1,7 @@ import scala.tools.partest._ import java.io._ import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser +import scala.tools.cmd.CommandLineParser import scala.tools.nsc.{Global, Settings, CompilerCommand} import scala.tools.nsc.reporters.ConsoleReporter diff --git a/test/files/run/t6308.scala b/test/files/run/t6308.scala index bcd89359b0..d23cd6e13e 100644 --- a/test/files/run/t6308.scala +++ b/test/files/run/t6308.scala @@ -1,21 +1,25 @@ import scala.{specialized => sp} +// NOTE: `{ val c = caller; print(""); c }` is used instead of a simple `caller`, +// because we want to prevent tail-call optimization from eliding the stack- +// frames we want to inspect. + object Test { def caller = new Exception().getStackTrace()(1).getMethodName - def f1[@sp(Int) A](a: A, b: Any) = caller - def f2[@sp(Int) A, B](a: A, b: String) = caller - def f3[B, @sp(Int) A](a: A, b: List[B]) = caller - def f4[B, @sp(Int) A](a: A, b: List[(A, B)]) = caller + def f1[@sp(Int) A](a: A, b: Any) = { val c = caller; print(""); c } + def f2[@sp(Int) A, B](a: A, b: String) = { val c = caller; print(""); c } + def f3[B, @sp(Int) A](a: A, b: List[B]) = { val c = caller; print(""); c } + def f4[B, @sp(Int) A](a: A, b: List[(A, B)]) = { val c = caller; print(""); c } - def f5[@sp(Int) A, B <: Object](a: A, b: B) = caller + def f5[@sp(Int) A, B <: Object](a: A, b: B) = { val c = caller; print(""); c } // `uncurryTreeType` calls a TypeMap on the call to this method and we end up with new // type parameter symbols, which are not found in `TypeEnv.includes(typeEnv(member), env)` // in `specSym`. (One of `uncurry`'s tasks is to expand type aliases in signatures.) type T = Object - def todo1[@sp(Int) A, B <: T](a: A, b: String) = caller - def todo2[@sp(Int) A, B <: AnyRef](a: A, b: String) = caller - def todo3[B <: List[A], @specialized(Int) A](a: A, b: B) = caller + def todo1[@sp(Int) A, B <: T](a: A, b: String) = { val c = caller; print(""); c } + def todo2[@sp(Int) A, B <: AnyRef](a: A, b: String) = { val c = caller; print(""); c } + def todo3[B <: List[A], @specialized(Int) A](a: A, b: B) = { val c = caller; print(""); c } def main(args: Array[String]) { val s = "" diff --git a/test/files/run/t6331.scala b/test/files/run/t6331.scala index 4e43a7686e..d9d46f10ea 100644 --- a/test/files/run/t6331.scala +++ b/test/files/run/t6331.scala @@ -1,9 +1,4 @@ -import scala.tools.partest._ -import java.io._ -import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser -import scala.tools.nsc.{Global, Settings, CompilerCommand} -import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.partest.DirectTest // Test of Constant#equals, which must must account for floating point intricacies. object Test extends DirectTest { diff --git a/test/files/run/t6331b.scala b/test/files/run/t6331b.scala index f966abea51..3e09965ee8 100644 --- a/test/files/run/t6331b.scala +++ b/test/files/run/t6331b.scala @@ -1,12 +1,5 @@ -import scala.tools.partest._ -import java.io._ -import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser -import scala.tools.nsc.{Global, Settings, CompilerCommand} -import scala.tools.nsc.reporters.ConsoleReporter - import scala.tools.partest.trace -import scala.util.control.Exception._ +import scala.util.control.Exception.allCatch object Test extends App { diff --git a/test/files/run/t7008-scala-defined.flags b/test/files/run/t7008-scala-defined.flags new file mode 100644 index 0000000000..49f2d2c4c8 --- /dev/null +++ b/test/files/run/t7008-scala-defined.flags @@ -0,0 +1 @@ +-Ybackend:GenASM diff --git a/test/files/run/t7271.scala b/test/files/run/t7271.scala index cb43331a29..69d5ea377e 100644 --- a/test/files/run/t7271.scala +++ b/test/files/run/t7271.scala @@ -1,7 +1,6 @@ import scala.tools.partest._ -import java.io._ import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser +import scala.tools.cmd.CommandLineParser import scala.tools.nsc.{Global, Settings, CompilerCommand} import scala.tools.nsc.reporters.ConsoleReporter import scala.reflect.internal.Positions diff --git a/test/files/run/t7337.scala b/test/files/run/t7337.scala index d878182ed0..9913f8ae45 100644 --- a/test/files/run/t7337.scala +++ b/test/files/run/t7337.scala @@ -1,6 +1,6 @@ import scala.tools.partest._ import scala.tools.nsc._ -import util.{CommandLineParser} +import scala.tools.cmd.CommandLineParser object Test extends DirectTest { override def code = "class C" diff --git a/test/files/run/t7571.scala b/test/files/run/t7571.scala new file mode 100644 index 0000000000..00b9695168 --- /dev/null +++ b/test/files/run/t7571.scala @@ -0,0 +1,12 @@ +class Foo(val a: Int) extends AnyVal { + def foo = { {case x => x + a}: PartialFunction[Int, Int]} + + def bar = (new {}).toString +} + +object Test extends App { + val x = new Foo(1).foo.apply(2) + assert(x == 3, x) + val s = new Foo(1).bar + assert(s.nonEmpty, s) +} diff --git a/test/files/run/t7584.check b/test/files/run/t7584.check new file mode 100644 index 0000000000..9f53e5dde5 --- /dev/null +++ b/test/files/run/t7584.check @@ -0,0 +1,6 @@ +no calls +call A +a +call B twice +b +b diff --git a/test/files/run/t7584.flags b/test/files/run/t7584.flags new file mode 100644 index 0000000000..e8fb65d50c --- /dev/null +++ b/test/files/run/t7584.flags @@ -0,0 +1 @@ +-Xfatal-warnings
\ No newline at end of file diff --git a/test/files/run/t7584.scala b/test/files/run/t7584.scala new file mode 100644 index 0000000000..6d7f4f7ebb --- /dev/null +++ b/test/files/run/t7584.scala @@ -0,0 +1,14 @@ +// Test case added to show the behaviour of functions with +// by-name parameters. The evaluation behaviour was already correct. +// +// We did flush out a spurious "pure expression does nothing in statement position" +// warning, hence -Xfatal-warnings in the flags file. +object Test extends App { + def foo(f: (=> Int, => Int) => Unit) = f({println("a"); 0}, {println("b"); 1}) + println("no calls") + foo((a, b) => ()) + println("call A") + foo((a, b) => a) + println("call B twice") + foo((a, b) => {b; b}) +} diff --git a/test/files/scalacheck/si4147.scala b/test/files/scalacheck/si4147.scala index 1453440ef1..05507b1b18 100644 --- a/test/files/scalacheck/si4147.scala +++ b/test/files/scalacheck/si4147.scala @@ -1,4 +1,4 @@ -import org.scalacheck.Prop.forAll +import org.scalacheck.Prop.{forAll, throws} import org.scalacheck.Properties import org.scalacheck.ConsoleReporter.testStatsEx import org.scalacheck.Gen @@ -64,4 +64,7 @@ object Test extends Properties("Mutable TreeSet") { view.filter(_ < 50) == Set[Int]() && view.filter(_ >= 150) == Set[Int]() } } + + property("ordering must not be null") = + throws(mutable.TreeSet.empty[Int](null), classOf[NullPointerException]) } diff --git a/test/files/scalacheck/treeset.scala b/test/files/scalacheck/treeset.scala index 98e38c8219..018c1b9e29 100644 --- a/test/files/scalacheck/treeset.scala +++ b/test/files/scalacheck/treeset.scala @@ -149,4 +149,7 @@ object Test extends Properties("TreeSet") { val result = subject.foldLeft(subject)((acc, elt) => acc - elt) result.isEmpty } + + property("ordering must not be null") = + throws(TreeSet.empty[Int](null), classOf[NullPointerException]) } diff --git a/test/files/specialized/SI-7343.scala b/test/files/specialized/SI-7343.scala new file mode 100644 index 0000000000..5ee683064c --- /dev/null +++ b/test/files/specialized/SI-7343.scala @@ -0,0 +1,55 @@ +class Parent[@specialized(Int) T] + +object Test extends App { + + /** + * This method will check if specialization is correctly rewiring parents + * for classes defined inside methods. The pattern is important since this + * is how closures are currently represented: as locally-defined anonymous + * classes, which usually end up inside methods. For these closures we do + * want their parents rewired correctly: + * + * ``` + * def checkSuperClass$mIc$sp[T](t: T, ...) = { + * class X extends Parent$mcI$sp // instead of just Parent + * ... + * } + */ + def checkSuperClass[@specialized(Int) T](t: T, expectedXSuper: String) = { + // test target: + // - in checkSuperClass, X should extend Parent + // - in checkSuperClass$mIc$sp, X should extend Parent$mcI$sp + class X extends Parent[T]() + + // get the superclass for X and make sure it's correct + val actualXSuper = (new X).getClass().getSuperclass().getSimpleName() + assert(actualXSuper == expectedXSuper, actualXSuper + " != " + expectedXSuper) + } + + checkSuperClass("x", "Parent") + checkSuperClass(101, "Parent$mcI$sp") + + /** + * This is the same check, but in value. It should work exactly the same + * as its method counterpart. + */ + class Val[@specialized(Int) T](t: T, expectedXSuper: String) { + val check: T = { + class X extends Parent[T]() + + // get the superclass for X and make sure it's correct + val actualXSuper = (new X).getClass().getSuperclass().getSimpleName() + assert(actualXSuper == expectedXSuper, actualXSuper + " != " + expectedXSuper) + t + } + } + + new Val("x", "Parent") + new Val(101, "Parent$mcI$sp") + + /** + * NOTE: The the same check, only modified to affect constructors, won't + * work since the class X definition will always be lifted to become a + * member of the class, making it impossible to force its duplication. + */ +} diff --git a/test/files/specialized/SI-7344.scala b/test/files/specialized/SI-7344.scala new file mode 100644 index 0000000000..1040460bd1 --- /dev/null +++ b/test/files/specialized/SI-7344.scala @@ -0,0 +1,53 @@ +/* Test for SI-7344, where specialized methods inside the bodies of other + * methods are not specialized, although they might as well be. The name + * for the specialized method should not be different depending on the + * outside method/class' specialization. */ + +class Test[@specialized(Int, Double) X](val x: X) { + + def checkSpecialization[Y](@specialized(Int, Double) y: Y): X = { + + // checking the specialization using the method name, which we can + // extract from an exception's stack trace. We can match just the + // prefix, since the compiler will add a suffix to the method name + // during lambdalift, when it lifts the local methods outside. + def specMe[@specialized(Int, Double) T, N](t: T, n: N): Unit = checkNameStartsWith(n.toString) + + // expected to specialize: + specMe("x", "specMe") + specMe(123, "specMe$mIc$sp") + specMe(1.3, new { override def toString = "specMe$mDc$sp" }) + + x + } + + // name matching: + private[this] def checkNameStartsWith(prefix: String): Unit = { + val method = (new Exception).getStackTrace()(1).getMethodName() + assert(method.startsWith(prefix), method + ".startsWith(" + prefix + ") should be true") + } +} + +object Test extends App { + val t1 = new Test("x") + val t2 = new Test(123) + val t3 = new Test(1.3) + + // we want specialization to rewire these, + // that's why they're not in a for loop: + t1.checkSpecialization("x") + + // Prevented by SI-7579: + // The duplicator loses the @specialized annotation, + // so our tree transformation doesn't know it needs to + // specialize specMe inside the duplicated (and specialized) + // variants of the `checkSpecialization` method + // t1.checkSpecialization(123) + // t1.checkSpecialization(1.3) + // t2.checkSpecialization("x") + // t2.checkSpecialization(123) + // t2.checkSpecialization(1.3) + // t3.checkSpecialization("x") + // t3.checkSpecialization(123) + // t3.checkSpecialization(1.3) +} diff --git a/test/files/specialized/spec-ame.check b/test/files/specialized/spec-ame.check index 9c1713cc8a..cf18c01191 100644 --- a/test/files/specialized/spec-ame.check +++ b/test/files/specialized/spec-ame.check @@ -1,3 +1,3 @@ abc 10 -3
\ No newline at end of file +2 diff --git a/test/files/specialized/spec-ame.scala b/test/files/specialized/spec-ame.scala index 79ee4217ed..129fb9f447 100644 --- a/test/files/specialized/spec-ame.scala +++ b/test/files/specialized/spec-ame.scala @@ -13,6 +13,9 @@ object Test { def main(args: Array[String]) { println((new A("abc")).foo.value) println((new A(10)).foo.value) + // before fixing SI-7343, this was printing 3. Now it's printing 2, + // since the anonymous class created by doing new B[T] { ... } when + // T = Int is now rewired to B$mcI$sp instead of just B[Int] println(runtime.BoxesRunTime.integerBoxCount) } } diff --git a/test/scaladoc/run/t5527.scala b/test/scaladoc/run/t5527.scala index 60ae23c1a7..770d4ad13f 100644 --- a/test/scaladoc/run/t5527.scala +++ b/test/scaladoc/run/t5527.scala @@ -1,7 +1,7 @@ import scala.tools.partest._ import java.io._ import scala.tools.nsc._ -import scala.tools.nsc.util.CommandLineParser +import scala.tools.cmd.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory} import scala.tools.nsc.reporters.ConsoleReporter |