diff options
Diffstat (limited to 'src')
371 files changed, 33526 insertions, 8011 deletions
diff --git a/src/actors/scala/actors/package.scala b/src/actors/scala/actors/package.scala index d809816ff6..d176487e03 100644 --- a/src/actors/scala/actors/package.scala +++ b/src/actors/scala/actors/package.scala @@ -7,7 +7,7 @@ package scala * == Guide == * * A detailed guide for the actors library is available - * [[http://www.scala-lang.org/docu/files/actors-api/actors_api_guide.html#]]. + * [[http://docs.scala-lang.org/overviews/core/actors.html]]. * * == Getting Started == * diff --git a/src/attic/README b/src/attic/README deleted file mode 100644 index 9fb600ae57..0000000000 --- a/src/attic/README +++ /dev/null @@ -1,2 +0,0 @@ -This is a holding area for source files which aren't used in -trunk anymore but which we're keeping available for a time.
\ No newline at end of file diff --git a/src/attic/scala/tools/nsc/models/SemanticTokens.scala b/src/attic/scala/tools/nsc/models/SemanticTokens.scala deleted file mode 100644 index a94188a3c1..0000000000 --- a/src/attic/scala/tools/nsc/models/SemanticTokens.scala +++ /dev/null @@ -1,701 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package models - -import java.lang.Character.isJavaIdentifierPart -import java.lang.Thread - -import scala.collection.mutable.{HashMap, HashSet} -import scala.tools.nsc.Global -import scala.tools.nsc.symtab.{Flags, Names} -import scala.tools.nsc.symtab.Flags.DEFERRED -import scala.tools.nsc.util.{BatchSourceFile, SourceFile} - -class SemanticTokens(val compiler: Global) { - import compiler._ - object walker extends symtab.SymbolWalker { - lazy val global : compiler.type = compiler - } - - abstract class Kind {} - object OBJECT extends Kind - object CLASS extends Kind - object TRAIT extends Kind - object DEF extends Kind - object VAL extends Kind - object VAR extends Kind - object ARG extends Kind - object TPARAM extends Kind - - type AnyClass = Class[_] - - // static constants here - - abstract class Token { - def length: Int - def prev: HasNext - def next: HasPrev - } - - def eatKeyword(source: BatchSourceFile, pos: Int, keywords: List[String]) : Int = { - if (keywords.isEmpty) - pos - else if (pos == source.length) - -1 - else if (source.beginsWith(pos, " ")) - eatKeywords(source, pos + 1) - else if (source.beginsWith(pos, keywords.head + " ")) - eatKeywords(source, pos + keywords.head.length + 1) - else - eatKeyword(source, pos, keywords.tail) - } - - def eatKeywords(source: BatchSourceFile, pos: Int): Int = { - val keywords = - "package" :: "val" :: "var" :: "def" :: "class" :: "trait" :: "override" :: "case" :: - "object" :: "sealed" :: "private" :: "protected" :: Nil - if (pos != -1) eatKeyword(source, pos, keywords) - else pos - } - - trait HasNext extends Token { - var next0: HasPrev = _ - def next = next0 - } - - trait HasPrev extends Token { - var prev0: HasNext = _ - def prev = prev0 - } - - abstract class Actual extends HasNext with HasPrev { - def convertToGap: (Int, Actual) = { - val nextGap = next.isInstanceOf[Gap] - val prevGap = prev.isInstanceOf[Gap] - - if (prevGap) { - val ret = prev.length - val gap = prev.asInstanceOf[Gap] - gap.setLength(gap.length + length) - if (nextGap) { - gap.setLength(gap.length + next.length) - gap.next0 = next.next - next.next.prev0 = gap - } else { - gap.next0 = next - next.prev0 = gap - } - (ret, gap) - } - else if (nextGap) { - val gap = next.asInstanceOf[Gap] - gap.setLength(gap.length + length) - gap.prev0 = prev - prev.next0 = gap - (0, gap) - } - else { - prev.next0 = next - next.prev0 = prev - val gap = new Gap(prev) - gap.setLength(length) - (0, gap) - } - - } - def insert(prev1: HasNext) { - next0 = prev1.next - prev0 = prev1 - prev0.next0 = this - next0.prev0 = this - } - - } // Actual - - final class Gap extends Actual { - def this(prev1: HasNext) = { - this() - insert(prev1) - } - override def toString() = "gap-" + length - - var length0: Int = -1 - def length: Int = length0 - def setLength(length1: Int) = length0 = length1 - - // already gap - override def convertToGap: (Int, Actual) = (0, this) - } - - def Process(unit: CompilationUnit) = new Process(unit) - class Process(val unit: CompilationUnit) { - private var doLog = true - def source = unit.source - - def dbg(tree: Tree) = { - def treePos: Position = if (tree ne null) tree.pos else NoPosition; - ( - "TREE=" + tree + - (if (tree ne null) (" CLASS=" + tree.getClass()) else "") + - " SYM=" + tree.symbol + - " POS=" + - treePos.dbgString - )} - - val symbols = new HashMap[Symbol, Info] - - class Info(val symbol: Symbol) { - var defined : Def = _ - val uses = new HashSet[Use] - symbols.update(symbol, this) - } - - def info(symbol: Symbol): Info = - if (symbols.contains(symbol)) symbols(symbol) - else new Info(symbol) - - abstract class Semantic(val symbol: Symbol) extends Actual { - val name = symbol.name.decode.toString.trim - assert(symbol != NoSymbol) - def myOuter = Process.this - - def tpe: Type = symbol.tpe - - def length = name.length() - def info: Info = if (symbols.contains(symbol)) symbols(symbol) else new Info(symbol) - - def kind = { - val term0 = symbol - if (false) null - else if (term0.isVariable) VAR - else if (term0.isValueParameter) ARG - else if (term0.isMethod) DEF - else if (term0.isClass) CLASS - else if (term0.isModule) OBJECT - else if (term0.isValue) VAL - else if (term0.isTypeParameter) TPARAM - else if (term0.isType ) TPARAM - else { - // Console.err.println("UNRECOGNIZED SYMBOL: " + term0 + " " + name); - null - } - } - } - - class Def(symbol0: Symbol) extends Semantic(symbol0) { - info.defined = this - override def toString() = "def-" + name + "-" + symbol.getClass() - } - class Use(symbol0: Symbol, tpe0: Type) extends Semantic(symbol0) { - info.uses += this - - override def tpe : Type = if (tpe0 ne null) tpe0 else super.tpe; - override def toString() = "use-" + name + "-" + symbol.getClass(); - } - val list = new TokenList - - //build(unit.body) - val map = new scala.collection.mutable.LinkedHashMap[Int,Symbol] - map.clear // populate the map. - class visitor extends walker.Visitor { - def contains(pos : Position) = map.contains(pos.point) - def apply(pos : Position) = map(pos.point) - def update(pos : Position, sym : Symbol) : Unit = if (pos.isDefined) { - val offset = pos.point - map(offset) = sym - val isDef = pos.point == sym.pos.point - list.put(offset, (if (isDef) new Def(sym) else new Use(sym, NoType))); - } - } - walker.walk(unit.body, new visitor)(offset => unit.source.identifier(offset, compiler)) - - - // ok start building.... - def build[T <: Tree](trees: List[T]) { - for (tree <- trees) build(tree) - } - - def build(tree0: Tree): Unit = try { - /* if (tree0.pos != NoPosition) */ tree0 match { - case tree: ImplDef => - val pos = eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point) - if (pos == -1) { - - } else buildDef(tree.symbol, eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point)); - tree match { - case cdef: ClassDef => build(cdef.tparams) - case _ => ; - } - build(tree.impl.parents) - build(tree.impl.body) - case tree: ValOrDefDef => - if (!tree.symbol.hasAccessorFlag || tree.symbol.isDeferred) { - // MO: I added !tree.symbol.hasFlag(DEFERRED) in a refactoring where - // getters now can be abstract whereas before they could not. - // Adding the condition thus keeps the old behavior. - // todo: review whether this is correct, or whether abstract getters should be included. - { - val pos : Int = if (tree.name.toString().equals("<init>")) -1 else - eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point); - if (false) Console.err.println("VALDEF: tree=" + tree + " sym=" + tree.symbol + " pos0=" + - tree.symbol.pos + " alias=" + tree.symbol.alias + " pos1=" + - pos + " pos2=" + tree.pos.dbgString + " " + tree.symbol.isSynthetic); - - if (pos != -1 && !tree.isSynthetic) - buildDef(tree.symbol, pos); - } - - if (tree.isInstanceOf[DefDef]) { - val ddef = tree.asInstanceOf[DefDef]; - build(ddef.tparams); - - for (l0 <- ddef.vparamss; arg <- l0) { - val pos0 : Int = if (!unit.source.beginsWith(arg.pos.point, "val ")) arg.pos.point; - else unit.source.skipWhitespace(arg.pos.point + ("val ").length()); - buildDef(arg.symbol, pos0); - build(arg.tpt); - } - } - //TPT=scala.Iterator[DocGenerator.this.compiler0.CompilationUnit] 260 class scala.tools.nsc.ast.Trees$TypeTree scala.Iterator[DocGenerator.this.compiler0.CompilationUnit] class scala.tools.nsc.symtab.Types$$anon$5 - if ((tree.tpt eq null) || (tree.tpt.tpe eq null)) { - //Console.err.println("BAD: " + tree.tpt + " in " + tree); - } else { - //Console.err.println("TPT=" + tree.tpt + " " + tree.tpt.pos + " " + tree.tpt.getClass() + " " + tree.tpt.tpe + " " + tree.tpt.tpe.getClass() + " " + tree.tpt.tpe.getClass().getSuperclass()); - build(tree.tpt); - } - //Console.err.println("RHS: " + tree.rhs + " " + tree.rhs.getClass() + " " + tree.rhs.getClass().getSuperclass()); - build(tree.rhs); - } - case tree: PackageDef => - //Console.err.println("PACKAGE: " + tree.name); - if (false) { - val pos = eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.pointOrElse(-1)) - if (pos != -1) - buildDef(tree.symbol, pos) - } - build(tree.stats) - case tree: Function => - for (arg <- tree.vparams if arg.pos != NoPosition) { - val name = arg.name.toString().trim() - val pos: Int = - if (unit.source.beginsWith(arg.pos.pointOrElse(-1), "val ")) - unit.source.skipWhitespace(arg.pos.pointOrElse(-1) + ("val ").length()) - else if (unit.source.asInstanceOf[BatchSourceFile].content(arg.pos.point) == ':') { - var posx : Int = arg.pos.point - while (unit.source.asInstanceOf[BatchSourceFile].content(posx - 1).isWhitespace) posx = posx - 1 - posx - name.length() - } else arg.pos.point - buildDef(arg.symbol, pos) - build(arg.tpt) - } - build(tree.body) - case tree : TypeTree => - val treex = tree - val tree1 = if (tree.original ne null) tree.original else tree - def classes(clazz: AnyClass): List[AnyClass] = - if (clazz eq null) Nil - else clazz :: classes(clazz.getSuperclass()) - if (tree.original eq null) { - if (false) Console.err.println("NO_ORIGINAL: " + tree + " " + tree.tpe + " " + classes(tree.tpe.getClass())); - } - if (tree.tpe ne null) buildT(tree1, tree.tpe); - def buildT( tree : Tree, tpe : Type) : Unit = if (tree.pos != NoPosition) tpe match { - case tpe0 : TypeRef => tree match { - case apt : AppliedTypeTree => - buildUse(tpe.typeSymbol, apt.tpt.pos.pointOrElse(-1), tpe0); - //Console.err.println("APT: " + treex + " vs. " + treex.original); - //Console.err.println("APT: " + treex.pos + " vs. " + treex.original.pos + " " + unit.source.dbg(treex.original.pos)); - //Console.err.println("APT: " + apt.tpt + " sym0=" + apt.tpt.symbol + " sym1=" + tpe0.sym + " apt.args=" + apt.args + " tpe0.args=" + tpe0.args); - - buildTs (apt.args, tpe0.args); - case ident : Ident => buildUse(tpe0.sym, ident.pos.pointOrElse(-1), tpe0); - case select : Select => - if (select.symbol == NoSymbol) - try { - // build(select); - buildUse(tpe0.typeSymbol, selectPos(select), tpe0); - //Console.err.println("QUALIFIER: " + select.qualifier + " " + unit.source.dbg(select.qualifier.pos) + " " + tpe0.prefix + " " + tpe0.prefix.getClass() + " " + tpe0.prefix.getClass().getSuperclass() +" " + tpe0.prefix.widen + " " + tpe0.prefix.toLongString); - buildT(select.qualifier, tpe0.prefix); - } catch { - case e : Error => - Console.err.println("BUILD_SELECT: " + select + " @ " + tpe0 + " " + (select.pos).dbgString); - throw e; - } - case tpt : TypeTree => - if (tpt.symbol ne null) { - Console.err.println("SYM0 " + tpt.symbol + " " + (tpt.pos).dbgString); - buildUse(tpt.symbol, tpt.pos.pointOrElse(-1), tpe0); - } else if (tpe0.typeSymbol ne null) { - //Console.err.println("TYPE_SYM1 " + tpe0.symbol + " " + unit.source.dbg(tpt.pos)); - buildUse(tpe0.typeSymbol, tpt.pos.pointOrElse(-1), tpe0); - } else { - Console.err.println("UNKNOWN TPT0: " + (tpt.pos).dbgString + " tpt=" + tpt + " " + tpt.symbol + " tpe0="+ tpe0 + " " + tpe0.typeSymbol + " tpe0.args=" + tpe0.args); - } - case sft : SelectFromTypeTree => - build(sft.qualifier); // XXX: broken - if (false) Console.err.println("SFTT: " + sft + " sym=" + sft.symbol + " name=" + sft.name + " qual=" + sft.qualifier + " qual.sym=" + - sft.qualifier.symbol + - " qual.pos=" + (sft.qualifier.pos).dbgString + " symbol=" + sft.symbol + " type=" + tpe0 + - " type.sym=" + tpe0.typeSymbol); - case _ => Console.err.println("UNKNOWN TPT2: " + tree + " vs. " + tpe0 + " " + tree.getClass() + " " + (tree.pos).dbgString); - } - case tpe0 : MethodType => tree match { - case tpt: TypeTree => - if (tpt.original ne null) buildT(tpt.original, tpe); - else { - Console.err.println("UNKNOWN TPT3: " + tree + " vs. " + tpe0 + " " + (tree.pos).dbgString); - } - case ident : Ident => buildT(ident, tpe0.resultType); - case select : Select => buildT(select, tpe0.resultType); - case _ => Console.err.println("UNKNOWN TPE: " + tree + " vs. " + tpe0 + " " + tree.getClass()); - } - case tpe0 : RefinedType => tree match { - case cpt : CompoundTypeTree => - buildTs(cpt.templ.parents, tpe0.parents); - - case _ : TypeTree => - // Console.err.println("UNKNOWN TPE13: " + dbg(tree) + " tpe0=" + tpe0 + " " + tpe0.parents); - case _ => - if (false) Console.err.println("UNKNOWN TPE5: " + dbg(tree) + " tpe0=" + tpe0 + " " + tpe0.parents); - } - case tpe0 : ThisType => tree match { - case stt : SingletonTypeTree => stt.ref match { - case ths : This => build(ths); - - case _ => Console.err.println("UNKNOWN TPE11: " + tpe0 + " " + stt + " " + stt.ref + " " + stt.ref.getClass() + " " + (tree.pos).dbgString); - } - case tt : This => - case _ : Ident => - case _ : Select => - case tt : TypeTree => - if (false) Console.err.println("UNKNOWN TPE12: " + tpe0 + " " + tree + " " + tree.getClass() + " " + (tree.pos).dbgString); - case _ => - if (false) Console.err.println("UNKNOWN TPE10: " + tpe0 + " " + tree + " " + tree.getClass() + " " + (tree.pos).dbgString); - } - case tpe0 : SingleType => tree match { - case ident : Ident => buildUse(tpe0.sym, ident.pos.pointOrElse(-1), tpe0); - case select : Select => - buildUse(tpe0.termSymbol, selectPos(select), tpe0); - //Console.err.println("QUALIFIER-0: " + select.qualifier + " " + unit.source.dbg(select.qualifier.pos) + " " + tpe0.prefix + " " + tpe0.prefix.getClass() + " " + tpe0.prefix.getClass().getSuperclass() +" " + tpe0.prefix.widen + " " + tpe0.prefix.toLongString); - buildT(select.qualifier, tpe0.prefix); - - case _ => - if (false) Console.err.println("UNKNOWN TPE8: " + tree + " " + (tree.pos).dbgString + " TPE=" + tpe0 + " PRE=" + tpe0.pre + " SYM=" + tpe0.sym); - - } - case ctype : ConstantType => - case ErrorType => - case _ => { - if (false) Console.err.println("UNKNOWN TPE4: " + tree + " " + tpe + " " + tpe.getClass() + " " + (tree.pos).dbgString); - } - }; - def buildTs(trees : List[Tree], types : List[Type]): Unit = if (!trees.isEmpty && !types.isEmpty) { - buildT (trees.head, types.head); - buildTs(trees.tail, types.tail); - } else if (trees.isEmpty != types.isEmpty) { - if (false && doLog) { - Console.println("" + treex + " vs. " + treex.original); - if (treex.original ne null) - Console.println("" + treex.tpe + " vs. " + treex.original.tpe); - logError("Tree vs. Type mismatch: " + trees + " " + types + " " + (tree.pos).dbgString, null); - doLog = false; - } - }; - case tree: Bind => - buildDef(tree.symbol, tree.pos.pointOrElse(-1)) - build(tree.body) - case tree: Ident => - buildUse(tree.symbol, tree.pos.pointOrElse(-1), tree.tpe) - case tree: Select => - try { - build(tree.qualifier) - } catch { - case e : Error => Console.err.println("SELECTQ: " + tree + " " + tree.qualifier + " " + (tree.qualifier.pos).dbgString); throw e; - } - try { - if (tree.pos.isDefined && tree.pos.point >= unit.source.length) { - if (false) Console.err.println("BAD_SELECT_QUALIFIER " + tree + " @ " + (tree.pos).dbgString); - - } else { - //Console.err.println("SELECT-0: " + tree.symbol + " " + tree.pos.dbgString + " " + (tree.pos - selectPos(tree))); - buildUse(tree.symbol, selectPos(tree), tree.tpe); - } - } catch { - case e : Error => Console.err.println("SELECTU: " + tree + " " + tree.symbol + " " + tree.pos.dbgString); throw e; - } - case tree: TypeApply => - //Console.err.println("TYPE_APPLY: " + tree + " " + tree.pos.dbgString); - if (!tree.args.isEmpty) { - //Console.err.println("ARGS: " + unit.source.dbg(tree.args0.head.pos)); - } - build(tree.fun) - build(tree.args) - case tree: Apply => - - build(tree.fun) - build(tree.args) - case tree: GenericApply => - - build(tree.fun) - build(tree.args) - case tree: Typed => - build(tree.expr) - build(tree.tpt) - case tree: Block => - if (false) { - if (!tree.stats.isEmpty) - Console.err.println("BLOCKS: " + tree.stats.head + " " + tree.stats.head.getClass()); - Console.err.println("BLOCKE: " + tree.expr + " " + tree.expr.getClass()) - } - build(tree.stats) - build(tree.expr) - case tree: CaseDef => - build(tree.pat) - build(tree.guard) - build(tree.body) - case tree : Assign => build(tree.lhs); build(tree.rhs); - case tree : If => build(tree.cond); build(tree.thenp); build(tree.elsep); - case tree : New => - //Console.err.println("NEW: " + tree.tpt + " " + tree.tpt.getClass()); - build(tree.tpt); - case tree : Match => build(tree.selector); build(tree.cases); - case tree : Return => build(tree.expr); - case tree : LabelDef => build(tree.rhs); - case tree : Throw => build(tree.expr); - case tree : Try => build(tree.block); build(tree.catches); build(tree.finalizer); - case tree : Alternative => build(tree.trees); - case tree : This => - - if (tree.symbol ne null) buildUse(tree.symbol, tree.pos.pointOrElse(-1), tree.tpe); - //Thread.dumpStack(); - case tree : TypeDef => - //Console.err.println("ALIAS: " + tree); - build(tree.rhs); build(tree.tparams); buildDef(tree.symbol, tree.pos.pointOrElse(-1)); - case tree : DocDef => build(tree.definition); - case tree: Import => build(tree.expr) - case tree: AppliedTypeTree => ; - case tree: Annotated => ; - case tree: SingletonTypeTree => ; - case tree: Super => ; - case tree: Literal => ; - case EmptyTree => ; - case _ => ; - Console.err.println("BAIL: " + (tree0.pos) + " " + tree0 + " " + tree0.getClass()); - } - } catch { - case t: Throwable => - logError("Error occured at " + (tree0.pos), t) - } - - def buildUse(term: Symbol, pos: Int, tpe: Type) = buildSym(term, pos, false, tpe) - def buildDef(term: Symbol, pos: Int) = buildSym(term, pos, true, null) - - def buildSym(term: Symbol, pos: Int, isDef: Boolean, tpe: Type): Unit = - if (term.hasAccessorFlag) - buildSym(analyzer.underlying(term), pos, isDef, tpe) - else if (pos == -1) { - //Console.err.println("NOPOS: " + term) - //Thread.dumpStack() - } - else if (term != NoSymbol) { - val name = term.name.decode.toString.trim - val buf = unit.source.asInstanceOf[BatchSourceFile].content - val cs = name.toChars - var idx = 0 - if (cs.length + pos > buf.length) return - else while (idx < cs.length) { - if (buf(pos + idx) != cs(idx)) { - //Console.err.println("MISMATCH: " + name + "[" + idx + "] " + unit.source.dbg(pos)); - //Thread.dumpStack(); - return; - } - else idx = idx + 1; - } - if (cs.length + pos + 1 < buf.length) { - if (isJavaIdentifierPart(buf(pos + cs.length))) { - //Console.err.println("MISMATCH: " + name + "[last] " + unit.source.dbg(pos)); - return; - } - } - try { - list.put(pos, (if (isDef) new Def(term) else new Use(term, tpe))); - } catch { - case e : Error => e.printStackTrace(); - } - } - - def selectPos(tree: Select): Int = if (tree.pos == NoPosition) -1 else { - val buf = unit.source.asInstanceOf[BatchSourceFile].content - if (tree.pos.point >= buf.length) { - if (false) { - Console.err.println("" + tree + "@" + tree.pos + " not in " + - unit.source.file.name + "[" + buf.length + "]"); - Thread.dumpStack() - abort() - } - return 0 - } - - val pos : Int = - if (buf(tree.pos.point) != '.') tree.pos.point - else { - def f(x : Int) : Int = { - if (buf(x).isWhitespace) f(x + 1) - else x - } - f(tree.pos.point + 1) - } - pos - }; - - class TokenList { - object begin extends HasNext { - def prev = this - def length = 0 - } - object end extends HasPrev { - def next = this - def length = 0 - } - // initialize - begin.next0 = end - end.prev0 = begin - - def tokenAt(offset: Int) = { - cursor.seek(offset) - if (cursor.token.isInstanceOf[Semantic]) cursor.token.asInstanceOf[Semantic] - else null - } - - def put(offset: Int, tok: Actual): Unit = tok match { - case tok0: Semantic => put(offset, tok0) - case gap: Gap => - } - - def put(offset: Int, tok: Semantic) { - cursor.seek(offset); - if (cursor.token == end) { - assert(offset >= cursor.offset); - if (offset > cursor.offset) { - // add a gap. - val gap = new Gap(end.prev); - gap.setLength(offset - cursor.offset); - cursor.offset = offset; - } - // append. - tok.insert(end.prev); - cursor.offset = cursor.offset + tok.length; - } else if (!cursor.token.isInstanceOf[Gap]) { - val sem = cursor.token.asInstanceOf[Semantic]; - if (sem.symbol == tok.symbol) return; - if (sem.symbol != tok.symbol && - sem.symbol.getClass() == tok.symbol.getClass() && - sem.symbol.pos == tok.symbol.pos) return; - } else { - val gap = cursor.token.asInstanceOf[Gap]; - if (!(offset - cursor.offset + tok.length <= gap.length)) { - Console.err.println("LIST =" + this); - Console.err.println("OFFSET=" + offset + " " + tok + " " + tok.length); - Console.err.println(" " + cursor.offset + " " + gap.length); - gap.length0 = offset - cursor.offset + tok.length - //abort(); - } - if (offset == cursor.offset) { - // replace or prepend - tok.prev0 = gap.prev0; - if (tok.length == gap.length) { // replace gap - tok.next0 = gap.next0; - } else { - gap.setLength(gap.length - tok.length); - tok.next0 = gap; - } - tok.next0.prev0 = tok; - tok.prev0.next0 = tok; - cursor.token = tok; - } else { - // append - val diff = (cursor.offset + gap.length) - (offset + tok.length); - - gap.setLength(gap.length - tok.length - diff); - tok.prev0 = gap; - tok.next0 = gap.next; - tok.next0.prev0 = tok; - tok.prev0.next0 = tok; - if (diff != 0) { - val gap0 = new Gap(tok); - gap0.setLength(diff); - } - } - } - } - - override def toString(): String = { - var node = begin.next - var str = "" - while (node != end) { - str = str + " " + node - node = node.next - } - str - } - - object cursor { - var token: Token = end - var offset: Int = 0 - - def next(): Unit = if (token == end) end else { - offset = offset + token.length - token = token.next - } - def prev(): Unit = if (token.prev == begin) token else { - offset = offset - token.prev.length - token = token.prev - } - def seek(soffset: Int): Unit = if (soffset == 0) { - token = begin.next - offset = 0 - } else { - assert(soffset > 0) - while (offset > soffset) prev; - while (offset + token.length <= soffset && token != end) { - val len0 = offset; - next; - } - } - def convertToGap = if (token.isInstanceOf[Actual]) { - val ret = token.asInstanceOf[Actual].convertToGap; - offset = offset - ret._1; - token = ret._2; - } - } - - // add or delete characters - def adjust(offset: Int, /* where */ - length: Int, /* how many characters are modified */ - to : Int /* length of new string */) = { - cursor.seek(offset) - if (cursor.token != end) { - cursor.convertToGap - while (cursor.offset + cursor.token.length < offset + length && cursor.token.next != end) { - val save = cursor.offset - cursor.next - cursor.convertToGap - assert(cursor.offset == save) - } - if (length != to && cursor.token != end) { - val diff = to - length; - val gap = cursor.token.asInstanceOf[Gap]; - gap.setLength(gap.length + diff); - }; - } - } - - } // TokenList - - } -} - diff --git a/src/attic/scala/tools/nsc/models/Signatures.scala b/src/attic/scala/tools/nsc/models/Signatures.scala deleted file mode 100644 index a5dfce6c56..0000000000 --- a/src/attic/scala/tools/nsc/models/Signatures.scala +++ /dev/null @@ -1,84 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package models - -import scala.collection.mutable.{HashMap, HashSet} -import scala.tools.nsc.{Global => Compiler} -import scala.tools.nsc.symtab.{Flags, Names} -import scala.tools.nsc.util.{ Position, SourceFile } - -/** This class ... - * - * @author Sean McDirmid - * @version 1.0 - */ -class Signatures(val compiler: Compiler) { - import compiler._ - - class Signature(val name: String, val children: List[Signature]) { - def asString: String = name + "[" + asString0(children) + "]" - } - - def sort(sigs: List[Signature]) = sigs sortBy (_.name) reverse - - def asString0(sigs: List[Signature]): String = - sort(sigs) map (_.asString) mkString - - def signature(unit: CompilationUnit): String = - asString0(signature(unit.body, Nil)) - - def signature(trees: List[Tree]): List[Signature] = { - var ret : List[Signature] = Nil - for (tree <- trees) ret = signature(tree, ret) - ret - } - - /** - * @param tree0 ... - * @param rest ... - * @return ... - */ - def signature(tree0: Tree, rest: List[Signature]): List[Signature] = tree0 match { - case tree: MemberDef => if (!tree.mods.isPrivate) { - val name = "" + tree.name + "::" + - (tree.mods &~ Flags.SYNTHETIC) - - val children: List[Signature] = tree match { - case impl: ImplDef - //if (!impl.name.toString.contains("$anonfun$")) => - if (impl.name.pos("$anonfun$") == name.length) => - val supers = new Signature("$$supers", signature(impl.impl.parents)) - val body = new Signature("$$body", signature(impl.impl.body)) - val ret = supers :: body :: Nil - impl match { - case cdef: ClassDef => - new Signature("$$tparams", signature(cdef.tparams)) :: ret - case _ => - ret - } - case vdef: ValOrDefDef => - val ret = signature(vdef.tpt, Nil) - vdef match { - case ddef : DefDef => - val tparams = new Signature("$$tparams", signature(ddef.tparams)) - var vparamss : List[Signature] = Nil - for (list <- ddef.vparamss) - vparamss = signature(list) ::: vparamss - new Signature("$$ret", ret) :: tparams :: vparamss - case _ => - ret - } - case pdef: PackageDef => signature(pdef.stats) - case _ => Nil - } - new Signature(name, children) :: rest - - } else rest - case tree: TypeTree => new Signature("" + tree.tpe, Nil) :: rest - case _ => rest - } -} diff --git a/src/attic/scala/tools/nsc/symtab/SymbolWalker.scala b/src/attic/scala/tools/nsc/symtab/SymbolWalker.scala deleted file mode 100644 index 01b5c76cdc..0000000000 --- a/src/attic/scala/tools/nsc/symtab/SymbolWalker.scala +++ /dev/null @@ -1,252 +0,0 @@ -package scala.tools.nsc -package symtab - -trait SymbolWalker { - val global : Global - import global._ - import scala.collection.mutable.LinkedHashSet - trait Visitor { - def update(pos : Position, sym : Symbol) : Unit - def contains(pos : Position) : Boolean - def apply(pos : Position) : Symbol - def putDef(sym : Symbol, pos : Position) : Unit = () - } - /* - implicit def map2use(map : Map[Position,Symbol]) = new Visitor { - def update(pos : Position, sym : Symbol) : Unit = map.update(pos, sym) - def contains(pos : Position) : Boolean = map.contains(pos) - def apply(pos : Position) : Symbol = map.apply(pos) - } - */ - private def validSym(t: Tree) = t.symbol != NoSymbol && t.symbol != null - private def validSym(tp: Type) = tp != null && tp.typeSymbol != NoSymbol && tp.typeSymbol != null - private def notNull(tp: Type) = tp.typeSymbol != null - private def isNoSymbol(t: Tree) = t.symbol eq NoSymbol - - def walk(tree: Tree, visitor : Visitor)(fid : (util.Position) => Option[String]) : Unit = { - val visited = new LinkedHashSet[Tree] - def f(t : Tree) : Unit = { - if (visited.add(t)) return - - def fs(l: List[Tree]) = l foreach f - def fss(l: List[List[Tree]]) = l foreach fs - - val sym = (t, t.tpe) match { - case (Super(_,_),SuperType(_,supertp)) if validSym(supertp) => supertp.typeSymbol - case _ if validSym(t) => t.symbol - case (t: TypeTree, tp) if validSym(tp) => tp.typeSymbol - case (t: TypeTree, tp) if validSym(tp.resultType) => tp.resultType.typeSymbol - case (t, tpe: Type) if isNoSymbol(t) && tpe.termSymbol != null => - if (t.isTerm) tpe.termSymbol - else t.tpe match { - case x: TypeRef => x.sym // XXX: looks like a bug - case _ => tpe.typeSymbol - } - case _ => NoSymbol - } - - if (sym != null && sym != NoSymbol /* && !sym.hasFlag(SYNTHETIC) */) { - var id = fid(t.pos) - val doAdd = if (id.isDefined) { - if (id.get.charAt(0) == '`') id = Some(id.get.substring(1, id.get.length - 1)) - val name = sym.name.decode.trim - if ((name startsWith id.get) || (id.get startsWith name)) true - else { - false - } - } else false - if (doAdd) { - - if (!visitor.contains(t.pos)) { - visitor(t.pos) = sym - } else { - val existing = visitor(t.pos) - if (sym.sourceFile != existing.sourceFile || sym.pos != existing.pos) { - (sym,existing) match { - case (sym,existing) if sym.pos == existing.pos => - case (sym : TypeSymbol ,_ : ClassSymbol) => visitor(t.pos) = sym - case (_ : ClassSymbol,_ : TypeSymbol) => // nothing - case _ if sym.isModule && existing.isValue => // nothing - case _ if sym.isClass && existing.isMethod => // nothing - case _ => - assert(true) - } - } - }} - } - t match { - case t : DefTree if t.symbol != NoSymbol => - if (t.pos != NoPosition) - visitor.putDef(t.symbol, t.pos) - if (t.symbol.isClass) { - val factory = NoSymbol // XXX: t.symbol.caseFactory - if (factory != NoSymbol) { - visitor.putDef(factory, t.pos) - } - } - case t : TypeBoundsTree => f(t.lo); f(t.hi) - case t : TypeTree if t.original != null => - def h(original : Tree, tpe : Type): Unit = try { - if (original.tpe == null) - original.tpe = tpe - (original) match { - case (AppliedTypeTree(_,trees)) if tpe.isInstanceOf[TypeRef] => - val types = tpe.asInstanceOf[TypeRef].args - trees.zip(types).foreach{ - case (tree,tpe) => assert(tree != null && tpe != null); h(tree, tpe) - } - case _ => - } - } - if (t.original.tpe == null) { - val dup = t.original.duplicate - h(dup,t.tpe) - f(dup) - } else f(t.original) - () - case _ => - } - (t) match { - case (t : MemberDef) if t.symbol != null && t.symbol != NoSymbol => - val annotated = if (sym.isModule) sym.moduleClass else sym - val i = t.mods.annotations.iterator - val j = annotated.annotations.iterator - while (i.hasNext && j.hasNext) { - val tree = i.next - val ainfo = j.next - val sym = ainfo.atp.typeSymbol - tree.setType(ainfo.atp) - tree.setSymbol(sym) - f(tree) - } - - case _ => - } - t match { - case tree: ImplDef => - fs(tree.impl.parents); f(tree.impl.self); fs(tree.impl.body) - tree match { - case tree : ClassDef => fs(tree.tparams) - case _ => - } - case tree: PackageDef => fs(tree.stats) - case tree: ValOrDefDef => - f(tree.rhs); - if (tree.tpt != null) { - f(tree.tpt) - } - tree match { - case tree : DefDef => fs(tree.tparams); fss(tree.vparamss) - case _ => - } - case tree: Function => fs(tree.vparams); f(tree.body) - case tree : Bind => f(tree.body) - case tree : Select => - val qualifier = if (tree.tpe != null && tree.qualifier.tpe == null) { - val pre = tree.tpe.prefix - val qualifier = tree.qualifier.duplicate - qualifier.tpe = pre - qualifier - } else tree.qualifier - - f(qualifier) - case tree : Annotated => f(tree.annot); f(tree.arg) - case tree : GenericApply => f(tree.fun); fs(tree.args) - case tree : UnApply => f(tree.fun); fs(tree.args) - case tree : AppliedTypeTree => - if (tree.tpe != null) { - val i = tree.tpe.typeArgs.iterator - val j = tree.args.iterator - while (i.hasNext && j.hasNext) { - val tpe = i.next - val arg = j.next - if (arg.tpe == null) { - arg.tpe = tpe - } - } - if (tree.tpt.tpe == null) { - tree.tpt.tpe = tree.tpe - } - - } - f(tree.tpt); fs(tree.args) - - case tree : ExistentialTypeTree=> - if (tree.tpt.tpe == null) { - tree.tpt.tpe = tree.tpe - } - - f(tree.tpt) - fs(tree.whereClauses) - case tree : SingletonTypeTree => - if (tree.ref.tpe == null) { - val dup = tree.ref.duplicate - dup.tpe = tree.tpe - f(dup) - } else f(tree.ref) - case tree : CompoundTypeTree => - if (tree.tpe != null && tree.tpe.typeSymbol != null && tree.tpe.typeSymbol.isRefinementClass) tree.tpe.typeSymbol.info match { - case tpe : RefinedType => - tpe.parents.zip(tree.templ.parents).foreach{ - case (tpe,tree) => - if (tree.hasSymbol && (tree.symbol == NoSymbol || tree.symbol == null)) { - tree.symbol = tpe.typeSymbol - } - } - - case _ => - } - - f(tree.templ) - case tree : Template => fs(tree.parents); f(tree.self); fs(tree.body) - case tree : SelectFromTypeTree => { - if (tree.qualifier.tpe == null) tree.tpe match { - case tpe : TypeRef => - // give it a type! - tree.qualifier.tpe = tpe.prefix - case _ => - // tree.tpe.pre - } - f(tree.qualifier) - } - case tree : Literal => - /* - if (tree.tpe != null && tree.tpe.typeSymbol == definitions.ClassClass) { - // nothing we can do without original tree. - } - */ - - case tree : Typed => f(tree.expr); f(tree.tpt) - case tree : Block => fs(tree.stats); f(tree.expr) - case tree: CaseDef => f(tree.pat);f(tree.guard);f(tree.body) - case tree : Assign => f(tree.lhs); f(tree.rhs); - case tree : If => f(tree.cond); f(tree.thenp); f(tree.elsep); - case tree : New => f(tree.tpt); - case tree : Match => f(tree.selector); fs(tree.cases); - case tree : Return => f(tree.expr); - case tree : LabelDef => f(tree.rhs); - case tree : Throw => f(tree.expr); - case tree : Try => f(tree.block); fs(tree.catches); f(tree.finalizer); - case tree : Alternative => fs(tree.trees); - case tree : TypeDef => - (tree.tpe,sym) match { - case (null,sym : TypeSymbol) if (sym.rawInfo.isComplete) => - if (tree.tparams.isEmpty) { - if (tree.rhs.tpe == null) tree.rhs.tpe = sym.info - f(tree.rhs) - } else { - val tree0 = AppliedTypeTree(tree.rhs, tree.tparams) - tree0.tpe = sym.info - f(tree0) - } - case _ => f(tree.rhs); fs(tree.tparams) - } - case tree : DocDef => f(tree.definition); - case tree: Import => f(tree.expr) - case _ => - } - } - f(tree) - } - -} diff --git a/src/compiler/scala/reflect/internal/AnnotationInfos.scala b/src/compiler/scala/reflect/internal/AnnotationInfos.scala index fe0175fe72..91e1c3d50d 100644 --- a/src/compiler/scala/reflect/internal/AnnotationInfos.scala +++ b/src/compiler/scala/reflect/internal/AnnotationInfos.scala @@ -18,8 +18,6 @@ trait AnnotationInfos extends api.AnnotationInfos { self: SymbolTable => // the Symbol's field directly. For Type, a new AnnotatedType is // created which wraps the original type. trait Annotatable[Self] { - self: Self => - /** The annotations on this type. */ def annotations: List[AnnotationInfo] // Annotations on this type. def setAnnotations(annots: List[AnnotationInfo]): Self // Replace annotations with argument list. @@ -76,13 +74,34 @@ trait AnnotationInfos extends api.AnnotationInfos { self: SymbolTable => */ case class ScalaSigBytes(bytes: Array[Byte]) extends ClassfileAnnotArg { override def toString = (bytes map { byte => (byte & 0xff).toHexString }).mkString("[ ", " ", " ]") - lazy val encodedBytes = ByteCodecs.encode(bytes) - def isLong: Boolean = (encodedBytes.length > 65535) + lazy val encodedBytes = ByteCodecs.encode(bytes) // TODO remove after migration to ASM-based GenJVM complete + def isLong: Boolean = (encodedBytes.length > 65535) // TODO remove after migration to ASM-based GenJVM complete + lazy val sevenBitsMayBeZero: Array[Byte] = { + mapToNextModSevenBits(scala.reflect.internal.pickling.ByteCodecs.encode8to7(bytes)) + } + def fitsInOneString: Boolean = { + val numZeros = (sevenBitsMayBeZero count { b => b == 0 }) + val res = (sevenBitsMayBeZero.length + numZeros) <= 65535 + assert(this.isLong == !res, "As things stand, can't just swap in `fitsInOneString()` for `isLong()`") + res + } def sigAnnot: Type = if (this.isLong) definitions.ScalaLongSignatureAnnotation.tpe else definitions.ScalaSignatureAnnotation.tpe + + private def mapToNextModSevenBits(src: Array[Byte]): Array[Byte] = { + var i = 0 + val srclen = src.length + while (i < srclen) { + val in = src(i) + src(i) = (if (in == 0x7f) 0.toByte else (in + 1).toByte) + i += 1 + } + src + } + } /** Represents a nested classfile annotation */ @@ -119,7 +138,11 @@ trait AnnotationInfos extends api.AnnotationInfos { self: SymbolTable => // necessary for reification, see Reifiers.scala for more info private var orig: Tree = EmptyTree def original = orig - def setOriginal(t: Tree): this.type = { orig = t; this } + def setOriginal(t: Tree): this.type = { + orig = t + this setPos t.pos + this + } override def toString = ( atp + diff --git a/src/compiler/scala/reflect/internal/ClassfileModel.scala b/src/compiler/scala/reflect/internal/ClassfileModel.scala deleted file mode 100644 index c96459037f..0000000000 --- a/src/compiler/scala/reflect/internal/ClassfileModel.scala +++ /dev/null @@ -1,169 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.reflect.internal - -import java.io.DataInput -import ConstantPool._ -import ClassfileConstants._ - -trait ClassfileModel { - type Result - type Entry - type InterfaceInfo - type MemberInfo - type AttributeInfo - type InnerClassInfo - - protected implicit def EntryArrayTag: ArrayTag[Entry] - protected implicit def InterfaceInfoArrayTag: ArrayTag[InterfaceInfo] - protected implicit def MemberInfoArrayTag: ArrayTag[MemberInfo] - protected implicit def AttributeInfoArrayTag: ArrayTag[AttributeInfo] - protected implicit def InnerClassInfoArrayTag: ArrayTag[InnerClassInfo] - - // These could be implemented to jump forward in the stream if the - // result is not wanted. - def readConstantPoolEntry(): Entry - def readInterface(): InterfaceInfo - def readMember(): MemberInfo - def readAttribute(): AttributeInfo - def readInnerClass(): InnerClassInfo - - def createInfo( - version: JvmVersion, - entries: Array[Entry], - flags: Int, - name: String, - superName: String, - interfaces: Array[InterfaceInfo], - fields: Array[MemberInfo], - methods: Array[MemberInfo], - attributes: Array[AttributeInfo] - ): Result -} - -abstract class StreamingClassfileModel extends ClassfileModel { - protected[this] val in: DataInput - private[this] var name: String = _ - private[this] var entries: Array[PoolEntry] = _ - - type Entry = PoolEntry - - // These translate null into "", it's less troublesome. - protected def nameAt(idx: Int) = entries(idx) match { - case x: Name_Info => stringAt(x.name_index).replace('/', '.') - case _ => "" - } - protected def stringAt(idx: Int) = entries(idx) match { - case x: Utf8_info => x.stringValue - case _ => "" - } - - protected def u4 = in.readInt - protected def u2 = in.readUnsignedShort.toChar - protected def u1 = in.readUnsignedByte - - // The constant_pool table is indexed from 1 to constant_pool_count−1. - protected def readConstantPool(): Array[Entry] = { - val count = u2 - val entries = new Array[Entry](count) - var i = 1 - while (i < count) { - val entry = readConstantPoolEntry() - entries(i) = entry - i += entry.width - } - entries - } - protected def readInterfaces() = { - val count = u2 - val interfaces = new Array[InterfaceInfo](count) - var i = 0 - while (i < count) { - interfaces(i) = readInterface() - i += 1 - } - interfaces - } - protected def readMembers() = { - val count = u2 - val arr = new Array[MemberInfo](count) - var i = 0 - while (i < count) { - arr(i) = readMember() - i += 1 - } - arr - } - protected def readAttributes(): Array[AttributeInfo] = { - val count = u2 - val arr = new Array[AttributeInfo](count) - var i = 0 - while (i < count) { - arr(i) = readAttribute() - i += 1 - } - arr - } - protected def readInnerClasses() = { - val count = u2 - val arr = new Array[InnerClassInfo](count) - var i = 0 - while (i < count) { - arr(i) = readInnerClass() - i += 1 - } - arr - } - protected def thisClass = name - - def parse() = { - assert(u4 == JAVA_MAGIC, "Bad magic number") - val version = JvmVersion(u2, u2) - this.entries = readConstantPool() - val flags = u2.toShort - this.name = nameAt(u2) - val superName = nameAt(u2) - val interfaces = readInterfaces() - val fields = readMembers() - val methods = readMembers() - val attributes = readAttributes() - - try createInfo(version, entries, flags, name, superName, interfaces, fields, methods, attributes) - finally entries = null - } -} - -abstract class ScalacClassfileModel extends StreamingClassfileModel { - type Result = JvmClassInfo - type InterfaceInfo = String - type MemberInfo = JvmMemberInfo - type AttributeInfo = JvmAttributeInfo - type InnerClassInfo = JvmInnerClassInfo - - protected implicit def EntryArrayTag = arrayTag[PoolEntry] - protected implicit def InterfaceInfoArrayTag = arrayTag[InterfaceInfo] - protected implicit def MemberInfoArrayTag = arrayTag[MemberInfo] - protected implicit def AttributeInfoArrayTag = arrayTag[AttributeInfo] - protected implicit def InnerClassInfoArrayTag = arrayTag[InnerClassInfo] - - def readConstantPoolEntry(): PoolEntry - def readInterface(): String - def readMember(): JvmMemberInfo - def readAttribute(): JvmAttributeInfo - def readInnerClass(): JvmInnerClassInfo - - def createInfo( - version: JvmVersion, - entries: Array[PoolEntry], - flags: Int, - name: String, - superName: String, - interfaces: Array[String], - fields: Array[JvmMemberInfo], - methods: Array[JvmMemberInfo], - attributes: Array[JvmAttributeInfo] - ): JvmClassInfo = new JvmClassInfo(name, superName, interfaces, fields, methods, attributes) -} diff --git a/src/compiler/scala/reflect/internal/ConstantPool.scala b/src/compiler/scala/reflect/internal/ConstantPool.scala deleted file mode 100644 index f8dd7b9845..0000000000 --- a/src/compiler/scala/reflect/internal/ConstantPool.scala +++ /dev/null @@ -1,78 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.reflect.internal - -import ClassfileConstants._ - -object ConstantPool { - final case class JvmVersion(minorVersion: Int, majorVersion: Int) - - type UShort = Char - - final val CONSTANT_Utf8 = 1 - final val CONSTANT_Integer = 3 - final val CONSTANT_Float = 4 - final val CONSTANT_Long = 5 - final val CONSTANT_Double = 6 - final val CONSTANT_Class = 7 - final val CONSTANT_String = 8 - final val CONSTANT_Fieldref = 9 - final val CONSTANT_Methodref = 10 - final val CONSTANT_InterfaceMethodref = 11 - final val CONSTANT_NameAndType = 12 - - /* - 4.2.2 Unqualified Names - Names of methods, fields and local variables are stored as unqualified - names. Unqualified names must not contain the characters '.', ';', '[' - or '/'. Method names are further constrained so that, with the exception - of the special method names <init> and <clinit> (§3.9), they must not - contain the characters '<' or '>'. - - 4.3 Descriptors and Signatures - A descriptor is a string representing the type of a field or method. - Descriptors are represented in the class file format using modified - UTF-8 strings (§4.4.7) and thus may be drawn, where not further - constrained, from the entire Unicode character set. A signature is a - string representing the generic type of a field or method, or generic - type information for a class declaration. - */ - abstract class Name_Info(tag: Byte) extends PoolEntry(tag) { - def name_index: UShort - } - abstract class Ref_Info(tag: Byte) extends PoolEntry(tag) { - def class_index: UShort - def name_and_type_index: UShort - } - class Class_info(val name_index: UShort) extends Name_Info(CONSTANT_Class) { } - class Double_info(val value: Double) extends PoolEntry(CONSTANT_Double) { - override def width = 2 - } - class Fieldref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_Fieldref) - class Float_info(val value: Float) extends PoolEntry(CONSTANT_Float) - class Integer_info(val value: Int) extends PoolEntry(CONSTANT_Integer) - class InterfaceMethodref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_InterfaceMethodref) - class Long_info(val value: Long) extends PoolEntry(CONSTANT_Long) { - override def width = 2 - } - class Methodref_info(val class_index: UShort, val name_and_type_index: UShort) extends Ref_Info(CONSTANT_Methodref) - class NameAndType_info(val name_index: UShort, val descriptor_index: UShort) extends Name_Info(CONSTANT_NameAndType) { - override def toString = "NameAndType #%s:#%s;".format(name_index, descriptor_index) - } - class String_info(val string_index: UShort) extends PoolEntry(CONSTANT_String) { } - class Utf8_info(override val stringValue: String) extends PoolEntry(CONSTANT_Utf8) { - override def toString = ("Asciz " + stringValue).trim - } - - abstract class PoolEntry(tag: Byte) { - def width = 1 - def stringValue: String = sys.error("Not a String-valued constant pool entry: " + this) - override def toString = ( - getClass.getName.split("[.$]").last + "/" + tag - ) - } - object NoEntry extends PoolEntry(-1) { } -} diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index 0d9a821ae2..0612dcdfd4 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -896,6 +896,7 @@ trait Definitions extends reflect.api.StandardDefinitions { // boxed classes lazy val ObjectRefClass = requiredClass[scala.runtime.ObjectRef[_]] lazy val VolatileObjectRefClass = requiredClass[scala.runtime.VolatileObjectRef[_]] + lazy val RuntimeStaticsModule = getRequiredModule("scala.runtime.Statics") lazy val BoxesRunTimeModule = getRequiredModule("scala.runtime.BoxesRunTime") lazy val BoxesRunTimeClass = BoxesRunTimeModule.moduleClass lazy val BoxedNumberClass = getClass(sn.BoxedNumber) @@ -1153,7 +1154,7 @@ trait Definitions extends reflect.api.StandardDefinitions { */ private def getModuleOrClass(path: Name): Symbol = getModuleOrClass(path, path.length) - private def getClassByName(fullname: Name): Symbol = { + def getClassByName(fullname: Name): Symbol = { var result = getModuleOrClass(fullname.toTypeName) while (result.isAliasType) result = result.info.typeSymbol result @@ -1206,7 +1207,7 @@ trait Definitions extends reflect.api.StandardDefinitions { def isPrimitiveValueClass(sym: Symbol) = ScalaValueClasses contains sym def isNonUnitValueClass(sym: Symbol) = isPrimitiveValueClass(sym) && (sym != UnitClass) def isSpecializableClass(sym: Symbol) = isPrimitiveValueClass(sym) || (sym == AnyRefClass) - def isScalaValueType(tp: Type) = ScalaValueClasses contains tp.typeSymbol + def isPrimitiveValueType(tp: Type) = isPrimitiveValueClass(tp.typeSymbol) /** Is symbol a boxed value class, e.g. java.lang.Integer? */ def isBoxedValueClass(sym: Symbol) = boxedValueClassesSet(sym) diff --git a/src/compiler/scala/reflect/internal/HasFlags.scala b/src/compiler/scala/reflect/internal/HasFlags.scala index 348f81c51d..0937577ca3 100644 --- a/src/compiler/scala/reflect/internal/HasFlags.scala +++ b/src/compiler/scala/reflect/internal/HasFlags.scala @@ -75,70 +75,41 @@ trait HasFlags { * flag carrying entity. */ def resolveOverloadedFlag(flag: Long): String = Flags.flagToString(flag) - - def privateWithinString = if (hasAccessBoundary) privateWithin.toString else "" - - protected def isSetting(f: Long, mask: Long) = !hasFlag(f) && ((mask & f) != 0L) - protected def isClearing(f: Long, mask: Long) = hasFlag(f) && ((mask & f) != 0L) // Tests which come through cleanly: both Symbol and Modifiers use these // identically, testing for a single flag. - def isCase = hasFlag(CASE ) - def isFinal = hasFlag(FINAL ) - def isImplicit = hasFlag(IMPLICIT ) - def isLazy = hasFlag(LAZY ) - def isMutable = hasFlag(MUTABLE ) // in Modifiers, formerly isVariable - def isOverride = hasFlag(OVERRIDE ) - def isPrivate = hasFlag(PRIVATE ) - def isProtected = hasFlag(PROTECTED) - def isSynthetic = hasFlag(SYNTHETIC) - def isInterface = hasFlag(INTERFACE) - - // Newly introduced based on having a reasonably obvious clean translation. - def isPrivateLocal = hasAllFlags(PrivateLocal) - def isProtectedLocal = hasAllFlags(ProtectedLocal) - def isParamAccessor = hasFlag(PARAMACCESSOR) - def isCaseAccessor = hasFlag(CASEACCESSOR) - def isSuperAccessor = hasFlag(SUPERACCESSOR) - def isLifted = hasFlag(LIFTED) - - // Formerly the Modifiers impl did not include the access boundary check, - // which must have been a bug. - def isPublic = hasNoFlags(PRIVATE | PROTECTED) && !hasAccessBoundary - - // Removed isClass qualification since the flag isn't overloaded and - // sym.isClass is enforced in Namers#validate. - def isSealed = hasFlag(SEALED) - - // Removed !isClass qualification since the flag isn't overloaded. - def isDeferred = hasFlag(DEFERRED) - - // Dropped isTerm condition because flag isn't overloaded. + def hasAbstractFlag = hasFlag(ABSTRACT) + def hasAccessorFlag = hasFlag(ACCESSOR) + def hasDefault = hasAllFlags(DEFAULTPARAM | PARAM) + def hasLocalFlag = hasFlag(LOCAL) + def hasModuleFlag = hasFlag(MODULE) + def hasPackageFlag = hasFlag(PACKAGE) + def hasStableFlag = hasFlag(STABLE) + def hasStaticFlag = hasFlag(STATIC) def isAbstractOverride = hasFlag(ABSOVERRIDE) - def isAnyOverride = hasFlag(OVERRIDE | ABSOVERRIDE) - - // Disambiguating: DEFAULTPARAM, TRAIT - def hasDefault = hasAllFlags(DEFAULTPARAM | PARAM) - def isTrait = hasFlag(TRAIT) && !hasFlag(PARAM) - - // Straightforwardly named accessors already being used differently. - // These names are most likely temporary. - def hasAbstractFlag = hasFlag(ABSTRACT) - def hasAccessorFlag = hasFlag(ACCESSOR) - def hasLocalFlag = hasFlag(LOCAL) - def hasModuleFlag = hasFlag(MODULE) - def hasPackageFlag = hasFlag(PACKAGE) - def hasStableFlag = hasFlag(STABLE) - def hasStaticFlag = hasFlag(STATIC) - - // Disambiguating: LABEL, CONTRAVARIANT, INCONSTRUCTOR - def isLabel = hasAllFlags(LABEL | METHOD) && !hasAccessorFlag - // Cannot effectively disambiguate the others at this level. - def hasContravariantFlag = hasFlag(CONTRAVARIANT) - def hasInConstructorFlag = hasFlag(INCONSTRUCTOR) - - // Name - def isJavaDefined = hasFlag(JAVA) + def isAnyOverride = hasFlag(OVERRIDE | ABSOVERRIDE) + def isCase = hasFlag(CASE) + def isCaseAccessor = hasFlag(CASEACCESSOR) + def isDeferred = hasFlag(DEFERRED) + def isFinal = hasFlag(FINAL) + def isImplicit = hasFlag(IMPLICIT) + def isInterface = hasFlag(INTERFACE) + def isJavaDefined = hasFlag(JAVA) + def isLabel = hasAllFlags(LABEL | METHOD) && !hasAccessorFlag + def isLazy = hasFlag(LAZY) + def isLifted = hasFlag(LIFTED) + def isMutable = hasFlag(MUTABLE) + def isOverride = hasFlag(OVERRIDE) + def isParamAccessor = hasFlag(PARAMACCESSOR) + def isPrivate = hasFlag(PRIVATE) + def isPrivateLocal = hasAllFlags(PrivateLocal) + def isProtected = hasFlag(PROTECTED) + def isProtectedLocal = hasAllFlags(ProtectedLocal) + def isPublic = hasNoFlags(PRIVATE | PROTECTED) && !hasAccessBoundary + def isSealed = hasFlag(SEALED) + def isSuperAccessor = hasFlag(SUPERACCESSOR) + def isSynthetic = hasFlag(SYNTHETIC) + def isTrait = hasFlag(TRAIT) && !hasFlag(PARAM) def flagBitsToString(bits: Long): String = { // Fast path for common case @@ -162,7 +133,7 @@ trait HasFlags { } def accessString: String = { - val pw = privateWithinString + val pw = if (hasAccessBoundary) privateWithin.toString else "" if (pw == "") { if (hasAllFlags(PrivateLocal)) "private[this]" @@ -188,8 +159,6 @@ trait HasFlags { def hasTraitFlag = hasFlag(TRAIT) @deprecated("Use hasDefault", "2.10.0") def hasDefaultFlag = hasFlag(DEFAULTPARAM) - @deprecated("", "2.9.0") - def isAbstract = hasFlag(ABSTRACT) @deprecated("Use isValueParameter or isTypeParameter", "2.10.0") def isParameter = hasFlag(PARAM) @deprecated("Use flagString", "2.10.0") diff --git a/src/compiler/scala/reflect/internal/JvmClassInfo.scala b/src/compiler/scala/reflect/internal/JvmClassInfo.scala deleted file mode 100644 index 80d868f666..0000000000 --- a/src/compiler/scala/reflect/internal/JvmClassInfo.scala +++ /dev/null @@ -1,220 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.reflect.internal - -import java.io.{ DataInput, InputStream, DataInputStream, ByteArrayInputStream, BufferedInputStream, FileInputStream } -import scala.tools.nsc.io.{ Directory } -import scala.reflect.NameTransformer.decode -import scala.tools.util.StringOps.trimTrailingSpace -import ConstantPool._ -import ClassfileConstants._ - -abstract class JvmInfo(attributes: Array[JvmAttributeInfo]) { - // def flags: Short - def name: String - - val signature = attributes collectFirst { case x: SignatureAttr => x.value } getOrElse "" - val innerClasses = attributes collectFirst { case x: InnerClassesAttr => x.value } getOrElse Array() -} -abstract class JvmAttributeInfo { - // attribute_info { - // u2 attribute_name_index; - // u4 attribute_length; - // u1 info[attribute_length]; - // } - def name: String - def value: Any -} - -class JvmClassInfo( - val name: String, - val superName: String, - val interfaces: Array[String], - val fields: Array[JvmMemberInfo], - val methods: Array[JvmMemberInfo], - attributes: Array[JvmAttributeInfo] -) extends JvmInfo(attributes) { - - def members = fields ++ methods sortBy (_.decodedName) - def memberDescriptors = members map (_.toErasedString) - def memberSignatures = members filter (_.hasSignature) map (_.toGenericString) - def descriptorsString = if (memberDescriptors.nonEmpty) memberDescriptors.mkString("\n-- Member Descriptors --\n", "\n", "\n") else "" - def signaturesString = if (memberSignatures.nonEmpty) memberSignatures.mkString("\n-- Member Signatures --\n", "\n", "\n") else "" - def innersString = if (innerClasses.isEmpty) "" else innerClasses.mkString("\n-- Inner Classes --\n", "\n", "\n") - def membersString = descriptorsString + signaturesString - def extendsString = if (superName == "") "" else " extends " + superName - def implementsString = if (interfaces.isEmpty) "" else interfaces.mkString("Implements: ", ", ", "") - - private def group(label: String, xs: Traversable[(String, String)]) = - xs map { case (name, value) => line(label, name, value) } mkString "\n" - - private def line(label: String, name: String, data: String) = - trimTrailingSpace(" %-15s %30s %s".format(label, name, data)) - - override def toString = ( - List( - "class " + name + extendsString, - if (signature == "") "" else line("class sig", "", signature), - group("interface", interfaces map (x => (("", x)))), - (innerClasses map (ic => line(ic.kind, ic.innerName, ic.nestString))).sorted.mkString("\n"), - group("descriptor", members map (x => (x.name, x.descriptor))), - group("signature", members filter (_.hasSignature) map (x => (x.name, x.signature))) - ) map trimTrailingSpace filterNot (_ == "") mkString ("", "\n", "\n") - ) -} - -// method_info or field_info { -// u2 access_flags; -// u2 name_index; -// u2 descriptor_index; -// u2 attributes_count; -// attribute_info attributes[attributes_count]; -// } -class JvmMemberInfo( - val flags: Short, - val name: String, - val descriptor: String, - attributes: Array[JvmAttributeInfo] -) extends JvmInfo(attributes) { - - final def isAbstract = (flags & JAVA_ACC_ABSTRACT) != 0 - final def isAnnotated = (flags & JAVA_ACC_ANNOTATION) != 0 - final def isFinal = (flags & JAVA_ACC_FINAL) != 0 - final def isPrivate = (flags & JAVA_ACC_PRIVATE) != 0 - final def isProtected = (flags & JAVA_ACC_PROTECTED) != 0 - final def isPublic = (flags & JAVA_ACC_PUBLIC) != 0 - final def isStatic = (flags & JAVA_ACC_STATIC) != 0 - final def isSynthetic = (flags & JAVA_ACC_SYNTHETIC) != 0 - final def isVarargs = (flags & JAVA_ACC_VARARGS) != 0 - - // method only - final def isBridge = (flags & JAVA_ACC_BRIDGE) != 0 - - // field only - final def isEnum = (flags & JAVA_ACC_ENUM) != 0 - final def isTransient = (flags & JAVA_ACC_TRANSIENT) != 0 - - def isMethod = descriptor startsWith "(" // ) - def isField = !isMethod - def scalaFlags = toScalaMethodFlags(flags) - def decodedName = decode(name) - def hasSignature = signature != "" - def toErasedString = "%-30s %s".format(decodedName, descriptor) - def toGenericString = "%-30s %s".format(decodedName, signature) - - override def toString = ( - if (hasSignature) toGenericString else toErasedString - ) -} -class GenericAttr(val name: String, val value: Array[Byte]) extends JvmAttributeInfo { } -class SignatureAttr(val value: String) extends JvmAttributeInfo { def name = "Signature" } -class InnerClassesAttr(val value: Array[JvmInnerClassInfo]) extends JvmAttributeInfo { def name = "InnerClasses" } - -// package foo { class Foo { class Bar } } -// -// javap would say -// Bar = class foo.Foo$Bar of class foo.Foo -// which is translated as -// innerClass = foo.Foo$Bar -// outerClass = foo.Foo -// innerName = Bar - -class JvmInnerClassInfo( - thisClass: String, // classfile which is being parsed - val innerClass: String, // the full name of the inner/nested class - val outerClass: String, // the full name of the outer class - must be a prefix of innerClass - val innerName: String, // the simple name of the inner class - should (?) be a suffix of innerClass - val flags: Short // flags -) { - val isEntryOfEnclosingClass = !isAnonymousClass && (innerClass == thisClass) - val isEntryOfNestedClass = !isAnonymousClass && (outerClass == thisClass) - - def isTopLevelClass = outerClass == "" - def isAnonymousClass = innerName == "" - def isMemberClass = !isTopLevelClass - - def kind = ( - if (isEntryOfEnclosingClass) "inner/enclosing" - else if (isEntryOfNestedClass) "inner/nested" - else if (isAnonymousClass) "inner/anon" - else "inner" - ) - def nestString = ( - if (isEntryOfEnclosingClass) "enclosing class: " + outerClass - else if (isEntryOfNestedClass) "member class: " + innerClass - else if (isAnonymousClass) "anonymous class: " + innerClass - else innerClass + " in " + outerClass - ) - override def toString = innerName + "=" + nestString -} - -object JvmClassInfo { - class Builder(protected[this] val in: DataInput) extends ScalacClassfileModel { - def readInterface(): InterfaceInfo = nameAt(u2) - def readMember(): JvmMemberInfo = new JvmMemberInfo(u2.toShort, stringAt(u2), stringAt(u2), readAttributes()) - def readInnerClass(): JvmInnerClassInfo = new JvmInnerClassInfo(thisClass, nameAt(u2), nameAt(u2), stringAt(u2), u2.toShort) - - def readConstantPoolEntry(): Entry = (u1: @annotation.switch) match { - case CONSTANT_Utf8 => new Utf8_info(in.readUTF) - case CONSTANT_Integer => new Integer_info(in.readInt) - case CONSTANT_Float => new Float_info(in.readFloat) - case CONSTANT_Long => new Long_info(in.readLong) - case CONSTANT_Double => new Double_info(in.readDouble) - case CONSTANT_Class => new Class_info(u2) - case CONSTANT_String => new String_info(u2) - case CONSTANT_Fieldref => new Fieldref_info(u2, u2) - case CONSTANT_Methodref => new Methodref_info(u2, u2) - case CONSTANT_InterfaceMethodref => new InterfaceMethodref_info(u2, u2) - case CONSTANT_NameAndType => new NameAndType_info(u2, u2) - } - - // field_info attributes: - // ConstantValue (§4.7.2), Synthetic (§4.7.8), Signature (§4.7.9), Deprecated (§4.7.15), - // RuntimeVisibleAnnotations (§4.7.16) and RuntimeInvisibleAnnotations (§4.7.17). - // - // method_info attributes: - // Code (§4.7.3), Exceptions (§4.7.5), Synthetic (§4.7.8), Signature (§4.7.9), Deprecated (§4.7.15), - // RuntimeVisibleAnnotations (§4.7.16), RuntimeInvisibleAnnotations (§4.7.17), RuntimeVisibleParameterAnnotations (§4.7.18), - // RuntimeInvisibleParameterAnnotations (§4.7.19) and AnnotationDefault (§4.7.20). - def readAttribute(): AttributeInfo = { - val name = stringAt(u2) - val len = u4 - name match { - case "Signature" => new SignatureAttr(stringAt(u2)) - case "InnerClasses" => new InnerClassesAttr(readInnerClasses()) - case name => val bytes = new Array[Byte](len) ; in.readFully(bytes) ; new GenericAttr(name, bytes) - } - } - } - - private def classFiles(path: String) = - Directory(path).deepFiles filter (_ hasExtension "class") - - def classInfoMap(path: String): Map[String, JvmClassInfo] = { - classFiles(path) map (f => (f.path, JvmClassInfo fromFile f.jfile)) toMap - } - def classInfoList(path: String): List[(String, JvmClassInfo)] = { - classInfoMap(path).toList sortBy (_._1) - } - - def fromFile(file: java.io.File) = - fromStream(new BufferedInputStream(new FileInputStream(file))) - - def fromBytes(bytes: Array[Byte]) = - fromStream(new ByteArrayInputStream(bytes)) - - def fromPath(path: String) = - fromStream(new BufferedInputStream(new FileInputStream(path))) - - def fromStream(in0: InputStream) = { - val in = new DataInputStream(in0) - try fromDataInput(in) finally in.close() - } - - def fromDataInput(in: DataInput): JvmClassInfo = { - new Builder(in) parse - } -} diff --git a/src/compiler/scala/reflect/internal/StdNames.scala b/src/compiler/scala/reflect/internal/StdNames.scala index 6bbba3eaf3..bd4d9a9f34 100644 --- a/src/compiler/scala/reflect/internal/StdNames.scala +++ b/src/compiler/scala/reflect/internal/StdNames.scala @@ -293,6 +293,7 @@ trait StdNames { val FAKE_LOCAL_THIS: NameType = "this$" val INITIALIZER: NameType = CONSTRUCTOR // Is this buying us something? val LAZY_LOCAL: NameType = "$lzy" + val LAZY_SLOW_SUFFIX: NameType = "$lzycompute" val LOCAL_SUFFIX_STRING = " " val MIRROR_FREE_PREFIX: NameType = "free$" val MIRROR_FREE_THIS_SUFFIX: NameType = "$this" @@ -765,7 +766,6 @@ trait StdNames { object fulltpnme extends TypeNames { val RuntimeNothing: NameType = "scala.runtime.Nothing$" val RuntimeNull: NameType = "scala.runtime.Null$" - val JavaLangEnum: NameType = "java.lang.Enum" } /** Java binary names, like scala/runtime/Nothing$. @@ -821,10 +821,11 @@ trait StdNames { val BITMAP_NORMAL: NameType = BITMAP_PREFIX + "" // initialization bitmap for public/protected lazy vals val BITMAP_TRANSIENT: NameType = BITMAP_PREFIX + "trans$" // initialization bitmap for transient lazy vals - val BITMAP_PRIVATE: NameType = BITMAP_PREFIX + "priv$" // initialization bitmap for private lazy vals val BITMAP_CHECKINIT: NameType = BITMAP_PREFIX + "init$" // initialization bitmap for checkinit values val BITMAP_CHECKINIT_TRANSIENT: NameType = BITMAP_PREFIX + "inittrans$" // initialization bitmap for transient checkinit values + def newLazyValSlowComputeName(lzyValName: Name) = lzyValName append LAZY_SLOW_SUFFIX + def isModuleVarName(name: Name): Boolean = stripAnonNumberSuffix(name) endsWith MODULE_VAR_SUFFIX diff --git a/src/compiler/scala/reflect/internal/SymbolCreations.scala b/src/compiler/scala/reflect/internal/SymbolCreations.scala deleted file mode 100644 index ac82ffe62a..0000000000 --- a/src/compiler/scala/reflect/internal/SymbolCreations.scala +++ /dev/null @@ -1,112 +0,0 @@ - /* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.reflect -package internal - -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.ListBuffer -import util.Statistics._ -import Flags._ -import api.Modifier - -trait SymbolCreations { - self: SymbolTable => - - import definitions._ - - /** Symbol creation interface, possibly better moved somewhere else. - * It'd be nice if we had virtual classes, but since we - * don't: these methods insulate the direct instantiation of the symbols - * (which may be overridden, e.g. in SynchronizedSymbols) from the - * enforcement of preconditions and choice of symbol constructor based - * on flags, which are (or should be) final so they can be reasoned about - * without lots of surprises. - */ - trait SymbolCreatorInterface { - // Fallbacks; more precise creators should normally be called. - protected def createTermSymbol(name: TermName, pos: Position, newFlags: Long): TermSymbol - // This in fact does not exist anymore in the interests of better typed TypeSymbols. - // protected def createTypeSymbol(name: TypeName, pos: Position, newFlags: Long): TypeSymbol - - // I believe all but rogue TypeSymbols are one of: ClassSymbol, AbstractTypeSymbol, AliasTypeSymbol, or TypeSkolem. - protected def createAbstractTypeSymbol(name: TypeName, pos: Position, newFlags: Long): AbstractTypeSymbol - protected def createAliasTypeSymbol(name: TypeName, pos: Position, newFlags: Long): AliasTypeSymbol - protected def createTypeSkolemSymbol(name: TypeName, origin: AnyRef, pos: Position, newFlags: Long): TypeSkolem - protected def createClassSymbol(name: TypeName, pos: Position, newFlags: Long): ClassSymbol - - // More specific ClassSymbols. - // TODO - AnonymousClassSymbol. - // TODO maybe - PackageObjects, but that one cost me a lot of time when I tried it before - // because it broke reification some way I couldn't see. - protected def createModuleClassSymbol(name: TypeName, pos: Position, newFlags: Long): ModuleClassSymbol - protected def createPackageClassSymbol(name: TypeName, pos: Position, newFlags: Long): PackageClassSymbol - protected def createRefinementClassSymbol(pos: Position, newFlags: Long): RefinementClassSymbol - protected def createImplClassSymbol(name: TypeName, pos: Position, newFlags: Long): ClassSymbol - protected def createPackageObjectClassSymbol(pos: Position, newFlags: Long): PackageObjectClassSymbol - - // Distinguished term categories include methods, modules, packages, package objects, - // value parameters, and values (including vals, vars, and lazy vals.) - protected def createMethodSymbol(name: TermName, pos: Position, newFlags: Long): MethodSymbol - protected def createModuleSymbol(name: TermName, pos: Position, newFlags: Long): ModuleSymbol - protected def createPackageSymbol(name: TermName, pos: Position, newFlags: Long): PackageSymbol - - // TODO - // protected def createValueParameterSymbol(name: TermName, pos: Position, newFlags: Long): TermSymbol - // protected def createValueMemberSymbol(name: TermName, pos: Position, newFlags: Long): TermSymbol - } - - trait SymbolCreator extends SymbolCreatorInterface { - self: Symbol => - - /*** Predictable symbol creation. - * - * newTermSymbol, newClassSymbol, and newNonClassSymbol all create symbols based - * only on the flags (for reconstruction after reification.) It would be nice to - * combine the last two into newTypeSymbol, but this requires some flag which allows us - * to distinguish classes and type aliases, which as yet does not exist. - * - * The fundamental flags used to determine which Symbol subclass to instantiate are: - * METHOD, PACKAGE, MODULE, PARAM, DEFERRED. - */ - final def newTermSymbol(name: TermName, pos: Position = NoPosition, newFlags: Long = 0L): TermSymbol = { - if ((newFlags & METHOD) != 0) - createMethodSymbol(name, pos, newFlags) - else if ((newFlags & PACKAGE) != 0) - createPackageSymbol(name, pos, newFlags | PackageFlags) - else if ((newFlags & MODULE) != 0) - createModuleSymbol(name, pos, newFlags) - else if ((newFlags & PARAM) != 0) - createValueParameterSymbol(name, pos, newFlags) - else - createValueMemberSymbol(name, pos, newFlags) - } - - final def newClassSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): ClassSymbol = { - if (name == tpnme.REFINE_CLASS_NAME) - createRefinementClassSymbol(pos, newFlags) - else if ((newFlags & PACKAGE) != 0) - createPackageClassSymbol(name, pos, newFlags | PackageFlags) - else if (name == tpnme.PACKAGE) - createPackageObjectClassSymbol(pos, newFlags) - else if ((newFlags & MODULE) != 0) - createModuleClassSymbol(name, pos, newFlags) - else if ((newFlags & IMPLCLASS) != 0) - createImplClassSymbol(name, pos, newFlags) - else - createClassSymbol(name, pos, newFlags) - } - - final def newNonClassSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): TypeSymbol = { - if ((newFlags & DEFERRED) != 0) - createAbstractTypeSymbol(name, pos, newFlags) - else - createAliasTypeSymbol(name, pos, newFlags) - } - - def newTypeSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): TypeSymbol = - newNonClassSymbol(name, pos, newFlags) - } -} diff --git a/src/compiler/scala/reflect/internal/SymbolFlags.scala b/src/compiler/scala/reflect/internal/SymbolFlags.scala deleted file mode 100644 index 4ce8ac40df..0000000000 --- a/src/compiler/scala/reflect/internal/SymbolFlags.scala +++ /dev/null @@ -1,175 +0,0 @@ - /* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.reflect -package internal - -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.ListBuffer -import util.Statistics._ -import Flags._ -import api.Modifier - -trait SymbolFlags { - self: SymbolTable => - - import definitions._ - - /** Not mixed in under normal conditions; a powerful debugging aid. - */ - trait FlagVerifier extends Symbol { - private def assert0(cond: Boolean, message: => Any) { - if (!cond) { - Console.err.println("[flag verification failure]\n%s\n%s\n".format(atPhaseStackMessage, message)) - (new Throwable).getStackTrace.take(13).drop(3).foreach(println) - println("") - } - } - - protected def verifyChange(isAdd: Boolean, mask: Long, before: Long) { - val after = if (isAdd) before | mask else before & ~mask - val added = after & ~before - val removed = before & ~after - val ignored = mask & ~added & ~removed - val error = ( - (added & OverloadedFlagsMask) != 0 || (removed & OverloadedFlagsMask) != 0 - // || (ignored != 0) - ) - val addString = if (added == 0) "" else "+(" + flagsToString(added) + ")" - val removeString = if (removed == 0) "" else "-(" + flagsToString(removed) + ")" - val changeString = if (added == 0 && removed == 0) "no change" else addString + " " + removeString - - if (error) { - val templ = ( - """| symbol: %s %s in %s - | call: %s(%s) - | flags: %s - | result: %s""".stripMargin - ) - - assert0(false, templ.format( - shortSymbolClass, - name.decode, - owner, - if (isAdd) "+" else "-", - flagsToString(mask), - flagsToString(before), - changeString - )) - } - } - - protected def verifyFlags(what: String) { - assert0(this hasAllFlags alwaysHasFlags, symbolCreationString + "\n always=%s, what=%s\n".format(flagsToString(alwaysHasFlags), what)) - if (this hasFlag neverHasFlags) { - val hasRaw = (rawflags & neverHasFlags) != 0 - assert0(!hasRaw, symbolCreationString + "\n never=%s, what=%s".format(flagsToString(neverHasFlags), what)) - } - } - abstract override def initFlags(mask: Long): this.type = { - super.initFlags(mask) - verifyFlags("initFlags(" + flagsToString(mask) + ")") - this - } - abstract override def setFlag(mask: Long): this.type = { - verifyChange(true, mask, rawflags) - super.setFlag(mask) - verifyFlags("setFlag(" + flagsToString(mask) + ")") - this - } - abstract override def resetFlag(mask: Long): this.type = { - verifyChange(false, mask, rawflags) - super.resetFlag(mask) - verifyFlags("resetFlag(" + flagsToString(mask) + ")") - this - } - abstract override def flags_=(fs: Long) { - if ((fs & ~rawflags) != 0) - verifyChange(true, fs & ~rawflags, rawflags) - if ((rawflags & ~fs) != 0) - verifyChange(false, rawflags & ~fs, rawflags) - - super.flags_=(fs) - verifyFlags("flags_=(" + flagsToString(fs) + ")") - } - } - - /** Flags which should always be present on a particular class of - * Symbol, and never be present on any others. - */ - def AllDistinguishingFlags: Long = METHOD | MODULE | IMPLCLASS - - /** A distinguishing flag is one which the mixing class must always - * have, and which no other symbol class is allowed to have. - */ - trait DistinguishingFlag extends SymbolFlagLogic { - this: Symbol => - - def distinguishingFlag: Long - override protected def alwaysHasFlags = super.alwaysHasFlags | distinguishingFlag - override protected def neverHasFlags = super.neverHasFlags & ~distinguishingFlag - } - - trait SymbolFlagLogic { - this: Symbol => - - // Forced for performance reasons to define all the flag manipulation - // methods alongside the field being manipulated. - def getFlag(mask: Long): Long - def hasFlag(mask: Long): Boolean - def hasAllFlags(mask: Long): Boolean - def setFlag(mask: Long): this.type - def resetFlag(mask: Long): this.type - def initFlags(mask: Long): this.type - def resetFlags(): Unit - - protected def resolveOverloadedFlag(flag: Long): String - protected def calculateFlagString(basis: Long): String - - protected def alwaysHasFlags: Long = 0L - protected def neverHasFlags: Long = AllDistinguishingFlags - - def rawFlagString(mask: Long): String = calculateFlagString(rawflags & mask) - def rawFlagString: String = rawFlagString(flagMask) - def debugFlagString: String = flagString(AllFlags) - - /** String representation of symbol's variance */ - def varianceString: String = - if (variance == 1) "+" - else if (variance == -1) "-" - else "" - - override def flagMask = - if (settings.debug.value && !isAbstractType) AllFlags - else if (owner.isRefinementClass) ExplicitFlags & ~OVERRIDE - else ExplicitFlags - - // make the error message more googlable - def flagsExplanationString = - if (isGADTSkolem) " (this is a GADT skolem)" - else "" - - /** If the given flag is set on this symbol, also set the corresponding - * notFLAG. For instance if flag is PRIVATE, the notPRIVATE flag will - * be set if PRIVATE is currently set. - */ - final def setNotFlag(flag: Int) = if (hasFlag(flag)) setFlag((flag: @annotation.switch) match { - case PRIVATE => notPRIVATE - case PROTECTED => notPROTECTED - case OVERRIDE => notOVERRIDE - case _ => abort("setNotFlag on invalid flag: " + flag) - }) - - def shortSymbolClass = getClass.getName.split('.').last.stripPrefix("Symbols$") - def symbolCreationString: String = ( - "%s%25s | %-40s | %s".format( - if (settings.uniqid.value) "%06d | ".format(id) else "", - shortSymbolClass, - name.decode + " in " + owner, - rawFlagString - ) - ) - } -} diff --git a/src/compiler/scala/reflect/internal/SymbolTable.scala b/src/compiler/scala/reflect/internal/SymbolTable.scala index 76b4f5f53e..ddd6c43031 100644 --- a/src/compiler/scala/reflect/internal/SymbolTable.scala +++ b/src/compiler/scala/reflect/internal/SymbolTable.scala @@ -13,9 +13,7 @@ import scala.tools.nsc.util.WeakHashSet abstract class SymbolTable extends api.Universe with Collections with Names - with SymbolCreations with Symbols - with SymbolFlags with FreeVars with Types with Kinds @@ -290,35 +288,14 @@ abstract class SymbolTable extends api.Universe // letting us know when a cache is really out of commission. private val caches = mutable.HashSet[WeakReference[Clearable]]() - private def dumpCaches() { - println(caches.size + " structures are in perRunCaches.") - caches.zipWithIndex foreach { case (ref, index) => - val cache = ref.get() - cache match { - case xs: Traversable[_] => - println("(" + index + ")" + ( - if (cache == null) " has been collected." - else " has " + xs.size + " entries:\n" + stringOf(xs) - )) - case _ => - } - } - } - // if (settings.debug.value) { - // println(Signallable("dump compiler caches")(dumpCaches())) - // } - def recordCache[T <: Clearable](cache: T): T = { caches += new WeakReference(cache) cache } def clearAll() = { - if (settings.debug.value) { - // val size = caches flatMap (ref => Option(ref.get)) map (_.size) sum; - log("Clearing " + caches.size + " caches.") - // totalling " + size + " entries.") - } + debuglog("Clearing " + caches.size + " caches.") + caches foreach { ref => val cache = ref.get() if (cache == null) @@ -334,11 +311,6 @@ abstract class SymbolTable extends api.Universe def newWeakSet[K <: AnyRef]() = recordCache(new WeakHashSet[K]()) } - /** Break into repl debugger if assertion is true. */ - // def breakIf(assertion: => Boolean, args: Any*): Unit = - // if (assertion) - // ILoop.break(args.toList) - /** The set of all installed infotransformers. */ var infoTransformers = new InfoTransformer { val pid = NoPhase.id diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala index 2a5b759f94..b32b955631 100644 --- a/src/compiler/scala/reflect/internal/Symbols.scala +++ b/src/compiler/scala/reflect/internal/Symbols.scala @@ -157,9 +157,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => abstract class Symbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: Name) extends AbsSymbolImpl with HasFlags - with SymbolFlagLogic - with SymbolCreator - // with FlagVerifier // DEBUG with Annotatable[Symbol] { type AccessBoundaryType = Symbol @@ -209,6 +206,36 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } + def rawFlagString(mask: Long): String = calculateFlagString(rawflags & mask) + def rawFlagString: String = rawFlagString(flagMask) + def debugFlagString: String = flagString(AllFlags) + + /** String representation of symbol's variance */ + def varianceString: String = + if (variance == 1) "+" + else if (variance == -1) "-" + else "" + + override def flagMask = + if (settings.debug.value && !isAbstractType) AllFlags + else if (owner.isRefinementClass) ExplicitFlags & ~OVERRIDE + else ExplicitFlags + + // make the error message more googlable + def flagsExplanationString = + if (isGADTSkolem) " (this is a GADT skolem)" + else "" + + def shortSymbolClass = getClass.getName.split('.').last.stripPrefix("Symbols$") + def symbolCreationString: String = ( + "%s%25s | %-40s | %s".format( + if (settings.uniqid.value) "%06d | ".format(id) else "", + shortSymbolClass, + name.decode + " in " + owner, + rawFlagString + ) + ) + /** !!! The logic after "hasFlag" is far too opaque to be unexplained. * I'm guessing it's attempting to compensate for flag overloading, * and embedding such logic in an undocumented island like this is a @@ -620,15 +647,15 @@ trait Symbols extends api.Symbols { self: SymbolTable => && owner.isPackageClass && nme.isReplWrapperName(name) ) - @inline final override def getFlag(mask: Long): Long = flags & mask + @inline final def getFlag(mask: Long): Long = flags & mask /** Does symbol have ANY flag in `mask` set? */ - @inline final override def hasFlag(mask: Long): Boolean = (flags & mask) != 0 + @inline final def hasFlag(mask: Long): Boolean = (flags & mask) != 0 /** Does symbol have ALL the flags in `mask` set? */ - @inline final override def hasAllFlags(mask: Long): Boolean = (flags & mask) == mask + @inline final def hasAllFlags(mask: Long): Boolean = (flags & mask) == mask - override def setFlag(mask: Long): this.type = { _rawflags |= mask ; this } - override def resetFlag(mask: Long): this.type = { _rawflags &= ~mask ; this } - override def resetFlags() { rawflags &= (TopLevelCreationFlags | alwaysHasFlags) } + def setFlag(mask: Long): this.type = { _rawflags |= mask ; this } + def resetFlag(mask: Long): this.type = { _rawflags &= ~mask ; this } + def resetFlags() { rawflags &= TopLevelCreationFlags } /** Default implementation calls the generic string function, which * will print overloaded flags as <flag1/flag2/flag3>. Subclasses @@ -639,7 +666,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Set the symbol's flags to the given value, asserting * that the previous value was 0. */ - override def initFlags(mask: Long): this.type = { + def initFlags(mask: Long): this.type = { assert(rawflags == 0L, symbolCreationString) _rawflags = mask this @@ -1085,6 +1112,44 @@ trait Symbols extends api.Symbols { self: SymbolTable => protected def createValueMemberSymbol(name: TermName, pos: Position, newFlags: Long): TermSymbol = new TermSymbol(this, pos, name) initFlags newFlags + final def newTermSymbol(name: TermName, pos: Position = NoPosition, newFlags: Long = 0L): TermSymbol = { + if ((newFlags & METHOD) != 0) + createMethodSymbol(name, pos, newFlags) + else if ((newFlags & PACKAGE) != 0) + createPackageSymbol(name, pos, newFlags | PackageFlags) + else if ((newFlags & MODULE) != 0) + createModuleSymbol(name, pos, newFlags) + else if ((newFlags & PARAM) != 0) + createValueParameterSymbol(name, pos, newFlags) + else + createValueMemberSymbol(name, pos, newFlags) + } + + final def newClassSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): ClassSymbol = { + if (name == tpnme.REFINE_CLASS_NAME) + createRefinementClassSymbol(pos, newFlags) + else if ((newFlags & PACKAGE) != 0) + createPackageClassSymbol(name, pos, newFlags | PackageFlags) + else if (name == tpnme.PACKAGE) + createPackageObjectClassSymbol(pos, newFlags) + else if ((newFlags & MODULE) != 0) + createModuleClassSymbol(name, pos, newFlags) + else if ((newFlags & IMPLCLASS) != 0) + createImplClassSymbol(name, pos, newFlags) + else + createClassSymbol(name, pos, newFlags) + } + + final def newNonClassSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): TypeSymbol = { + if ((newFlags & DEFERRED) != 0) + createAbstractTypeSymbol(name, pos, newFlags) + else + createAliasTypeSymbol(name, pos, newFlags) + } + + def newTypeSymbol(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): TypeSymbol = + newNonClassSymbol(name, pos, newFlags) + /** The class or term up to which this symbol is accessible, * or RootClass if it is public. As java protected statics are * otherwise completely inaccessible in scala, they are treated @@ -2088,7 +2153,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** String representation of symbol's definition key word */ final def keyString: String = if (isJavaInterface) "interface" - else if (isTrait) "trait" + else if (isTrait && !isImplClass) "trait" else if (isClass) "class" else if (isType && !isParameter) "type" else if (isVariable) "var" @@ -2116,6 +2181,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => else if (isSetter) ("setter", if (isSourceMethod) "method" else "value", "SET") else if (isTerm && isLazy) ("lazy value", "lazy value", "LAZ") else if (isVariable) ("field", "variable", "VAR") + else if (isImplClass) ("implementation class", "class", "IMPL") else if (isTrait) ("trait", "trait", "TRT") else if (isClass) ("class", "class", "CLS") else if (isType) ("type", "type", "TPE") @@ -2232,7 +2298,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => } def infosString = infos.toString - def debugLocationString = fullLocationString + " " + debugFlagString + def debugLocationString = fullLocationString + " (flags: " + debugFlagString + ")" private def defStringCompose(infoString: String) = compose( flagString, @@ -2433,8 +2499,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** A class for module symbols */ class ModuleSymbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: TermName) - extends TermSymbol(initOwner, initPos, initName) with DistinguishingFlag with ModuleSymbolApi { - def distinguishingFlag = MODULE + extends TermSymbol(initOwner, initPos, initName) with ModuleSymbolApi { private var flatname: TermName = null override def associatedFile = moduleClass.associatedFile @@ -2461,19 +2526,13 @@ trait Symbols extends api.Symbols { self: SymbolTable => } class PackageSymbol protected[Symbols] (owner0: Symbol, pos0: Position, name0: TermName) - extends ModuleSymbol(owner0, pos0, name0) with DistinguishingFlag with PackageSymbolApi { - override def distinguishingFlag = super.distinguishingFlag | PACKAGE + extends ModuleSymbol(owner0, pos0, name0) with PackageSymbolApi { override def isPackage = true } /** A class for method symbols */ class MethodSymbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: TermName) - extends TermSymbol(initOwner, initPos, initName) with DistinguishingFlag with MethodSymbolApi { - def distinguishingFlag = METHOD - // MethodSymbols pick up MODULE when trait-owned object accessors are cloned - // during mixin composition. - override protected def neverHasFlags = super.neverHasFlags & ~MODULE - + extends TermSymbol(initOwner, initPos, initName) with MethodSymbolApi { private[this] var mtpePeriod = NoPeriod private[this] var mtpePre: Type = _ private[this] var mtpeResult: Type = _ @@ -2742,9 +2801,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => private[this] var thisTypeCache: Type = _ private[this] var thisTypePeriod = NoPeriod - override protected def alwaysHasFlags: Long = 0L - override protected def neverHasFlags: Long = 0L - override def resolveOverloadedFlag(flag: Long) = flag match { case INCONSTRUCTOR => "<inconstructor>" // INCONSTRUCTOR / CONTRAVARIANT / LABEL case EXISTENTIAL => "<existential>" // EXISTENTIAL / MIXEDIN @@ -2903,13 +2959,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => * plain class symbols! */ class ModuleClassSymbol protected[Symbols] (owner: Symbol, pos: Position, name: TypeName) - extends ClassSymbol(owner, pos, name) with DistinguishingFlag { + extends ClassSymbol(owner, pos, name) { private[this] var module: Symbol = _ private[this] var typeOfThisCache: Type = _ private[this] var typeOfThisPeriod = NoPeriod - def distinguishingFlag = MODULE - private var implicitMembersCacheValue: List[Symbol] = Nil private var implicitMembersCacheKey1: Type = NoType private var implicitMembersCacheKey2: ScopeEntry = null @@ -2964,8 +3018,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => } class PackageClassSymbol protected[Symbols] (owner0: Symbol, pos0: Position, name0: TypeName) - extends ModuleClassSymbol(owner0, pos0, name0) with DistinguishingFlag { - override def distinguishingFlag = super.distinguishingFlag | PACKAGE + extends ModuleClassSymbol(owner0, pos0, name0) { override def sourceModule = companionModule override def enclClassChain = Nil override def isPackageClass = true @@ -3053,7 +3106,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def originalEnclosingMethod = this override def owner: Symbol = - abort("no-symbol does not have an owner (this is a bug: scala " + scala.util.Properties.versionString + ")") + abort("no-symbol does not have an owner") override def typeConstructor: Type = abort("no-symbol does not have a type constructor (this may indicate scalac cannot find fundamental classes)") } diff --git a/src/compiler/scala/reflect/internal/TreeInfo.scala b/src/compiler/scala/reflect/internal/TreeInfo.scala index 2251310f35..1528061adb 100644 --- a/src/compiler/scala/reflect/internal/TreeInfo.scala +++ b/src/compiler/scala/reflect/internal/TreeInfo.scala @@ -221,9 +221,29 @@ abstract class TreeInfo { case _ => false } + /** + * Named arguments can transform a constructor call into a block, e.g. + * <init>(b = foo, a = bar) + * is transformed to + * { val x$1 = foo + * val x$2 = bar + * <init>(x$2, x$1) + * } + */ + def stripNamedApplyBlock(tree: Tree) = tree match { + case Block(stats, expr) if stats.forall(_.isInstanceOf[ValDef]) => + expr + case _ => + tree + } + /** Is tree a self or super constructor call? */ - def isSelfOrSuperConstrCall(tree: Tree) = - isSelfConstrCall(tree) || isSuperConstrCall(tree) + def isSelfOrSuperConstrCall(tree: Tree) = { + // stripNamedApply for SI-3584: adaptToImplicitMethod in Typers creates a special context + // for implicit search in constructor calls, adaptToImplicitMethod(isSelfOrConstrCall) + val tree1 = stripNamedApplyBlock(tree) + isSelfConstrCall(tree1) || isSuperConstrCall(tree1) + } /** Is tree a variable pattern? */ def isVarPattern(pat: Tree): Boolean = pat match { diff --git a/src/compiler/scala/reflect/internal/Trees.scala b/src/compiler/scala/reflect/internal/Trees.scala index c43528b8e9..3e7f23800c 100644 --- a/src/compiler/scala/reflect/internal/Trees.scala +++ b/src/compiler/scala/reflect/internal/Trees.scala @@ -145,11 +145,12 @@ trait Trees extends api.Trees { self: SymbolTable => */ def summaryString: String = tree match { case Literal(const) => "Literal(" + const + ")" - case Select(qual, name) => qual.summaryString + "." + name.decode + case Ident(name) => "Ident(%s)".format(name.decode) + case Select(qual, name) => "Select(%s, %s)".format(qual.summaryString, name.decode) case t: NameTree => t.name.longString case t => t.shortClass + ( - if (t.symbol != null && t.symbol != NoSymbol) " " + t.symbol + if (t.symbol != null && t.symbol != NoSymbol) "(" + t.symbol + ")" else "" ) } @@ -420,7 +421,7 @@ trait Trees extends api.Trees { self: SymbolTable => tree match { case Ident(name0) if tree.symbol != NoSymbol => treeCopy.Ident(tree, tree.symbol.name) - case Select(qual, name0) => + case Select(qual, name0) if tree.symbol != NoSymbol => treeCopy.Select(tree, transform(qual), tree.symbol.name) case _ => super.transform(tree) diff --git a/src/compiler/scala/reflect/internal/TypeDebugging.scala b/src/compiler/scala/reflect/internal/TypeDebugging.scala index 54efef8142..85a1767067 100644 --- a/src/compiler/scala/reflect/internal/TypeDebugging.scala +++ b/src/compiler/scala/reflect/internal/TypeDebugging.scala @@ -50,33 +50,6 @@ trait TypeDebugging { def refine(defs: Scope): String = defs.toList.mkString("{", " ;\n ", "}") } - def dump(tp: Type): Unit = { - println("** " + tp + " / " + tp.getClass + " **") - import tp._ - - println("typeSymbol = " + typeSymbol) - println("termSymbol = " + termSymbol) - println("widen = " + widen) - println("deconst = " + deconst) - println("typeOfThis = " + typeOfThis) - println("bounds = " + bounds) - println("parents = " + parents) - println("prefixChain = " + prefixChain) - println("typeConstructor = " + typeConstructor) - println(" .. typeConstructor.typeParams = " + typeConstructor.typeParams) - println(" .. _.variance = " + (typeConstructor.typeParams map (_.variance))) - println("typeArgs = " + typeArgs) - println("resultType = " + resultType) - println("finalResultType = " + finalResultType) - println("paramss = " + paramss) - println("paramTypes = " + paramTypes) - println("typeParams = " + typeParams) - println("boundSyms = " + boundSyms) - println("baseTypeSeq = " + baseTypeSeq) - println("baseClasses = " + baseClasses) - println("toLongString = " + toLongString) - } - private def debug(tp: Type): String = tp match { case TypeRef(pre, sym, args) => debug(pre) + "." + sym.nameString + str.tparams(args) case ThisType(sym) => sym.nameString + ".this" diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index 09e7303b90..93f359ae12 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -2663,6 +2663,10 @@ trait Types extends api.Types { self: SymbolTable => override def kind = "OverloadedType" } + def overloadedType(pre: Type, alternatives: List[Symbol]): Type = + if (alternatives.tail.isEmpty) pre memberType alternatives.head + else OverloadedType(pre, alternatives) + /** A class remembering a type instantiation for some a set of overloaded * polymorphic symbols. * Not used after phase `typer`. @@ -3064,7 +3068,8 @@ trait Types extends api.Types { self: SymbolTable => } def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = { - //println("regTypeEq: "+(safeToString, debugString(tp), typeVarLHS)) //@MDEBUG +// println("regTypeEq: "+(safeToString, debugString(tp), tp.getClass, if (typeVarLHS) "in LHS" else "in RHS", if (suspended) "ZZ" else if (constr.instValid) "IV" else "")) //@MDEBUG +// println("constr: "+ constr) def checkIsSameType(tp: Type) = if(typeVarLHS) constr.inst =:= tp else tp =:= constr.inst @@ -3121,10 +3126,7 @@ trait Types extends api.Types { self: SymbolTable => tparams map (_.defString) mkString("[", ",", "]") case _ => "" } - def originName = { - val name = origin.typeSymbolDirect.decodedName - if (name contains "_$") origin.typeSymbolDirect.decodedName else name - } + def originName = origin.typeSymbolDirect.decodedName def originLocation = { val sym = origin.typeSymbolDirect val encl = sym.owner.logicallyEnclosingMember @@ -3142,7 +3144,7 @@ trait Types extends api.Types { self: SymbolTable => protected def typeVarString = originName override def safeToString = ( if ((constr eq null) || (constr.inst eq null)) "TVar<" + originName + "=null>" - else if (constr.inst ne NoType) "" + constr.inst + else if (constr.inst ne NoType) "=?" + constr.inst else (if(untouchable) "!?" else "?") + levelString + originName ) override def kind = "TypeVar" @@ -4921,7 +4923,23 @@ trait Types extends api.Types { self: SymbolTable => override def hashCode = tp1.hashCode * 41 + tp2.hashCode override def equals(other: Any) = other match { case stp: SubTypePair => - (tp1 =:= stp.tp1) && (tp2 =:= stp.tp2) + // suspend TypeVars in types compared by =:=, + // since we don't want to mutate them simply to check whether a subtype test is pending + // in addition to making subtyping "more correct" for type vars, + // it should avoid the stackoverflow that's been plaguing us (https://groups.google.com/d/topic/scala-internals/2gHzNjtB4xA/discussion) + // this method is only called when subtyping hits a recursion threshold (subsametypeRecursions >= LogPendingSubTypesThreshold) + @inline def suspend(tp: Type) = + if (tp.isGround) null else suspendTypeVarsInType(tp) + @inline def revive(suspension: List[TypeVar]) = + if (suspension ne null) suspension foreach (_.suspended = false) + + val suspensions = Array(tp1, stp.tp1, tp2, stp.tp2) map suspend + + val sameTypes = (tp1 =:= stp.tp1) && (tp2 =:= stp.tp2) + + suspensions foreach revive + + sameTypes case _ => false } diff --git a/src/compiler/scala/reflect/internal/transform/Erasure.scala b/src/compiler/scala/reflect/internal/transform/Erasure.scala index 1b323f839b..5beec70d62 100644 --- a/src/compiler/scala/reflect/internal/transform/Erasure.scala +++ b/src/compiler/scala/reflect/internal/transform/Erasure.scala @@ -17,7 +17,14 @@ trait Erasure { * with primitive as well as class types)?. */ private def genericCore(tp: Type): Type = tp.normalize match { - case TypeRef(_, sym, _) if sym.isAbstractType && !sym.owner.isJavaDefined => + /* A Java Array<T> is erased to Array[Object] (T can only be a reference type), where as a Scala Array[T] is + * erased to Object. However, there is only symbol for the Array class. So to make the distinction between + * a Java and a Scala array, we check if the owner of T comes from a Java class. + * This however caused issue SI-5654. The additional test for EXSITENTIAL fixes it, see the ticket comments. + * In short, members of an existential type (e.g. `T` in `forSome { type T }`) can have pretty arbitrary + * owners (e.g. when computing lubs, <root> is used). All packageClass symbols have `isJavaDefined == true`. + */ + case TypeRef(_, sym, _) if sym.isAbstractType && (!sym.owner.isJavaDefined || sym.hasFlag(Flags.EXISTENTIAL)) => tp case ExistentialType(tparams, restp) => genericCore(restp) diff --git a/src/compiler/scala/reflect/internal/util/TraceSymbolActivity.scala b/src/compiler/scala/reflect/internal/util/TraceSymbolActivity.scala index eb384f9a85..1424226042 100644 --- a/src/compiler/scala/reflect/internal/util/TraceSymbolActivity.scala +++ b/src/compiler/scala/reflect/internal/util/TraceSymbolActivity.scala @@ -7,7 +7,7 @@ trait TraceSymbolActivity { val global: SymbolTable import global._ - if (traceSymbolActivity) + if (traceSymbolActivity && !global.inReflexiveMirror) scala.sys addShutdownHook showAllSymbols() private type Set[T] = scala.collection.immutable.Set[T] diff --git a/src/compiler/scala/reflect/makro/runtime/Typers.scala b/src/compiler/scala/reflect/makro/runtime/Typers.scala index c61e492250..704d3d7ac2 100644 --- a/src/compiler/scala/reflect/makro/runtime/Typers.scala +++ b/src/compiler/scala/reflect/makro/runtime/Typers.scala @@ -69,7 +69,7 @@ trait Typers { def unapply(error: TypeError): Option[(Position, String)] = Some((error.pos, error.msg)) } - def resetAllAttrs[T <: Tree](tree: T): T = mirror.resetAllAttrs(tree) + def resetAllAttrs(tree: Tree): Tree = mirror.resetAllAttrs(tree) - def resetLocalAttrs[T <: Tree](tree: T): T = mirror.resetLocalAttrs(tree) + def resetLocalAttrs(tree: Tree): Tree = mirror.resetLocalAttrs(tree) }
\ No newline at end of file diff --git a/src/compiler/scala/reflect/runtime/ToolBoxes.scala b/src/compiler/scala/reflect/runtime/ToolBoxes.scala index 7b1fc9fc0d..895c645c83 100644 --- a/src/compiler/scala/reflect/runtime/ToolBoxes.scala +++ b/src/compiler/scala/reflect/runtime/ToolBoxes.scala @@ -315,18 +315,16 @@ trait ToolBoxes extends { self: Universe => // todo. implement this ??? - def resetAllAttrs[T <: Tree](tree: T): T = { + def resetAllAttrs(tree: Tree): Tree = { val ctree: compiler.Tree = importer.importTree(tree) val ttree: compiler.Tree = compiler.resetAllAttrs(ctree) - val rmttree = exporter.importTree(ttree) - rmttree.asInstanceOf[T] + exporter.importTree(ttree) } - def resetLocalAttrs[T <: Tree](tree: T): T = { + def resetLocalAttrs(tree: Tree): Tree = { val ctree: compiler.Tree = importer.importTree(tree) val ttree: compiler.Tree = compiler.resetLocalAttrs(ctree) - val rmttree = exporter.importTree(ttree) - rmttree.asInstanceOf[T] + exporter.importTree(ttree) } def showAttributed(tree: Tree, printTypes: Boolean = true, printIds: Boolean = true, printKinds: Boolean = false): String = diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl index 599936f6f8..85bb5f2056 100644 --- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl +++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl @@ -9,10 +9,19 @@ ############################################################################## findScalaHome () { - # see #2092 + # see SI-2092 local SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done - ( cd -P "$( dirname "$SOURCE" )"/.. && pwd ) + local bindir="$( dirname "$SOURCE" )" + if [[ -d "$bindir"/.. ]]; then + ( cd -P "$bindir"/.. && pwd ) + else + # See SI-5792 + local dir=$(dirname "${BASH_SOURCE[0]}") + local link=$(dirname "$(readlink "${BASH_SOURCE[0]}")") + local path="$dir/$link/.." + ( cd "$path" && pwd ) + fi } execCommand () { [[ -n $SCALA_RUNNER_DEBUG ]] && echo "" && for arg in "$@@"; do echo "$arg"; done && echo ""; diff --git a/src/compiler/scala/tools/asm/AnnotationVisitor.java b/src/compiler/scala/tools/asm/AnnotationVisitor.java new file mode 100644 index 0000000000..b96e730a73 --- /dev/null +++ b/src/compiler/scala/tools/asm/AnnotationVisitor.java @@ -0,0 +1,157 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be + * called in the following order: ( <tt>visit</tt> | <tt>visitEnum</tt> | + * <tt>visitAnnotation</tt> | <tt>visitArray</tt> )* <tt>visitEnd</tt>. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. + * May be null. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param av the annotation visitor to which this visitor must delegate + * method calls. May be null. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor av) { + /*if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + }*/ + this.api = api; + this.av = av; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name the value name. + * @param value the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, {@link Integer} + * , {@link Long}, {@link Float}, {@link Double}, {@link String} or + * {@link Type} or OBJECT or ARRAY sort. This value can also be an + * array of byte, boolean, short, char, int, long, float or double + * values (this is equivalent to using {@link #visitArray visitArray} + * and visiting each array element in turn, but is more convenient). + */ + public void visit(String name, Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name the value name. + * @param desc the class descriptor of the enumeration class. + * @param value the actual enumeration value. + */ + public void visitEnum(String name, String desc, String value) { + if (av != null) { + av.visitEnum(name, desc, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name the value name. + * @param desc the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or + * <tt>null</tt> if this visitor is not interested in visiting + * this nested annotation. <i>The nested annotation value must be + * fully visited before calling other methods on this annotation + * visitor</i>. + */ + public AnnotationVisitor visitAnnotation(String name, String desc) { + if (av != null) { + return av.visitAnnotation(name, desc); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link ClassReader} does. + * + * @param name the value name. + * @return a visitor to visit the actual array value elements, or + * <tt>null</tt> if this visitor is not interested in visiting + * these values. The 'name' parameters passed to the methods of this + * visitor are ignored. <i>All the array values must be visited + * before calling other methods on this annotation visitor</i>. + */ + public AnnotationVisitor visitArray(String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** + * Visits the end of the annotation. + */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/src/compiler/scala/tools/asm/AnnotationWriter.java b/src/compiler/scala/tools/asm/AnnotationWriter.java new file mode 100644 index 0000000000..e530780249 --- /dev/null +++ b/src/compiler/scala/tools/asm/AnnotationWriter.java @@ -0,0 +1,322 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** + * The class writer to which this annotation must be added. + */ + private final ClassWriter cw; + + /** + * The number of values in this annotation. + */ + private int size; + + /** + * <tt>true<tt> if values are named, <tt>false</tt> otherwise. Annotation + * writers used for annotation default and annotation arrays use unnamed + * values. + */ + private final boolean named; + + /** + * The annotation values in bytecode form. This byte vector only contains + * the values themselves, i.e. the number of values must be stored as a + * unsigned short just before these bytes. + */ + private final ByteVector bv; + + /** + * The byte vector to be used to store the number of values of this + * annotation. See {@link #bv}. + */ + private final ByteVector parent; + + /** + * Where the number of values of this annotation must be stored in + * {@link #parent}. + */ + private final int offset; + + /** + * Next annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter next; + + /** + * Previous annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter prev; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param cw the class writer to which this annotation must be added. + * @param named <tt>true<tt> if values are named, <tt>false</tt> otherwise. + * @param bv where the annotation values must be stored. + * @param parent where the number of annotation values must be stored. + * @param offset where in <tt>parent</tt> the number of annotation values must + * be stored. + */ + AnnotationWriter( + final ClassWriter cw, + final boolean named, + final ByteVector bv, + final ByteVector parent, + final int offset) + { + super(Opcodes.ASM4); + this.cw = cw; + this.named = named; + this.bv = bv; + this.parent = parent; + this.offset = offset; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + if (value instanceof String) { + bv.put12('s', cw.newUTF8((String) value)); + } else if (value instanceof Byte) { + bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int v = ((Boolean) value).booleanValue() ? 1 : 0; + bv.put12('Z', cw.newInteger(v).index); + } else if (value instanceof Character) { + bv.put12('C', cw.newInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] v = (byte[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('B', cw.newInteger(v[i]).index); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('S', cw.newInteger(v[i]).index); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('C', cw.newInteger(v[i]).index); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('I', cw.newInteger(v[i]).index); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('J', cw.newLong(v[i]).index); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('F', cw.newFloat(v[i]).index); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('D', cw.newDouble(v[i]).index); + } + } else { + Item i = cw.newConstItem(value); + bv.put12(".s.IFJDCS".charAt(i.type), i.index); + } + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String name, + final String desc) + { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag and type, and reserve space for values count + bv.put12('@', cw.newUTF8(desc)).putShort(0); + return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag, and reserve space for array size + bv.put12('[', 0); + return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + } + + @Override + public void visitEnd() { + if (parent != null) { + byte[] data = parent.data; + data[offset] = (byte) (size >>> 8); + data[offset + 1] = (byte) size; + } + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this annotation writer list. + * + * @return the size of this annotation writer list. + */ + int getSize() { + int size = 0; + AnnotationWriter aw = this; + while (aw != null) { + size += aw.bv.length; + aw = aw.next; + } + return size; + } + + /** + * Puts the annotations of this annotation writer list into the given byte + * vector. + * + * @param out where the annotations must be put. + */ + void put(final ByteVector out) { + int n = 0; + int size = 2; + AnnotationWriter aw = this; + AnnotationWriter last = null; + while (aw != null) { + ++n; + size += aw.bv.length; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putInt(size); + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + + /** + * Puts the given annotation lists into the given byte vector. + * + * @param panns an array of annotation writer lists. + * @param off index of the first annotation to be written. + * @param out where the annotations must be put. + */ + static void put( + final AnnotationWriter[] panns, + final int off, + final ByteVector out) + { + int size = 1 + 2 * (panns.length - off); + for (int i = off; i < panns.length; ++i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + out.putInt(size).putByte(panns.length - off); + for (int i = off; i < panns.length; ++i) { + AnnotationWriter aw = panns[i]; + AnnotationWriter last = null; + int n = 0; + while (aw != null) { + ++n; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + } +} diff --git a/src/compiler/scala/tools/asm/Attribute.java b/src/compiler/scala/tools/asm/Attribute.java new file mode 100644 index 0000000000..408f21ce1e --- /dev/null +++ b/src/compiler/scala/tools/asm/Attribute.java @@ -0,0 +1,254 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A non standard class, field, method or code attribute. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** + * The type of this attribute. + */ + public final String type; + + /** + * The raw value of this attribute, used only for unknown attributes. + */ + byte[] value; + + /** + * The next attribute in this attribute list. May be <tt>null</tt>. + */ + Attribute next; + + /** + * Constructs a new empty attribute. + * + * @param type the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns <tt>true</tt> if this type of attribute is unknown. The default + * implementation of this method always returns <tt>true</tt>. + * + * @return <tt>true</tt> if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns <tt>true</tt> if this type of attribute is a code attribute. + * + * @return <tt>true</tt> if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or <tt>null</tt> if + * this attribute is not a code attribute that contains labels. + */ + protected Label[] getLabels() { + return null; + } + + /** + * Reads a {@link #type type} attribute. This method must return a <i>new</i> + * {@link Attribute} object, of type {@link #type type}, corresponding to + * the <tt>len</tt> bytes starting at the given offset, in the given class + * reader. + * + * @param cr the class that contains the attribute to be read. + * @param off index of the first byte of the attribute's content in {@link + * ClassReader#b cr.b}. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into account + * here. + * @param len the length of the attribute's content. + * @param buf buffer to be used to call + * {@link ClassReader#readUTF8 readUTF8}, + * {@link ClassReader#readClass(int,char[]) readClass} or + * {@link ClassReader#readConst readConst}. + * @param codeOff index of the first byte of code's attribute content in + * {@link ClassReader#b cr.b}, or -1 if the attribute to be read is + * not a code attribute. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into account + * here. + * @param labels the labels of the method's code, or <tt>null</tt> if the + * attribute to be read is not a code attribute. + * @return a <i>new</i> {@link Attribute} object corresponding to the given + * bytes. + */ + protected Attribute read( + final ClassReader cr, + final int off, + final int len, + final char[] buf, + final int codeOff, + final Label[] labels) + { + Attribute attr = new Attribute(type); + attr.value = new byte[len]; + System.arraycopy(cr.b, off, attr.value, 0, len); + return attr; + } + + /** + * Returns the byte array form of this attribute. + * + * @param cw the class to which this attribute must be added. This parameter + * can be used to add to the constant pool of this class the items + * that corresponds to this attribute. + * @param code the bytecode of the method corresponding to this code + * attribute, or <tt>null</tt> if this attribute is not a code + * attributes. + * @param len the length of the bytecode of the method corresponding to this + * code attribute, or <tt>null</tt> if this attribute is not a code + * attribute. + * @param maxStack the maximum stack size of the method corresponding to + * this code attribute, or -1 if this attribute is not a code + * attribute. + * @param maxLocals the maximum number of local variables of the method + * corresponding to this code attribute, or -1 if this attribute is + * not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals) + { + ByteVector v = new ByteVector(); + v.data = value; + v.length = value.length; + return v; + } + + /** + * Returns the length of the attribute list that begins with this attribute. + * + * @return the length of the attribute list that begins with this attribute. + */ + final int getCount() { + int count = 0; + Attribute attr = this; + while (attr != null) { + count += 1; + attr = attr.next; + } + return count; + } + + /** + * Returns the size of all the attributes in this attribute list. + * + * @param cw the class writer to be used to convert the attributes into byte + * arrays, with the {@link #write write} method. + * @param code the bytecode of the method corresponding to these code + * attributes, or <tt>null</tt> if these attributes are not code + * attributes. + * @param len the length of the bytecode of the method corresponding to + * these code attributes, or <tt>null</tt> if these attributes are + * not code attributes. + * @param maxStack the maximum stack size of the method corresponding to + * these code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these attributes + * are not code attributes. + * @return the size of all the attributes in this attribute list. This size + * includes the size of the attribute headers. + */ + final int getSize( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals) + { + Attribute attr = this; + int size = 0; + while (attr != null) { + cw.newUTF8(attr.type); + size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; + attr = attr.next; + } + return size; + } + + /** + * Writes all the attributes of this attribute list in the given byte + * vector. + * + * @param cw the class writer to be used to convert the attributes into byte + * arrays, with the {@link #write write} method. + * @param code the bytecode of the method corresponding to these code + * attributes, or <tt>null</tt> if these attributes are not code + * attributes. + * @param len the length of the bytecode of the method corresponding to + * these code attributes, or <tt>null</tt> if these attributes are + * not code attributes. + * @param maxStack the maximum stack size of the method corresponding to + * these code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these attributes + * are not code attributes. + * @param out where the attributes must be written. + */ + final void put( + final ClassWriter cw, + final byte[] code, + final int len, + final int maxStack, + final int maxLocals, + final ByteVector out) + { + Attribute attr = this; + while (attr != null) { + ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); + out.putShort(cw.newUTF8(attr.type)).putInt(b.length); + out.putByteArray(b.data, 0, b.length); + attr = attr.next; + } + } +} diff --git a/src/compiler/scala/tools/asm/ByteVector.java b/src/compiler/scala/tools/asm/ByteVector.java new file mode 100644 index 0000000000..5081f0184b --- /dev/null +++ b/src/compiler/scala/tools/asm/ByteVector.java @@ -0,0 +1,293 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to + * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** + * The content of this vector. + */ + byte[] data; + + /** + * Actual number of bytes in this vector. + */ + int length; + + /** + * Constructs a new {@link ByteVector ByteVector} with a default initial + * size. + */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector ByteVector} with the given initial + * size. + * + * @param initialSize the initial size of the byte vector to be constructed. + */ + public ByteVector(final int initialSize) { + data = new byte[initialSize]; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int b) { + int length = this.length; + if (length + 1 > data.length) { + enlarge(1); + } + data[length++] = (byte) b; + this.length = length; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b1 a byte. + * @param b2 another byte. + * @return this byte vector. + */ + ByteVector put11(final int b1, final int b2) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) b1; + data[length++] = (byte) b2; + this.length = length; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param s a short. + * @return this byte vector. + */ + public ByteVector putShort(final int s) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b a byte. + * @param s a short. + * @return this byte vector. + */ + ByteVector put12(final int b, final int s) { + int length = this.length; + if (length + 3 > data.length) { + enlarge(3); + } + byte[] data = this.data; + data[length++] = (byte) b; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param i an int. + * @return this byte vector. + */ + public ByteVector putInt(final int i) { + int length = this.length; + if (length + 4 > data.length) { + enlarge(4); + } + byte[] data = this.data; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param l a long. + * @return this byte vector. + */ + public ByteVector putLong(final long l) { + int length = this.length; + if (length + 8 > data.length) { + enlarge(8); + } + byte[] data = this.data; + int i = (int) (l >>> 32); + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + i = (int) l; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param s a String. + * @return this byte vector. + */ + public ByteVector putUTF8(final String s) { + int charLength = s.length(); + int len = length; + if (len + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] data = this.data; + // optimistic algorithm: instead of computing the byte length and then + // serializing the string (which requires two loops), we assume the byte + // length is equal to char length (which is the most frequent case), and + // we start serializing the string right away. During the serialization, + // if we find that this assumption is wrong, we continue with the + // general method. + data[len++] = (byte) (charLength >>> 8); + data[len++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char c = s.charAt(i); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else { + int byteLength = i; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + data[length] = (byte) (byteLength >>> 8); + data[length + 1] = (byte) byteLength; + if (length + 2 + byteLength > data.length) { + length = len; + enlarge(2 + byteLength); + data = this.data; + } + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); + } + } + break; + } + } + length = len; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b an array of bytes. May be <tt>null</tt> to put <tt>len</tt> + * null bytes into this byte vector. + * @param off index of the fist byte of b that must be copied. + * @param len number of bytes of b that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray(final byte[] b, final int off, final int len) + { + if (length + len > data.length) { + enlarge(len); + } + if (b != null) { + System.arraycopy(b, off, data, length, len); + } + length += len; + return this; + } + + /** + * Enlarge this byte vector so that it can receive n more bytes. + * + * @param size number of additional bytes that this byte vector should be + * able to receive. + */ + private void enlarge(final int size) { + int length1 = 2 * data.length; + int length2 = length + size; + byte[] newData = new byte[length1 > length2 ? length1 : length2]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/src/compiler/scala/tools/asm/ClassReader.java b/src/compiler/scala/tools/asm/ClassReader.java new file mode 100644 index 0000000000..f3287d41ae --- /dev/null +++ b/src/compiler/scala/tools/asm/ClassReader.java @@ -0,0 +1,2216 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A Java class parser to make a {@link ClassVisitor} visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * True to enable signatures support. + */ + static final boolean SIGNATURES = true; + + /** + * True to enable annotations support. + */ + static final boolean ANNOTATIONS = true; + + /** + * True to enable stack map frames support. + */ + static final boolean FRAMES = true; + + /** + * True to enable bytecode writing support. + */ + static final boolean WRITER = true; + + /** + * True to enable JSR_W and GOTO_W support. + */ + static final boolean RESIZE = true; + + /** + * Flag to skip method code. If this class is set <code>CODE</code> + * attribute won't be visited. This can be used, for example, to retrieve + * annotations for methods and method parameters. + */ + public static final int SKIP_CODE = 1; + + /** + * Flag to skip the debug information in the class. If this flag is set the + * debug information of the class is not visited, i.e. the + * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be + * called. + */ + public static final int SKIP_DEBUG = 2; + + /** + * Flag to skip the stack map frames in the class. If this flag is set the + * stack map frames of the class is not visited, i.e. the + * {@link MethodVisitor#visitFrame visitFrame} method will not be called. + * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is + * used: it avoids visiting frames that will be ignored and recomputed from + * scratch in the class writer. + */ + public static final int SKIP_FRAMES = 4; + + /** + * Flag to expand the stack map frames. By default stack map frames are + * visited in their original format (i.e. "expanded" for classes whose + * version is less than V1_6, and "compressed" for the other classes). If + * this flag is set, stack map frames are always visited in expanded format + * (this option adds a decompression/recompression step in ClassReader and + * ClassWriter which degrades performances quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * The class to be parsed. <i>The content of this array must not be + * modified. This field is intended for {@link Attribute} sub classes, and + * is normally not needed by class generators or adapters.</i> + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. + * The one byte offset skips the constant pool item tag that indicates its + * type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + */ + public ClassReader(final byte[] b) { + this(b, 0, b.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + * @param off the start offset of the class data. + * @param len the length of the class data. + */ + public ClassReader(final byte[] b, final int off, final int len) { + this.b = b; + // checks the class version + if (readShort(6) > Opcodes.V1_7) { + throw new IllegalArgumentException(); + } + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + case ClassWriter.INT: + case ClassWriter.FLOAT: + case ClassWriter.NAME_TYPE: + case ClassWriter.INDY: + size = 5; + break; + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + size = 9; + ++i; + break; + case ClassWriter.UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + case ClassWriter.HANDLE: + size = 4; + break; + // case ClassWriter.CLASS: + // case ClassWriter.STR: + // case ClassWriter.MTYPE + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may + * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 + * and those flags are represented by attributes. + * + * @return the class access flags + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the internal class name + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, the + * super class is {@link Object}. + * + * @return the internal name of super class, or <tt>null</tt> for + * {@link Object} class. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + int n = items[readUnsignedShort(header + 4)]; + return n == 0 ? null : readUTF8(n, new char[maxStringLength]); + } + + /** + * Returns the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the array of internal names for all implemented interfaces or + * <tt>null</tt>. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + int index = header + 6; + int n = readUnsignedShort(index); + String[] interfaces = new String[n]; + if (n > 0) { + char[] buf = new char[maxStringLength]; + for (int i = 0; i < n; ++i) { + index += 2; + interfaces[i] = readClass(index, buf); + } + } + return interfaces; + } + + /** + * Copies the constant pool data into the given {@link ClassWriter}. Should + * be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter the {@link ClassWriter} to copy constant pool into. + */ + void copyPool(final ClassWriter classWriter) { + char[] buf = new char[maxStringLength]; + int ll = items.length; + Item[] items2 = new Item[ll]; + for (int i = 1; i < ll; i++) { + int index = items[i]; + int tag = b[index - 1]; + Item item = new Item(i); + int nameType; + switch (tag) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + nameType = items[readUnsignedShort(index + 2)]; + item.set(tag, + readClass(index, buf), + readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + break; + + case ClassWriter.INT: + item.set(readInt(index)); + break; + + case ClassWriter.FLOAT: + item.set(Float.intBitsToFloat(readInt(index))); + break; + + case ClassWriter.NAME_TYPE: + item.set(tag, + readUTF8(index, buf), + readUTF8(index + 2, buf), + null); + break; + + case ClassWriter.LONG: + item.set(readLong(index)); + ++i; + break; + + case ClassWriter.DOUBLE: + item.set(Double.longBitsToDouble(readLong(index))); + ++i; + break; + + case ClassWriter.UTF8: { + String s = strings[i]; + if (s == null) { + index = items[i]; + s = strings[i] = readUTF(index + 2, + readUnsignedShort(index), + buf); + } + item.set(tag, s, null, null); + } + break; + + case ClassWriter.HANDLE: { + int fieldOrMethodRef = items[readUnsignedShort(index + 1)]; + nameType = items[readUnsignedShort(fieldOrMethodRef + 2)]; + item.set(ClassWriter.HANDLE_BASE + readByte(index), + readClass(fieldOrMethodRef, buf), + readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + + } + break; + + + case ClassWriter.INDY: + if (classWriter.bootstrapMethods == null) { + copyBootstrapMethods(classWriter, items2, buf); + } + nameType = items[readUnsignedShort(index + 2)]; + item.set(readUTF8(nameType, buf), + readUTF8(nameType + 2, buf), + readUnsignedShort(index)); + break; + + + // case ClassWriter.STR: + // case ClassWriter.CLASS: + // case ClassWriter.MTYPE + default: + item.set(tag, readUTF8(index, buf), null, null); + break; + } + + int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + } + + int off = items[1] - 1; + classWriter.pool.putByteArray(b, off, header - off); + classWriter.items = items2; + classWriter.threshold = (int) (0.75d * ll); + classWriter.index = ll; + } + + private void copyBootstrapMethods(ClassWriter classWriter, Item[] items2, char[] buf) { + int i, j, k, u, v; + + // skip class header + v = header; + v += 8 + (readUnsignedShort(v + 6) << 1); + + // skips fields and methods + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + + // read class attributes + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + String attrName = readUTF8(v, buf); + int size = readInt(v + 2); + if ("BootstrapMethods".equals(attrName)) { + int boostrapMethodCount = readUnsignedShort(v + 6); + int x = v + 8; + for (j = 0; j < boostrapMethodCount; j++) { + int hashCode = readConst(readUnsignedShort(x), buf).hashCode(); + k = readUnsignedShort(x + 2); + u = x + 4; + for(; k > 0; --k) { + hashCode ^= readConst(readUnsignedShort(u), buf).hashCode(); + u += 2; + } + Item item = new Item(j); + item.set(x - v - 8, hashCode & 0x7FFFFFFF); + + int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + + x = u; + } + + classWriter.bootstrapMethodsCount = boostrapMethodCount; + ByteVector bootstrapMethods = new ByteVector(size + 62); + bootstrapMethods.putByteArray(b, v + 8, size - 2); + classWriter.bootstrapMethods = bootstrapMethods; + return; + } + v += 6 + size; + } + + // we are in trouble !!! + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is an input stream from which to read the class. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream is) throws IOException { + this(readClass(is, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param name the binary qualified name of the class to be read. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String name) throws IOException { + this(readClass(ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + + ".class"), true)); + } + + /** + * Reads the bytecode of a class. + * + * @param is an input stream from which to read the class. + * @param close true to close the input stream after reading. + * @return the bytecode read from the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is, boolean close) + throws IOException + { + if (is == null) { + throw new IOException("Class not found"); + } + try { + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + int last = is.read(); + if (last < 0) { + return b; + } + byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } finally { + if (close) { + is.close(); + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor the visitor that must visit this class. + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}, + * {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, final int flags) { + accept(classVisitor, new Attribute[0], flags); + } + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor the visitor that must visit this class. + * @param attrs prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to the + * type of one the prototypes will not be parsed: its byte array + * value will be passed unchanged to the ClassWriter. <i>This may + * corrupt it if this value contains references to the constant pool, + * or has syntactic or semantic links with a class element that has + * been transformed by a class adapter between the reader and the + * writer</i>. + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}, + * {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attrs, + final int flags) + { + byte[] b = this.b; // the bytecode array + char[] c = new char[maxStringLength]; // buffer used to read strings + int i, j, k; // loop variables + int u, v, w; // indexes in b + Attribute attr; + + int access; + String name; + String desc; + String attrName; + String signature; + int anns = 0; + int ianns = 0; + Attribute cattrs = null; + + // visits the header + u = header; + access = readUnsignedShort(u); + name = readClass(u + 2, c); + v = items[readUnsignedShort(u + 4)]; + String superClassName = v == 0 ? null : readUTF8(v, c); + String[] implementedItfs = new String[readUnsignedShort(u + 6)]; + w = 0; + u += 8; + for (i = 0; i < implementedItfs.length; ++i) { + implementedItfs[i] = readClass(u, c); + u += 2; + } + + boolean skipCode = (flags & SKIP_CODE) != 0; + boolean skipDebug = (flags & SKIP_DEBUG) != 0; + boolean unzip = (flags & EXPAND_FRAMES) != 0; + + // skips fields and methods + v = u; + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + // reads the class's attributes + signature = null; + String sourceFile = null; + String sourceDebug = null; + String enclosingOwner = null; + String enclosingName = null; + String enclosingDesc = null; + int[] bootstrapMethods = null; // start indexed of the bsms + + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + attrName = readUTF8(v, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("SourceFile".equals(attrName)) { + sourceFile = readUTF8(v + 6, c); + } else if ("InnerClasses".equals(attrName)) { + w = v + 6; + } else if ("EnclosingMethod".equals(attrName)) { + enclosingOwner = readClass(v + 6, c); + int item = readUnsignedShort(v + 8); + if (item != 0) { + enclosingName = readUTF8(items[item], c); + enclosingDesc = readUTF8(items[item] + 2, c); + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(v + 6, c); + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = v + 6; + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if ("SourceDebugExtension".equals(attrName)) { + int len = readInt(v + 2); + sourceDebug = readUTF(v + 6, len, new char[len]); + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = v + 6; + } else if ("BootstrapMethods".equals(attrName)) { + int boostrapMethodCount = readUnsignedShort(v + 6); + bootstrapMethods = new int[boostrapMethodCount]; + int x = v + 8; + for (j = 0; j < boostrapMethodCount; j++) { + bootstrapMethods[j] = x; + x += 2 + readUnsignedShort(x + 2) << 1; + } + } else { + attr = readAttribute(attrs, + attrName, + v + 6, + readInt(v + 2), + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + v += 6 + readInt(v + 2); + } + // calls the visit method + classVisitor.visit(readInt(4), + access, + name, + signature, + superClassName, + implementedItfs); + + // calls the visitSource method + if (!skipDebug && (sourceFile != null || sourceDebug != null)) { + classVisitor.visitSource(sourceFile, sourceDebug); + } + + // calls the visitOuterClass method + if (enclosingOwner != null) { + classVisitor.visitOuterClass(enclosingOwner, + enclosingName, + enclosingDesc); + } + + // visits the class annotations + if (ANNOTATIONS) { + for (i = 1; i >= 0; --i) { + v = i == 0 ? ianns : anns; + if (v != 0) { + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + v = readAnnotationValues(v + 2, + c, + true, + classVisitor.visitAnnotation(readUTF8(v, c), i != 0)); + } + } + } + } + + // visits the class attributes + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + classVisitor.visitAttribute(cattrs); + cattrs = attr; + } + + // calls the visitInnerClass method + if (w != 0) { + i = readUnsignedShort(w); + w += 2; + for (; i > 0; --i) { + classVisitor.visitInnerClass(readUnsignedShort(w) == 0 + ? null + : readClass(w, c), readUnsignedShort(w + 2) == 0 + ? null + : readClass(w + 2, c), readUnsignedShort(w + 4) == 0 + ? null + : readUTF8(w + 4, c), readUnsignedShort(w + 6)); + w += 8; + } + } + + // visits the fields + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + access = readUnsignedShort(u); + name = readUTF8(u + 2, c); + desc = readUTF8(u + 4, c); + // visits the field's attributes and looks for a ConstantValue + // attribute + int fieldValueItem = 0; + signature = null; + anns = 0; + ianns = 0; + cattrs = null; + + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + attrName = readUTF8(u, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("ConstantValue".equals(attrName)) { + fieldValueItem = readUnsignedShort(u + 6); + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 6, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 6; + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 6; + } else { + attr = readAttribute(attrs, + attrName, + u + 6, + readInt(u + 2), + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + u += 6 + readInt(u + 2); + } + // visits the field + FieldVisitor fv = classVisitor.visitField(access, + name, + desc, + signature, + fieldValueItem == 0 ? null : readConst(fieldValueItem, c)); + // visits the field annotations and attributes + if (fv != null) { + if (ANNOTATIONS) { + for (j = 1; j >= 0; --j) { + v = j == 0 ? ianns : anns; + if (v != 0) { + k = readUnsignedShort(v); + v += 2; + for (; k > 0; --k) { + v = readAnnotationValues(v + 2, + c, + true, + fv.visitAnnotation(readUTF8(v, c), j != 0)); + } + } + } + } + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + fv.visitAttribute(cattrs); + cattrs = attr; + } + fv.visitEnd(); + } + } + + // visits the methods + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + int u0 = u + 6; + access = readUnsignedShort(u); + name = readUTF8(u + 2, c); + desc = readUTF8(u + 4, c); + signature = null; + anns = 0; + ianns = 0; + int dann = 0; + int mpanns = 0; + int impanns = 0; + cattrs = null; + v = 0; + w = 0; + + // looks for Code and Exceptions attributes + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + attrName = readUTF8(u, c); + int attrSize = readInt(u + 2); + u += 6; + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("Code".equals(attrName)) { + if (!skipCode) { + v = u; + } + } else if ("Exceptions".equals(attrName)) { + w = u; + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u; + } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { + dann = u; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u; + } else if (ANNOTATIONS && "RuntimeVisibleParameterAnnotations".equals(attrName)) + { + mpanns = u; + } else if (ANNOTATIONS && "RuntimeInvisibleParameterAnnotations".equals(attrName)) + { + impanns = u; + } else { + attr = readAttribute(attrs, + attrName, + u, + attrSize, + c, + -1, + null); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + u += attrSize; + } + // reads declared exceptions + String[] exceptions; + if (w == 0) { + exceptions = null; + } else { + exceptions = new String[readUnsignedShort(w)]; + w += 2; + for (j = 0; j < exceptions.length; ++j) { + exceptions[j] = readClass(w, c); + w += 2; + } + } + + // visits the method's code, if any + MethodVisitor mv = classVisitor.visitMethod(access, + name, + desc, + signature, + exceptions); + + if (mv != null) { + /* + * if the returned MethodVisitor is in fact a MethodWriter, it + * means there is no method adapter between the reader and the + * writer. If, in addition, the writer's constant pool was + * copied from this reader (mw.cw.cr == this), and the signature + * and exceptions of the method have not been changed, then it + * is possible to skip all visit events and just copy the + * original code of the method to the writer (the access, name + * and descriptor can have been changed, this is not important + * since they are not copied as is from the reader). + */ + if (WRITER && mv instanceof MethodWriter) { + MethodWriter mw = (MethodWriter) mv; + if (mw.cw.cr == this) { + if (signature == mw.signature) { + boolean sameExceptions = false; + if (exceptions == null) { + sameExceptions = mw.exceptionCount == 0; + } else { + if (exceptions.length == mw.exceptionCount) { + sameExceptions = true; + for (j = exceptions.length - 1; j >= 0; --j) + { + w -= 2; + if (mw.exceptions[j] != readUnsignedShort(w)) + { + sameExceptions = false; + break; + } + } + } + } + if (sameExceptions) { + /* + * we do not copy directly the code into + * MethodWriter to save a byte array copy + * operation. The real copy will be done in + * ClassWriter.toByteArray(). + */ + mw.classReaderOffset = u0; + mw.classReaderLength = u - u0; + continue; + } + } + } + } + + if (ANNOTATIONS && dann != 0) { + AnnotationVisitor dv = mv.visitAnnotationDefault(); + readAnnotationValue(dann, c, null, dv); + if (dv != null) { + dv.visitEnd(); + } + } + if (ANNOTATIONS) { + for (j = 1; j >= 0; --j) { + w = j == 0 ? ianns : anns; + if (w != 0) { + k = readUnsignedShort(w); + w += 2; + for (; k > 0; --k) { + w = readAnnotationValues(w + 2, + c, + true, + mv.visitAnnotation(readUTF8(w, c), j != 0)); + } + } + } + } + if (ANNOTATIONS && mpanns != 0) { + readParameterAnnotations(mpanns, desc, c, true, mv); + } + if (ANNOTATIONS && impanns != 0) { + readParameterAnnotations(impanns, desc, c, false, mv); + } + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + mv.visitAttribute(cattrs); + cattrs = attr; + } + } + + if (mv != null && v != 0) { + int maxStack = readUnsignedShort(v); + int maxLocals = readUnsignedShort(v + 2); + int codeLength = readInt(v + 4); + v += 8; + + int codeStart = v; + int codeEnd = v + codeLength; + + mv.visitCode(); + + // 1st phase: finds the labels + int label; + Label[] labels = new Label[codeLength + 2]; + readLabel(codeLength + 1, labels); + while (v < codeEnd) { + w = v - codeStart; + int opcode = b[v] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + v += 1; + break; + case ClassWriter.LABEL_INSN: + readLabel(w + readShort(v + 1), labels); + v += 3; + break; + case ClassWriter.LABELW_INSN: + readLabel(w + readInt(v + 1), labels); + v += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[v + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + v += 6; + } else { + v += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes* + v = v + 4 - (w & 3); + // reads instruction + readLabel(w + readInt(v), labels); + j = readInt(v + 8) - readInt(v + 4) + 1; + v += 12; + for (; j > 0; --j) { + readLabel(w + readInt(v), labels); + v += 4; + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes* + v = v + 4 - (w & 3); + // reads instruction + readLabel(w + readInt(v), labels); + j = readInt(v + 4); + v += 8; + for (; j > 0; --j) { + readLabel(w + readInt(v + 4), labels); + v += 8; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + v += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + v += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + v += 5; + break; + // case MANA_INSN: + default: + v += 4; + break; + } + } + // parses the try catch entries + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + Label start = readLabel(readUnsignedShort(v), labels); + Label end = readLabel(readUnsignedShort(v + 2), labels); + Label handler = readLabel(readUnsignedShort(v + 4), labels); + int type = readUnsignedShort(v + 6); + if (type == 0) { + mv.visitTryCatchBlock(start, end, handler, null); + } else { + mv.visitTryCatchBlock(start, + end, + handler, + readUTF8(items[type], c)); + } + v += 8; + } + // parses the local variable, line number tables, and code + // attributes + int varTable = 0; + int varTypeTable = 0; + int stackMap = 0; + int stackMapSize = 0; + int frameCount = 0; + int frameMode = 0; + int frameOffset = 0; + int frameLocalCount = 0; + int frameLocalDiff = 0; + int frameStackCount = 0; + Object[] frameLocal = null; + Object[] frameStack = null; + boolean zip = true; + cattrs = null; + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + attrName = readUTF8(v, c); + if ("LocalVariableTable".equals(attrName)) { + if (!skipDebug) { + varTable = v + 6; + k = readUnsignedShort(v + 6); + w = v + 8; + for (; k > 0; --k) { + label = readUnsignedShort(w); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + label += readUnsignedShort(w + 2); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + w += 10; + } + } + } else if ("LocalVariableTypeTable".equals(attrName)) { + varTypeTable = v + 6; + } else if ("LineNumberTable".equals(attrName)) { + if (!skipDebug) { + k = readUnsignedShort(v + 6); + w = v + 8; + for (; k > 0; --k) { + label = readUnsignedShort(w); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + labels[label].line = readUnsignedShort(w + 2); + w += 4; + } + } + } else if (FRAMES && "StackMapTable".equals(attrName)) { + if ((flags & SKIP_FRAMES) == 0) { + stackMap = v + 8; + stackMapSize = readInt(v + 2); + frameCount = readUnsignedShort(v + 6); + } + /* + * here we do not extract the labels corresponding to + * the attribute content. This would require a full + * parsing of the attribute, which would need to be + * repeated in the second phase (see below). Instead the + * content of the attribute is read one frame at a time + * (i.e. after a frame has been visited, the next frame + * is read), and the labels it contains are also + * extracted one frame at a time. Thanks to the ordering + * of frames, having only a "one frame lookahead" is not + * a problem, i.e. it is not possible to see an offset + * smaller than the offset of the current insn and for + * which no Label exist. + */ + /* + * This is not true for UNINITIALIZED type offsets. We + * solve this by parsing the stack map table without a + * full decoding (see below). + */ + } else if (FRAMES && "StackMap".equals(attrName)) { + if ((flags & SKIP_FRAMES) == 0) { + stackMap = v + 8; + stackMapSize = readInt(v + 2); + frameCount = readUnsignedShort(v + 6); + zip = false; + } + /* + * IMPORTANT! here we assume that the frames are + * ordered, as in the StackMapTable attribute, although + * this is not guaranteed by the attribute format. + */ + } else { + for (k = 0; k < attrs.length; ++k) { + if (attrs[k].type.equals(attrName)) { + attr = attrs[k].read(this, + v + 6, + readInt(v + 2), + c, + codeStart - 8, + labels); + if (attr != null) { + attr.next = cattrs; + cattrs = attr; + } + } + } + } + v += 6 + readInt(v + 2); + } + + // 2nd phase: visits each instruction + if (FRAMES && stackMap != 0) { + // creates the very first (implicit) frame from the method + // descriptor + frameLocal = new Object[maxLocals]; + frameStack = new Object[maxStack]; + if (unzip) { + int local = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ("<init>".equals(name)) { + frameLocal[local++] = Opcodes.UNINITIALIZED_THIS; + } else { + frameLocal[local++] = readClass(header + 2, c); + } + } + j = 1; + loop: while (true) { + k = j; + switch (desc.charAt(j++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + frameLocal[local++] = Opcodes.INTEGER; + break; + case 'F': + frameLocal[local++] = Opcodes.FLOAT; + break; + case 'J': + frameLocal[local++] = Opcodes.LONG; + break; + case 'D': + frameLocal[local++] = Opcodes.DOUBLE; + break; + case '[': + while (desc.charAt(j) == '[') { + ++j; + } + if (desc.charAt(j) == 'L') { + ++j; + while (desc.charAt(j) != ';') { + ++j; + } + } + frameLocal[local++] = desc.substring(k, ++j); + break; + case 'L': + while (desc.charAt(j) != ';') { + ++j; + } + frameLocal[local++] = desc.substring(k + 1, + j++); + break; + default: + break loop; + } + } + frameLocalCount = local; + } + /* + * for the first explicit frame the offset is not + * offset_delta + 1 but only offset_delta; setting the + * implicit frame offset to -1 allow the use of the + * "offset_delta + 1" rule in all cases + */ + frameOffset = -1; + /* + * Finds labels for UNINITIALIZED frame types. Instead of + * decoding each element of the stack map table, we look + * for 3 consecutive bytes that "look like" an UNINITIALIZED + * type (tag 8, offset within code bounds, NEW instruction + * at this offset). We may find false positives (i.e. not + * real UNINITIALIZED types), but this should be rare, and + * the only consequence will be the creation of an unneeded + * label. This is better than creating a label for each NEW + * instruction, and faster than fully decoding the whole + * stack map table. + */ + for (j = stackMap; j < stackMap + stackMapSize - 2; ++j) { + if (b[j] == 8) { // UNINITIALIZED FRAME TYPE + k = readUnsignedShort(j + 1); + if (k >= 0 && k < codeLength) { // potential offset + if ((b[codeStart + k] & 0xFF) == Opcodes.NEW) { // NEW at this offset + readLabel(k, labels); + } + } + } + } + } + v = codeStart; + Label l; + while (v < codeEnd) { + w = v - codeStart; + + l = labels[w]; + if (l != null) { + mv.visitLabel(l); + if (!skipDebug && l.line > 0) { + mv.visitLineNumber(l.line, l); + } + } + + while (FRAMES && frameLocal != null + && (frameOffset == w || frameOffset == -1)) + { + // if there is a frame for this offset, + // makes the visitor visit it, + // and reads the next frame if there is one. + if (!zip || unzip) { + mv.visitFrame(Opcodes.F_NEW, + frameLocalCount, + frameLocal, + frameStackCount, + frameStack); + } else if (frameOffset != -1) { + mv.visitFrame(frameMode, + frameLocalDiff, + frameLocal, + frameStackCount, + frameStack); + } + + if (frameCount > 0) { + int tag, delta, n; + if (zip) { + tag = b[stackMap++] & 0xFF; + } else { + tag = MethodWriter.FULL_FRAME; + frameOffset = -1; + } + frameLocalDiff = 0; + if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) + { + delta = tag; + frameMode = Opcodes.F_SAME; + frameStackCount = 0; + } else if (tag < MethodWriter.RESERVED) { + delta = tag + - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; + stackMap = readFrameType(frameStack, + 0, + stackMap, + c, + labels); + frameMode = Opcodes.F_SAME1; + frameStackCount = 1; + } else { + delta = readUnsignedShort(stackMap); + stackMap += 2; + if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + { + stackMap = readFrameType(frameStack, + 0, + stackMap, + c, + labels); + frameMode = Opcodes.F_SAME1; + frameStackCount = 1; + } else if (tag >= MethodWriter.CHOP_FRAME + && tag < MethodWriter.SAME_FRAME_EXTENDED) + { + frameMode = Opcodes.F_CHOP; + frameLocalDiff = MethodWriter.SAME_FRAME_EXTENDED + - tag; + frameLocalCount -= frameLocalDiff; + frameStackCount = 0; + } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) + { + frameMode = Opcodes.F_SAME; + frameStackCount = 0; + } else if (tag < MethodWriter.FULL_FRAME) { + j = unzip ? frameLocalCount : 0; + for (k = tag + - MethodWriter.SAME_FRAME_EXTENDED; k > 0; k--) + { + stackMap = readFrameType(frameLocal, + j++, + stackMap, + c, + labels); + } + frameMode = Opcodes.F_APPEND; + frameLocalDiff = tag + - MethodWriter.SAME_FRAME_EXTENDED; + frameLocalCount += frameLocalDiff; + frameStackCount = 0; + } else { // if (tag == FULL_FRAME) { + frameMode = Opcodes.F_FULL; + n = frameLocalDiff = frameLocalCount = readUnsignedShort(stackMap); + stackMap += 2; + for (j = 0; n > 0; n--) { + stackMap = readFrameType(frameLocal, + j++, + stackMap, + c, + labels); + } + n = frameStackCount = readUnsignedShort(stackMap); + stackMap += 2; + for (j = 0; n > 0; n--) { + stackMap = readFrameType(frameStack, + j++, + stackMap, + c, + labels); + } + } + } + frameOffset += delta + 1; + readLabel(frameOffset, labels); + + --frameCount; + } else { + frameLocal = null; + } + } + + int opcode = b[v] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + mv.visitInsn(opcode); + v += 1; + break; + case ClassWriter.IMPLVAR_INSN: + if (opcode > Opcodes.ISTORE) { + opcode -= 59; // ISTORE_0 + mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), + opcode & 0x3); + } else { + opcode -= 26; // ILOAD_0 + mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), + opcode & 0x3); + } + v += 1; + break; + case ClassWriter.LABEL_INSN: + mv.visitJumpInsn(opcode, labels[w + + readShort(v + 1)]); + v += 3; + break; + case ClassWriter.LABELW_INSN: + mv.visitJumpInsn(opcode - 33, labels[w + + readInt(v + 1)]); + v += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[v + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + mv.visitIincInsn(readUnsignedShort(v + 2), + readShort(v + 4)); + v += 6; + } else { + mv.visitVarInsn(opcode, + readUnsignedShort(v + 2)); + v += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (w & 3); + // reads instruction + label = w + readInt(v); + int min = readInt(v + 4); + int max = readInt(v + 8); + v += 12; + Label[] table = new Label[max - min + 1]; + for (j = 0; j < table.length; ++j) { + table[j] = labels[w + readInt(v)]; + v += 4; + } + mv.visitTableSwitchInsn(min, + max, + labels[label], + table); + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = v + 4 - (w & 3); + // reads instruction + label = w + readInt(v); + j = readInt(v + 4); + v += 8; + int[] keys = new int[j]; + Label[] values = new Label[j]; + for (j = 0; j < keys.length; ++j) { + keys[j] = readInt(v); + values[j] = labels[w + readInt(v + 4)]; + v += 8; + } + mv.visitLookupSwitchInsn(labels[label], + keys, + values); + break; + case ClassWriter.VAR_INSN: + mv.visitVarInsn(opcode, b[v + 1] & 0xFF); + v += 2; + break; + case ClassWriter.SBYTE_INSN: + mv.visitIntInsn(opcode, b[v + 1]); + v += 2; + break; + case ClassWriter.SHORT_INSN: + mv.visitIntInsn(opcode, readShort(v + 1)); + v += 3; + break; + case ClassWriter.LDC_INSN: + mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c)); + v += 2; + break; + case ClassWriter.LDCW_INSN: + mv.visitLdcInsn(readConst(readUnsignedShort(v + 1), + c)); + v += 3; + break; + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.ITFMETH_INSN: { + int cpIndex = items[readUnsignedShort(v + 1)]; + String iowner = readClass(cpIndex, c); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + if (opcode < Opcodes.INVOKEVIRTUAL) { + mv.visitFieldInsn(opcode, iowner, iname, idesc); + } else { + mv.visitMethodInsn(opcode, iowner, iname, idesc); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + v += 5; + } else { + v += 3; + } + break; + } + case ClassWriter.INDYMETH_INSN: { + int cpIndex = items[readUnsignedShort(v + 1)]; + int bsmIndex = bootstrapMethods[readUnsignedShort(cpIndex)]; + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + + int mhIndex = readUnsignedShort(bsmIndex); + Handle bsm = (Handle) readConst(mhIndex, c); + int bsmArgCount = readUnsignedShort(bsmIndex + 2); + Object[] bsmArgs = new Object[bsmArgCount]; + bsmIndex += 4; + for(int a = 0; a < bsmArgCount; a++) { + int argIndex = readUnsignedShort(bsmIndex); + bsmArgs[a] = readConst(argIndex, c); + bsmIndex += 2; + } + mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); + + v += 5; + break; + } + case ClassWriter.TYPE_INSN: + mv.visitTypeInsn(opcode, readClass(v + 1, c)); + v += 3; + break; + case ClassWriter.IINC_INSN: + mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]); + v += 3; + break; + // case MANA_INSN: + default: + mv.visitMultiANewArrayInsn(readClass(v + 1, c), + b[v + 3] & 0xFF); + v += 4; + break; + } + } + l = labels[codeEnd - codeStart]; + if (l != null) { + mv.visitLabel(l); + } + // visits the local variable tables + if (!skipDebug && varTable != 0) { + int[] typeTable = null; + if (varTypeTable != 0) { + k = readUnsignedShort(varTypeTable) * 3; + w = varTypeTable + 2; + typeTable = new int[k]; + while (k > 0) { + typeTable[--k] = w + 6; // signature + typeTable[--k] = readUnsignedShort(w + 8); // index + typeTable[--k] = readUnsignedShort(w); // start + w += 10; + } + } + k = readUnsignedShort(varTable); + w = varTable + 2; + for (; k > 0; --k) { + int start = readUnsignedShort(w); + int length = readUnsignedShort(w + 2); + int index = readUnsignedShort(w + 8); + String vsignature = null; + if (typeTable != null) { + for (int a = 0; a < typeTable.length; a += 3) { + if (typeTable[a] == start + && typeTable[a + 1] == index) + { + vsignature = readUTF8(typeTable[a + 2], c); + break; + } + } + } + mv.visitLocalVariable(readUTF8(w + 4, c), + readUTF8(w + 6, c), + vsignature, + labels[start], + labels[start + length], + index); + w += 10; + } + } + // visits the other attributes + while (cattrs != null) { + attr = cattrs.next; + cattrs.next = null; + mv.visitAttribute(cattrs); + cattrs = attr; + } + // visits the max stack and max locals values + mv.visitMaxs(maxStack, maxLocals); + } + + if (mv != null) { + mv.visitEnd(); + } + } + + // visits the end of the class + classVisitor.visitEnd(); + } + + /** + * Reads parameter annotations and makes the given visitor visit them. + * + * @param v start offset in {@link #b b} of the annotations to be read. + * @param desc the method descriptor. + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or + * {@link #readConst readConst}. + * @param visible <tt>true</tt> if the annotations to be read are visible + * at runtime. + * @param mv the visitor that must visit the annotations. + */ + private void readParameterAnnotations( + int v, + final String desc, + final char[] buf, + final boolean visible, + final MethodVisitor mv) + { + int i; + int n = b[v++] & 0xFF; + // workaround for a bug in javac (javac compiler generates a parameter + // annotation array whose size is equal to the number of parameters in + // the Java source file, while it should generate an array whose size is + // equal to the number of parameters in the method descriptor - which + // includes the synthetic parameters added by the compiler). This work- + // around supposes that the synthetic parameters are the first ones. + int synthetics = Type.getArgumentTypes(desc).length - n; + AnnotationVisitor av; + for (i = 0; i < synthetics; ++i) { + // virtual annotation to detect synthetic parameters in MethodWriter + av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); + if (av != null) { + av.visitEnd(); + } + } + for (; i < n + synthetics; ++i) { + int j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); + v = readAnnotationValues(v + 2, buf, true, av); + } + } + } + + /** + * Reads the values of an annotation and makes the given visitor visit them. + * + * @param v the start offset in {@link #b b} of the values to be read + * (including the unsigned short that gives the number of values). + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or + * {@link #readConst readConst}. + * @param named if the annotation values are named or not. + * @param av the visitor that must visit the values. + * @return the end offset of the annotation values. + */ + private int readAnnotationValues( + int v, + final char[] buf, + final boolean named, + final AnnotationVisitor av) + { + int i = readUnsignedShort(v); + v += 2; + if (named) { + for (; i > 0; --i) { + v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); + } + } else { + for (; i > 0; --i) { + v = readAnnotationValue(v, buf, null, av); + } + } + if (av != null) { + av.visitEnd(); + } + return v; + } + + /** + * Reads a value of an annotation and makes the given visitor visit it. + * + * @param v the start offset in {@link #b b} of the value to be read (<i>not + * including the value name constant pool index</i>). + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or + * {@link #readConst readConst}. + * @param name the name of the value to be read. + * @param av the visitor that must visit the value. + * @return the end offset of the annotation value. + */ + private int readAnnotationValue( + int v, + final char[] buf, + final String name, + final AnnotationVisitor av) + { + int i; + if (av == null) { + switch (b[v] & 0xFF) { + case 'e': // enum_const_value + return v + 5; + case '@': // annotation_value + return readAnnotationValues(v + 3, buf, true, null); + case '[': // array_value + return readAnnotationValues(v + 1, buf, false, null); + default: + return v + 3; + } + } + switch (b[v++] & 0xFF) { + case 'I': // pointer to CONSTANT_Integer + case 'J': // pointer to CONSTANT_Long + case 'F': // pointer to CONSTANT_Float + case 'D': // pointer to CONSTANT_Double + av.visit(name, readConst(readUnsignedShort(v), buf)); + v += 2; + break; + case 'B': // pointer to CONSTANT_Byte + av.visit(name, + new Byte((byte) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'Z': // pointer to CONSTANT_Boolean + av.visit(name, readInt(items[readUnsignedShort(v)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + v += 2; + break; + case 'S': // pointer to CONSTANT_Short + av.visit(name, + new Short((short) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'C': // pointer to CONSTANT_Char + av.visit(name, + new Character((char) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 's': // pointer to CONSTANT_Utf8 + av.visit(name, readUTF8(v, buf)); + v += 2; + break; + case 'e': // enum_const_value + av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); + v += 4; + break; + case 'c': // class_info + av.visit(name, Type.getType(readUTF8(v, buf))); + v += 2; + break; + case '@': // annotation_value + v = readAnnotationValues(v + 2, + buf, + true, + av.visitAnnotation(name, readUTF8(v, buf))); + break; + case '[': // array_value + int size = readUnsignedShort(v); + v += 2; + if (size == 0) { + return readAnnotationValues(v - 2, + buf, + false, + av.visitArray(name)); + } + switch (this.b[v++] & 0xFF) { + case 'B': + byte[] bv = new byte[size]; + for (i = 0; i < size; i++) { + bv[i] = (byte) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, bv); + --v; + break; + case 'Z': + boolean[] zv = new boolean[size]; + for (i = 0; i < size; i++) { + zv[i] = readInt(items[readUnsignedShort(v)]) != 0; + v += 3; + } + av.visit(name, zv); + --v; + break; + case 'S': + short[] sv = new short[size]; + for (i = 0; i < size; i++) { + sv[i] = (short) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, sv); + --v; + break; + case 'C': + char[] cv = new char[size]; + for (i = 0; i < size; i++) { + cv[i] = (char) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, cv); + --v; + break; + case 'I': + int[] iv = new int[size]; + for (i = 0; i < size; i++) { + iv[i] = readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, iv); + --v; + break; + case 'J': + long[] lv = new long[size]; + for (i = 0; i < size; i++) { + lv[i] = readLong(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, lv); + --v; + break; + case 'F': + float[] fv = new float[size]; + for (i = 0; i < size; i++) { + fv[i] = Float.intBitsToFloat(readInt(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, fv); + --v; + break; + case 'D': + double[] dv = new double[size]; + for (i = 0; i < size; i++) { + dv[i] = Double.longBitsToDouble(readLong(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, dv); + --v; + break; + default: + v = readAnnotationValues(v - 3, + buf, + false, + av.visitArray(name)); + } + } + return v; + } + + private int readFrameType( + final Object[] frame, + final int index, + int v, + final char[] buf, + final Label[] labels) + { + int type = b[v++] & 0xFF; + switch (type) { + case 0: + frame[index] = Opcodes.TOP; + break; + case 1: + frame[index] = Opcodes.INTEGER; + break; + case 2: + frame[index] = Opcodes.FLOAT; + break; + case 3: + frame[index] = Opcodes.DOUBLE; + break; + case 4: + frame[index] = Opcodes.LONG; + break; + case 5: + frame[index] = Opcodes.NULL; + break; + case 6: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case 7: // Object + frame[index] = readClass(v, buf); + v += 2; + break; + default: // Uninitialized + frame[index] = readLabel(readUnsignedShort(v), labels); + v += 2; + } + return v; + } + + /** + * Returns the label corresponding to the given offset. The default + * implementation of this method creates a label for the given offset if it + * has not been already created. + * + * @param offset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a + * label already exists for offset this method must not create a new + * one. Otherwise it must store the new label in this array. + * @return a non null Label, which must be equal to labels[offset]. + */ + protected Label readLabel(int offset, Label[] labels) { + if (labels[offset] == null) { + labels[offset] = new Label(); + } + return labels[offset]; + } + + /** + * Reads an attribute in {@link #b b}. + * + * @param attrs prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to the + * type of one the prototypes is ignored (i.e. an empty + * {@link Attribute} instance is returned). + * @param type the type of the attribute. + * @param off index of the first byte of the attribute's content in + * {@link #b b}. The 6 attribute header bytes, containing the type + * and the length of the attribute, are not taken into account here + * (they have already been read). + * @param len the length of the attribute's content. + * @param buf buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or + * {@link #readConst readConst}. + * @param codeOff index of the first byte of code's attribute content in + * {@link #b b}, or -1 if the attribute to be read is not a code + * attribute. The 6 attribute header bytes, containing the type and + * the length of the attribute, are not taken into account here. + * @param labels the labels of the method's code, or <tt>null</tt> if the + * attribute to be read is not a code attribute. + * @return the attribute that has been read, or <tt>null</tt> to skip this + * attribute. + */ + private Attribute readAttribute( + final Attribute[] attrs, + final String type, + final int off, + final int len, + final char[] buf, + final int codeOff, + final Label[] labels) + { + for (int i = 0; i < attrs.length; ++i) { + if (attrs[i].type.equals(type)) { + return attrs[i].read(this, off, len, buf, codeOff, labels); + } + } + return new Attribute(type).read(this, off, len, null, -1, null); + } + + // ------------------------------------------------------------------------ + // Utility methods: low level parsing + // ------------------------------------------------------------------------ + + /** + * Returns the number of constant pool items in {@link #b b}. + * + * @return the number of constant pool items in {@link #b b}. + */ + public int getItemCount() { + return items.length; + } + + /** + * Returns the start index of the constant pool item in {@link #b b}, plus + * one. <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param item the index a constant pool item. + * @return the start index of the constant pool item in {@link #b b}, plus + * one. + */ + public int getItem(final int item) { + return items[item]; + } + + /** + * Returns the maximum length of the strings contained in the constant pool + * of the class. + * + * @return the maximum length of the strings contained in the constant pool + * of the class. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in {@link #b b}. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readByte(final int index) { + return b[index] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b b}. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readUnsignedShort(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in {@link #b b}. <i>This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public short readShort(final int index) { + byte[] b = this.b; + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in {@link #b b}. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readInt(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Reads a signed long value in {@link #b b}. <i>This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public long readLong(final int index) { + long l1 = readInt(index); + long l0 = readInt(index + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method + * is intended for {@link Attribute} sub classes, and is normally not needed + * by class generators or adapters.</i> + * + * @param index the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + public String readUTF8(int index, final char[] buf) { + int item = readUnsignedShort(index); + String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index start offset of the UTF8 string to be read. + * @param utfLen length of the UTF8 string to be read. + * @param buf buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + int endIndex = index + utfLen; + byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) ((cc << 6) | (c & 0x3F)); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + /** + * Reads a class constant pool item in {@link #b b}. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param index the start index of an unsigned short value in {@link #b b}, + * whose value is the index of a class constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified class item. + */ + public String readClass(final int index, final char[] buf) { + // computes the start index of the CONSTANT_Class item in b + // and reads the CONSTANT_Utf8 item designated by + // the first two bytes of this CONSTANT_Class item + return readUTF8(items[readUnsignedShort(index)], buf); + } + + /** + * Reads a numeric or string constant pool item in {@link #b b}. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param item the index of a constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, + * {@link String}, {@link Type} or {@link Handle} corresponding to + * the given constant pool item. + */ + public Object readConst(final int item, final char[] buf) { + int index = items[item]; + switch (b[index - 1]) { + case ClassWriter.INT: + return new Integer(readInt(index)); + case ClassWriter.FLOAT: + return new Float(Float.intBitsToFloat(readInt(index))); + case ClassWriter.LONG: + return new Long(readLong(index)); + case ClassWriter.DOUBLE: + return new Double(Double.longBitsToDouble(readLong(index))); + case ClassWriter.CLASS: + return Type.getObjectType(readUTF8(index, buf)); + case ClassWriter.STR: + return readUTF8(index, buf); + case ClassWriter.MTYPE: + return Type.getMethodType(readUTF8(index, buf)); + + //case ClassWriter.HANDLE_BASE + [1..9]: + default: { + int tag = readByte(index); + int[] items = this.items; + int cpIndex = items[readUnsignedShort(index + 1)]; + String owner = readClass(cpIndex, buf); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String name = readUTF8(cpIndex, buf); + String desc = readUTF8(cpIndex + 2, buf); + return new Handle(tag, owner, name, desc); + } + } + } +} diff --git a/src/compiler/scala/tools/asm/ClassVisitor.java b/src/compiler/scala/tools/asm/ClassVisitor.java new file mode 100644 index 0000000000..ae38ae0ab9 --- /dev/null +++ b/src/compiler/scala/tools/asm/ClassVisitor.java @@ -0,0 +1,277 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called + * in the following order: <tt>visit</tt> [ <tt>visitSource</tt> ] [ + * <tt>visitOuterClass</tt> ] ( <tt>visitAnnotation</tt> | + * <tt>visitAttribute</tt> )* ( <tt>visitInnerClass</tt> | + * <tt>visitField</tt> | <tt>visitMethod</tt> )* <tt>visitEnd</tt>. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The class visitor to which this visitor must delegate method calls. May + * be null. + */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param cv the class visitor to which this visitor must delegate method + * calls. May be null. + */ + public ClassVisitor(final int api, final ClassVisitor cv) { + /*if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + }*/ + this.api = api; + this.cv = cv; + } + + /** + * Visits the header of the class. + * + * @param version the class version. + * @param access the class's access flags (see {@link Opcodes}). This + * parameter also indicates if the class is deprecated. + * @param name the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * @param signature the signature of this class. May be <tt>null</tt> if + * the class is not a generic one, and does not extend or implement + * generic classes or interfaces. + * @param superName the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, + * the super class is {@link Object}. May be <tt>null</tt>, but + * only for the {@link Object} class. + * @param interfaces the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + */ + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) + { + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source the name of the source file from which the class was + * compiled. May be <tt>null</tt>. + * @param debug additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * <tt>null</tt>. + */ + public void visitSource(String source, String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner internal name of the enclosing class of the class. + * @param name the name of the method that contains the class, or + * <tt>null</tt> if the class is not enclosed in a method of its + * enclosing class. + * @param desc the descriptor of the method that contains the class, or + * <tt>null</tt> if the class is not enclosed in a method of its + * enclosing class. + */ + public void visitOuterClass(String owner, String name, String desc) { + if (cv != null) { + cv.visitOuterClass(owner, name, desc); + } + } + + /** + * Visits an annotation of the class. + * + * @param desc the class descriptor of the annotation class. + * @param visible <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (cv != null) { + return cv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attr an attribute. + */ + public void visitAttribute(Attribute attr) { + if (cv != null) { + cv.visitAttribute(attr); + } + } + + /** + * Visits information about an inner class. This inner class is not + * necessarily a member of the class being visited. + * + * @param name the internal name of an inner class (see + * {@link Type#getInternalName() getInternalName}). + * @param outerName the internal name of the class to which the inner class + * belongs (see {@link Type#getInternalName() getInternalName}). May + * be <tt>null</tt> for not member classes. + * @param innerName the (simple) name of the inner class inside its + * enclosing class. May be <tt>null</tt> for anonymous inner + * classes. + * @param access the access flags of the inner class as originally declared + * in the enclosing class. + */ + public void visitInnerClass( + String name, + String outerName, + String innerName, + int access) + { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a field of the class. + * + * @param access the field's access flags (see {@link Opcodes}). This + * parameter also indicates if the field is synthetic and/or + * deprecated. + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type Type}). + * @param signature the field's signature. May be <tt>null</tt> if the + * field's type does not use generic types. + * @param value the field's initial value. This parameter, which may be + * <tt>null</tt> if the field does not have an initial value, must + * be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for <tt>int</tt>, + * <tt>float</tt>, <tt>long</tt> or <tt>String</tt> fields + * respectively). <i>This parameter is only used for static fields</i>. + * Its value is ignored for non static fields, which must be + * initialized through bytecode instructions in constructors or + * methods. + * @return a visitor to visit field annotations and attributes, or + * <tt>null</tt> if this class visitor is not interested in + * visiting these annotations and attributes. + */ + public FieldVisitor visitField( + int access, + String name, + String desc, + String signature, + Object value) + { + if (cv != null) { + return cv.visitField(access, name, desc, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method <i>must</i> return a new + * {@link MethodVisitor} instance (or <tt>null</tt>) each time it is + * called, i.e., it should not return a previously returned visitor. + * + * @param access the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + * @param signature the method's signature. May be <tt>null</tt> if the + * method parameters, return type and exceptions do not use generic + * types. + * @param exceptions the internal names of the method's exception classes + * (see {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + * @return an object to visit the byte code of the method, or <tt>null</tt> + * if this class visitor is not interested in visiting the code of + * this method. + */ + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions) + { + if (cv != null) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be + * called, is used to inform the visitor that all the fields and methods of + * the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/src/compiler/scala/tools/asm/ClassWriter.java b/src/compiler/scala/tools/asm/ClassWriter.java new file mode 100644 index 0000000000..c7a0736b51 --- /dev/null +++ b/src/compiler/scala/tools/asm/ClassWriter.java @@ -0,0 +1,1672 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A {@link ClassVisitor} that generates classes in bytecode form. More + * precisely this visitor generates a byte array conforming to the Java class + * file format. It can be used alone, to generate a Java class "from scratch", + * or with one or more {@link ClassReader ClassReader} and adapter class visitor + * to generate a modified class from one or more existing Java classes. + * + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * Flag to automatically compute the maximum stack size and the maximum + * number of local variables of methods. If this flag is set, then the + * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the + * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} + * method will be ignored, and computed automatically from the signature and + * the bytecode of each method. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * Flag to automatically compute the stack map frames of methods from + * scratch. If this flag is set, then the calls to the + * {@link MethodVisitor#visitFrame} method are ignored, and the stack map + * frames are recomputed from the methods bytecode. The arguments of the + * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and + * recomputed from the bytecode. In other words, computeFrames implies + * computeMaxs. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * Pseudo access flag to distinguish between the synthetic attribute and + * the synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFMETH_INSN = 7; + + /** + * The type of the INVOKEDYNAMIC instruction. + */ + static final int INDYMETH_INSN = 8; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 9; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 10; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 11; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 12; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 13; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 14; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 15; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 16; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 17; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * The type of CONSTANT_MethodType constant pool items. + */ + static final int MTYPE = 16; + + /** + * The type of CONSTANT_MethodHandle constant pool items. + */ + static final int HANDLE = 15; + + /** + * The type of CONSTANT_InvokeDynamic constant pool items. + */ + static final int INDY = 18; + + /** + * The base value for all CONSTANT_MethodHandle constant pool items. + * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into + * 9 different items. + */ + static final int HANDLE_BASE = 20; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 30; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 31; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 32; + + /** + * The type of BootstrapMethods items. These items are stored in a + * special class attribute named BootstrapMethods and + * not in the constant pool. + */ + static final int BSM = 33; + + /** + * The class reader from which this class writer was constructed, if any. + */ + ClassReader cr; + + /** + * Minor and major version numbers of the class to be generated. + */ + int version; + + /** + * Index of the next item to be added in the constant pool. + */ + int index; + + /** + * The constant pool of this class. + */ + final ByteVector pool; + + /** + * The constant pool's hash table data. + */ + Item[] items; + + /** + * The threshold of the constant pool's hash table. + */ + int threshold; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key2; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key3; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key4; + + /** + * A type table used to temporarily store internal names that will not + * necessarily be stored in the constant pool. This type table is used by + * the control flow and data flow analysis algorithm used to compute stack + * map frames from scratch. This array associates to each index <tt>i</tt> + * the Item whose index is <tt>i</tt>. All Item objects stored in this + * array are also stored in the {@link #items} hash table. These two arrays + * allow to retrieve an Item from its index or, conversely, to get the index + * of an Item from its value. Each Item stores an internal name in its + * {@link Item#strVal1} field. + */ + Item[] typeTable; + + /** + * Number of elements in the {@link #typeTable} array. + */ + private short typeCount; + + /** + * The access flags of this class. + */ + private int access; + + /** + * The constant pool item that contains the internal name of this class. + */ + private int name; + + /** + * The internal name of this class. + */ + String thisName; + + /** + * The constant pool item that contains the signature of this class. + */ + private int signature; + + /** + * The constant pool item that contains the internal name of the super class + * of this class. + */ + private int superName; + + /** + * Number of interfaces implemented or extended by this class or interface. + */ + private int interfaceCount; + + /** + * The interfaces implemented or extended by this class or interface. More + * precisely, this array contains the indexes of the constant pool items + * that contain the internal names of these interfaces. + */ + private int[] interfaces; + + /** + * The index of the constant pool item that contains the name of the source + * file from which this class was compiled. + */ + private int sourceFile; + + /** + * The SourceDebug attribute of this class. + */ + private ByteVector sourceDebug; + + /** + * The constant pool item that contains the name of the enclosing class of + * this class. + */ + private int enclosingMethodOwner; + + /** + * The constant pool item that contains the name and descriptor of the + * enclosing method of this class. + */ + private int enclosingMethod; + + /** + * The runtime visible annotations of this class. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this class. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this class. + */ + private Attribute attrs; + + /** + * The number of entries in the InnerClasses attribute. + */ + private int innerClassesCount; + + /** + * The InnerClasses attribute. + */ + private ByteVector innerClasses; + + /** + * The number of entries in the BootstrapMethods attribute. + */ + int bootstrapMethodsCount; + + /** + * The BootstrapMethods attribute. + */ + ByteVector bootstrapMethods; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the first element of + * this list. + */ + FieldWriter firstField; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the last element of + * this list. + */ + FieldWriter lastField; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the first element of + * this list. + */ + MethodWriter firstMethod; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the last element of + * this list. + */ + MethodWriter lastMethod; + + /** + * <tt>true</tt> if the maximum stack size and number of local variables + * must be automatically computed. + */ + private final boolean computeMaxs; + + /** + * <tt>true</tt> if the stack map frames must be recomputed from scratch. + */ + private final boolean computeFrames; + + /** + * <tt>true</tt> if the stack map tables of this class are invalid. The + * {@link MethodWriter#resizeInstructions} method cannot transform existing + * stack map tables, and so produces potentially invalid classes when it is + * executed. In this case the class is reread and rewritten with the + * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize + * stack map tables when this option is used). + */ + boolean invalidFrames; + + // ------------------------------------------------------------------------ + // Static initializer + // ------------------------------------------------------------------------ + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + byte[] b = new byte[220]; + String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + + // code to generate the above string + // + // // SBYTE_INSN instructions + // b[Constants.NEWARRAY] = SBYTE_INSN; + // b[Constants.BIPUSH] = SBYTE_INSN; + // + // // SHORT_INSN instructions + // b[Constants.SIPUSH] = SHORT_INSN; + // + // // (IMPL)VAR_INSN instructions + // b[Constants.RET] = VAR_INSN; + // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3 + // b[i] = IMPLVAR_INSN; + // } + // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3 + // b[i] = IMPLVAR_INSN; + // } + // + // // TYPE_INSN instructions + // b[Constants.NEW] = TYPE_INSN; + // b[Constants.ANEWARRAY] = TYPE_INSN; + // b[Constants.CHECKCAST] = TYPE_INSN; + // b[Constants.INSTANCEOF] = TYPE_INSN; + // + // // (Set)FIELDORMETH_INSN instructions + // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) { + // b[i] = FIELDORMETH_INSN; + // } + // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN; + // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN; + // + // // LABEL(W)_INSN instructions + // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) { + // b[i] = LABEL_INSN; + // } + // b[Constants.IFNULL] = LABEL_INSN; + // b[Constants.IFNONNULL] = LABEL_INSN; + // b[200] = LABELW_INSN; // GOTO_W + // b[201] = LABELW_INSN; // JSR_W + // // temporary opcodes used internally by ASM - see Label and + // MethodWriter + // for (i = 202; i < 220; ++i) { + // b[i] = LABEL_INSN; + // } + // + // // LDC(_W) instructions + // b[Constants.LDC] = LDC_INSN; + // b[19] = LDCW_INSN; // LDC_W + // b[20] = LDCW_INSN; // LDC2_W + // + // // special instructions + // b[Constants.IINC] = IINC_INSN; + // b[Constants.TABLESWITCH] = TABL_INSN; + // b[Constants.LOOKUPSWITCH] = LOOK_INSN; + // b[Constants.MULTIANEWARRAY] = MANA_INSN; + // b[196] = WIDE_INSN; // WIDE + // + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('A' + b[i])); + // } + // System.err.println(); + } + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags option flags that can be used to modify the default behavior + * of this class. See {@link #COMPUTE_MAXS}, {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + super(Opcodes.ASM4); + index = 1; + pool = new ByteVector(); + items = new Item[256]; + threshold = (int) (0.75d * items.length); + key = new Item(); + key2 = new Item(); + key3 = new Item(); + key4 = new Item(); + this.computeMaxs = (flags & COMPUTE_MAXS) != 0; + this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for + * "mostly add" bytecode transformations. These optimizations are the + * following: + * + * <ul> <li>The constant pool from the original class is copied as is in the + * new class, which saves time. New constant pool entries will be added at + * the end if necessary, but unused constant pool entries <i>won't be + * removed</i>.</li> <li>Methods that are not transformed are copied as is + * in the new class, directly from the original class bytecode (i.e. without + * emitting visit events for all the method instructions), which saves a + * <i>lot</i> of time. Untransformed methods are detected by the fact that + * the {@link ClassReader} receives {@link MethodVisitor} objects that come + * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} + * instance).</li> </ul> + * + * @param classReader the {@link ClassReader} used to read the original + * class. It will be used to copy the entire constant pool from the + * original class and also to copy other fragments of original + * bytecode where applicable. + * @param flags option flags that can be used to modify the default behavior + * of this class. <i>These option flags do not affect methods that + * are copied as is in the new class. This means that the maximum + * stack size nor the stack frames will be computed for these + * methods</i>. See {@link #COMPUTE_MAXS}, {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + this(flags); + classReader.copyPool(this); + this.cr = classReader; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public final void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + this.version = version; + this.access = access; + this.name = newClass(name); + thisName = name; + if (ClassReader.SIGNATURES && signature != null) { + this.signature = newUTF8(signature); + } + this.superName = superName == null ? 0 : newClass(superName); + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = newClass(interfaces[i]); + } + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFile = newUTF8(file); + } + if (debug != null) { + sourceDebug = new ByteVector().putUTF8(debug); + } + } + + @Override + public final void visitOuterClass( + final String owner, + final String name, + final String desc) + { + enclosingMethodOwner = newClass(owner); + if (name != null && desc != null) { + enclosingMethod = newNameType(name, desc); + } + } + + @Override + public final AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public final void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public final void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + ++innerClassesCount; + innerClasses.putShort(name == null ? 0 : newClass(name)); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + } + + @Override + public final FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + return new FieldWriter(this, access, name, desc, signature, value); + } + + @Override + public final MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + return new MethodWriter(this, + access, + name, + desc, + signature, + exceptions, + computeMaxs, + computeFrames); + } + + @Override + public final void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Other public methods + // ------------------------------------------------------------------------ + + /** + * Returns the bytecode of the class that was build with this class writer. + * + * @return the bytecode of the class that was build with this class writer. + */ + public byte[] toByteArray() { + if (index > Short.MAX_VALUE) { + throw new RuntimeException("Class file too large!"); + } + // computes the real size of the bytecode of this class + int size = 24 + 2 * interfaceCount; + int nbFields = 0; + FieldWriter fb = firstField; + while (fb != null) { + ++nbFields; + size += fb.getSize(); + fb = (FieldWriter) fb.fv; + } + int nbMethods = 0; + MethodWriter mb = firstMethod; + while (mb != null) { + ++nbMethods; + size += mb.getSize(); + mb = (MethodWriter) mb.mv; + } + int attributeCount = 0; + if (bootstrapMethods != null) { // we put it as first argument in order + // to improve a bit ClassReader.copyBootstrapMethods + ++attributeCount; + size += 8 + bootstrapMethods.length; + newUTF8("BootstrapMethods"); + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + size += 8; + newUTF8("Signature"); + } + if (sourceFile != 0) { + ++attributeCount; + size += 8; + newUTF8("SourceFile"); + } + if (sourceDebug != null) { + ++attributeCount; + size += sourceDebug.length + 4; + newUTF8("SourceDebugExtension"); + } + if (enclosingMethodOwner != 0) { + ++attributeCount; + size += 10; + newUTF8("EnclosingMethod"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + size += 6; + newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((version & 0xFFFF) < Opcodes.V1_5 || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + ++attributeCount; + size += 6; + newUTF8("Synthetic"); + } + if (innerClasses != null) { + ++attributeCount; + size += 8 + innerClasses.length; + newUTF8("InnerClasses"); + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + size += 8 + anns.getSize(); + newUTF8("RuntimeVisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + size += 8 + ianns.getSize(); + newUTF8("RuntimeInvisibleAnnotations"); + } + if (attrs != null) { + attributeCount += attrs.getCount(); + size += attrs.getSize(this, null, 0, -1, -1); + } + size += pool.length; + // allocates a byte vector of this size, in order to avoid unnecessary + // arraycopy operations in the ByteVector.enlarge() method + ByteVector out = new ByteVector(size); + out.putInt(0xCAFEBABE).putInt(version); + out.putShort(index).putByteArray(pool.data, 0, pool.length); + int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC)); + out.putShort(access & ~mask).putShort(name).putShort(superName); + out.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + out.putShort(interfaces[i]); + } + out.putShort(nbFields); + fb = firstField; + while (fb != null) { + fb.put(out); + fb = (FieldWriter) fb.fv; + } + out.putShort(nbMethods); + mb = firstMethod; + while (mb != null) { + mb.put(out); + mb = (MethodWriter) mb.mv; + } + out.putShort(attributeCount); + if (bootstrapMethods != null) { // should be the first class attribute ? + out.putShort(newUTF8("BootstrapMethods")); + out.putInt(bootstrapMethods.length + 2).putShort(bootstrapMethodsCount); + out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); + } + if (sourceFile != 0) { + out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); + } + if (sourceDebug != null) { + int len = sourceDebug.length - 2; + out.putShort(newUTF8("SourceDebugExtension")).putInt(len); + out.putByteArray(sourceDebug.data, 2, len); + } + if (enclosingMethodOwner != 0) { + out.putShort(newUTF8("EnclosingMethod")).putInt(4); + out.putShort(enclosingMethodOwner).putShort(enclosingMethod); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(newUTF8("Deprecated")).putInt(0); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((version & 0xFFFF) < Opcodes.V1_5 || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + out.putShort(newUTF8("Synthetic")).putInt(0); + } + if (innerClasses != null) { + out.putShort(newUTF8("InnerClasses")); + out.putInt(innerClasses.length + 2).putShort(innerClassesCount); + out.putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(this, null, 0, -1, -1, out); + } + if (invalidFrames) { + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES); + return cw.toByteArray(); + } + return out.data; + } + + // ------------------------------------------------------------------------ + // Utility methods: constant pool management + // ------------------------------------------------------------------------ + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * + * @param cst the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double}, a {@link String} or a + * {@link Type}. + * @return a new or already existing constant item with the given value. + */ + Item newConstItem(final Object cst) { + if (cst instanceof Integer) { + int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Byte) { + int val = ((Byte) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Character) { + int val = ((Character) cst).charValue(); + return newInteger(val); + } else if (cst instanceof Short) { + int val = ((Short) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Boolean) { + int val = ((Boolean) cst).booleanValue() ? 1 : 0; + return newInteger(val); + } else if (cst instanceof Float) { + float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + Type t = (Type) cst; + int s = t.getSort(); + if (s == Type.ARRAY) { + return newClassItem(t.getDescriptor()); + } else if (s == Type.OBJECT) { + return newClassItem(t.getInternalName()); + } else { // s == Type.METHOD + return newMethodTypeItem(t.getDescriptor()); + } + } else if (cst instanceof Handle) { + Handle h = (Handle) cst; + return newHandleItem(h.tag, h.owner, h.name, h.desc); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param cst the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the + * given value. + */ + public int newConst(final Object cst) { + return newConstItem(cst).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param value the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + key.set(UTF8, value, null, null); + Item result = get(key); + if (result == null) { + pool.putByte(UTF8).putUTF8(value); + result = new Item(index++, key); + put(result); + } + return result.index; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param value the internal name of the class. + * @return a new or already existing class reference item. + */ + Item newClassItem(final String value) { + key2.set(CLASS, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(CLASS, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param value the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return newClassItem(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param methodDesc method descriptor of the method type. + * @return a new or already existing method type reference item. + */ + Item newMethodTypeItem(final String methodDesc) { + key2.set(MTYPE, methodDesc, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(MTYPE, newUTF8(methodDesc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param methodDesc method descriptor of the method type. + * @return the index of a new or already existing method type reference + * item. + */ + public int newMethodType(final String methodDesc) { + return newMethodTypeItem(methodDesc).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param desc the descriptor of the field or method. + * @return a new or an already existing method type reference item. + */ + Item newHandleItem( + final int tag, + final String owner, + final String name, + final String desc) + { + key4.set(HANDLE_BASE + tag, owner, name, desc); + Item result = get(key4); + if (result == null) { + if (tag <= Opcodes.H_PUTSTATIC) { + put112(HANDLE, tag, newField(owner, name, desc)); + } else { + put112(HANDLE, tag, newMethod(owner, + name, + desc, + tag == Opcodes.H_INVOKEINTERFACE)); + } + result = new Item(index++, key4); + put(result); + } + return result; + } + + /** + * Adds a handle to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class. + * @param name the name of the field or method. + * @param desc the descriptor of the field or method. + * @return the index of a new or already existing method type reference + * item. + */ + public int newHandle( + final int tag, + final String owner, + final String name, + final String desc) + { + return newHandleItem(tag, owner, name, desc).index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param name name of the invoked method. + * @param desc descriptor of the invoke method. + * @param bsm the bootstrap method. + * @param bsmArgs the bootstrap method constant arguments. + * + * @return a new or an already existing invokedynamic type reference item. + */ + Item newInvokeDynamicItem( + final String name, + final String desc, + final Handle bsm, + final Object... bsmArgs) + { + // cache for performance + ByteVector bootstrapMethods = this.bootstrapMethods; + if (bootstrapMethods == null) { + bootstrapMethods = this.bootstrapMethods = new ByteVector(); + } + + int position = bootstrapMethods.length; // record current position + + int hashCode = bsm.hashCode(); + bootstrapMethods.putShort(newHandle(bsm.tag, + bsm.owner, + bsm.name, + bsm.desc)); + + int argsLength = bsmArgs.length; + bootstrapMethods.putShort(argsLength); + + for (int i = 0; i < argsLength; i++) { + Object bsmArg = bsmArgs[i]; + hashCode ^= bsmArg.hashCode(); + bootstrapMethods.putShort(newConst(bsmArg)); + } + + byte[] data = bootstrapMethods.data; + int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments) + hashCode &= 0x7FFFFFFF; + Item result = items[hashCode % items.length]; + loop: while (result != null) { + if (result.type != BSM || result.hashCode != hashCode) { + result = result.next; + continue; + } + + // because the data encode the size of the argument + // we don't need to test if these size are equals + int resultPosition = result.intVal; + for (int p = 0; p < length; p++) { + if (data[position + p] != data[resultPosition + p]) { + result = result.next; + continue loop; + } + } + break; + } + + int bootstrapMethodIndex; + if (result != null) { + bootstrapMethodIndex = result.index; + bootstrapMethods.length = position; // revert to old position + } else { + bootstrapMethodIndex = bootstrapMethodsCount++; + result = new Item(bootstrapMethodIndex); + result.set(position, hashCode); + put(result); + } + + // now, create the InvokeDynamic constant + key3.set(name, desc, bootstrapMethodIndex); + result = get(key3); + if (result == null) { + put122(INDY, bootstrapMethodIndex, newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param name name of the invoked method. + * @param desc descriptor of the invoke method. + * @param bsm the bootstrap method. + * @param bsmArgs the bootstrap method constant arguments. + * + * @return the index of a new or already existing invokedynamic + * reference item. + */ + public int newInvokeDynamic( + final String name, + final String desc, + final Handle bsm, + final Object... bsmArgs) + { + return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param desc the field's descriptor. + * @return a new or already existing field reference item. + */ + Item newFieldItem(final String owner, final String name, final String desc) + { + key3.set(FIELD, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(FIELD, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param owner the internal name of the field's owner class. + * @param name the field's name. + * @param desc the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String desc) + { + return newFieldItem(owner, name, desc).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param desc the method's descriptor. + * @param itf <tt>true</tt> if <tt>owner</tt> is an interface. + * @return a new or already existing method reference item. + */ + Item newMethodItem( + final String owner, + final String name, + final String desc, + final boolean itf) + { + int type = itf ? IMETH : METH; + key3.set(type, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(type, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param owner the internal name of the method's owner class. + * @param name the method's name. + * @param desc the method's descriptor. + * @param itf <tt>true</tt> if <tt>owner</tt> is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod( + final String owner, + final String name, + final String desc, + final boolean itf) + { + return newMethodItem(owner, name, desc, itf).index; + } + + /** + * Adds an integer to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param value the int value. + * @return a new or already existing int item. + */ + Item newInteger(final int value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(INT).putInt(value); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a float to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the float value. + * @return a new or already existing float item. + */ + Item newFloat(final float value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(FLOAT).putInt(key.intVal); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a long to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the long value. + * @return a new or already existing long item. + */ + Item newLong(final long value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(LONG).putLong(value); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a double to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the double value. + * @return a new or already existing double item. + */ + Item newDouble(final double value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(DOUBLE).putLong(key.longVal); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a string to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value the String value. + * @return a new or already existing string item. + */ + private Item newString(final String value) { + key2.set(STR, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(STR, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param name a name. + * @param desc a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String desc) { + return newNameTypeItem(name, desc).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param name a name. + * @param desc a type descriptor. + * @return a new or already existing name and type item. + */ + Item newNameTypeItem(final String name, final String desc) { + key2.set(NAME_TYPE, name, desc, null); + Item result = get(key2); + if (result == null) { + put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds the given internal name to {@link #typeTable} and returns its index. + * Does nothing if the type table already contains this internal name. + * + * @param type the internal name to be added to the type table. + * @return the index of this internal name in the type table. + */ + int addType(final String type) { + key.set(TYPE_NORMAL, type, null, null); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given "uninitialized" type to {@link #typeTable} and returns its + * index. This method is used for UNINITIALIZED types, made of an internal + * name and a bytecode offset. + * + * @param type the internal name to be added to the type table. + * @param offset the bytecode offset of the NEW instruction that created + * this UNINITIALIZED type value. + * @return the index of this internal name in the type table. + */ + int addUninitializedType(final String type, final int offset) { + key.type = TYPE_UNINIT; + key.intVal = offset; + key.strVal1 = type; + key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given Item to {@link #typeTable}. + * + * @param item the value to be added to the type table. + * @return the added Item, which a new Item instance with the same value as + * the given Item. + */ + private Item addType(final Item item) { + ++typeCount; + Item result = new Item(typeCount, key); + put(result); + if (typeTable == null) { + typeTable = new Item[16]; + } + if (typeCount == typeTable.length) { + Item[] newTable = new Item[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); + typeTable = newTable; + } + typeTable[typeCount] = result; + return result; + } + + /** + * Returns the index of the common super type of the two given types. This + * method calls {@link #getCommonSuperClass} and caches the result in the + * {@link #items} hash table to speedup future calls with the same + * parameters. + * + * @param type1 index of an internal name in {@link #typeTable}. + * @param type2 index of an internal name in {@link #typeTable}. + * @return the index of the common super type of the two given types. + */ + int getMergedType(final int type1, final int type2) { + key2.type = TYPE_MERGED; + key2.longVal = type1 | (((long) type2) << 32); + key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); + Item result = get(key2); + if (result == null) { + String t = typeTable[type1].strVal1; + String u = typeTable[type2].strVal1; + key2.intVal = addType(getCommonSuperClass(t, u)); + result = new Item((short) 0, key2); + put(result); + } + return result.intVal; + } + + /** + * Returns the common super type of the two given types. The default + * implementation of this method <i>loads<i> the two given classes and uses + * the java.lang.Class methods to find the common super class. It can be + * overridden to compute this common super type in other ways, in particular + * without actually loading any class, or to take into account the class + * that is currently being generated by this ClassWriter, which can of + * course not be loaded since it is under construction. + * + * @param type1 the internal name of a class. + * @param type2 the internal name of another class. + * @return the internal name of the common super class of the two given + * classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) + { + Class<?> c, d; + ClassLoader classLoader = getClass().getClassLoader(); + try { + c = Class.forName(type1.replace('/', '.'), false, classLoader); + d = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + + /** + * Returns the constant pool's hash table item which is equal to the given + * item. + * + * @param key a constant pool item. + * @return the constant pool's hash table item which is equal to the given + * item, or <tt>null</tt> if there is no such item. + */ + private Item get(final Item key) { + Item i = items[key.hashCode % items.length]; + while (i != null && (i.type != key.type || !key.isEqualTo(i))) { + i = i.next; + } + return i; + } + + /** + * Puts the given item in the constant pool's hash table. The hash table + * <i>must</i> not already contains this item. + * + * @param i the item to be added to the constant pool's hash table. + */ + private void put(final Item i) { + if (index + typeCount > threshold) { + int ll = items.length; + int nl = ll * 2 + 1; + Item[] newItems = new Item[nl]; + for (int l = ll - 1; l >= 0; --l) { + Item j = items[l]; + while (j != null) { + int index = j.hashCode % newItems.length; + Item k = j.next; + j.next = newItems[index]; + newItems[index] = j; + j = k; + } + } + items = newItems; + threshold = (int) (nl * 0.75); + } + int index = i.hashCode % items.length; + i.next = items[index]; + items[index] = i; + } + + /** + * Puts one byte and two shorts into the constant pool. + * + * @param b a byte. + * @param s1 a short. + * @param s2 another short. + */ + private void put122(final int b, final int s1, final int s2) { + pool.put12(b, s1).putShort(s2); + } + + /** + * Puts two bytes and one short into the constant pool. + * + * @param b1 a byte. + * @param b2 another byte. + * @param s a short. + */ + private void put112(final int b1, final int b2, final int s) { + pool.put11(b1, b2).putShort(s); + } +} diff --git a/src/compiler/scala/tools/asm/CustomAttr.java b/src/compiler/scala/tools/asm/CustomAttr.java new file mode 100644 index 0000000000..5ecfd283d0 --- /dev/null +++ b/src/compiler/scala/tools/asm/CustomAttr.java @@ -0,0 +1,20 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + */ + +package scala.tools.asm; + +import scala.tools.asm.Attribute; + +/** + * A subclass of ASM's Attribute for the sole purpose of accessing a protected field there. + * + */ +public class CustomAttr extends Attribute { + + public CustomAttr(final String type, final byte[] value) { + super(type); + super.value = value; + } + +} diff --git a/src/compiler/scala/tools/asm/Edge.java b/src/compiler/scala/tools/asm/Edge.java new file mode 100644 index 0000000000..daac1f7bb0 --- /dev/null +++ b/src/compiler/scala/tools/asm/Edge.java @@ -0,0 +1,75 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * An edge in the control flow graph of a method body. See {@link Label Label}. + * + * @author Eric Bruneton + */ +class Edge { + + /** + * Denotes a normal control flow graph edge. + */ + static final int NORMAL = 0; + + /** + * Denotes a control flow graph edge corresponding to an exception handler. + * More precisely any {@link Edge} whose {@link #info} is strictly positive + * corresponds to an exception handler. The actual value of {@link #info} is + * the index, in the {@link ClassWriter} type table, of the exception that + * is catched. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. If + * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) + * stack size in the basic block from which this edge originates. This size + * is equal to the stack size at the "jump" instruction to which this edge + * corresponds, relatively to the stack size at the beginning of the + * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, + * this field is the kind of this control flow graph edge (i.e. NORMAL or + * EXCEPTION). + */ + int info; + + /** + * The successor block of the basic block from which this edge originates. + */ + Label successor; + + /** + * The next edge in the list of successors of the originating basic block. + * See {@link Label#successors successors}. + */ + Edge next; +} diff --git a/src/compiler/scala/tools/asm/FieldVisitor.java b/src/compiler/scala/tools/asm/FieldVisitor.java new file mode 100644 index 0000000000..9ac0f6236f --- /dev/null +++ b/src/compiler/scala/tools/asm/FieldVisitor.java @@ -0,0 +1,115 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called + * in the following order: ( <tt>visitAnnotation</tt> | + * <tt>visitAttribute</tt> )* <tt>visitEnd</tt>. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The field visitor to which this visitor must delegate method calls. May + * be null. + */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param fv the field visitor to which this visitor must delegate method + * calls. May be null. + */ + public FieldVisitor(final int api, final FieldVisitor fv) { + /*if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + }*/ + this.api = api; + this.fv = fv; + } + + /** + * Visits an annotation of the field. + * + * @param desc the class descriptor of the annotation class. + * @param visible <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (fv != null) { + return fv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attr an attribute. + */ + public void visitAttribute(Attribute attr) { + if (fv != null) { + fv.visitAttribute(attr); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/src/compiler/scala/tools/asm/FieldWriter.java b/src/compiler/scala/tools/asm/FieldWriter.java new file mode 100644 index 0000000000..45ef6d0df3 --- /dev/null +++ b/src/compiler/scala/tools/asm/FieldWriter.java @@ -0,0 +1,271 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * An {@link FieldVisitor} that generates Java fields in bytecode form. + * + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** + * The class writer to which this field must be added. + */ + private final ClassWriter cw; + + /** + * Access flags of this field. + */ + private final int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * field. + */ + private final int desc; + + /** + * The index of the constant pool item that contains the signature of this + * field. + */ + private int signature; + + /** + * The index of the constant pool item that contains the constant value of + * this field. + */ + private int value; + + /** + * The runtime visible annotations of this field. May be <tt>null</tt>. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this field. May be <tt>null</tt>. + */ + private AnnotationWriter ianns; + + /** + * The non standard attributes of this field. May be <tt>null</tt>. + */ + private Attribute attrs; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link FieldWriter}. + * + * @param cw the class writer to which this field must be added. + * @param access the field's access flags (see {@link Opcodes}). + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be <tt>null</tt>. + * @param value the field's constant value. May be <tt>null</tt>. + */ + FieldWriter( + final ClassWriter cw, + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + super(Opcodes.ASM4); + if (cw.firstField == null) { + cw.firstField = this; + } else { + cw.lastField.fv = this; + } + cw.lastField = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + if (ClassReader.SIGNATURES && signature != null) { + this.signature = cw.newUTF8(signature); + } + if (value != null) { + this.value = cw.newConstItem(value).index; + } + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this field. + * + * @return the size of this field. + */ + int getSize() { + int size = 8; + if (value != 0) { + cw.newUTF8("ConstantValue"); + size += 8; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + cw.newUTF8("Synthetic"); + size += 6; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != 0) { + cw.newUTF8("Signature"); + size += 8; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the content of this field into the given byte vector. + * + * @param out where the content of this field must be put. + */ + void put(final ByteVector out) { + int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC)); + out.putShort(access & ~mask).putShort(name).putShort(desc); + int attributeCount = 0; + if (value != 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + ++attributeCount; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (value != 0) { + out.putShort(cw.newUTF8("ConstantValue")); + out.putInt(2).putShort(value); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(cw.newUTF8("Signature")); + out.putInt(2).putShort(signature); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } +} diff --git a/src/compiler/scala/tools/asm/Frame.java b/src/compiler/scala/tools/asm/Frame.java new file mode 100644 index 0000000000..387b56796d --- /dev/null +++ b/src/compiler/scala/tools/asm/Frame.java @@ -0,0 +1,1435 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * Information about the input and output stack map frames of a basic block. + * + * @author Eric Bruneton + */ +final class Frame { + + /* + * Frames are computed in a two steps process: during the visit of each + * instruction, the state of the frame at the end of current basic block is + * updated by simulating the action of the instruction on the previous state + * of this so called "output frame". In visitMaxs, a fix point algorithm is + * used to compute the "input frame" of each basic block, i.e. the stack map + * frame at the beginning of the basic block, starting from the input frame + * of the first basic block (which is computed from the method descriptor), + * and by using the previously computed output frames to compute the input + * state of the other blocks. + * + * All output and input frames are stored as arrays of integers. Reference + * and array types are represented by an index into a type table (which is + * not the same as the constant pool of the class, in order to avoid adding + * unnecessary constants in the pool - not all computed frames will end up + * being stored in the stack map table). This allows very fast type + * comparisons. + * + * Output stack map frames are computed relatively to the input frame of the + * basic block, which is not yet known when output frames are computed. It + * is therefore necessary to be able to represent abstract types such as + * "the type at position x in the input frame locals" or "the type at + * position x from the top of the input frame stack" or even "the type at + * position x in the input frame, with y more (or less) array dimensions". + * This explains the rather complicated type format used in output frames. + * + * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a + * signed number of array dimensions (from -8 to 7). KIND is either BASE, + * LOCAL or STACK. BASE is used for types that are not relative to the input + * frame. LOCAL is used for types that are relative to the input local + * variable types. STACK is used for types that are relative to the input + * stack types. VALUE depends on KIND. For LOCAL types, it is an index in + * the input local variable types. For STACK types, it is a position + * relatively to the top of input frame stack. For BASE types, it is either + * one of the constants defined in FrameVisitor, or for OBJECT and + * UNINITIALIZED types, a tag and an index in the type table. + * + * Output frames can contain types of any kind and with a positive or + * negative dimension (and even unassigned types, represented by 0 - which + * does not correspond to any valid type value). Input frames can only + * contain BASE types of positive or null dimension. In all cases the type + * table contains only internal type names (array type descriptors are + * forbidden - dimensions must be represented through the DIM field). + * + * The LONG and DOUBLE types are always represented by using two slots (LONG + + * TOP or DOUBLE + TOP), for local variable types as well as in the operand + * stack. This is necessary to be able to simulate DUPx_y instructions, + * whose effect would be dependent on the actual type values if types were + * always represented by a single slot in the stack (and this is not + * possible, since actual type values are not always known - cf LOCAL and + * STACK type kinds). + */ + + /** + * Mask to get the dimension of a frame type. This dimension is a signed + * integer between -8 and 7. + */ + static final int DIM = 0xF0000000; + + /** + * Constant to be added to a type to get a type with one more dimension. + */ + static final int ARRAY_OF = 0x10000000; + + /** + * Constant to be added to a type to get a type with one less dimension. + */ + static final int ELEMENT_OF = 0xF0000000; + + /** + * Mask to get the kind of a frame type. + * + * @see #BASE + * @see #LOCAL + * @see #STACK + */ + static final int KIND = 0xF000000; + + /** + * Flag used for LOCAL and STACK types. Indicates that if this type happens + * to be a long or double type (during the computations of input frames), + * then it must be set to TOP because the second word of this value has + * been reused to store other data in the basic block. Hence the first word + * no longer stores a valid long or double value. + */ + static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + + /** + * Mask to get the value of a frame type. + */ + static final int VALUE = 0x7FFFFF; + + /** + * Mask to get the kind of base types. + */ + static final int BASE_KIND = 0xFF00000; + + /** + * Mask to get the value of base types. + */ + static final int BASE_VALUE = 0xFFFFF; + + /** + * Kind of the types that are not relative to an input stack map frame. + */ + static final int BASE = 0x1000000; + + /** + * Base kind of the base reference types. The BASE_VALUE of such types is an + * index into the type table. + */ + static final int OBJECT = BASE | 0x700000; + + /** + * Base kind of the uninitialized base types. The BASE_VALUE of such types + * in an index into the type table (the Item at that index contains both an + * instruction offset and an internal class name). + */ + static final int UNINITIALIZED = BASE | 0x800000; + + /** + * Kind of the types that are relative to the local variable types of an + * input stack map frame. The value of such types is a local variable index. + */ + private static final int LOCAL = 0x2000000; + + /** + * Kind of the the types that are relative to the stack of an input stack + * map frame. The value of such types is a position relatively to the top of + * this stack. + */ + private static final int STACK = 0x3000000; + + /** + * The TOP type. This is a BASE type. + */ + static final int TOP = BASE | 0; + + /** + * The BOOLEAN type. This is a BASE type mainly used for array types. + */ + static final int BOOLEAN = BASE | 9; + + /** + * The BYTE type. This is a BASE type mainly used for array types. + */ + static final int BYTE = BASE | 10; + + /** + * The CHAR type. This is a BASE type mainly used for array types. + */ + static final int CHAR = BASE | 11; + + /** + * The SHORT type. This is a BASE type mainly used for array types. + */ + static final int SHORT = BASE | 12; + + /** + * The INTEGER type. This is a BASE type. + */ + static final int INTEGER = BASE | 1; + + /** + * The FLOAT type. This is a BASE type. + */ + static final int FLOAT = BASE | 2; + + /** + * The DOUBLE type. This is a BASE type. + */ + static final int DOUBLE = BASE | 3; + + /** + * The LONG type. This is a BASE type. + */ + static final int LONG = BASE | 4; + + /** + * The NULL type. This is a BASE type. + */ + static final int NULL = BASE | 5; + + /** + * The UNINITIALIZED_THIS type. This is a BASE type. + */ + static final int UNINITIALIZED_THIS = BASE | 6; + + /** + * The stack size variation corresponding to each JVM instruction. This + * stack variation is equal to the size of the values produced by an + * instruction, minus the size of the values consumed by this instruction. + */ + static final int[] SIZE; + + /** + * Computes the stack size variation corresponding to each JVM instruction. + */ + static { + int i; + int[] b = new int[202]; + String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; + for (i = 0; i < b.length; ++i) { + b[i] = s.charAt(i) - 'E'; + } + SIZE = b; + + // code to generate the above string + // + // int NA = 0; // not applicable (unused opcode or variable size opcode) + // + // b = new int[] { + // 0, //NOP, // visitInsn + // 1, //ACONST_NULL, // - + // 1, //ICONST_M1, // - + // 1, //ICONST_0, // - + // 1, //ICONST_1, // - + // 1, //ICONST_2, // - + // 1, //ICONST_3, // - + // 1, //ICONST_4, // - + // 1, //ICONST_5, // - + // 2, //LCONST_0, // - + // 2, //LCONST_1, // - + // 1, //FCONST_0, // - + // 1, //FCONST_1, // - + // 1, //FCONST_2, // - + // 2, //DCONST_0, // - + // 2, //DCONST_1, // - + // 1, //BIPUSH, // visitIntInsn + // 1, //SIPUSH, // - + // 1, //LDC, // visitLdcInsn + // NA, //LDC_W, // - + // NA, //LDC2_W, // - + // 1, //ILOAD, // visitVarInsn + // 2, //LLOAD, // - + // 1, //FLOAD, // - + // 2, //DLOAD, // - + // 1, //ALOAD, // - + // NA, //ILOAD_0, // - + // NA, //ILOAD_1, // - + // NA, //ILOAD_2, // - + // NA, //ILOAD_3, // - + // NA, //LLOAD_0, // - + // NA, //LLOAD_1, // - + // NA, //LLOAD_2, // - + // NA, //LLOAD_3, // - + // NA, //FLOAD_0, // - + // NA, //FLOAD_1, // - + // NA, //FLOAD_2, // - + // NA, //FLOAD_3, // - + // NA, //DLOAD_0, // - + // NA, //DLOAD_1, // - + // NA, //DLOAD_2, // - + // NA, //DLOAD_3, // - + // NA, //ALOAD_0, // - + // NA, //ALOAD_1, // - + // NA, //ALOAD_2, // - + // NA, //ALOAD_3, // - + // -1, //IALOAD, // visitInsn + // 0, //LALOAD, // - + // -1, //FALOAD, // - + // 0, //DALOAD, // - + // -1, //AALOAD, // - + // -1, //BALOAD, // - + // -1, //CALOAD, // - + // -1, //SALOAD, // - + // -1, //ISTORE, // visitVarInsn + // -2, //LSTORE, // - + // -1, //FSTORE, // - + // -2, //DSTORE, // - + // -1, //ASTORE, // - + // NA, //ISTORE_0, // - + // NA, //ISTORE_1, // - + // NA, //ISTORE_2, // - + // NA, //ISTORE_3, // - + // NA, //LSTORE_0, // - + // NA, //LSTORE_1, // - + // NA, //LSTORE_2, // - + // NA, //LSTORE_3, // - + // NA, //FSTORE_0, // - + // NA, //FSTORE_1, // - + // NA, //FSTORE_2, // - + // NA, //FSTORE_3, // - + // NA, //DSTORE_0, // - + // NA, //DSTORE_1, // - + // NA, //DSTORE_2, // - + // NA, //DSTORE_3, // - + // NA, //ASTORE_0, // - + // NA, //ASTORE_1, // - + // NA, //ASTORE_2, // - + // NA, //ASTORE_3, // - + // -3, //IASTORE, // visitInsn + // -4, //LASTORE, // - + // -3, //FASTORE, // - + // -4, //DASTORE, // - + // -3, //AASTORE, // - + // -3, //BASTORE, // - + // -3, //CASTORE, // - + // -3, //SASTORE, // - + // -1, //POP, // - + // -2, //POP2, // - + // 1, //DUP, // - + // 1, //DUP_X1, // - + // 1, //DUP_X2, // - + // 2, //DUP2, // - + // 2, //DUP2_X1, // - + // 2, //DUP2_X2, // - + // 0, //SWAP, // - + // -1, //IADD, // - + // -2, //LADD, // - + // -1, //FADD, // - + // -2, //DADD, // - + // -1, //ISUB, // - + // -2, //LSUB, // - + // -1, //FSUB, // - + // -2, //DSUB, // - + // -1, //IMUL, // - + // -2, //LMUL, // - + // -1, //FMUL, // - + // -2, //DMUL, // - + // -1, //IDIV, // - + // -2, //LDIV, // - + // -1, //FDIV, // - + // -2, //DDIV, // - + // -1, //IREM, // - + // -2, //LREM, // - + // -1, //FREM, // - + // -2, //DREM, // - + // 0, //INEG, // - + // 0, //LNEG, // - + // 0, //FNEG, // - + // 0, //DNEG, // - + // -1, //ISHL, // - + // -1, //LSHL, // - + // -1, //ISHR, // - + // -1, //LSHR, // - + // -1, //IUSHR, // - + // -1, //LUSHR, // - + // -1, //IAND, // - + // -2, //LAND, // - + // -1, //IOR, // - + // -2, //LOR, // - + // -1, //IXOR, // - + // -2, //LXOR, // - + // 0, //IINC, // visitIincInsn + // 1, //I2L, // visitInsn + // 0, //I2F, // - + // 1, //I2D, // - + // -1, //L2I, // - + // -1, //L2F, // - + // 0, //L2D, // - + // 0, //F2I, // - + // 1, //F2L, // - + // 1, //F2D, // - + // -1, //D2I, // - + // 0, //D2L, // - + // -1, //D2F, // - + // 0, //I2B, // - + // 0, //I2C, // - + // 0, //I2S, // - + // -3, //LCMP, // - + // -1, //FCMPL, // - + // -1, //FCMPG, // - + // -3, //DCMPL, // - + // -3, //DCMPG, // - + // -1, //IFEQ, // visitJumpInsn + // -1, //IFNE, // - + // -1, //IFLT, // - + // -1, //IFGE, // - + // -1, //IFGT, // - + // -1, //IFLE, // - + // -2, //IF_ICMPEQ, // - + // -2, //IF_ICMPNE, // - + // -2, //IF_ICMPLT, // - + // -2, //IF_ICMPGE, // - + // -2, //IF_ICMPGT, // - + // -2, //IF_ICMPLE, // - + // -2, //IF_ACMPEQ, // - + // -2, //IF_ACMPNE, // - + // 0, //GOTO, // - + // 1, //JSR, // - + // 0, //RET, // visitVarInsn + // -1, //TABLESWITCH, // visiTableSwitchInsn + // -1, //LOOKUPSWITCH, // visitLookupSwitch + // -1, //IRETURN, // visitInsn + // -2, //LRETURN, // - + // -1, //FRETURN, // - + // -2, //DRETURN, // - + // -1, //ARETURN, // - + // 0, //RETURN, // - + // NA, //GETSTATIC, // visitFieldInsn + // NA, //PUTSTATIC, // - + // NA, //GETFIELD, // - + // NA, //PUTFIELD, // - + // NA, //INVOKEVIRTUAL, // visitMethodInsn + // NA, //INVOKESPECIAL, // - + // NA, //INVOKESTATIC, // - + // NA, //INVOKEINTERFACE, // - + // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn + // 1, //NEW, // visitTypeInsn + // 0, //NEWARRAY, // visitIntInsn + // 0, //ANEWARRAY, // visitTypeInsn + // 0, //ARRAYLENGTH, // visitInsn + // NA, //ATHROW, // - + // 0, //CHECKCAST, // visitTypeInsn + // 0, //INSTANCEOF, // - + // -1, //MONITORENTER, // visitInsn + // -1, //MONITOREXIT, // - + // NA, //WIDE, // NOT VISITED + // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn + // -1, //IFNULL, // visitJumpInsn + // -1, //IFNONNULL, // - + // NA, //GOTO_W, // - + // NA, //JSR_W, // - + // }; + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('E' + b[i])); + // } + // System.err.println(); + } + + /** + * The label (i.e. basic block) to which these input and output stack map + * frames correspond. + */ + Label owner; + + /** + * The input stack map frame locals. + */ + int[] inputLocals; + + /** + * The input stack map frame stack. + */ + int[] inputStack; + + /** + * The output stack map frame locals. + */ + private int[] outputLocals; + + /** + * The output stack map frame stack. + */ + private int[] outputStack; + + /** + * Relative size of the output stack. The exact semantics of this field + * depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the size of + * the output stack relatively to the top of the input stack. + * + * When the stack map frames are completely computed, this field is the + * actual number of types in {@link #outputStack}. + */ + private int outputStackTop; + + /** + * Number of types that are initialized in the basic block. + * + * @see #initializations + */ + private int initializationCount; + + /** + * The types that are initialized in the basic block. A constructor + * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace + * <i>every occurence</i> of this type in the local variables and in the + * operand stack. This cannot be done during the first phase of the + * algorithm since, during this phase, the local variables and the operand + * stack are not completely computed. It is therefore necessary to store the + * types on which constructors are invoked in the basic block, in order to + * do this replacement during the second phase of the algorithm, where the + * frames are fully computed. Note that this array can contain types that + * are relative to input locals or to the input stack (see below for the + * description of the algorithm). + */ + private int[] initializations; + + /** + * Returns the output frame local variable type at the given index. + * + * @param local the index of the local that must be returned. + * @return the output frame local variable type at the given index. + */ + private int get(final int local) { + if (outputLocals == null || local >= outputLocals.length) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + return LOCAL | local; + } else { + int type = outputLocals[local]; + if (type == 0) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + type = outputLocals[local] = LOCAL | local; + } + return type; + } + } + + /** + * Sets the output frame local variable type at the given index. + * + * @param local the index of the local that must be set. + * @param type the value of the local that must be set. + */ + private void set(final int local, final int type) { + // creates and/or resizes the output local variables array if necessary + if (outputLocals == null) { + outputLocals = new int[10]; + } + int n = outputLocals.length; + if (local >= n) { + int[] t = new int[Math.max(local + 1, 2 * n)]; + System.arraycopy(outputLocals, 0, t, 0, n); + outputLocals = t; + } + // sets the local variable + outputLocals[local] = type; + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param type the type that must be pushed. + */ + private void push(final int type) { + // creates and/or resizes the output stack array if necessary + if (outputStack == null) { + outputStack = new int[10]; + } + int n = outputStack.length; + if (outputStackTop >= n) { + int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; + System.arraycopy(outputStack, 0, t, 0, n); + outputStack = t; + } + // pushes the type on the output stack + outputStack[outputStackTop++] = type; + // updates the maximun height reached by the output stack, if needed + int top = owner.inputStackTop + outputStackTop; + if (top > owner.outputStackMax) { + owner.outputStackMax = top; + } + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param cw the ClassWriter to which this label belongs. + * @param desc the descriptor of the type to be pushed. Can also be a method + * descriptor (in this case this method pushes its return type onto + * the output frame stack). + */ + private void push(final ClassWriter cw, final String desc) { + int type = type(cw, desc); + if (type != 0) { + push(type); + if (type == LONG || type == DOUBLE) { + push(TOP); + } + } + } + + /** + * Returns the int encoding of the given type. + * + * @param cw the ClassWriter to which this label belongs. + * @param desc a type descriptor. + * @return the int encoding of the given type. + */ + private static int type(final ClassWriter cw, final String desc) { + String t; + int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + // stores the internal name, not the descriptor! + t = desc.substring(index + 1, desc.length() - 1); + return OBJECT | cw.addType(t); + // case '[': + default: + // extracts the dimensions and the element type + int data; + int dims = index + 1; + while (desc.charAt(dims) == '[') { + ++dims; + } + switch (desc.charAt(dims)) { + case 'Z': + data = BOOLEAN; + break; + case 'C': + data = CHAR; + break; + case 'B': + data = BYTE; + break; + case 'S': + data = SHORT; + break; + case 'I': + data = INTEGER; + break; + case 'F': + data = FLOAT; + break; + case 'J': + data = LONG; + break; + case 'D': + data = DOUBLE; + break; + // case 'L': + default: + // stores the internal name, not the descriptor + t = desc.substring(dims + 1, desc.length() - 1); + data = OBJECT | cw.addType(t); + } + return (dims - index) << 28 | data; + } + } + + /** + * Pops a type from the output frame stack and returns its value. + * + * @return the type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // if the output frame stack is empty, pops from the input stack + return STACK | -(--owner.inputStackTop); + } + } + + /** + * Pops the given number of types from the output frame stack. + * + * @param elements the number of types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // if the number of elements to be popped is greater than the number + // of elements in the output stack, clear it, and pops the remaining + // elements from the input stack. + owner.inputStackTop -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops a type from the output frame stack. + * + * @param desc the descriptor of the type to be popped. Can also be a method + * descriptor (in this case this method pops the types corresponding + * to the method arguments). + */ + private void pop(final String desc) { + char c = desc.charAt(0); + if (c == '(') { + pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); + } else if (c == 'J' || c == 'D') { + pop(2); + } else { + pop(1); + } + } + + /** + * Adds a new type to the list of types on which a constructor is invoked in + * the basic block. + * + * @param var a type on a which a constructor is invoked. + */ + private void init(final int var) { + // creates and/or resizes the initializations array if necessary + if (initializations == null) { + initializations = new int[2]; + } + int n = initializations.length; + if (initializationCount >= n) { + int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; + System.arraycopy(initializations, 0, t, 0, n); + initializations = t; + } + // stores the type to be initialized + initializations[initializationCount++] = var; + } + + /** + * Replaces the given type with the appropriate type if it is one of the + * types on which a constructor is invoked in the basic block. + * + * @param cw the ClassWriter to which this label belongs. + * @param t a type + * @return t or, if t is one of the types on which a constructor is invoked + * in the basic block, the type corresponding to this constructor. + */ + private int init(final ClassWriter cw, final int t) { + int s; + if (t == UNINITIALIZED_THIS) { + s = OBJECT | cw.addType(cw.thisName); + } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { + String type = cw.typeTable[t & BASE_VALUE].strVal1; + s = OBJECT | cw.addType(type); + } else { + return t; + } + for (int j = 0; j < initializationCount; ++j) { + int u = initializations[j]; + int dim = u & DIM; + int kind = u & KIND; + if (kind == LOCAL) { + u = dim + inputLocals[u & VALUE]; + } else if (kind == STACK) { + u = dim + inputStack[inputStack.length - (u & VALUE)]; + } + if (t == u) { + return s; + } + } + return t; + } + + /** + * Initializes the input frame of the first basic block from the method + * descriptor. + * + * @param cw the ClassWriter to which this label belongs. + * @param access the access flags of the method to which this label belongs. + * @param args the formal parameter types of this method. + * @param maxLocals the maximum number of local variables of this method. + */ + void initInputFrame( + final ClassWriter cw, + final int access, + final Type[] args, + final int maxLocals) + { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int i = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { + inputLocals[i++] = OBJECT | cw.addType(cw.thisName); + } else { + inputLocals[i++] = UNINITIALIZED_THIS; + } + } + for (int j = 0; j < args.length; ++j) { + int t = type(cw, args[j].getDescriptor()); + inputLocals[i++] = t; + if (t == LONG || t == DOUBLE) { + inputLocals[i++] = TOP; + } + } + while (i < maxLocals) { + inputLocals[i++] = TOP; + } + } + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode the opcode of the instruction. + * @param arg the operand of the instruction, if any. + * @param cw the class writer to which this label belongs. + * @param item the operand of the instructions, if any. + */ + void execute( + final int opcode, + final int arg, + final ClassWriter cw, + final Item item) + { + int t1, t2, t3, t4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (item.type) { + case ClassWriter.INT: + push(INTEGER); + break; + case ClassWriter.LONG: + push(LONG); + push(TOP); + break; + case ClassWriter.FLOAT: + push(FLOAT); + break; + case ClassWriter.DOUBLE: + push(DOUBLE); + push(TOP); + break; + case ClassWriter.CLASS: + push(OBJECT | cw.addType("java/lang/Class")); + break; + case ClassWriter.STR: + push(OBJECT | cw.addType("java/lang/String")); + break; + case ClassWriter.MTYPE: + push(OBJECT | cw.addType("java/lang/invoke/MethodType")); + break; + // case ClassWriter.HANDLE_BASE + [1..9]: + default: + push(OBJECT | cw.addType("java/lang/invoke/MethodHandle")); + } + break; + case Opcodes.ALOAD: + push(get(arg)); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INTEGER); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + pop(2); + push(FLOAT); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + t1 = pop(); + push(ELEMENT_OF + t1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + t1 = pop(); + set(arg, t1); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + t1 = pop(); + set(arg, t1); + set(arg + 1, TOP); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + t1 = pop(); + push(t1); + push(t1); + break; + case Opcodes.DUP_X1: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2: + t1 = pop(); + t2 = pop(); + push(t2); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X1: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t2); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.SWAP: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + break; + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + set(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new RuntimeException("JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(cw, item.strVal3); + break; + case Opcodes.PUTSTATIC: + pop(item.strVal3); + break; + case Opcodes.GETFIELD: + pop(1); + push(cw, item.strVal3); + break; + case Opcodes.PUTFIELD: + pop(item.strVal3); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(item.strVal3); + if (opcode != Opcodes.INVOKESTATIC) { + t1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL + && item.strVal2.charAt(0) == '<') + { + init(t1); + } + } + push(cw, item.strVal3); + break; + case Opcodes.INVOKEDYNAMIC: + pop(item.strVal2); + push(cw, item.strVal2); + break; + case Opcodes.NEW: + push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + // case Opcodes.T_LONG: + default: + push(ARRAY_OF | LONG); + break; + } + break; + case Opcodes.ANEWARRAY: + String s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, '[' + s); + } else { + push(ARRAY_OF | OBJECT | cw.addType(s)); + } + break; + case Opcodes.CHECKCAST: + s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, s); + } else { + push(OBJECT | cw.addType(s)); + } + break; + // case Opcodes.MULTIANEWARRAY: + default: + pop(arg); + push(cw, item.strVal1); + break; + } + } + + /** + * Merges the input frame of the given basic block with the input and output + * frames of this basic block. Returns <tt>true</tt> if the input frame of + * the given label has been changed by this operation. + * + * @param cw the ClassWriter to which this label belongs. + * @param frame the basic block whose input frame must be updated. + * @param edge the kind of the {@link Edge} between this label and 'label'. + * See {@link Edge#info}. + * @return <tt>true</tt> if the input frame of the given label has been + * changed by this operation. + */ + boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + boolean changed = false; + int i, s, dim, kind, t; + + int nLocal = inputLocals.length; + int nStack = inputStack.length; + if (frame.inputLocals == null) { + frame.inputLocals = new int[nLocal]; + changed = true; + } + + for (i = 0; i < nLocal; ++i) { + if (outputLocals != null && i < outputLocals.length) { + s = outputLocals[i]; + if (s == 0) { + t = inputLocals[i]; + } else { + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + } + } else { + t = inputLocals[i]; + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputLocals, i); + } + + if (edge > 0) { + for (i = 0; i < nLocal; ++i) { + t = inputLocals[i]; + changed |= merge(cw, t, frame.inputLocals, i); + } + if (frame.inputStack == null) { + frame.inputStack = new int[1]; + changed = true; + } + changed |= merge(cw, edge, frame.inputStack, 0); + return changed; + } + + int nInputStack = inputStack.length + owner.inputStackTop; + if (frame.inputStack == null) { + frame.inputStack = new int[nInputStack + outputStackTop]; + changed = true; + } + + for (i = 0; i < nInputStack; ++i) { + t = inputStack[i]; + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, i); + } + for (i = 0; i < outputStackTop; ++i) { + s = outputStack[i]; + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } + return changed; + } + + /** + * Merges the type at the given index in the given type array with the given + * type. Returns <tt>true</tt> if the type array has been modified by this + * operation. + * + * @param cw the ClassWriter to which this label belongs. + * @param t the type with which the type array element must be merged. + * @param types an array of types. + * @param index the index of the type that must be merged in 'types'. + * @return <tt>true</tt> if the type array has been modified by this + * operation. + */ + private static boolean merge( + final ClassWriter cw, + int t, + final int[] types, + final int index) + { + int u = types[index]; + if (u == t) { + // if the types are equal, merge(u,t)=u, so there is no change + return false; + } + if ((t & ~DIM) == NULL) { + if (u == NULL) { + return false; + } + t = NULL; + } + if (u == 0) { + // if types[index] has never been assigned, merge(u,t)=t + types[index] = t; + return true; + } + int v; + if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { + // if u is a reference type of any dimension + if (t == NULL) { + // if t is the NULL type, merge(u,t)=u, so there is no change + return false; + } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + if ((u & BASE_KIND) == OBJECT) { + // if t is also a reference type, and if u and t have the + // same dimension merge(u,t) = dim(t) | common parent of the + // element types of u and t + v = (t & DIM) | OBJECT + | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); + } else { + // if u and t are array types, but not with the same element + // type, merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } + } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { + // if t is any other reference or array type, + // merge(u,t)=java/lang/Object + v = OBJECT | cw.addType("java/lang/Object"); + } else { + // if t is any other type, merge(u,t)=TOP + v = TOP; + } + } else if (u == NULL) { + // if u is the NULL type, merge(u,t)=t, + // or TOP if t is not a reference type + v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; + } else { + // if u is any other type, merge(u,t)=TOP whatever t + v = TOP; + } + if (u != v) { + types[index] = v; + return true; + } + return false; + } +} diff --git a/src/compiler/scala/tools/asm/Handle.java b/src/compiler/scala/tools/asm/Handle.java new file mode 100644 index 0000000000..be8f334192 --- /dev/null +++ b/src/compiler/scala/tools/asm/Handle.java @@ -0,0 +1,159 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package scala.tools.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + final int tag; + + /** + * The internal name of the field or method designed by this handle. + */ + final String owner; + + /** + * The name of the field or method designated by this handle. + */ + final String name; + + /** + * The descriptor of the field or method designated by this handle. + */ + final String desc; + + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method designed by this + * handle. + * @param name the name of the field or method designated by this handle. + * @param desc the descriptor of the field or method designated by this + * handle. + */ + public Handle(int tag, String owner, String name, String desc) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.desc = desc; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the field or method designed by this + * handle. + * + * @return the internal name of the field or method designed by this + * handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return desc; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Handle)) { + return false; + } + Handle h = (Handle) obj; + return tag == h.tag && owner.equals(h.owner) + && name.equals(h.name) && desc.equals(h.desc); + } + + @Override + public int hashCode() { + return tag + owner.hashCode() * name.hashCode() * desc.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual + * representation is: <pre>owner '.' name desc ' ' '(' tag ')'</pre>. As + * this format is unambiguous, it can be parsed if necessary. + */ + @Override + public String toString() { + return owner + '.' + name + desc + " (" + tag + ')'; + } +} diff --git a/src/compiler/scala/tools/asm/Handler.java b/src/compiler/scala/tools/asm/Handler.java new file mode 100644 index 0000000000..9e92bb98be --- /dev/null +++ b/src/compiler/scala/tools/asm/Handler.java @@ -0,0 +1,118 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * Information about an exception handler block. + * + * @author Eric Bruneton + */ +class Handler { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + Label start; + + /** + * End of the exception handler's scope (exclusive). + */ + Label end; + + /** + * Beginning of the exception handler's code. + */ + Label handler; + + /** + * Internal name of the type of exceptions handled by this handler, or + * <tt>null</tt> to catch any exceptions. + */ + String desc; + + /** + * Constant pool index of the internal name of the type of exceptions + * handled by this handler, or 0 to catch any exceptions. + */ + int type; + + /** + * Next exception handler block info. + */ + Handler next; + + /** + * Removes the range between start and end from the given exception + * handlers. + * + * @param h an exception handler list. + * @param start the start of the range to be removed. + * @param end the end of the range to be removed. Maybe null. + * @return the exception handler list with the start-end range removed. + */ + static Handler remove(Handler h, Label start, Label end) { + if (h == null) { + return null; + } else { + h.next = remove(h.next, start, end); + } + int hstart = h.start.position; + int hend = h.end.position; + int s = start.position; + int e = end == null ? Integer.MAX_VALUE : end.position; + // if [hstart,hend[ and [s,e[ intervals intersect... + if (s < hend && e > hstart) { + if (s <= hstart) { + if (e >= hend) { + // [hstart,hend[ fully included in [s,e[, h removed + h = h.next; + } else { + // [hstart,hend[ minus [s,e[ = [e,hend[ + h.start = end; + } + } else if (e >= hend) { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + h.end = start; + } else { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[ + Handler g = new Handler(); + g.start = end; + g.end = h.end; + g.handler = h.handler; + g.desc = h.desc; + g.type = h.type; + g.next = h.next; + h.end = start; + h.next = g; + } + } + return h; + } +} diff --git a/src/compiler/scala/tools/asm/Item.java b/src/compiler/scala/tools/asm/Item.java new file mode 100644 index 0000000000..021a0b11d3 --- /dev/null +++ b/src/compiler/scala/tools/asm/Item.java @@ -0,0 +1,297 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A constant pool item. Constant pool items can be created with the 'newXXX' + * methods in the {@link ClassWriter} class. + * + * @author Eric Bruneton + */ +final class Item { + + /** + * Index of this item in the constant pool. + */ + int index; + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is one of {@link ClassWriter#INT}, + * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, + * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, + * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, + * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, + * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}, + * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}. + * + * MethodHandle constant 9 variations are stored using a range + * of 9 values from {@link ClassWriter#HANDLE_BASE} + 1 to + * {@link ClassWriter#HANDLE_BASE} + 9. + * + * Special Item types are used for Items that are stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. These special item types are + * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and + * {@link ClassWriter#TYPE_MERGED}. + */ + int type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal3; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + /** + * Link to another constant pool item, used for collision lists in the + * constant pool's hash table. + */ + Item next; + + /** + * Constructs an uninitialized {@link Item}. + */ + Item() { + } + + /** + * Constructs an uninitialized {@link Item} for constant pool element at + * given position. + * + * @param index index of the item to be constructed. + */ + Item(final int index) { + this.index = index; + } + + /** + * Constructs a copy of the given item. + * + * @param index index of the item to be constructed. + * @param i the item that must be copied into the item to be constructed. + */ + Item(final int index, final Item i) { + this.index = index; + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + strVal3 = i.strVal3; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal the value of this item. + */ + void set(final int intVal) { + this.type = ClassWriter.INT; + this.intVal = intVal; + this.hashCode = 0x7FFFFFFF & (type + intVal); + } + + /** + * Sets this item to a long item. + * + * @param longVal the value of this item. + */ + void set(final long longVal) { + this.type = ClassWriter.LONG; + this.longVal = longVal; + this.hashCode = 0x7FFFFFFF & (type + (int) longVal); + } + + /** + * Sets this item to a float item. + * + * @param floatVal the value of this item. + */ + void set(final float floatVal) { + this.type = ClassWriter.FLOAT; + this.intVal = Float.floatToRawIntBits(floatVal); + this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); + } + + /** + * Sets this item to a double item. + * + * @param doubleVal the value of this item. + */ + void set(final double doubleVal) { + this.type = ClassWriter.DOUBLE; + this.longVal = Double.doubleToRawLongBits(doubleVal); + this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type the type of this item. + * @param strVal1 first part of the value of this item. + * @param strVal2 second part of the value of this item. + * @param strVal3 third part of the value of this item. + */ + void set( + final int type, + final String strVal1, + final String strVal2, + final String strVal3) + { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.strVal3 = strVal3; + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case ClassWriter.NAME_TYPE: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode()); + return; + // ClassWriter.FIELD: + // ClassWriter.METH: + // ClassWriter.IMETH: + // ClassWriter.HANDLE_BASE + 1..9 + default: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode()); + } + } + + /** + * Sets the item to an InvokeDynamic item. + * + * @param name invokedynamic's name. + * @param desc invokedynamic's desc. + * @param bsmIndex zero based index into the class attribute BootrapMethods. + */ + void set(String name, String desc, int bsmIndex) { + this.type = ClassWriter.INDY; + this.longVal = bsmIndex; + this.strVal1 = name; + this.strVal2 = desc; + this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex + * strVal1.hashCode() * strVal2.hashCode()); + } + + /** + * Sets the item to a BootstrapMethod item. + * + * @param position position in byte in the class attribute BootrapMethods. + * @param hashCode hashcode of the item. This hashcode is processed from + * the hashcode of the bootstrap method and the hashcode of + * all bootstrap arguments. + */ + void set(int position, int hashCode) { + this.type = ClassWriter.BSM; + this.intVal = position; + this.hashCode = hashCode; + } + + /** + * Indicates if the given item is equal to this one. <i>This method assumes + * that the two items have the same {@link #type}</i>. + * + * @param i the item to be compared to this one. Both items must have the + * same {@link #type}. + * @return <tt>true</tt> if the given item if equal to this one, + * <tt>false</tt> otherwise. + */ + boolean isEqualTo(final Item i) { + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + return i.strVal1.equals(strVal1); + case ClassWriter.TYPE_MERGED: + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + return i.longVal == longVal; + case ClassWriter.INT: + case ClassWriter.FLOAT: + return i.intVal == intVal; + case ClassWriter.TYPE_UNINIT: + return i.intVal == intVal && i.strVal1.equals(strVal1); + case ClassWriter.NAME_TYPE: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); + case ClassWriter.INDY: + return i.longVal == longVal && i.strVal1.equals(strVal1) + && i.strVal2.equals(strVal2); + + // case ClassWriter.FIELD: + // case ClassWriter.METH: + // case ClassWriter.IMETH: + // case ClassWriter.HANDLE_BASE + 1..9 + default: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) + && i.strVal3.equals(strVal3); + } + } + +} diff --git a/src/compiler/scala/tools/asm/Label.java b/src/compiler/scala/tools/asm/Label.java new file mode 100644 index 0000000000..712c7f251f --- /dev/null +++ b/src/compiler/scala/tools/asm/Label.java @@ -0,0 +1,555 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A label represents a position in the bytecode of a method. Labels are used + * for jump, goto, and switch instructions, and for try catch blocks. A label + * designates the <i>instruction</i> that is just after. Note however that + * there can be other elements between a label and the instruction it + * designates (such as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * Indicates if this label is only used for debug attributes. Such a label + * is not the start of a basic block, the target of a jump instruction, or + * an exception handler. It can be safely ignored in control flow graph + * analysis algorithms (for optimization purposes). + */ + static final int DEBUG = 1; + + /** + * Indicates if the position of this label is known. + */ + static final int RESOLVED = 2; + + /** + * Indicates if this label has been updated, after instruction resizing. + */ + static final int RESIZED = 4; + + /** + * Indicates if this basic block has been pushed in the basic block stack. + * See {@link MethodWriter#visitMaxs visitMaxs}. + */ + static final int PUSHED = 8; + + /** + * Indicates if this label is the target of a jump instruction, or the start + * of an exception handler. + */ + static final int TARGET = 16; + + /** + * Indicates if a stack map frame must be stored for this label. + */ + static final int STORE = 32; + + /** + * Indicates if this label corresponds to a reachable basic block. + */ + static final int REACHABLE = 64; + + /** + * Indicates if this basic block ends with a JSR instruction. + */ + static final int JSR = 128; + + /** + * Indicates if this basic block ends with a RET instruction. + */ + static final int RET = 256; + + /** + * Indicates if this basic block is the start of a subroutine. + */ + static final int SUBROUTINE = 512; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(null, ...) call. + */ + static final int VISITED = 1024; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(!null, ...) call. + */ + static final int VISITED2 = 2048; + + /** + * Field used to associate user information to a label. Warning: this field + * is used by the ASM tree package. In order to use it with the ASM tree + * package you must override the {@link + * org.objectweb.asm.tree.MethodNode#getLabelNode} method. + */ + public Object info; + + /** + * Flags that indicate the status of this label. + * + * @see #DEBUG + * @see #RESOLVED + * @see #RESIZED + * @see #PUSHED + * @see #TARGET + * @see #STORE + * @see #REACHABLE + * @see #JSR + * @see #RET + */ + int status; + + /** + * The line number corresponding to this label, if known. + */ + int line; + + /** + * The position of this label in the code, if known. + */ + int position; + + /** + * Number of forward references to this label, times two. + */ + private int referenceCount; + + /** + * Informations about forward references. Each forward reference is + * described by two consecutive integers in this array: the first one is the + * position of the first byte of the bytecode instruction that contains the + * forward reference, while the second is the position of the first byte of + * the forward reference itself. In fact the sign of the first integer + * indicates if this reference uses 2 or 4 bytes, and its absolute value + * gives the position of the bytecode instruction. This array is also used + * as a bitset to store the subroutines to which a basic block belongs. This + * information is needed in {@linked MethodWriter#visitMaxs}, after all + * forward references have been resolved. Hence the same array can be used + * for both purposes without problems. + */ + private int[] srcAndRefPositions; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow and data flow graph analysis algorithms (used + * to compute the maximum stack size or the stack map frames). A control + * flow graph contains one node per "basic block", and one edge per "jump" + * from one basic block to another. Each node (i.e., each basic block) is + * represented by the Label object that corresponds to the first instruction + * of this basic block. Each node also stores the list of its successors in + * the graph, as a linked list of Edge objects. + * + * The control flow analysis algorithms used to compute the maximum stack + * size or the stack map frames are similar and use two steps. The first + * step, during the visit of each instruction, builds information about the + * state of the local variables and the operand stack at the end of each + * basic block, called the "output frame", <i>relatively</i> to the frame + * state at the beginning of the basic block, which is called the "input + * frame", and which is <i>unknown</i> during this step. The second step, + * in {@link MethodWriter#visitMaxs}, is a fix point algorithm that + * computes information about the input frame of each basic block, from the + * input state of the first basic block (known from the method signature), + * and by the using the previously computed relative output frames. + * + * The algorithm used to compute the maximum stack size only computes the + * relative output and absolute input stack heights, while the algorithm + * used to compute stack map frames computes relative output frames and + * absolute input frames. + */ + + /** + * Start of the output stack relatively to the input stack. The exact + * semantics of this field depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the number of + * elements in the input stack. + * + * When the stack map frames are completely computed, this field is the + * offset of the first output stack element relatively to the top of the + * input stack. This offset is always negative or null. A null offset means + * that the output stack must be appended to the input stack. A -n offset + * means that the first n output stack elements must replace the top n input + * stack elements, and that the other elements must be appended to the input + * stack. + */ + int inputStackTop; + + /** + * Maximum height reached by the output stack, relatively to the top of the + * input stack. This maximum is always positive or null. + */ + int outputStackMax; + + /** + * Information about the input and output stack map frames of this basic + * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} + * option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited. This linked + * list does not include labels used for debug info only. If + * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it + * does not contain successive labels that denote the same bytecode position + * (in this case only the first label appears in this list). + */ + Label successor; + + /** + * The successors of this node in the control flow graph. These successors + * are stored in a linked list of {@link Edge Edge} objects, linked to each + * other by their {@link Edge#next} field. + */ + Edge successors; + + /** + * The next basic block in the basic block stack. This stack is used in the + * main loop of the fix point algorithm used in the second step of the + * control flow analysis algorithms. It is also used in + * {@link #visitSubroutine} to avoid using a recursive method. + * + * @see MethodWriter#visitMaxs + */ + Label next; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new label. + */ + public Label() { + } + + // ------------------------------------------------------------------------ + // Methods to compute offsets and to manage forward references + // ------------------------------------------------------------------------ + + /** + * Returns the offset corresponding to this label. This offset is computed + * from the start of the method's bytecode. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @return the offset corresponding to this label. + * @throws IllegalStateException if this label is not resolved yet. + */ + public int getOffset() { + if ((status & RESOLVED) == 0) { + throw new IllegalStateException("Label offset position has not been resolved yet"); + } + return position; + } + + /** + * Puts a reference to this label in the bytecode of a method. If the + * position of the label is known, the offset is computed and written + * directly. Otherwise, a null offset is written and a new forward reference + * is declared for this label. + * + * @param owner the code writer that calls this method. + * @param out the bytecode of the method. + * @param source the position of first byte of the bytecode instruction that + * contains this label. + * @param wideOffset <tt>true</tt> if the reference must be stored in 4 + * bytes, or <tt>false</tt> if it must be stored with 2 bytes. + * @throws IllegalArgumentException if this label has not been created by + * the given code writer. + */ + void put( + final MethodWriter owner, + final ByteVector out, + final int source, + final boolean wideOffset) + { + if ((status & RESOLVED) == 0) { + if (wideOffset) { + addReference(-1 - source, out.length); + out.putInt(-1); + } else { + addReference(source, out.length); + out.putShort(-1); + } + } else { + if (wideOffset) { + out.putInt(position - source); + } else { + out.putShort(position - source); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only + * for a true forward reference, i.e. only if this label is not resolved + * yet. For backward references, the offset of the reference can be, and + * must be, computed and stored directly. + * + * @param sourcePosition the position of the referencing instruction. This + * position will be used to compute the offset of this forward + * reference. + * @param referencePosition the position where the offset for this forward + * reference must be stored. + */ + private void addReference( + final int sourcePosition, + final int referencePosition) + { + if (srcAndRefPositions == null) { + srcAndRefPositions = new int[6]; + } + if (referenceCount >= srcAndRefPositions.length) { + int[] a = new int[srcAndRefPositions.length + 6]; + System.arraycopy(srcAndRefPositions, + 0, + a, + 0, + srcAndRefPositions.length); + srcAndRefPositions = a; + } + srcAndRefPositions[referenceCount++] = sourcePosition; + srcAndRefPositions[referenceCount++] = referencePosition; + } + + /** + * Resolves all forward references to this label. This method must be called + * when this label is added to the bytecode of the method, i.e. when its + * position becomes known. This method fills in the blanks that where left + * in the bytecode by each forward reference previously added to this label. + * + * @param owner the code writer that calls this method. + * @param position the position of this label in the bytecode. + * @param data the bytecode of the method. + * @return <tt>true</tt> if a blank that was left for this label was to + * small to store the offset. In such a case the corresponding jump + * instruction is replaced with a pseudo instruction (using unused + * opcodes) using an unsigned two bytes offset. These pseudo + * instructions will need to be replaced with true instructions with + * wider offsets (4 bytes instead of 2). This is done in + * {@link MethodWriter#resizeInstructions}. + * @throws IllegalArgumentException if this label has already been resolved, + * or if it has not been created by the given code writer. + */ + boolean resolve( + final MethodWriter owner, + final int position, + final byte[] data) + { + boolean needUpdate = false; + this.status |= RESOLVED; + this.position = position; + int i = 0; + while (i < referenceCount) { + int source = srcAndRefPositions[i++]; + int reference = srcAndRefPositions[i++]; + int offset; + if (source >= 0) { + offset = position - source; + if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { + /* + * changes the opcode of the jump instruction, in order to + * be able to find it later (see resizeInstructions in + * MethodWriter). These temporary opcodes are similar to + * jump instruction opcodes, except that the 2 bytes offset + * is unsigned (and can therefore represent values from 0 to + * 65535, which is sufficient since the size of a method is + * limited to 65535 bytes). + */ + int opcode = data[reference - 1] & 0xFF; + if (opcode <= Opcodes.JSR) { + // changes IFEQ ... JSR to opcodes 202 to 217 + data[reference - 1] = (byte) (opcode + 49); + } else { + // changes IFNULL and IFNONNULL to opcodes 218 and 219 + data[reference - 1] = (byte) (opcode + 20); + } + needUpdate = true; + } + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } else { + offset = position + source + 1; + data[reference++] = (byte) (offset >>> 24); + data[reference++] = (byte) (offset >>> 16); + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } + } + return needUpdate; + } + + /** + * Returns the first label of the series to which this label belongs. For an + * isolated label or for the first label in a series of successive labels, + * this method returns the label itself. For other labels it returns the + * first label of the series. + * + * @return the first label of the series to which this label belongs. + */ + Label getFirst() { + return !ClassReader.FRAMES || frame == null ? this : frame.owner; + } + + // ------------------------------------------------------------------------ + // Methods related to subroutines + // ------------------------------------------------------------------------ + + /** + * Returns true is this basic block belongs to the given subroutine. + * + * @param id a subroutine id. + * @return true is this basic block belongs to the given subroutine. + */ + boolean inSubroutine(final long id) { + if ((status & Label.VISITED) != 0) { + return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; + } + return false; + } + + /** + * Returns true if this basic block and the given one belong to a common + * subroutine. + * + * @param block another basic block. + * @return true if this basic block and the given one belong to a common + * subroutine. + */ + boolean inSameSubroutine(final Label block) { + if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { + return false; + } + for (int i = 0; i < srcAndRefPositions.length; ++i) { + if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { + return true; + } + } + return false; + } + + /** + * Marks this basic block as belonging to the given subroutine. + * + * @param id a subroutine id. + * @param nbSubroutines the total number of subroutines in the method. + */ + void addToSubroutine(final long id, final int nbSubroutines) { + if ((status & VISITED) == 0) { + status |= VISITED; + srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; + } + srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + } + + /** + * Finds the basic blocks that belong to a given subroutine, and marks these + * blocks as belonging to this subroutine. This method follows the control + * flow graph to find all the blocks that are reachable from the current + * block WITHOUT following any JSR target. + * + * @param JSR a JSR block that jumps to this subroutine. If this JSR is not + * null it is added to the successor of the RET blocks found in the + * subroutine. + * @param id the id of this subroutine. + * @param nbSubroutines the total number of subroutines in the method. + */ + void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) + { + // user managed stack of labels, to avoid using a recursive method + // (recursivity can lead to stack overflow with very large methods) + Label stack = this; + while (stack != null) { + // removes a label l from the stack + Label l = stack; + stack = l.next; + l.next = null; + + if (JSR != null) { + if ((l.status & VISITED2) != 0) { + continue; + } + l.status |= VISITED2; + // adds JSR to the successors of l, if it is a RET block + if ((l.status & RET) != 0) { + if (!l.inSameSubroutine(JSR)) { + Edge e = new Edge(); + e.info = l.inputStackTop; + e.successor = JSR.successors.successor; + e.next = l.successors; + l.successors = e; + } + } + } else { + // if the l block already belongs to subroutine 'id', continue + if (l.inSubroutine(id)) { + continue; + } + // marks the l block as belonging to subroutine 'id' + l.addToSubroutine(id, nbSubroutines); + } + // pushes each successor of l on the stack, except JSR targets + Edge e = l.successors; + while (e != null) { + // if the l block is a JSR block, then 'l.successors.next' leads + // to the JSR target (see {@link #visitJumpInsn}) and must + // therefore not be followed + if ((l.status & Label.JSR) == 0 || e != l.successors.next) { + // pushes e.successor on the stack if it not already added + if (e.successor.next == null) { + e.successor.next = stack; + stack = e.successor; + } + } + e = e.next; + } + } + } + + // ------------------------------------------------------------------------ + // Overriden Object methods + // ------------------------------------------------------------------------ + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/src/compiler/scala/tools/asm/MethodVisitor.java b/src/compiler/scala/tools/asm/MethodVisitor.java new file mode 100644 index 0000000000..a8a859a6a9 --- /dev/null +++ b/src/compiler/scala/tools/asm/MethodVisitor.java @@ -0,0 +1,588 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be + * called in the following order: [ <tt>visitAnnotationDefault</tt> ] ( + * <tt>visitAnnotation</tt> | <tt>visitParameterAnnotation</tt> | + * <tt>visitAttribute</tt> )* [ <tt>visitCode</tt> ( <tt>visitFrame</tt> | + * <tt>visit</tt><i>X</i>Insn</tt> | <tt>visitLabel</tt> | <tt>visitTryCatchBlock</tt> | + * <tt>visitLocalVariable</tt> | <tt>visitLineNumber</tt> )* <tt>visitMaxs</tt> ] + * <tt>visitEnd</tt>. In addition, the <tt>visit</tt><i>X</i>Insn</tt> + * and <tt>visitLabel</tt> methods must be called in the sequential order of + * the bytecode instructions of the visited code, <tt>visitTryCatchBlock</tt> + * must be called <i>before</i> the labels passed as arguments have been + * visited, and the <tt>visitLocalVariable</tt> and <tt>visitLineNumber</tt> + * methods must be called <i>after</i> the labels passed as arguments have been + * visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May + * be null. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param mv the method visitor to which this visitor must delegate method + * calls. May be null. + */ + public MethodVisitor(final int api, final MethodVisitor mv) { + /*if (api != Opcodes.ASM4) { + throw new IllegalArgumentException(); + }*/ + this.api = api; + this.mv = mv; + } + + // ------------------------------------------------------------------------- + // Annotations and non standard attributes + // ------------------------------------------------------------------------- + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this + * annotation interface method, or <tt>null</tt> if this visitor + * is not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are + * ignored. Moreover, exacly one visit method must be called on this + * annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param desc the class descriptor of the annotation class. + * @param visible <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (mv != null) { + return mv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter the parameter index. + * @param desc the class descriptor of the annotation class. + * @param visible <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation( + int parameter, + String desc, + boolean visible) + { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attr an attribute. + */ + public void visitAttribute(Attribute attr) { + if (mv != null) { + mv.visitAttribute(attr); + } + } + + /** + * Starts the visit of the method's code, if any (i.e. non abstract method). + */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called <i>just before</i> any + * instruction <b>i</b> that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements <i>just + * before</i> <b>i</b> is executed. <br> <br> (*) this is mandatory only + * for classes whose version is greater than or equal to + * {@link Opcodes#V1_6 V1_6}. <br> <br> Packed frames are basically + * "deltas" from the state of the previous frame (very first frame is + * implicitly defined by the method's parameters and access flags): <ul> + * <li>{@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.</li> <li>{@link Opcodes#F_SAME1} + * representing frame with exactly the same locals as the previous frame and + * with single value on the stack (<code>nStack</code> is 1 and + * <code>stack[0]</code> contains value for the type of the stack item).</li> + * <li>{@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (<code>nLocal</code> is 1, 2 or 3 and + * <code>local</code> elements contains values representing added types).</li> + * <li>{@link Opcodes#F_CHOP} representing frame with current locals are + * the same as the locals in the previous frame, except that the last 1-3 + * locals are absent and with the empty stack (<code>nLocals</code> is 1, + * 2 or 3). </li> <li>{@link Opcodes#F_FULL} representing complete frame + * data.</li> </li> </ul> + * + * @param type the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed + * frames. + * @param nLocal the number of local variables in the visited frame. + * @param local the local variable types in this frame. This array must not + * be modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are represented + * by String objects (representing internal names), and uninitialized + * types by Label objects (this label designates the NEW instruction + * that created this uninitialized value). + * @param nStack the number of operand stack elements in the visited frame. + * @param stack the operand stack types in this frame. This array must not + * be modified. Its content has the same format as the "local" array. + * @throws IllegalStateException if a frame is visited just after another + * one, without any instruction between the two (unless this frame + * is a Opcodes#F_SAME frame, in which case it is silently ignored). + */ + public void visitFrame( + int type, + int nLocal, + Object[] local, + int nStack, + Object[] stack) + { + if (mv != null) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + } + + // ------------------------------------------------------------------------- + // Normal instructions + // ------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode the opcode of the instruction to be visited. This opcode is + * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, + * ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, + * FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, + * DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, + * DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, + * DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, + * DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, + * LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, + * I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, + * I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, + * FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, + * MONITORENTER, or MONITOREXIT. + */ + public void visitInsn(int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand the operand of the instruction to be visited.<br> When + * opcode is BIPUSH, operand value should be between Byte.MIN_VALUE + * and Byte.MAX_VALUE.<br> When opcode is SIPUSH, operand value + * should be between Short.MIN_VALUE and Short.MAX_VALUE.<br> When + * opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(int opcode, int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an + * instruction that loads or stores the value of a local variable. + * + * @param opcode the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, + * LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var the operand of the instruction to be visited. This operand is + * the index of a local variable. + */ + public void visitVarInsn(int opcode, int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see {@link + * Type#getInternalName() getInternalName}). + */ + public void visitTypeInsn(int opcode, String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that + * loads or stores the value of a field of an object. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see {@link + * Type#getInternalName() getInternalName}). + * @param name the field's name. + * @param desc the field's descriptor (see {@link Type Type}). + */ + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC + * or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName() getInternalName}). + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + */ + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + * @param bsm the bootstrap method. + * @param bsmArgs the bootstrap method constant arguments. Each argument + * must be an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String}, {@link Type} or {@link Handle} + * value. This method is allowed to modify the content of the array + * so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + if (mv != null) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may + * jump to another instruction. + * + * @param opcode the opcode of the type instruction to be visited. This + * opcode is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, + * IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be visited. This operand + * is a label that designates the instruction to which the jump + * instruction may jump. + */ + public void visitJumpInsn(int opcode, Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited + * just after it. + * + * @param label a {@link Label Label} object. + */ + public void visitLabel(Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ------------------------------------------------------------------------- + // Special instructions + // ------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in + * future versions of the Java Virtual Machine. To easily detect new + * constant types, implementations of this method should check for + * unexpected constant types, like this: + * <pre> + * if (cst instanceof Integer) { + * // ... + * } else if (cst instanceof Float) { + * // ... + * } else if (cst instanceof Long) { + * // ... + * } else if (cst instanceof Double) { + * // ... + * } else if (cst instanceof String) { + * // ... + * } else if (cst instanceof Type) { + * int sort = ((Type) cst).getSort(); + * if (sort == Type.OBJECT) { + * // ... + * } else if (sort == Type.ARRAY) { + * // ... + * } else if (sort == Type.METHOD) { + * // ... + * } else { + * // throw an exception + * } + * } else if (cst instanceof Handle) { + * // ... + * } else { + * // throw an exception + * }</pre> + * + * @param cst the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double}, a {@link String}, a {@link Type} of OBJECT or ARRAY + * sort for <tt>.class</tt> constants, for classes whose version is + * 49.0, a {@link Type} of METHOD sort or a {@link Handle} for + * MethodType and MethodHandle constants, for classes whose version + * is 51.0. + */ + public void visitLdcInsn(Object cst) { + if (mv != null) { + mv.visitLdcInsn(cst); + } + } + + /** + * Visits an IINC instruction. + * + * @param var index of the local variable to be incremented. + * @param increment amount to increment the local variable by. + */ + public void visitIincInsn(int var, int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. <tt>labels[i]</tt> is + * the beginning of the handler block for the <tt>min + i</tt> key. + */ + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. <tt>labels[i]</tt> is + * the beginning of the handler block for the <tt>keys[i]</tt> key. + */ + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param desc an array type descriptor (see {@link Type Type}). + * @param dims number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(String desc, int dims) { + if (mv != null) { + mv.visitMultiANewArrayInsn(desc, dims); + } + } + + // ------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start beginning of the exception handler's scope (inclusive). + * @param end end of the exception handler's scope (exclusive). + * @param handler beginning of the exception handler's code. + * @param type internal name of the type of exceptions handled by the + * handler, or <tt>null</tt> to catch any exceptions (for "finally" + * blocks). + * @throws IllegalArgumentException if one of the labels has already been + * visited by this visitor (by the {@link #visitLabel visitLabel} + * method). + */ + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits a local variable declaration. + * + * @param name the name of a local variable. + * @param desc the type descriptor of this local variable. + * @param signature the type signature of this local variable. May be + * <tt>null</tt> if the local variable type does not use generic + * types. + * @param start the first instruction corresponding to the scope of this + * local variable (inclusive). + * @param end the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index the local variable's index. + * @throws IllegalArgumentException if one of the labels has not already + * been visited by this visitor (by the + * {@link #visitLabel visitLabel} method). + */ + public void visitLocalVariable( + String name, + String desc, + String signature, + Label start, + Label end, + int index) + { + if (mv != null) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + /** + * Visits a line number declaration. + * + * @param line a line number. This number refers to the source file from + * which the class was compiled. + * @param start the first instruction corresponding to this line number. + * @throws IllegalArgumentException if <tt>start</tt> has not already been + * visited by this visitor (by the {@link #visitLabel visitLabel} + * method). + */ + public void visitLineNumber(int line, Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables + * of the method. + * + * @param maxStack maximum stack size of the method. + * @param maxLocals maximum number of local variables for the method. + */ + public void visitMaxs(int maxStack, int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/src/compiler/scala/tools/asm/MethodWriter.java b/src/compiler/scala/tools/asm/MethodWriter.java new file mode 100644 index 0000000000..321bacb6fc --- /dev/null +++ b/src/compiler/scala/tools/asm/MethodWriter.java @@ -0,0 +1,2666 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * A {@link MethodVisitor} that generates methods in bytecode form. Each visit + * method of this class appends the bytecode corresponding to the visited + * instruction to a byte vector, in the order these methods are called. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +class MethodWriter extends MethodVisitor { + + /** + * Pseudo access flag used to denote constructors. + */ + static final int ACC_CONSTRUCTOR = 262144; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. + */ + static final int SAME_FRAME = 0; // to 63 (0-3f) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1 + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) + + /** + * Reserved for future use + */ + static final int RESERVED = 128; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1. Offset is bigger then 63; + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that the k last locals are absent. The value of k is given + * by the formula 251-frame_type. + */ + static final int CHOP_FRAME = 248; // to 250 (f8-fA) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. Offset is bigger then 63; + */ + static final int SAME_FRAME_EXTENDED = 251; // fb + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that k additional locals are defined. The value of k is + * given by the formula frame_type-251. + */ + static final int APPEND_FRAME = 252; // to 254 // fc-fe + + /** + * Full frame + */ + static final int FULL_FRAME = 255; // ff + + /** + * Indicates that the stack map frames must be recomputed from scratch. In + * this case the maximum stack size and number of local variables is also + * recomputed from scratch. + * + * @see #compute + */ + private static final int FRAMES = 0; + + /** + * Indicates that the maximum stack size and number of local variables must + * be automatically computed. + * + * @see #compute + */ + private static final int MAXS = 1; + + /** + * Indicates that nothing must be automatically computed. + * + * @see #compute + */ + private static final int NOTHING = 2; + + /** + * The class writer to which this method must be added. + */ + final ClassWriter cw; + + /** + * Access flags of this method. + */ + private int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * method. + */ + private final int desc; + + /** + * The descriptor of this method. + */ + private final String descriptor; + + /** + * The signature of this method. + */ + String signature; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in <code>cw.cr</code>. More + * precisely, this field gives the index of the first byte to copied from + * <code>cw.cr.b</code>. + */ + int classReaderOffset; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in <code>cw.cr</code>. More + * precisely, this field gives the number of bytes to copied from + * <code>cw.cr.b</code>. + */ + int classReaderLength; + + /** + * Number of exceptions that can be thrown by this method. + */ + int exceptionCount; + + /** + * The exceptions that can be thrown by this method. More precisely, this + * array contains the indexes of the constant pool items that contain the + * internal names of these exception classes. + */ + int[] exceptions; + + /** + * The annotation default attribute of this method. May be <tt>null</tt>. + */ + private ByteVector annd; + + /** + * The runtime visible annotations of this method. May be <tt>null</tt>. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this method. May be <tt>null</tt>. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible parameter annotations of this method. May be + * <tt>null</tt>. + */ + private AnnotationWriter[] panns; + + /** + * The runtime invisible parameter annotations of this method. May be + * <tt>null</tt>. + */ + private AnnotationWriter[] ipanns; + + /** + * The number of synthetic parameters of this method. + */ + private int synthetics; + + /** + * The non standard attributes of the method. + */ + private Attribute attrs; + + /** + * The bytecode of this method. + */ + private ByteVector code = new ByteVector(); + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + /** + * Maximum number of local variables for this method. + */ + private int maxLocals; + + /** + * Number of local variables in the current stack map frame. + */ + private int currentLocals; + + /** + * Number of stack map frames in the StackMapTable attribute. + */ + private int frameCount; + + /** + * The StackMapTable attribute. + */ + private ByteVector stackMap; + + /** + * The offset of the last frame that was written in the StackMapTable + * attribute. + */ + private int previousFrameOffset; + + /** + * The last frame that was written in the StackMapTable attribute. + * + * @see #frame + */ + private int[] previousFrame; + + /** + * Index of the next element to be added in {@link #frame}. + */ + private int frameIndex; + + /** + * The current stack map frame. The first element contains the offset of the + * instruction to which the frame corresponds, the second element is the + * number of locals and the third one is the number of stack elements. The + * local variables start at index 3 and are followed by the operand stack + * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = + * nStack, frame[3] = nLocal. All types are encoded as integers, with the + * same format as the one used in {@link Label}, but limited to BASE types. + */ + private int[] frame; + + /** + * Number of elements in the exception handler list. + */ + private int handlerCount; + + /** + * The first element in the exception handler list. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list. + */ + private Handler lastHandler; + + /** + * Number of entries in the LocalVariableTable attribute. + */ + private int localVarCount; + + /** + * The LocalVariableTable attribute. + */ + private ByteVector localVar; + + /** + * Number of entries in the LocalVariableTypeTable attribute. + */ + private int localVarTypeCount; + + /** + * The LocalVariableTypeTable attribute. + */ + private ByteVector localVarType; + + /** + * Number of entries in the LineNumberTable attribute. + */ + private int lineNumberCount; + + /** + * The LineNumberTable attribute. + */ + private ByteVector lineNumber; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; + + /** + * Indicates if some jump instructions are too small and need to be resized. + */ + private boolean resize; + + /** + * The number of subroutines in this method. + */ + private int subroutines; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow graph analysis algorithm (used to compute the + * maximum stack size). A control flow graph contains one node per "basic + * block", and one edge per "jump" from one basic block to another. Each + * node (i.e., each basic block) is represented by the Label object that + * corresponds to the first instruction of this basic block. Each node also + * stores the list of its successors in the graph, as a linked list of Edge + * objects. + */ + + /** + * Indicates what must be automatically computed. + * + * @see #FRAMES + * @see #MAXS + * @see #NOTHING + */ + private final int compute; + + /** + * A list of labels. This list is the list of basic blocks in the method, + * i.e. a list of Label objects linked to each other by their + * {@link Label#successor} field, in the order they are visited by + * {@link MethodVisitor#visitLabel}, and starting with the first basic block. + */ + private Label labels; + + /** + * The previous basic block. + */ + private Label previousBlock; + + /** + * The current basic block. + */ + private Label currentBlock; + + /** + * The (relative) stack size after the last visited instruction. This size + * is relative to the beginning of the current basic block, i.e., the true + * stack size after the last visited instruction is equal to the + * {@link Label#inputStackTop beginStackSize} of the current basic block + * plus <tt>stackSize</tt>. + */ + private int stackSize; + + /** + * The (relative) maximum stack size after the last visited instruction. + * This size is relative to the beginning of the current basic block, i.e., + * the true maximum stack size after the last visited instruction is equal + * to the {@link Label#inputStackTop beginStackSize} of the current basic + * block plus <tt>stackSize</tt>. + */ + private int maxStackSize; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link MethodWriter}. + * + * @param cw the class writer in which the method must be added. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be <tt>null</tt>. + * @param exceptions the internal names of the method's exceptions. May be + * <tt>null</tt>. + * @param computeMaxs <tt>true</tt> if the maximum stack size and number + * of local variables must be automatically computed. + * @param computeFrames <tt>true</tt> if the stack map tables must be + * recomputed from scratch. + */ + MethodWriter( + final ClassWriter cw, + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions, + final boolean computeMaxs, + final boolean computeFrames) + { + super(Opcodes.ASM4); + if (cw.firstMethod == null) { + cw.firstMethod = this; + } else { + cw.lastMethod.mv = this; + } + cw.lastMethod = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + this.descriptor = desc; + if (ClassReader.SIGNATURES) { + this.signature = signature; + } + if (exceptions != null && exceptions.length > 0) { + exceptionCount = exceptions.length; + this.exceptions = new int[exceptionCount]; + for (int i = 0; i < exceptionCount; ++i) { + this.exceptions[i] = cw.newClass(exceptions[i]); + } + } + this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); + if (computeMaxs || computeFrames) { + if (computeFrames && "<init>".equals(name)) { + this.access |= ACC_CONSTRUCTOR; + } + // updates maxLocals + int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --size; + } + maxLocals = size; + currentLocals = size; + // creates and visits the label for the first basic block + labels = new Label(); + labels.status |= Label.PUSHED; + visitLabel(labels); + } + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotationDefault() { + if (!ClassReader.ANNOTATIONS) { + return null; + } + annd = new ByteVector(); + return new AnnotationWriter(cw, false, annd, null, 0); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + if ("Ljava/lang/Synthetic;".equals(desc)) { + // workaround for a bug in javac with synthetic parameters + // see ClassReader.readParameterAnnotations + synthetics = Math.max(synthetics, parameter + 1); + return new AnnotationWriter(cw, false, bv, null, 0); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + if (panns == null) { + panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = panns[parameter]; + panns[parameter] = aw; + } else { + if (ipanns == null) { + ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = ipanns[parameter]; + ipanns[parameter] = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attr.isCodeAttribute()) { + attr.next = cattrs; + cattrs = attr; + } else { + attr.next = attrs; + attrs = attr; + } + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + if (!ClassReader.FRAMES || compute == FRAMES) { + return; + } + + if (type == Opcodes.F_NEW) { + currentLocals = nLocal; + startFrame(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + if (local[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) local[i]); + } else if (local[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) local[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) local[i]).position); + } + } + for (int i = 0; i < nStack; ++i) { + if (stack[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) stack[i]); + } else if (stack[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) stack[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) stack[i]).position); + } + } + endFrame(); + } else { + int delta; + if (stackMap == null) { + stackMap = new ByteVector(); + delta = code.length; + } else { + delta = code.length - previousFrameOffset - 1; + if (delta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = nLocal; + stackMap.putByte(FULL_FRAME) + .putShort(delta) + .putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + stackMap.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + writeFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED + nLocal) + .putShort(delta); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED - nLocal) + .putShort(delta); + break; + case Opcodes.F_SAME: + if (delta < 64) { + stackMap.putByte(delta); + } else { + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + } + break; + case Opcodes.F_SAME1: + if (delta < 64) { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + } else { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + } + writeFrameType(stack[0]); + break; + } + + previousFrameOffset = code.length; + ++frameCount; + } + + maxStack = Math.max(maxStack, nStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + // adds the instruction to the bytecode of the method + code.putByte(opcode); + // update currentBlock + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + } else { + // updates current and max stack sizes + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + // if opcode == ATHROW or xRETURN, ends current block (no successor) + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) + || opcode == Opcodes.ATHROW) + { + noSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // updates current and max stack sizes only for NEWARRAY + // (stack size variation = 0 for BIPUSH or SIPUSH) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, var, null, null); + } else { + // updates current and max stack sizes + if (opcode == Opcodes.RET) { + // no stack change, but end of current block (no successor) + currentBlock.status |= Label.RET; + // save 'stackSize' here for future use + // (see {@link #findSubroutineSuccessors}) + currentBlock.inputStackTop = stackSize; + noSuccessor(); + } else { // xLOAD or xSTORE + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + } + if (compute != NOTHING) { + // updates max locals + int n; + if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) + { + n = var + 2; + } else { + n = var + 1; + } + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var < 4 && opcode != Opcodes.RET) { + int opt; + if (opcode < Opcodes.ISTORE) { + /* ILOAD_0 */ + opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + /* ISTORE_0 */ + opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(opt); + } else if (var >= 256) { + code.putByte(196 /* WIDE */).put12(opcode, var); + } else { + code.put11(opcode, var); + } + if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + Item i = cw.newClassItem(type); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, code.length, cw, i); + } else if (opcode == Opcodes.NEW) { + // updates current and max stack sizes only if opcode == NEW + // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + Item i = cw.newFieldItem(owner, name, desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + int size; + // computes the stack size variation + char c = desc.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); + break; + // case Constants.PUTFIELD: + default: + size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); + break; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + Item i = cw.newMethodItem(owner, name, desc, itf); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + } else { + size = stackSize - (argSize >> 2) + (argSize & 0x03); + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (itf) { + if (argSize == 0) { + argSize = Type.getArgumentsAndReturnSizes(desc); + i.intVal = argSize; + } + code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); + } else { + code.put12(opcode, i.index); + } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String desc, + final Handle bsm, + final Object... bsmArgs) + { + Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.INVOKEDYNAMIC, i.index); + code.putShort(0); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + Label nextInsn = null; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + // 'label' is the target of a jump instruction + label.getFirst().status |= Label.TARGET; + // adds 'label' as a successor of this basic block + addSuccessor(Edge.NORMAL, label); + if (opcode != Opcodes.GOTO) { + // creates a Label for the next basic block + nextInsn = new Label(); + } + } else { + if (opcode == Opcodes.JSR) { + if ((label.status & Label.SUBROUTINE) == 0) { + label.status |= Label.SUBROUTINE; + ++subroutines; + } + currentBlock.status |= Label.JSR; + addSuccessor(stackSize + 1, label); + // creates a Label for the next basic block + nextInsn = new Label(); + /* + * note that, by construction in this method, a JSR block + * has at least two successors in the control flow graph: + * the first one leads the next instruction after the JSR, + * while the second one leads to the JSR target. + */ + } else { + // updates current stack size (max stack size unchanged + // because stack size variation always negative in this + // case) + stackSize += Frame.SIZE[opcode]; + addSuccessor(stackSize, label); + } + } + } + // adds the instruction to the bytecode of the method + if ((label.status & Label.RESOLVED) != 0 + && label.position - code.length < Short.MIN_VALUE) + { + /* + * case of a backward jump with an offset < -32768. In this case we + * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx + * <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is the + * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where <l'> + * designates the instruction just after the GOTO_W. + */ + if (opcode == Opcodes.GOTO) { + code.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + code.putByte(201); // JSR_W + } else { + // if the IF instruction is transformed into IFNOT GOTO_W the + // next instruction becomes the target of the IFNOT instruction + if (nextInsn != null) { + nextInsn.status |= Label.TARGET; + } + code.putByte(opcode <= 166 + ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + code.putShort(8); // jump offset + code.putByte(200); // GOTO_W + } + label.put(this, code, code.length - 1, true); + } else { + /* + * case of a backward jump with an offset >= -32768, or of a forward + * jump with, of course, an unknown offset. In these cases we store + * the offset in 2 bytes (which will be increased in + * resizeInstructions, if needed). + */ + code.putByte(opcode); + label.put(this, code, code.length - 1, false); + } + if (currentBlock != null) { + if (nextInsn != null) { + // if the jump instruction is not a GOTO, the next instruction + // is also a successor of this instruction. Calling visitLabel + // adds the label of this next instruction as a successor of the + // current block, and starts a new basic block + visitLabel(nextInsn); + } + if (opcode == Opcodes.GOTO) { + noSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // resolves previous forward references to label, if any + resize |= label.resolve(this, code.length, code.data); + // updates currentBlock + if ((label.status & Label.DEBUG) != 0) { + return; + } + if (compute == FRAMES) { + if (currentBlock != null) { + if (label.position == currentBlock.position) { + // successive labels, do not start a new basic block + currentBlock.status |= (label.status & Label.TARGET); + label.frame = currentBlock.frame; + return; + } + // ends current block (with one new successor) + addSuccessor(Edge.NORMAL, label); + } + // begins a new current block + currentBlock = label; + if (label.frame == null) { + label.frame = new Frame(); + label.frame.owner = label; + } + // updates the basic block list + if (previousBlock != null) { + if (label.position == previousBlock.position) { + previousBlock.status |= (label.status & Label.TARGET); + label.frame = previousBlock.frame; + currentBlock = previousBlock; + return; + } + previousBlock.successor = label; + } + previousBlock = label; + } else if (compute == MAXS) { + if (currentBlock != null) { + // ends current block (with one new successor) + currentBlock.outputStackMax = maxStackSize; + addSuccessor(stackSize, label); + } + // begins a new current block + currentBlock = label; + // resets the relative current and max stack sizes + stackSize = 0; + maxStackSize = 0; + // updates the basic block list + if (previousBlock != null) { + previousBlock.successor = label; + } + previousBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object cst) { + Item i = cw.newConstItem(cst); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); + } else { + int size; + // computes the stack size variation + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) + { + size = stackSize + 2; + } else { + size = stackSize + 1; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + int index = i.index; + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + code.put12(20 /* LDC2_W */, index); + } else if (index >= 256) { + code.put12(19 /* LDC_W */, index); + } else { + code.put11(Opcodes.LDC, index); + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.IINC, var, null, null); + } + } + if (compute != NOTHING) { + // updates max locals + int n = var + 1; + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(196 /* WIDE */) + .put12(Opcodes.IINC, var) + .putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.TABLESWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(min).putInt(max); + for (int i = 0; i < labels.length; ++i) { + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.LOOKUPSWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // adds current block successors + addSuccessor(Edge.NORMAL, dflt); + dflt.getFirst().status |= Label.TARGET; + for (int i = 0; i < labels.length; ++i) { + addSuccessor(Edge.NORMAL, labels[i]); + labels[i].getFirst().status |= Label.TARGET; + } + } else { + // updates current stack size (max stack size unchanged) + --stackSize; + // adds current block successors + addSuccessor(stackSize, dflt); + for (int i = 0; i < labels.length; ++i) { + addSuccessor(stackSize, labels[i]); + } + } + // ends current block + noSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + Item i = cw.newClassItem(desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); + } else { + // updates current stack size (max stack size unchanged because + // stack size variation always negative or null) + stackSize += 1 - dims; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + ++handlerCount; + Handler h = new Handler(); + h.start = start; + h.end = end; + h.handler = handler; + h.desc = type; + h.type = type != null ? cw.newClass(type) : 0; + if (lastHandler == null) { + firstHandler = h; + } else { + lastHandler.next = h; + } + lastHandler = h; + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + if (signature != null) { + if (localVarType == null) { + localVarType = new ByteVector(); + } + ++localVarTypeCount; + localVarType.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)) + .putShort(cw.newUTF8(signature)) + .putShort(index); + } + if (localVar == null) { + localVar = new ByteVector(); + } + ++localVarCount; + localVar.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)) + .putShort(cw.newUTF8(desc)) + .putShort(index); + if (compute != NOTHING) { + // updates max locals + char c = desc.charAt(0); + int n = index + (c == 'J' || c == 'D' ? 2 : 1); + if (n > maxLocals) { + maxLocals = n; + } + } + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumber == null) { + lineNumber = new ByteVector(); + } + ++lineNumberCount; + lineNumber.putShort(start.position); + lineNumber.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (ClassReader.FRAMES && compute == FRAMES) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start.getFirst(); + Label h = handler.handler.getFirst(); + Label e = handler.end.getFirst(); + // computes the kind of the edges to 'h' + String t = handler.desc == null + ? "java/lang/Throwable" + : handler.desc; + int kind = Frame.OBJECT | cw.addType(t); + // h is an exception handler + h.status |= Label.TARGET; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = kind; + b.successor = h; + // adds it to the successors of 'l' + b.next = l.successors; + l.successors = b; + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + // creates and visits the first (implicit) frame + Frame f = labels.frame; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, this.maxLocals); + visitFrame(f); + + /* + * fix point algorithm: mark the first basic block as 'changed' + * (i.e. put it in the 'changed' list) and, while there are changed + * basic blocks, choose one, mark it as unchanged, and update its + * successors (which can be changed in the process). + */ + int max = 0; + Label changed = labels; + while (changed != null) { + // removes a basic block from the list of changed basic blocks + Label l = changed; + changed = changed.next; + l.next = null; + f = l.frame; + // a reachable jump target must be stored in the stack map + if ((l.status & Label.TARGET) != 0) { + l.status |= Label.STORE; + } + // all visited labels are reachable, by definition + l.status |= Label.REACHABLE; + // updates the (absolute) maximum stack size + int blockMax = f.inputStack.length + l.outputStackMax; + if (blockMax > max) { + max = blockMax; + } + // updates the successors of the current basic block + Edge e = l.successors; + while (e != null) { + Label n = e.successor.getFirst(); + boolean change = f.merge(cw, n.frame, e.info); + if (change && n.next == null) { + // if n has changed and is not already in the 'changed' + // list, adds it to this list + n.next = changed; + changed = n; + } + e = e.next; + } + } + + // visits all the frames that must be stored in the stack map + Label l = labels; + while (l != null) { + f = l.frame; + if ((l.status & Label.STORE) != 0) { + visitFrame(f); + } + if ((l.status & Label.REACHABLE) == 0) { + // finds start and end of dead basic block + Label k = l.successor; + int start = l.position; + int end = (k == null ? code.length : k.position) - 1; + // if non empty basic block + if (end >= start) { + max = Math.max(max, 1); + // replaces instructions with NOP ... NOP ATHROW + for (int i = start; i < end; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[end] = (byte) Opcodes.ATHROW; + // emits a frame for this unreachable block + startFrame(start, 0, 1); + frame[frameIndex++] = Frame.OBJECT + | cw.addType("java/lang/Throwable"); + endFrame(); + // removes the start-end range from the exception handlers + firstHandler = Handler.remove(firstHandler, l, k); + } + } + l = l.successor; + } + + handler = firstHandler; + handlerCount = 0; + while (handler != null) { + handlerCount += 1; + handler = handler.next; + } + + this.maxStack = max; + } else if (compute == MAXS) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start; + Label h = handler.handler; + Label e = handler.end; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = Edge.EXCEPTION; + b.successor = h; + // adds it to the successors of 'l' + if ((l.status & Label.JSR) == 0) { + b.next = l.successors; + l.successors = b; + } else { + // if l is a JSR block, adds b after the first two edges + // to preserve the hypothesis about JSR block successors + // order (see {@link #visitJumpInsn}) + b.next = l.successors.next.next; + l.successors.next.next = b; + } + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + if (subroutines > 0) { + // completes the control flow graph with the RET successors + /* + * first step: finds the subroutines. This step determines, for + * each basic block, to which subroutine(s) it belongs. + */ + // finds the basic blocks that belong to the "main" subroutine + int id = 0; + labels.visitSubroutine(null, 1, subroutines); + // finds the basic blocks that belong to the real subroutines + Label l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + // if this subroutine has not been visited yet... + if ((subroutine.status & Label.VISITED) == 0) { + // ...assigns it a new id and finds its basic blocks + id += 1; + subroutine.visitSubroutine(null, (id / 32L) << 32 + | (1L << (id % 32)), subroutines); + } + } + l = l.successor; + } + // second step: finds the successors of RET blocks + l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + Label L = labels; + while (L != null) { + L.status &= ~Label.VISITED2; + L = L.successor; + } + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + subroutine.visitSubroutine(l, 0, subroutines); + } + l = l.successor; + } + } + + /* + * control flow analysis algorithm: while the block stack is not + * empty, pop a block from this stack, update the max stack size, + * compute the true (non relative) begin stack size of the + * successors of this block, and push these successors onto the + * stack (unless they have already been pushed onto the stack). + * Note: by hypothesis, the {@link Label#inputStackTop} of the + * blocks in the block stack are the true (non relative) beginning + * stack sizes of these blocks. + */ + int max = 0; + Label stack = labels; + while (stack != null) { + // pops a block from the stack + Label l = stack; + stack = stack.next; + // computes the true (non relative) max stack size of this block + int start = l.inputStackTop; + int blockMax = start + l.outputStackMax; + // updates the global max stack size + if (blockMax > max) { + max = blockMax; + } + // analyzes the successors of the block + Edge b = l.successors; + if ((l.status & Label.JSR) != 0) { + // ignores the first edge of JSR blocks (virtual successor) + b = b.next; + } + while (b != null) { + l = b.successor; + // if this successor has not already been pushed... + if ((l.status & Label.PUSHED) == 0) { + // computes its true beginning stack size... + l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start + + b.info; + // ...and pushes it onto the stack + l.status |= Label.PUSHED; + l.next = stack; + stack = l; + } + b = b.next; + } + } + this.maxStack = Math.max(maxStack, max); + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods: control flow analysis algorithm + // ------------------------------------------------------------------------ + + /** + * Adds a successor to the {@link #currentBlock currentBlock} block. + * + * @param info information about the control flow edge to be added. + * @param successor the successor block to be added to the current block. + */ + private void addSuccessor(final int info, final Label successor) { + // creates and initializes an Edge object... + Edge b = new Edge(); + b.info = info; + b.successor = successor; + // ...and adds it to the successor list of the currentBlock block + b.next = currentBlock.successors; + currentBlock.successors = b; + } + + /** + * Ends the current basic block. This method must be used in the case where + * the current basic block does not have any successor. + */ + private void noSuccessor() { + if (compute == FRAMES) { + Label l = new Label(); + l.frame = new Frame(); + l.frame.owner = l; + l.resolve(this, code.length, code.data); + previousBlock.successor = l; + previousBlock = l; + } else { + currentBlock.outputStackMax = maxStackSize; + } + currentBlock = null; + } + + // ------------------------------------------------------------------------ + // Utility methods: stack map frames + // ------------------------------------------------------------------------ + + /** + * Visits a frame that has been computed from scratch. + * + * @param f the frame that must be visited. + */ + private void visitFrame(final Frame f) { + int i, t; + int nTop = 0; + int nLocal = 0; + int nStack = 0; + int[] locals = f.inputLocals; + int[] stacks = f.inputStack; + // computes the number of locals (ignores TOP types that are just after + // a LONG or a DOUBLE, and all trailing TOP types) + for (i = 0; i < locals.length; ++i) { + t = locals[i]; + if (t == Frame.TOP) { + ++nTop; + } else { + nLocal += nTop + 1; + nTop = 0; + } + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // computes the stack size (ignores TOP types that are just after + // a LONG or a DOUBLE) + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + ++nStack; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // visits the frame and its content + startFrame(f.owner.position, nLocal, nStack); + for (i = 0; nLocal > 0; ++i, --nLocal) { + t = locals[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + endFrame(); + } + + /** + * Starts the visit of a stack map frame. + * + * @param offset the offset of the instruction to which the frame + * corresponds. + * @param nLocal the number of local variables in the frame. + * @param nStack the number of stack elements in the frame. + */ + private void startFrame(final int offset, final int nLocal, final int nStack) + { + int n = 3 + nLocal + nStack; + if (frame == null || frame.length < n) { + frame = new int[n]; + } + frame[0] = offset; + frame[1] = nLocal; + frame[2] = nStack; + frameIndex = 3; + } + + /** + * Checks if the visit of the current frame {@link #frame} is finished, and + * if yes, write it in the StackMapTable attribute. + */ + private void endFrame() { + if (previousFrame != null) { // do not write the first frame + if (stackMap == null) { + stackMap = new ByteVector(); + } + writeFrame(); + ++frameCount; + } + previousFrame = frame; + frame = null; + } + + /** + * Compress and writes the current frame {@link #frame} in the StackMapTable + * attribute. + */ + private void writeFrame() { + int clocalsSize = frame[1]; + int cstackSize = frame[2]; + if ((cw.version & 0xFFFF) < Opcodes.V1_6) { + stackMap.putShort(frame[0]).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + return; + } + int localsSize = previousFrame[1]; + int type = FULL_FRAME; + int k = 0; + int delta; + if (frameCount == 0) { + delta = frame[0]; + } else { + delta = frame[0] - previousFrame[0] - 1; + } + if (cstackSize == 0) { + k = clocalsSize - localsSize; + switch (k) { + case -3: + case -2: + case -1: + type = CHOP_FRAME; + localsSize = clocalsSize; + break; + case 0: + type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = APPEND_FRAME; + break; + } + } else if (clocalsSize == localsSize && cstackSize == 1) { + type = delta < 63 + ? SAME_LOCALS_1_STACK_ITEM_FRAME + : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != FULL_FRAME) { + // verify if locals are the same + int l = 3; + for (int j = 0; j < localsSize; j++) { + if (frame[l] != previousFrame[l]) { + type = FULL_FRAME; + break; + } + l++; + } + } + switch (type) { + case SAME_FRAME: + stackMap.putByte(delta); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_FRAME_EXTENDED: + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + break; + case CHOP_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + break; + case APPEND_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + writeFrameTypes(3 + localsSize, 3 + clocalsSize); + break; + // case FULL_FRAME: + default: + stackMap.putByte(FULL_FRAME) + .putShort(delta) + .putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + } + } + + /** + * Writes some types of the current frame {@link #frame} into the + * StackMapTableAttribute. This method converts types from the format used + * in {@link Label} to the format used in StackMapTable attributes. In + * particular, it converts type table indexes to constant pool indexes. + * + * @param start index of the first type in {@link #frame} to write. + * @param end index of last type in {@link #frame} to write (exclusive). + */ + private void writeFrameTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + int t = frame[i]; + int d = t & Frame.DIM; + if (d == 0) { + int v = t & Frame.BASE_VALUE; + switch (t & Frame.BASE_KIND) { + case Frame.OBJECT: + stackMap.putByte(7) + .putShort(cw.newClass(cw.typeTable[v].strVal1)); + break; + case Frame.UNINITIALIZED: + stackMap.putByte(8).putShort(cw.typeTable[v].intVal); + break; + default: + stackMap.putByte(v); + } + } else { + StringBuffer buf = new StringBuffer(); + d >>= 28; + while (d-- > 0) { + buf.append('['); + } + if ((t & Frame.BASE_KIND) == Frame.OBJECT) { + buf.append('L'); + buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + buf.append(';'); + } else { + switch (t & 0xF) { + case 1: + buf.append('I'); + break; + case 2: + buf.append('F'); + break; + case 3: + buf.append('D'); + break; + case 9: + buf.append('Z'); + break; + case 10: + buf.append('B'); + break; + case 11: + buf.append('C'); + break; + case 12: + buf.append('S'); + break; + default: + buf.append('J'); + } + } + stackMap.putByte(7).putShort(cw.newClass(buf.toString())); + } + } + } + + private void writeFrameType(final Object type) { + if (type instanceof String) { + stackMap.putByte(7).putShort(cw.newClass((String) type)); + } else if (type instanceof Integer) { + stackMap.putByte(((Integer) type).intValue()); + } else { + stackMap.putByte(8).putShort(((Label) type).position); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: dump bytecode array + // ------------------------------------------------------------------------ + + /** + * Returns the size of the bytecode of this method. + * + * @return the size of the bytecode of this method. + */ + final int getSize() { + if (classReaderOffset != 0) { + return 6 + classReaderLength; + } + if (resize) { + // replaces the temporary jump opcodes introduced by Label.resolve. + if (ClassReader.RESIZE) { + resizeInstructions(); + } else { + throw new RuntimeException("Method code too large!"); + } + } + int size = 8; + if (code.length > 0) { + if (code.length > 65536) { + throw new RuntimeException("Method code too large!"); + } + cw.newUTF8("Code"); + size += 18 + code.length + 8 * handlerCount; + if (localVar != null) { + cw.newUTF8("LocalVariableTable"); + size += 8 + localVar.length; + } + if (localVarType != null) { + cw.newUTF8("LocalVariableTypeTable"); + size += 8 + localVarType.length; + } + if (lineNumber != null) { + cw.newUTF8("LineNumberTable"); + size += 8 + lineNumber.length; + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + cw.newUTF8(zip ? "StackMapTable" : "StackMap"); + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, + code.data, + code.length, + maxStack, + maxLocals); + } + } + if (exceptionCount > 0) { + cw.newUTF8("Exceptions"); + size += 8 + 2 * exceptionCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + cw.newUTF8("Synthetic"); + size += 6; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != null) { + cw.newUTF8("Signature"); + cw.newUTF8(signature); + size += 8; + } + if (ClassReader.ANNOTATIONS && annd != null) { + cw.newUTF8("AnnotationDefault"); + size += 6 + annd.length; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && panns != null) { + cw.newUTF8("RuntimeVisibleParameterAnnotations"); + size += 7 + 2 * (panns.length - synthetics); + for (int i = panns.length - 1; i >= synthetics; --i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + cw.newUTF8("RuntimeInvisibleParameterAnnotations"); + size += 7 + 2 * (ipanns.length - synthetics); + for (int i = ipanns.length - 1; i >= synthetics; --i) { + size += ipanns[i] == null ? 0 : ipanns[i].getSize(); + } + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the bytecode of this method in the given byte vector. + * + * @param out the byte vector into which the bytecode of this method must be + * copied. + */ + final void put(final ByteVector out) { + int mask = Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / (ClassWriter.ACC_SYNTHETIC_ATTRIBUTE / Opcodes.ACC_SYNTHETIC)); + out.putShort(access & ~mask).putShort(name).putShort(desc); + if (classReaderOffset != 0) { + out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); + return; + } + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (exceptionCount > 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + ++attributeCount; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && annd != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && panns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (code.length > 0) { + int size = 12 + code.length + 8 * handlerCount; + if (localVar != null) { + size += 8 + localVar.length; + } + if (localVarType != null) { + size += 8 + localVarType.length; + } + if (lineNumber != null) { + size += 8 + lineNumber.length; + } + if (stackMap != null) { + size += 8 + stackMap.length; + } + if (cattrs != null) { + size += cattrs.getSize(cw, + code.data, + code.length, + maxStack, + maxLocals); + } + out.putShort(cw.newUTF8("Code")).putInt(size); + out.putShort(maxStack).putShort(maxLocals); + out.putInt(code.length).putByteArray(code.data, 0, code.length); + out.putShort(handlerCount); + if (handlerCount > 0) { + Handler h = firstHandler; + while (h != null) { + out.putShort(h.start.position) + .putShort(h.end.position) + .putShort(h.handler.position) + .putShort(h.type); + h = h.next; + } + } + attributeCount = 0; + if (localVar != null) { + ++attributeCount; + } + if (localVarType != null) { + ++attributeCount; + } + if (lineNumber != null) { + ++attributeCount; + } + if (stackMap != null) { + ++attributeCount; + } + if (cattrs != null) { + attributeCount += cattrs.getCount(); + } + out.putShort(attributeCount); + if (localVar != null) { + out.putShort(cw.newUTF8("LocalVariableTable")); + out.putInt(localVar.length + 2).putShort(localVarCount); + out.putByteArray(localVar.data, 0, localVar.length); + } + if (localVarType != null) { + out.putShort(cw.newUTF8("LocalVariableTypeTable")); + out.putInt(localVarType.length + 2).putShort(localVarTypeCount); + out.putByteArray(localVarType.data, 0, localVarType.length); + } + if (lineNumber != null) { + out.putShort(cw.newUTF8("LineNumberTable")); + out.putInt(lineNumber.length + 2).putShort(lineNumberCount); + out.putByteArray(lineNumber.data, 0, lineNumber.length); + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); + out.putInt(stackMap.length + 2).putShort(frameCount); + out.putByteArray(stackMap.data, 0, stackMap.length); + } + if (cattrs != null) { + cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); + } + } + if (exceptionCount > 0) { + out.putShort(cw.newUTF8("Exceptions")) + .putInt(2 * exceptionCount + 2); + out.putShort(exceptionCount); + for (int i = 0; i < exceptionCount; ++i) { + out.putShort(exceptions[i]); + } + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0 + && ((cw.version & 0xFFFF) < Opcodes.V1_5 || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0)) + { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != null) { + out.putShort(cw.newUTF8("Signature")) + .putInt(2) + .putShort(cw.newUTF8(signature)); + } + if (ClassReader.ANNOTATIONS && annd != null) { + out.putShort(cw.newUTF8("AnnotationDefault")); + out.putInt(annd.length); + out.putByteArray(annd.data, 0, annd.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && panns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); + AnnotationWriter.put(panns, synthetics, out); + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); + AnnotationWriter.put(ipanns, synthetics, out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) + // ------------------------------------------------------------------------ + + /** + * Resizes and replaces the temporary instructions inserted by + * {@link Label#resolve} for wide forward jumps, while keeping jump offsets + * and instruction addresses consistent. This may require to resize other + * existing instructions, or even to introduce new instructions: for + * example, increasing the size of an instruction by 2 at the middle of a + * method can increases the offset of an IFEQ instruction from 32766 to + * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W + * 32765. This, in turn, may require to increase the size of another jump + * instruction, and so on... All these operations are handled automatically + * by this method. <p> <i>This method must be called after all the method + * that is being built has been visited</i>. In particular, the + * {@link Label Label} objects used to construct the method are no longer + * valid after this method has been called. + */ + private void resizeInstructions() { + byte[] b = code.data; // bytecode of the method + int u, v, label; // indexes in b + int i, j; // loop indexes + /* + * 1st step: As explained above, resizing an instruction may require to + * resize another one, which may require to resize yet another one, and + * so on. The first step of the algorithm consists in finding all the + * instructions that need to be resized, without modifying the code. + * This is done by the following "fix point" algorithm: + * + * Parse the code to find the jump instructions whose offset will need + * more than 2 bytes to be stored (the future offset is computed from + * the current offset and from the number of bytes that will be inserted + * or removed between the source and target instructions). For each such + * instruction, adds an entry in (a copy of) the indexes and sizes + * arrays (if this has not already been done in a previous iteration!). + * + * If at least one entry has been added during the previous step, go + * back to the beginning, otherwise stop. + * + * In fact the real algorithm is complicated by the fact that the size + * of TABLESWITCH and LOOKUPSWITCH instructions depends on their + * position in the bytecode (because of padding). In order to ensure the + * convergence of the algorithm, the number of bytes to be added or + * removed from these instructions is over estimated during the previous + * loop, and computed exactly only after the loop is finished (this + * requires another pass to parse the bytecode of the method). + */ + int[] allIndexes = new int[0]; // copy of indexes + int[] allSizes = new int[0]; // copy of sizes + boolean[] resize; // instructions to be resized + int newOffset; // future offset of a jump instruction + + resize = new boolean[code.length]; + + // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done + int state = 3; + do { + if (state == 3) { + state = 2; + } + u = 0; + while (u < b.length) { + int opcode = b[u] & 0xFF; // opcode of current instruction + int insert = 0; // bytes to be added after this instruction + + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // converts temporary opcodes 202 to 217, 218 and + // 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (newOffset < Short.MIN_VALUE + || newOffset > Short.MAX_VALUE) + { + if (!resize[u]) { + if (opcode == Opcodes.GOTO + || opcode == Opcodes.JSR) + { + // two additional bytes will be required to + // replace this GOTO or JSR instruction with + // a GOTO_W or a JSR_W + insert = 2; + } else { + // five additional bytes will be required to + // replace this IFxxx <l> instruction with + // IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx + // is the "opposite" opcode of IFxxx (i.e., + // IFNE for IFEQ) and where <l'> designates + // the instruction just after the GOTO_W. + insert = 5; + } + resize[u] = true; + } + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + u += 5; + break; + case ClassWriter.TABL_INSN: + if (state == 1) { + // true number of bytes to be added (or removed) + // from this instruction = (future number of padding + // bytes - current number of padding byte) - + // previously over estimated variation = + // = ((3 - newOffset%4) - (3 - u%4)) - u%4 + // = (-newOffset%4 + u%4) - u%4 + // = -(newOffset & 3) + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // over estimation of the number of bytes to be + // added to this instruction = 3 - current number + // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; + break; + case ClassWriter.LOOK_INSN: + if (state == 1) { + // like TABL_INSN + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // like TABL_INSN + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 8 * readInt(b, u + 4) + 8; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case ClassWriter.MANA_INSN: + default: + u += 4; + break; + } + if (insert != 0) { + // adds a new (u, insert) entry in the allIndexes and + // allSizes arrays + int[] newIndexes = new int[allIndexes.length + 1]; + int[] newSizes = new int[allSizes.length + 1]; + System.arraycopy(allIndexes, + 0, + newIndexes, + 0, + allIndexes.length); + System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); + newIndexes[allIndexes.length] = u; + newSizes[allSizes.length] = insert; + allIndexes = newIndexes; + allSizes = newSizes; + if (insert > 0) { + state = 3; + } + } + } + if (state < 3) { + --state; + } + } while (state != 0); + + // 2nd step: + // copies the bytecode of the method into a new bytevector, updates the + // offsets, and inserts (or removes) bytes as requested. + + ByteVector newCode = new ByteVector(code.length); + + u = 0; + while (u < code.length) { + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + newCode.putByte(opcode); + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (resize[u]) { + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where <l'> designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO) { + newCode.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + newCode.putByte(201); // JSR_W + } else { + newCode.putByte(opcode <= 166 + ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + newCode.putShort(8); // jump offset + newCode.putByte(200); // GOTO_W + // newOffset now computed from start of GOTO_W + newOffset -= 3; + } + newCode.putInt(newOffset); + } else { + newCode.putByte(opcode); + newCode.putShort(newOffset); + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + label = u + readInt(b, u + 1); + newOffset = getNewOffset(allIndexes, allSizes, u, label); + newCode.putByte(opcode); + newCode.putInt(newOffset); + u += 5; + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.TABLESWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + j = readInt(b, u) - j + 1; + u += 4; + newCode.putInt(readInt(b, u - 4)); + for (; j > 0; --j) { + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.LOOKUPSWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + for (; j > 0; --j) { + newCode.putInt(readInt(b, u)); + u += 4; + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + newCode.putByteArray(b, u, 6); + u += 6; + } else { + newCode.putByteArray(b, u, 4); + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + newCode.putByteArray(b, u, 2); + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + newCode.putByteArray(b, u, 3); + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + newCode.putByteArray(b, u, 5); + u += 5; + break; + // case MANA_INSN: + default: + newCode.putByteArray(b, u, 4); + u += 4; + break; + } + } + + // recomputes the stack map frames + if (frameCount > 0) { + if (compute == FRAMES) { + frameCount = 0; + stackMap = null; + previousFrame = null; + frame = null; + Frame f = new Frame(); + f.owner = labels; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, maxLocals); + visitFrame(f); + Label l = labels; + while (l != null) { + /* + * here we need the original label position. getNewOffset + * must therefore never have been called for this label. + */ + u = l.position - 3; + if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) + { + getNewOffset(allIndexes, allSizes, l); + // TODO update offsets in UNINITIALIZED values + visitFrame(l.frame); + } + l = l.successor; + } + } else { + /* + * Resizing an existing stack map frame table is really hard. + * Not only the table must be parsed to update the offets, but + * new frames may be needed for jump instructions that were + * inserted by this method. And updating the offsets or + * inserting frames can change the format of the following + * frames, in case of packed frames. In practice the whole table + * must be recomputed. For this the frames are marked as + * potentially invalid. This will cause the whole class to be + * reread and rewritten with the COMPUTE_FRAMES option (see the + * ClassWriter.toByteArray method). This is not very efficient + * but is much easier and requires much less code than any other + * method I can think of. + */ + cw.invalidFrames = true; + } + } + // updates the exception handler block labels + Handler h = firstHandler; + while (h != null) { + getNewOffset(allIndexes, allSizes, h.start); + getNewOffset(allIndexes, allSizes, h.end); + getNewOffset(allIndexes, allSizes, h.handler); + h = h.next; + } + // updates the instructions addresses in the + // local var and line number tables + for (i = 0; i < 2; ++i) { + ByteVector bv = i == 0 ? localVar : localVarType; + if (bv != null) { + b = bv.data; + u = 0; + while (u < bv.length) { + label = readUnsignedShort(b, u); + newOffset = getNewOffset(allIndexes, allSizes, 0, label); + writeShort(b, u, newOffset); + label += readUnsignedShort(b, u + 2); + newOffset = getNewOffset(allIndexes, allSizes, 0, label) + - newOffset; + writeShort(b, u + 2, newOffset); + u += 10; + } + } + } + if (lineNumber != null) { + b = lineNumber.data; + u = 0; + while (u < lineNumber.length) { + writeShort(b, u, getNewOffset(allIndexes, + allSizes, + 0, + readUnsignedShort(b, u))); + u += 4; + } + } + // updates the labels of the other attributes + Attribute attr = cattrs; + while (attr != null) { + Label[] labels = attr.getLabels(); + if (labels != null) { + for (i = labels.length - 1; i >= 0; --i) { + getNewOffset(allIndexes, allSizes, labels[i]); + } + } + attr = attr.next; + } + + // replaces old bytecodes with new ones + code = newCode; + } + + /** + * Reads an unsigned short value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static int readUnsignedShort(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static short readShort(final byte[] b, final int index) { + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in the given byte array. + * + * @param b a byte array. + * @param index the start index of the value to be read. + * @return the read value. + */ + static int readInt(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Writes a short value in the given byte array. + * + * @param b a byte array. + * @param index where the first byte of the short value must be written. + * @param s the value to be written in the given byte array. + */ + static void writeShort(final byte[] b, final int index, final int s) { + b[index] = (byte) (s >>> 8); + b[index + 1] = (byte) s; + } + + /** + * Computes the future value of a bytecode offset. <p> Note: it is possible + * to have several entries for the same instruction in the <tt>indexes</tt> + * and <tt>sizes</tt>: two entries (index=a,size=b) and (index=a,size=b') + * are equivalent to a single entry (index=a,size=b+b'). + * + * @param indexes current positions of the instructions to be resized. Each + * instruction must be designated by the index of its <i>last</i> + * byte, plus one (or, in other words, by the index of the <i>first</i> + * byte of the <i>next</i> instruction). + * @param sizes the number of bytes to be <i>added</i> to the above + * instructions. More precisely, for each i < <tt>len</tt>, + * <tt>sizes</tt>[i] bytes will be added at the end of the + * instruction designated by <tt>indexes</tt>[i] or, if + * <tt>sizes</tt>[i] is negative, the <i>last</i> |<tt>sizes[i]</tt>| + * bytes of the instruction will be removed (the instruction size + * <i>must not</i> become negative or null). + * @param begin index of the first byte of the source instruction. + * @param end index of the first byte of the target instruction. + * @return the future value of the given bytecode offset. + */ + static int getNewOffset( + final int[] indexes, + final int[] sizes, + final int begin, + final int end) + { + int offset = end - begin; + for (int i = 0; i < indexes.length; ++i) { + if (begin < indexes[i] && indexes[i] <= end) { + // forward jump + offset += sizes[i]; + } else if (end < indexes[i] && indexes[i] <= begin) { + // backward jump + offset -= sizes[i]; + } + } + return offset; + } + + /** + * Updates the offset of the given label. + * + * @param indexes current positions of the instructions to be resized. Each + * instruction must be designated by the index of its <i>last</i> + * byte, plus one (or, in other words, by the index of the <i>first</i> + * byte of the <i>next</i> instruction). + * @param sizes the number of bytes to be <i>added</i> to the above + * instructions. More precisely, for each i < <tt>len</tt>, + * <tt>sizes</tt>[i] bytes will be added at the end of the + * instruction designated by <tt>indexes</tt>[i] or, if + * <tt>sizes</tt>[i] is negative, the <i>last</i> |<tt>sizes[i]</tt>| + * bytes of the instruction will be removed (the instruction size + * <i>must not</i> become negative or null). + * @param label the label whose offset must be updated. + */ + static void getNewOffset( + final int[] indexes, + final int[] sizes, + final Label label) + { + if ((label.status & Label.RESIZED) == 0) { + label.position = getNewOffset(indexes, sizes, 0, label.position); + label.status |= Label.RESIZED; + } + } +} diff --git a/src/compiler/scala/tools/asm/Opcodes.java b/src/compiler/scala/tools/asm/Opcodes.java new file mode 100644 index 0000000000..809e5ae590 --- /dev/null +++ b/src/compiler/scala/tools/asm/Opcodes.java @@ -0,0 +1,358 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +/** + * Defines the JVM opcodes, access flags and array type codes. This interface + * does not define all the JVM opcodes because some opcodes are automatically + * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced + * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n + * opcodes are therefore not defined in this interface. Likewise for LDC, + * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and + * JSR_W. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface Opcodes { + + // ASM API versions + + int ASM4 = 4 << 16 | 0 << 8 | 0; + + // versions + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + + // access flags + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + + // ASM specific pseudo access flags + + int ACC_DEPRECATED = 0x20000; // class, field, method + + // types for NEWARRAY + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // tags for Handle + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // stack map frame types + + /** + * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. + */ + int F_NEW = -1; + + /** + * Represents a compressed frame with complete frame data. + */ + int F_FULL = 0; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that additional 1-3 locals are defined, and + * with an empty stack. + */ + int F_APPEND = 1; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that the last 1-3 locals are absent and with + * an empty stack. + */ + int F_CHOP = 2; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with a single value on the stack. + */ + int F_SAME1 = 4; + + Integer TOP = new Integer(0); + Integer INTEGER = new Integer(1); + Integer FLOAT = new Integer(2); + Integer DOUBLE = new Integer(3); + Integer LONG = new Integer(4); + Integer NULL = new Integer(5); + Integer UNINITIALIZED_THIS = new Integer(6); + + // opcodes // visit method (- = idem) + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + // int LDC_W = 19; // - + // int LDC2_W = 20; // - + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + // int ILOAD_0 = 26; // - + // int ILOAD_1 = 27; // - + // int ILOAD_2 = 28; // - + // int ILOAD_3 = 29; // - + // int LLOAD_0 = 30; // - + // int LLOAD_1 = 31; // - + // int LLOAD_2 = 32; // - + // int LLOAD_3 = 33; // - + // int FLOAD_0 = 34; // - + // int FLOAD_1 = 35; // - + // int FLOAD_2 = 36; // - + // int FLOAD_3 = 37; // - + // int DLOAD_0 = 38; // - + // int DLOAD_1 = 39; // - + // int DLOAD_2 = 40; // - + // int DLOAD_3 = 41; // - + // int ALOAD_0 = 42; // - + // int ALOAD_1 = 43; // - + // int ALOAD_2 = 44; // - + // int ALOAD_3 = 45; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + // int ISTORE_0 = 59; // - + // int ISTORE_1 = 60; // - + // int ISTORE_2 = 61; // - + // int ISTORE_3 = 62; // - + // int LSTORE_0 = 63; // - + // int LSTORE_1 = 64; // - + // int LSTORE_2 = 65; // - + // int LSTORE_3 = 66; // - + // int FSTORE_0 = 67; // - + // int FSTORE_1 = 68; // - + // int FSTORE_2 = 69; // - + // int FSTORE_3 = 70; // - + // int DSTORE_0 = 71; // - + // int DSTORE_1 = 72; // - + // int DSTORE_2 = 73; // - + // int DSTORE_3 = 74; // - + // int ASTORE_0 = 75; // - + // int ASTORE_1 = 76; // - + // int ASTORE_2 = 77; // - + // int ASTORE_3 = 78; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + // int WIDE = 196; // NOT VISITED + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - + // int GOTO_W = 200; // - + // int JSR_W = 201; // - +} diff --git a/src/compiler/scala/tools/asm/Type.java b/src/compiler/scala/tools/asm/Type.java new file mode 100644 index 0000000000..bf1107182a --- /dev/null +++ b/src/compiler/scala/tools/asm/Type.java @@ -0,0 +1,865 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to + * manipulate type and method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public class Type { + + /** + * The sort of the <tt>void</tt> type. See {@link #getSort getSort}. + */ + public static final int VOID = 0; + + /** + * The sort of the <tt>boolean</tt> type. See {@link #getSort getSort}. + */ + public static final int BOOLEAN = 1; + + /** + * The sort of the <tt>char</tt> type. See {@link #getSort getSort}. + */ + public static final int CHAR = 2; + + /** + * The sort of the <tt>byte</tt> type. See {@link #getSort getSort}. + */ + public static final int BYTE = 3; + + /** + * The sort of the <tt>short</tt> type. See {@link #getSort getSort}. + */ + public static final int SHORT = 4; + + /** + * The sort of the <tt>int</tt> type. See {@link #getSort getSort}. + */ + public static final int INT = 5; + + /** + * The sort of the <tt>float</tt> type. See {@link #getSort getSort}. + */ + public static final int FLOAT = 6; + + /** + * The sort of the <tt>long</tt> type. See {@link #getSort getSort}. + */ + public static final int LONG = 7; + + /** + * The sort of the <tt>double</tt> type. See {@link #getSort getSort}. + */ + public static final int DOUBLE = 8; + + /** + * The sort of array reference types. See {@link #getSort getSort}. + */ + public static final int ARRAY = 9; + + /** + * The sort of object reference types. See {@link #getSort getSort}. + */ + public static final int OBJECT = 10; + + /** + * The sort of method types. See {@link #getSort getSort}. + */ + public static final int METHOD = 11; + + /** + * The <tt>void</tt> type. + */ + public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) + | (5 << 16) | (0 << 8) | 0, 1); + + /** + * The <tt>boolean</tt> type. + */ + public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The <tt>char</tt> type. + */ + public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) + | (0 << 16) | (6 << 8) | 1, 1); + + /** + * The <tt>byte</tt> type. + */ + public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The <tt>short</tt> type. + */ + public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) + | (0 << 16) | (7 << 8) | 1, 1); + + /** + * The <tt>int</tt> type. + */ + public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) + | (0 << 16) | (0 << 8) | 1, 1); + + /** + * The <tt>float</tt> type. + */ + public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) + | (2 << 16) | (2 << 8) | 1, 1); + + /** + * The <tt>long</tt> type. + */ + public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) + | (1 << 16) | (1 << 8) | 2, 1); + + /** + * The <tt>double</tt> type. + */ + public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) + | (3 << 16) | (3 << 8) | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private final char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private final int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a reference type. + * + * @param sort the sort of the reference type to be constructed. + * @param buf a buffer containing the descriptor of the previous type. + * @param off the offset of this descriptor in the previous buffer. + * @param len the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) + { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param typeDescriptor a field or method type descriptor. + * @return the Java type corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java type corresponding to the given internal name. + * + * @param internalName an internal name. + * @return the Java type corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + char[] buf = internalName.toCharArray(); + return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); + } + + /** + * Returns the Java type corresponding to the given method descriptor. + * Equivalent to <code>Type.getType(methodDescriptor)</code>. + * + * @param methodDescriptor a method descriptor. + * @return the Java type corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return getType(methodDescriptor.toCharArray(), 0); + } + + /** + * 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. + */ + public static Type getMethodType(final Type returnType, final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the Java type corresponding to the given class. + * + * @param c a class. + * @return the Java type corresponding to the given class. + */ + public static Type getType(final Class<?> c) { + if (c.isPrimitive()) { + if (c == Integer.TYPE) { + return INT_TYPE; + } else if (c == Void.TYPE) { + return VOID_TYPE; + } else if (c == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (c == Byte.TYPE) { + return BYTE_TYPE; + } else if (c == Character.TYPE) { + return CHAR_TYPE; + } else if (c == Short.TYPE) { + return SHORT_TYPE; + } else if (c == Double.TYPE) { + return DOUBLE_TYPE; + } else if (c == Float.TYPE) { + return FLOAT_TYPE; + } else /* if (c == Long.TYPE) */{ + return LONG_TYPE; + } + } else { + return getType(getDescriptor(c)); + } + } + + /** + * Returns the Java method type corresponding to the given constructor. + * + * @param c a {@link Constructor Constructor} object. + * @return the Java method type corresponding to the given constructor. + */ + public static Type getType(final Constructor<?> c) { + return getType(getConstructorDescriptor(c)); + } + + /** + * Returns the Java method type corresponding to the given method. + * + * @param m a {@link Method Method} object. + * @return the Java method type corresponding to the given method. + */ + public static Type getType(final Method m) { + return getType(getMethodDescriptor(m)); + } + + /** + * 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. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method. + * + * @param method a method. + * @return the Java types corresponding to the argument types of the given + * method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class<?>[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * 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. + */ + public static Type getReturnType(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + return getType(buf, methodDescriptor.indexOf(')') + 1); + } + + /** + * Returns the Java type corresponding to the return type of the given + * method. + * + * @param method a method. + * @return the Java type corresponding to the return type of the given + * method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param desc the descriptor of a method. + * @return the size of the arguments of the method (plus one for the + * implicit this argument), argSize, and the size of its return + * value, retSize, packed into a single int i = + * <tt>(argSize << 2) | retSize</tt> (argSize is therefore equal + * to <tt>i >> 2</tt>, and retSize to <tt>i & 0x03</tt>). + */ + public static int getArgumentsAndReturnSizes(final String desc) { + int n = 1; + int c = 1; + while (true) { + char car = desc.charAt(c++); + if (car == ')') { + car = desc.charAt(c); + return n << 2 + | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1)); + } else if (car == 'L') { + while (desc.charAt(c++) != ';') { + } + n += 1; + } else if (car == '[') { + while ((car = desc.charAt(c)) == '[') { + ++c; + } + if (car == 'D' || car == 'J') { + n -= 1; + } + } else if (car == 'D' || car == 'J') { + n += 2; + } else { + n += 1; + } + } + } + + /** + * Returns the Java type corresponding to the given type descriptor. For + * method descriptors, buf is supposed to contain nothing more than the + * descriptor itself. + * + * @param buf a buffer containing a type descriptor. + * @param off the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + case 'L': + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + // case '(': + default: + return new Type(METHOD, buf, 0, buf.length); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the sort of this Java type. + * + * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, + * {@link #CHAR CHAR}, {@link #BYTE BYTE}, {@link #SHORT SHORT}, + * {@link #INT INT}, {@link #FLOAT FLOAT}, {@link #LONG LONG}, + * {@link #DOUBLE DOUBLE}, {@link #ARRAY ARRAY}, + * {@link #OBJECT OBJECT} or {@link #METHOD METHOD}. + */ + public int getSort() { + return sort; + } + + /** + * 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. + */ + public int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements 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. + */ + public Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the binary name of the class corresponding to this type. This + * method must not be used on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuffer b = new StringBuffer(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + b.append("[]"); + } + return b.toString(); + case OBJECT: + return new String(buf, off, len).replace('/', '.'); + default: + return null; + } + } + + /** + * 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. + */ + public String getInternalName() { + return new String(buf, off, len); + } + + /** + * 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. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * 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. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the size of the arguments and of the return value of methods of + * this type. This method should only be used for method types. + * + * @return the size of the arguments (plus one for the implicit this + * argument), argSize, and the size of the return value, retSize, + * packed into a single int i = <tt>(argSize << 2) | retSize</tt> + * (argSize is therefore equal to <tt>i >> 2</tt>, and retSize to + * <tt>i & 0x03</tt>). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /** + * Returns the descriptor corresponding to this Java type. + * + * @return the descriptor corresponding to this Java type. + */ + public String getDescriptor() { + StringBuffer buf = new StringBuffer(); + getDescriptor(buf); + return buf.toString(); + } + + /** + * Returns the descriptor 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 descriptor corresponding to the given argument and return + * types. + */ + public static String getMethodDescriptor( + final Type returnType, + final Type... argumentTypes) + { + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].getDescriptor(buf); + } + buf.append(')'); + returnType.getDescriptor(buf); + return 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. + */ + private void getDescriptor(final StringBuffer buf) { + if (this.buf == null) { + // descriptor is in byte 3 of 'off' for primitive types (buf == null) + buf.append((char) ((off & 0xFF000000) >>> 24)); + } else if (sort == OBJECT) { + buf.append('L'); + buf.append(this.buf, off, len); + buf.append(';'); + } else { // sort == ARRAY || sort == METHOD + buf.append(this.buf, off, len); + } + } + + // ------------------------------------------------------------------------ + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ------------------------------------------------------------------------ + + /** + * Returns the internal name of the given class. The internal name of a + * class is its fully qualified name, as returned by Class.getName(), where + * '.' are replaced by '/'. + * + * @param c an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class<?> c) { + return c.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given Java type. + * + * @param c an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class<?> c) { + StringBuffer buf = new StringBuffer(); + getDescriptor(buf, c); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param c a {@link Constructor Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor<?> c) { + Class<?>[] parameters = c.getParameterTypes(); + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + return buf.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param m a {@link Method Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method m) { + Class<?>[] parameters = m.getParameterTypes(); + StringBuffer buf = new StringBuffer(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + buf.append(')'); + getDescriptor(buf, m.getReturnType()); + return buf.toString(); + } + + /** + * Appends the descriptor of the given class to the given string buffer. + * + * @param buf the string buffer to which the descriptor must be appended. + * @param c the class whose descriptor must be computed. + */ + private static void getDescriptor(final StringBuffer buf, final Class<?> c) { + Class<?> d = c; + while (true) { + if (d.isPrimitive()) { + char car; + if (d == Integer.TYPE) { + car = 'I'; + } else if (d == Void.TYPE) { + car = 'V'; + } else if (d == Boolean.TYPE) { + car = 'Z'; + } else if (d == Byte.TYPE) { + car = 'B'; + } else if (d == Character.TYPE) { + car = 'C'; + } else if (d == Short.TYPE) { + car = 'S'; + } else if (d == Double.TYPE) { + car = 'D'; + } else if (d == Float.TYPE) { + car = 'F'; + } else /* if (d == Long.TYPE) */{ + car = 'J'; + } + buf.append(car); + return; + } else if (d.isArray()) { + buf.append('['); + d = d.getComponentType(); + } else { + buf.append('L'); + String name = d.getName(); + int len = name.length(); + for (int i = 0; i < len; ++i) { + char car = name.charAt(i); + buf.append(car == '.' ? '/' : car); + } + buf.append(';'); + return; + } + } + } + + // ------------------------------------------------------------------------ + // 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. + */ + public int getSize() { + // the size is in byte 0 of 'off' for primitive types (buf == null) + return buf == null ? (off & 0xFF) : 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. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 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. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Type)) { + return false; + } + Type t = (Type) o; + if (sort != t.sort) { + return false; + } + if (sort >= ARRAY) { + if (len != t.len) { + return false; + } + for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { + if (buf[i] != t.buf[j]) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hc = 13 * sort; + if (sort >= ARRAY) { + for (int i = off, end = i + len; i < end; i++) { + hc = 17 * (hc + buf[i]); + } + } + return hc; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/src/compiler/scala/tools/asm/signature/SignatureReader.java b/src/compiler/scala/tools/asm/signature/SignatureReader.java new file mode 100644 index 0000000000..22e6427e63 --- /dev/null +++ b/src/compiler/scala/tools/asm/signature/SignatureReader.java @@ -0,0 +1,229 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.signature; + +/** + * A type signature parser to make a signature visitor visit an existing + * signature. + * + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public class SignatureReader { + + /** + * The signature to be read. + */ + private final String signature; + + /** + * Constructs a {@link SignatureReader} for the given signature. + * + * @param signature A <i>ClassSignature</i>, <i>MethodTypeSignature</i>, + * or <i>FieldTypeSignature</i>. + */ + public SignatureReader(final String signature) { + this.signature = signature; + } + + /** + * Makes the given visitor visit the signature of this + * {@link SignatureReader}. This signature is the one specified in the + * constructor (see {@link #SignatureReader(String) SignatureReader}). This + * method is intended to be called on a {@link SignatureReader} that was + * created using a <i>ClassSignature</i> (such as the + * <code>signature</code> parameter of the + * {@link org.objectweb.asm.ClassVisitor#visit ClassVisitor.visit} method) + * or a <i>MethodTypeSignature</i> (such as the <code>signature</code> + * parameter of the + * {@link org.objectweb.asm.ClassVisitor#visitMethod ClassVisitor.visitMethod} + * method). + * + * @param v the visitor that must visit this signature. + */ + public void accept(final SignatureVisitor v) { + String signature = this.signature; + int len = signature.length(); + int pos; + char c; + + if (signature.charAt(0) == '<') { + pos = 2; + do { + int end = signature.indexOf(':', pos); + v.visitFormalTypeParameter(signature.substring(pos - 1, end)); + pos = end + 1; + + c = signature.charAt(pos); + if (c == 'L' || c == '[' || c == 'T') { + pos = parseType(signature, pos, v.visitClassBound()); + } + + while ((c = signature.charAt(pos++)) == ':') { + pos = parseType(signature, pos, v.visitInterfaceBound()); + } + } while (c != '>'); + } else { + pos = 0; + } + + if (signature.charAt(pos) == '(') { + pos++; + while (signature.charAt(pos) != ')') { + pos = parseType(signature, pos, v.visitParameterType()); + } + pos = parseType(signature, pos + 1, v.visitReturnType()); + while (pos < len) { + pos = parseType(signature, pos + 1, v.visitExceptionType()); + } + } else { + pos = parseType(signature, pos, v.visitSuperclass()); + while (pos < len) { + pos = parseType(signature, pos, v.visitInterface()); + } + } + } + + /** + * Makes the given visitor visit the signature of this + * {@link SignatureReader}. This signature is the one specified in the + * constructor (see {@link #SignatureReader(String) SignatureReader}). This + * method is intended to be called on a {@link SignatureReader} that was + * created using a <i>FieldTypeSignature</i>, such as the + * <code>signature</code> parameter of the + * {@link org.objectweb.asm.ClassVisitor#visitField + * ClassVisitor.visitField} or {@link + * org.objectweb.asm.MethodVisitor#visitLocalVariable + * MethodVisitor.visitLocalVariable} methods. + * + * @param v the visitor that must visit this signature. + */ + public void acceptType(final SignatureVisitor v) { + parseType(this.signature, 0, v); + } + + /** + * Parses a field type signature and makes the given visitor visit it. + * + * @param signature a string containing the signature that must be parsed. + * @param pos index of the first character of the signature to parsed. + * @param v the visitor that must visit this signature. + * @return the index of the first character after the parsed signature. + */ + private static int parseType( + final String signature, + int pos, + final SignatureVisitor v) + { + char c; + int start, end; + boolean visited, inner; + String name; + + switch (c = signature.charAt(pos++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + case 'F': + case 'J': + case 'D': + case 'V': + v.visitBaseType(c); + return pos; + + case '[': + return parseType(signature, pos, v.visitArrayType()); + + case 'T': + end = signature.indexOf(';', pos); + v.visitTypeVariable(signature.substring(pos, end)); + return end + 1; + + default: // case 'L': + start = pos; + visited = false; + inner = false; + for (;;) { + switch (c = signature.charAt(pos++)) { + case '.': + case ';': + if (!visited) { + name = signature.substring(start, pos - 1); + if (inner) { + v.visitInnerClassType(name); + } else { + v.visitClassType(name); + } + } + if (c == ';') { + v.visitEnd(); + return pos; + } + start = pos; + visited = false; + inner = true; + break; + + case '<': + name = signature.substring(start, pos - 1); + if (inner) { + v.visitInnerClassType(name); + } else { + v.visitClassType(name); + } + visited = true; + top: for (;;) { + switch (c = signature.charAt(pos)) { + case '>': + break top; + case '*': + ++pos; + v.visitTypeArgument(); + break; + case '+': + case '-': + pos = parseType(signature, + pos + 1, + v.visitTypeArgument(c)); + break; + default: + pos = parseType(signature, + pos, + v.visitTypeArgument('=')); + break; + } + } + } + } + } + } +} diff --git a/src/compiler/scala/tools/asm/signature/SignatureVisitor.java b/src/compiler/scala/tools/asm/signature/SignatureVisitor.java new file mode 100644 index 0000000000..2fc364e374 --- /dev/null +++ b/src/compiler/scala/tools/asm/signature/SignatureVisitor.java @@ -0,0 +1,228 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.signature; + +import scala.tools.asm.Opcodes; + +/** + * A visitor to visit a generic signature. The methods of this interface must be + * called in one of the three following orders (the last one is the only valid + * order for a {@link SignatureVisitor} that is returned by a method of this + * interface): <ul> <li><i>ClassSignature</i> = ( + * <tt>visitFormalTypeParameter</tt> + * <tt>visitClassBound</tt>? + * <tt>visitInterfaceBound</tt>* )* ( <tt>visitSuperClass</tt> + * <tt>visitInterface</tt>* )</li> + * <li><i>MethodSignature</i> = ( <tt>visitFormalTypeParameter</tt> + * <tt>visitClassBound</tt>? + * <tt>visitInterfaceBound</tt>* )* ( <tt>visitParameterType</tt>* + * <tt>visitReturnType</tt> + * <tt>visitExceptionType</tt>* )</li> <li><i>TypeSignature</i> = + * <tt>visitBaseType</tt> | <tt>visitTypeVariable</tt> | + * <tt>visitArrayType</tt> | ( + * <tt>visitClassType</tt> <tt>visitTypeArgument</tt>* ( + * <tt>visitInnerClassType</tt> <tt>visitTypeArgument</tt>* )* + * <tt>visitEnd</tt> ) )</li> </ul> + * + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public abstract class SignatureVisitor { + + /** + * Wildcard for an "extends" type argument. + */ + public final static char EXTENDS = '+'; + + /** + * Wildcard for a "super" type argument. + */ + public final static char SUPER = '-'; + + /** + * Wildcard for a normal type argument. + */ + public final static char INSTANCEOF = '='; + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * Constructs a new {@link SignatureVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public SignatureVisitor(final int api) { + this.api = api; + } + + /** + * Visits a formal type parameter. + * + * @param name the name of the formal parameter. + */ + public void visitFormalTypeParameter(String name) { + } + + /** + * Visits the class bound of the last visited formal type parameter. + * + * @return a non null visitor to visit the signature of the class bound. + */ + public SignatureVisitor visitClassBound() { + return this; + } + + /** + * Visits an interface bound of the last visited formal type parameter. + * + * @return a non null visitor to visit the signature of the interface bound. + */ + public SignatureVisitor visitInterfaceBound() { + return this; + } + + /** + * Visits the type of the super class. + * + * @return a non null visitor to visit the signature of the super class + * type. + */ + public SignatureVisitor visitSuperclass() { + return this; + } + + /** + * Visits the type of an interface implemented by the class. + * + * @return a non null visitor to visit the signature of the interface type. + */ + public SignatureVisitor visitInterface() { + return this; + } + + /** + * Visits the type of a method parameter. + * + * @return a non null visitor to visit the signature of the parameter type. + */ + public SignatureVisitor visitParameterType() { + return this; + } + + /** + * Visits the return type of the method. + * + * @return a non null visitor to visit the signature of the return type. + */ + public SignatureVisitor visitReturnType() { + return this; + } + + /** + * Visits the type of a method exception. + * + * @return a non null visitor to visit the signature of the exception type. + */ + public SignatureVisitor visitExceptionType() { + return this; + } + + /** + * Visits a signature corresponding to a primitive type. + * + * @param descriptor the descriptor of the primitive type, or 'V' for + * <tt>void</tt>. + */ + public void visitBaseType(char descriptor) { + } + + /** + * Visits a signature corresponding to a type variable. + * + * @param name the name of the type variable. + */ + public void visitTypeVariable(String name) { + } + + /** + * Visits a signature corresponding to an array type. + * + * @return a non null visitor to visit the signature of the array element + * type. + */ + public SignatureVisitor visitArrayType() { + return this; + } + + /** + * Starts the visit of a signature corresponding to a class or interface + * type. + * + * @param name the internal name of the class or interface. + */ + public void visitClassType(String name) { + } + + /** + * Visits an inner class. + * + * @param name the local name of the inner class in its enclosing class. + */ + public void visitInnerClassType(String name) { + } + + /** + * Visits an unbounded type argument of the last visited class or inner + * class type. + */ + public void visitTypeArgument() { + } + + /** + * Visits a type argument of the last visited class or inner class type. + * + * @param wildcard '+', '-' or '='. + * @return a non null visitor to visit the signature of the type argument. + */ + public SignatureVisitor visitTypeArgument(char wildcard) { + return this; + } + + /** + * Ends the visit of a signature corresponding to a class or interface type. + */ + public void visitEnd() { + } +} diff --git a/src/compiler/scala/tools/asm/signature/SignatureWriter.java b/src/compiler/scala/tools/asm/signature/SignatureWriter.java new file mode 100644 index 0000000000..a59fdfde2b --- /dev/null +++ b/src/compiler/scala/tools/asm/signature/SignatureWriter.java @@ -0,0 +1,227 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.signature; + +import scala.tools.asm.Opcodes; + +/** + * A signature visitor that generates signatures in string format. + * + * @author Thomas Hallgren + * @author Eric Bruneton + */ +public class SignatureWriter extends SignatureVisitor { + + /** + * Buffer used to construct the signature. + */ + private final StringBuffer buf = new StringBuffer(); + + /** + * Indicates if the signature contains formal type parameters. + */ + private boolean hasFormals; + + /** + * Indicates if the signature contains method parameter types. + */ + private boolean hasParameters; + + /** + * Stack used to keep track of class types that have arguments. Each element + * of this stack is a boolean encoded in one bit. The top of the stack is + * the lowest order bit. Pushing false = *2, pushing true = *2+1, popping = + * /2. + */ + private int argumentStack; + + /** + * Constructs a new {@link SignatureWriter} object. + */ + public SignatureWriter() { + super(Opcodes.ASM4); + } + + // ------------------------------------------------------------------------ + // Implementation of the SignatureVisitor interface + // ------------------------------------------------------------------------ + + @Override + public void visitFormalTypeParameter(final String name) { + if (!hasFormals) { + hasFormals = true; + buf.append('<'); + } + buf.append(name); + buf.append(':'); + } + + @Override + public SignatureVisitor visitClassBound() { + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + buf.append(':'); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + endFormals(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + endFormals(); + if (!hasParameters) { + hasParameters = true; + buf.append('('); + } + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + endFormals(); + if (!hasParameters) { + buf.append('('); + } + buf.append(')'); + return this; + } + + @Override + public SignatureVisitor visitExceptionType() { + buf.append('^'); + return this; + } + + @Override + public void visitBaseType(final char descriptor) { + buf.append(descriptor); + } + + @Override + public void visitTypeVariable(final String name) { + buf.append('T'); + buf.append(name); + buf.append(';'); + } + + @Override + public SignatureVisitor visitArrayType() { + buf.append('['); + return this; + } + + @Override + public void visitClassType(final String name) { + buf.append('L'); + buf.append(name); + argumentStack *= 2; + } + + @Override + public void visitInnerClassType(final String name) { + endArguments(); + buf.append('.'); + buf.append(name); + argumentStack *= 2; + } + + @Override + public void visitTypeArgument() { + if (argumentStack % 2 == 0) { + ++argumentStack; + buf.append('<'); + } + buf.append('*'); + } + + @Override + public SignatureVisitor visitTypeArgument(final char wildcard) { + if (argumentStack % 2 == 0) { + ++argumentStack; + buf.append('<'); + } + if (wildcard != '=') { + buf.append(wildcard); + } + return this; + } + + @Override + public void visitEnd() { + endArguments(); + buf.append(';'); + } + + /** + * Returns the signature that was built by this signature writer. + * + * @return the signature that was built by this signature writer. + */ + @Override + public String toString() { + return buf.toString(); + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Ends the formal type parameters section of the signature. + */ + private void endFormals() { + if (hasFormals) { + hasFormals = false; + buf.append('>'); + } + } + + /** + * Ends the type arguments of a class or inner class type. + */ + private void endArguments() { + if (argumentStack % 2 != 0) { + buf.append('>'); + } + argumentStack /= 2; + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/AbstractInsnNode.java b/src/compiler/scala/tools/asm/tree/AbstractInsnNode.java new file mode 100644 index 0000000000..471f842ffc --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/AbstractInsnNode.java @@ -0,0 +1,238 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.List; +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a bytecode instruction. <i>An instruction can appear + * at most once in at most one {@link InsnList} at a time</i>. + * + * @author Eric Bruneton + */ +public abstract class AbstractInsnNode { + + /** + * The type of {@link InsnNode} instructions. + */ + public static final int INSN = 0; + + /** + * The type of {@link IntInsnNode} instructions. + */ + public static final int INT_INSN = 1; + + /** + * The type of {@link VarInsnNode} instructions. + */ + public static final int VAR_INSN = 2; + + /** + * The type of {@link TypeInsnNode} instructions. + */ + public static final int TYPE_INSN = 3; + + /** + * The type of {@link FieldInsnNode} instructions. + */ + public static final int FIELD_INSN = 4; + + /** + * The type of {@link MethodInsnNode} instructions. + */ + public static final int METHOD_INSN = 5; + + /** + * The type of {@link InvokeDynamicInsnNode} instructions. + */ + public static final int INVOKE_DYNAMIC_INSN = 6; + + /** + * The type of {@link JumpInsnNode} instructions. + */ + public static final int JUMP_INSN = 7; + + /** + * The type of {@link LabelNode} "instructions". + */ + public static final int LABEL = 8; + + /** + * The type of {@link LdcInsnNode} instructions. + */ + public static final int LDC_INSN = 9; + + /** + * The type of {@link IincInsnNode} instructions. + */ + public static final int IINC_INSN = 10; + + /** + * The type of {@link TableSwitchInsnNode} instructions. + */ + public static final int TABLESWITCH_INSN = 11; + + /** + * The type of {@link LookupSwitchInsnNode} instructions. + */ + public static final int LOOKUPSWITCH_INSN = 12; + + /** + * The type of {@link MultiANewArrayInsnNode} instructions. + */ + public static final int MULTIANEWARRAY_INSN = 13; + + /** + * The type of {@link FrameNode} "instructions". + */ + public static final int FRAME = 14; + + /** + * The type of {@link LineNumberNode} "instructions". + */ + public static final int LINE = 15; + + /** + * The opcode of this instruction. + */ + protected int opcode; + + /** + * Previous instruction in the list to which this instruction belongs. + */ + AbstractInsnNode prev; + + /** + * Next instruction in the list to which this instruction belongs. + */ + AbstractInsnNode next; + + /** + * Index of this instruction in the list to which it belongs. The value of + * this field is correct only when {@link InsnList#cache} is not null. A + * value of -1 indicates that this instruction does not belong to any + * {@link InsnList}. + */ + int index; + + /** + * Constructs a new {@link AbstractInsnNode}. + * + * @param opcode the opcode of the instruction to be constructed. + */ + protected AbstractInsnNode(final int opcode) { + this.opcode = opcode; + this.index = -1; + } + + /** + * Returns the opcode of this instruction. + * + * @return the opcode of this instruction. + */ + public int getOpcode() { + return opcode; + } + + /** + * Returns the type of this instruction. + * + * @return the type of this instruction, i.e. one the constants defined in + * this class. + */ + public abstract int getType(); + + /** + * Returns the previous instruction in the list to which this instruction + * belongs, if any. + * + * @return the previous instruction in the list to which this instruction + * belongs, if any. May be <tt>null</tt>. + */ + public AbstractInsnNode getPrevious() { + return prev; + } + + /** + * Returns the next instruction in the list to which this instruction + * belongs, if any. + * + * @return the next instruction in the list to which this instruction + * belongs, if any. May be <tt>null</tt>. + */ + public AbstractInsnNode getNext() { + return next; + } + + /** + * Makes the given code visitor visit this instruction. + * + * @param cv a code visitor. + */ + public abstract void accept(final MethodVisitor cv); + + /** + * Returns a copy of this instruction. + * + * @param labels a map from LabelNodes to cloned LabelNodes. + * @return a copy of this instruction. The returned instruction does not + * belong to any {@link InsnList}. + */ + public abstract AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels); + + /** + * Returns the clone of the given label. + * + * @param label a label. + * @param map a map from LabelNodes to cloned LabelNodes. + * @return the clone of the given label. + */ + static LabelNode clone(final LabelNode label, final Map<LabelNode, LabelNode> map) { + return map.get(label); + } + + /** + * Returns the clones of the given labels. + * + * @param labels a list of labels. + * @param map a map from LabelNodes to cloned LabelNodes. + * @return the clones of the given labels. + */ + static LabelNode[] clone(final List<LabelNode> labels, final Map<LabelNode, LabelNode> map) { + LabelNode[] clones = new LabelNode[labels.size()]; + for (int i = 0; i < clones.length; ++i) { + clones[i] = map.get(labels.get(i)); + } + return clones; + } +} diff --git a/src/compiler/scala/tools/asm/tree/AnnotationNode.java b/src/compiler/scala/tools/asm/tree/AnnotationNode.java new file mode 100644 index 0000000000..9f132550e6 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/AnnotationNode.java @@ -0,0 +1,224 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.List; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents an annotationn. + * + * @author Eric Bruneton + */ +public class AnnotationNode extends AnnotationVisitor { + + /** + * The class descriptor of the annotation class. + */ + public String desc; + + /** + * The name value pairs of this annotation. Each name value pair is stored + * as two consecutive elements in the list. The name is a {@link String}, + * and the value may be a {@link Byte}, {@link Boolean}, {@link Character}, + * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, + * {@link Double}, {@link String} or {@link org.objectweb.asm.Type}, or an + * two elements String array (for enumeration values), a + * {@link AnnotationNode}, or a {@link List} of values of one of the + * preceding types. The list may be <tt>null</tt> if there is no name + * value pair. + */ + public List<Object> values; + + /** + * Constructs a new {@link AnnotationNode}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the + * {@link #AnnotationNode(int, String)} version. + * + * @param desc the class descriptor of the annotation class. + */ + public AnnotationNode(final String desc) { + this(Opcodes.ASM4, desc); + } + + /** + * Constructs a new {@link AnnotationNode}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param desc the class descriptor of the annotation class. + */ + public AnnotationNode(final int api, final String desc) { + super(api); + this.desc = desc; + } + + /** + * Constructs a new {@link AnnotationNode} to visit an array value. + * + * @param values where the visited values must be stored. + */ + AnnotationNode(final List<Object> values) { + super(Opcodes.ASM4); + this.values = values; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + if (values == null) { + values = new ArrayList<Object>(this.desc != null ? 2 : 1); + } + if (this.desc != null) { + values.add(name); + } + values.add(value); + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + if (values == null) { + values = new ArrayList<Object>(this.desc != null ? 2 : 1); + } + if (this.desc != null) { + values.add(name); + } + values.add(new String[] { desc, value }); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String name, + final String desc) + { + if (values == null) { + values = new ArrayList<Object>(this.desc != null ? 2 : 1); + } + if (this.desc != null) { + values.add(name); + } + AnnotationNode annotation = new AnnotationNode(desc); + values.add(annotation); + return annotation; + } + + @Override + public AnnotationVisitor visitArray(final String name) { + if (values == null) { + values = new ArrayList<Object>(this.desc != null ? 2 : 1); + } + if (this.desc != null) { + values.add(name); + } + List<Object> array = new ArrayList<Object>(); + values.add(array); + return new AnnotationNode(array); + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Accept methods + // ------------------------------------------------------------------------ + + /** + * Checks that this annotation node is compatible with the given ASM API + * version. This methods checks that this node, and all its nodes + * recursively, do not contain elements that were introduced in more recent + * versions of the ASM API than the given version. + * + * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}. + */ + public void check(final int api) { + // nothing to do + } + + /** + * Makes the given visitor visit this annotation. + * + * @param av an annotation visitor. Maybe <tt>null</tt>. + */ + public void accept(final AnnotationVisitor av) { + if (av != null) { + if (values != null) { + for (int i = 0; i < values.size(); i += 2) { + String name = (String) values.get(i); + Object value = values.get(i + 1); + accept(av, name, value); + } + } + av.visitEnd(); + } + } + + /** + * Makes the given visitor visit a given annotation value. + * + * @param av an annotation visitor. Maybe <tt>null</tt>. + * @param name the value name. + * @param value the actual value. + */ + static void accept( + final AnnotationVisitor av, + final String name, + final Object value) + { + if (av != null) { + if (value instanceof String[]) { + String[] typeconst = (String[]) value; + av.visitEnum(name, typeconst[0], typeconst[1]); + } else if (value instanceof AnnotationNode) { + AnnotationNode an = (AnnotationNode) value; + an.accept(av.visitAnnotation(name, an.desc)); + } else if (value instanceof List) { + AnnotationVisitor v = av.visitArray(name); + List<?> array = (List<?>) value; + for (int j = 0; j < array.size(); ++j) { + accept(v, null, array.get(j)); + } + v.visitEnd(); + } else { + av.visit(name, value); + } + } + } +} diff --git a/src/compiler/scala/tools/asm/tree/ClassNode.java b/src/compiler/scala/tools/asm/tree/ClassNode.java new file mode 100644 index 0000000000..64effae698 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/ClassNode.java @@ -0,0 +1,371 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a class. + * + * @author Eric Bruneton + */ +public class ClassNode extends ClassVisitor { + + /** + * The class version. + */ + public int version; + + /** + * The class's access flags (see {@link org.objectweb.asm.Opcodes}). This + * field also indicates if the class is deprecated. + */ + public int access; + + /** + * The internal name of the class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + */ + public String name; + + /** + * The signature of the class. Mayt be <tt>null</tt>. + */ + public String signature; + + /** + * The internal of name of the super class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). For + * interfaces, the super class is {@link Object}. May be <tt>null</tt>, + * but only for the {@link Object} class. + */ + public String superName; + + /** + * The internal names of the class's interfaces (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). This + * list is a list of {@link String} objects. + */ + public List<String> interfaces; + + /** + * The name of the source file from which this class was compiled. May be + * <tt>null</tt>. + */ + public String sourceFile; + + /** + * Debug information to compute the correspondance between source and + * compiled elements of the class. May be <tt>null</tt>. + */ + public String sourceDebug; + + /** + * The internal name of the enclosing class of the class. May be + * <tt>null</tt>. + */ + public String outerClass; + + /** + * The name of the method that contains the class, or <tt>null</tt> if the + * class is not enclosed in a method. + */ + public String outerMethod; + + /** + * The descriptor of the method that contains the class, or <tt>null</tt> + * if the class is not enclosed in a method. + */ + public String outerMethodDesc; + + /** + * The runtime visible annotations of this class. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label visible + */ + public List<AnnotationNode> visibleAnnotations; + + /** + * The runtime invisible annotations of this class. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label invisible + */ + public List<AnnotationNode> invisibleAnnotations; + + /** + * The non standard attributes of this class. This list is a list of + * {@link Attribute} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.Attribute + */ + public List<Attribute> attrs; + + /** + * Informations about the inner classes of this class. This list is a list + * of {@link InnerClassNode} objects. + * + * @associates org.objectweb.asm.tree.InnerClassNode + */ + public List<InnerClassNode> innerClasses; + + /** + * The fields of this class. This list is a list of {@link FieldNode} + * objects. + * + * @associates org.objectweb.asm.tree.FieldNode + */ + public List<FieldNode> fields; + + /** + * The methods of this class. This list is a list of {@link MethodNode} + * objects. + * + * @associates org.objectweb.asm.tree.MethodNode + */ + public List<MethodNode> methods; + + /** + * Constructs a new {@link ClassNode}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the {@link #ClassNode(int)} + * version. + */ + public ClassNode() { + this(Opcodes.ASM4); + } + + /** + * Constructs a new {@link ClassNode}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public ClassNode(final int api) { + super(api); + this.interfaces = new ArrayList<String>(); + this.innerClasses = new ArrayList<InnerClassNode>(); + this.fields = new ArrayList<FieldNode>(); + this.methods = new ArrayList<MethodNode>(); + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + this.version = version; + this.access = access; + this.name = name; + this.signature = signature; + this.superName = superName; + if (interfaces != null) { + this.interfaces.addAll(Arrays.asList(interfaces)); + } + } + + @Override + public void visitSource(final String file, final String debug) { + sourceFile = file; + sourceDebug = debug; + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) + { + outerClass = owner; + outerMethod = name; + outerMethodDesc = desc; + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + AnnotationNode an = new AnnotationNode(desc); + if (visible) { + if (visibleAnnotations == null) { + visibleAnnotations = new ArrayList<AnnotationNode>(1); + } + visibleAnnotations.add(an); + } else { + if (invisibleAnnotations == null) { + invisibleAnnotations = new ArrayList<AnnotationNode>(1); + } + invisibleAnnotations.add(an); + } + return an; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attrs == null) { + attrs = new ArrayList<Attribute>(1); + } + attrs.add(attr); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + InnerClassNode icn = new InnerClassNode(name, + outerName, + innerName, + access); + innerClasses.add(icn); + } + + @Override + public FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + FieldNode fn = new FieldNode(access, name, desc, signature, value); + fields.add(fn); + return fn; + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + MethodNode mn = new MethodNode(access, + name, + desc, + signature, + exceptions); + methods.add(mn); + return mn; + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Accept method + // ------------------------------------------------------------------------ + + /** + * Checks that this class node is compatible with the given ASM API version. + * This methods checks that this node, and all its nodes recursively, do not + * contain elements that were introduced in more recent versions of the ASM + * API than the given version. + * + * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}. + */ + public void check(final int api) { + // nothing to do + } + + /** + * Makes the given class visitor visit this class. + * + * @param cv a class visitor. + */ + public void accept(final ClassVisitor cv) { + // visits header + String[] interfaces = new String[this.interfaces.size()]; + this.interfaces.toArray(interfaces); + cv.visit(version, access, name, signature, superName, interfaces); + // visits source + if (sourceFile != null || sourceDebug != null) { + cv.visitSource(sourceFile, sourceDebug); + } + // visits outer class + if (outerClass != null) { + cv.visitOuterClass(outerClass, outerMethod, outerMethodDesc); + } + // visits attributes + int i, n; + n = visibleAnnotations == null ? 0 : visibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = visibleAnnotations.get(i); + an.accept(cv.visitAnnotation(an.desc, true)); + } + n = invisibleAnnotations == null ? 0 : invisibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = invisibleAnnotations.get(i); + an.accept(cv.visitAnnotation(an.desc, false)); + } + n = attrs == null ? 0 : attrs.size(); + for (i = 0; i < n; ++i) { + cv.visitAttribute(attrs.get(i)); + } + // visits inner classes + for (i = 0; i < innerClasses.size(); ++i) { + innerClasses.get(i).accept(cv); + } + // visits fields + for (i = 0; i < fields.size(); ++i) { + fields.get(i).accept(cv); + } + // visits methods + for (i = 0; i < methods.size(); ++i) { + methods.get(i).accept(cv); + } + // visits end + cv.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/tree/FieldInsnNode.java b/src/compiler/scala/tools/asm/tree/FieldInsnNode.java new file mode 100644 index 0000000000..6b7a6a142a --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/FieldInsnNode.java @@ -0,0 +1,106 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a field instruction. A field instruction is an + * instruction that loads or stores the value of a field of an object. + * + * @author Eric Bruneton + */ +public class FieldInsnNode extends AbstractInsnNode { + + /** + * The internal name of the field's owner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + */ + public String owner; + + /** + * The field's name. + */ + public String name; + + /** + * The field's descriptor (see {@link org.objectweb.asm.Type}). + */ + public String desc; + + /** + * Constructs a new {@link FieldInsnNode}. + * + * @param opcode the opcode of the type instruction to be constructed. This + * opcode must be GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + * @param name the field's name. + * @param desc the field's descriptor (see {@link org.objectweb.asm.Type}). + */ + public FieldInsnNode( + final int opcode, + final String owner, + final String name, + final String desc) + { + super(opcode); + this.owner = owner; + this.name = name; + this.desc = desc; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be GETSTATIC, + * PUTSTATIC, GETFIELD or PUTFIELD. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return FIELD_INSN; + } + + @Override + public void accept(final MethodVisitor cv) { + cv.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new FieldInsnNode(opcode, owner, name, desc); + } +} diff --git a/src/compiler/scala/tools/asm/tree/FieldNode.java b/src/compiler/scala/tools/asm/tree/FieldNode.java new file mode 100644 index 0000000000..9a1e17033c --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/FieldNode.java @@ -0,0 +1,243 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.List; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a field. + * + * @author Eric Bruneton + */ +public class FieldNode extends FieldVisitor { + + /** + * The field's access flags (see {@link org.objectweb.asm.Opcodes}). This + * field also indicates if the field is synthetic and/or deprecated. + */ + public int access; + + /** + * The field's name. + */ + public String name; + + /** + * The field's descriptor (see {@link org.objectweb.asm.Type}). + */ + public String desc; + + /** + * The field's signature. May be <tt>null</tt>. + */ + public String signature; + + /** + * The field's initial value. This field, which may be <tt>null</tt> if + * the field does not have an initial value, must be an {@link Integer}, a + * {@link Float}, a {@link Long}, a {@link Double} or a {@link String}. + */ + public Object value; + + /** + * The runtime visible annotations of this field. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label visible + */ + public List<AnnotationNode> visibleAnnotations; + + /** + * The runtime invisible annotations of this field. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label invisible + */ + public List<AnnotationNode> invisibleAnnotations; + + /** + * The non standard attributes of this field. This list is a list of + * {@link Attribute} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.Attribute + */ + public List<Attribute> attrs; + + /** + * Constructs a new {@link FieldNode}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the + * {@link #FieldNode(int, int, String, String, String, Object)} version. + * + * @param access the field's access flags (see + * {@link org.objectweb.asm.Opcodes}). This parameter also indicates + * if the field is synthetic and/or deprecated. + * @param name the field's name. + * @param desc the field's descriptor (see {@link org.objectweb.asm.Type + * Type}). + * @param signature the field's signature. + * @param value the field's initial value. This parameter, which may be + * <tt>null</tt> if the field does not have an initial value, must be + * an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String}. + */ + public FieldNode( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + this(Opcodes.ASM4, access, name, desc, signature, value); + } + + /** + * Constructs a new {@link FieldNode}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the + * {@link #FieldNode(int, int, String, String, String, Object)} version. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param access the field's access flags (see + * {@link org.objectweb.asm.Opcodes}). This parameter also indicates + * if the field is synthetic and/or deprecated. + * @param name the field's name. + * @param desc the field's descriptor (see {@link org.objectweb.asm.Type + * Type}). + * @param signature the field's signature. + * @param value the field's initial value. This parameter, which may be + * <tt>null</tt> if the field does not have an initial value, must be + * an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String}. + */ + public FieldNode( + final int api, + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + super(api); + this.access = access; + this.name = name; + this.desc = desc; + this.signature = signature; + this.value = value; + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + AnnotationNode an = new AnnotationNode(desc); + if (visible) { + if (visibleAnnotations == null) { + visibleAnnotations = new ArrayList<AnnotationNode>(1); + } + visibleAnnotations.add(an); + } else { + if (invisibleAnnotations == null) { + invisibleAnnotations = new ArrayList<AnnotationNode>(1); + } + invisibleAnnotations.add(an); + } + return an; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attrs == null) { + attrs = new ArrayList<Attribute>(1); + } + attrs.add(attr); + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Accept methods + // ------------------------------------------------------------------------ + + /** + * Checks that this field node is compatible with the given ASM API version. + * This methods checks that this node, and all its nodes recursively, do not + * contain elements that were introduced in more recent versions of the ASM + * API than the given version. + * + * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}. + */ + public void check(final int api) { + // nothing to do + } + + /** + * Makes the given class visitor visit this field. + * + * @param cv a class visitor. + */ + public void accept(final ClassVisitor cv) { + FieldVisitor fv = cv.visitField(access, name, desc, signature, value); + if (fv == null) { + return; + } + int i, n; + n = visibleAnnotations == null ? 0 : visibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = visibleAnnotations.get(i); + an.accept(fv.visitAnnotation(an.desc, true)); + } + n = invisibleAnnotations == null ? 0 : invisibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = invisibleAnnotations.get(i); + an.accept(fv.visitAnnotation(an.desc, false)); + } + n = attrs == null ? 0 : attrs.size(); + for (i = 0; i < n; ++i) { + fv.visitAttribute(attrs.get(i)); + } + fv.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/tree/FrameNode.java b/src/compiler/scala/tools/asm/tree/FrameNode.java new file mode 100644 index 0000000000..66825de0ac --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/FrameNode.java @@ -0,0 +1,211 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a stack map frame. These nodes are pseudo instruction + * nodes in order to be inserted in an instruction list. In fact these nodes + * must(*) be inserted <i>just before</i> any instruction node <b>i</b> that + * follows an unconditionnal branch instruction such as GOTO or THROW, that is + * the target of a jump instruction, or that starts an exception handler block. + * The stack map frame types must describe the values of the local variables and + * of the operand stack elements <i>just before</i> <b>i</b> is executed. <br> + * <br> (*) this is mandatory only for classes whose version is greater than or + * equal to {@link Opcodes#V1_6 V1_6}. + * + * @author Eric Bruneton + */ +public class FrameNode extends AbstractInsnNode { + + /** + * The type of this frame. Must be {@link Opcodes#F_NEW} for expanded + * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. + */ + public int type; + + /** + * The types of the local variables of this stack map frame. Elements of + * this list can be Integer, String or LabelNode objects (for primitive, + * reference and uninitialized types respectively - see + * {@link MethodVisitor}). + */ + public List<Object> local; + + /** + * The types of the operand stack elements of this stack map frame. Elements + * of this list can be Integer, String or LabelNode objects (for primitive, + * reference and uninitialized types respectively - see + * {@link MethodVisitor}). + */ + public List<Object> stack; + + private FrameNode() { + super(-1); + } + + /** + * Constructs a new {@link FrameNode}. + * + * @param type the type of this frame. Must be {@link Opcodes#F_NEW} for + * expanded frames, or {@link Opcodes#F_FULL}, + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, + * {@link Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_SAME1} for compressed frames. + * @param nLocal number of local variables of this stack map frame. + * @param local the types of the local variables of this stack map frame. + * Elements of this list can be Integer, String or LabelNode objects + * (for primitive, reference and uninitialized types respectively - + * see {@link MethodVisitor}). + * @param nStack number of operand stack elements of this stack map frame. + * @param stack the types of the operand stack elements of this stack map + * frame. Elements of this list can be Integer, String or LabelNode + * objects (for primitive, reference and uninitialized types + * respectively - see {@link MethodVisitor}). + */ + public FrameNode( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + super(-1); + this.type = type; + switch (type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + this.local = asList(nLocal, local); + this.stack = asList(nStack, stack); + break; + case Opcodes.F_APPEND: + this.local = asList(nLocal, local); + break; + case Opcodes.F_CHOP: + this.local = Arrays.asList(new Object[nLocal]); + break; + case Opcodes.F_SAME: + break; + case Opcodes.F_SAME1: + this.stack = asList(1, stack); + break; + } + } + + @Override + public int getType() { + return FRAME; + } + + /** + * Makes the given visitor visit this stack map frame. + * + * @param mv a method visitor. + */ + @Override + public void accept(final MethodVisitor mv) { + switch (type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + mv.visitFrame(type, + local.size(), + asArray(local), + stack.size(), + asArray(stack)); + break; + case Opcodes.F_APPEND: + mv.visitFrame(type, local.size(), asArray(local), 0, null); + break; + case Opcodes.F_CHOP: + mv.visitFrame(type, local.size(), null, 0, null); + break; + case Opcodes.F_SAME: + mv.visitFrame(type, 0, null, 0, null); + break; + case Opcodes.F_SAME1: + mv.visitFrame(type, 0, null, 1, asArray(stack)); + break; + } + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + FrameNode clone = new FrameNode(); + clone.type = type; + if (local != null) { + clone.local = new ArrayList<Object>(); + for (int i = 0; i < local.size(); ++i) { + Object l = local.get(i); + if (l instanceof LabelNode) { + l = labels.get(l); + } + clone.local.add(l); + } + } + if (stack != null) { + clone.stack = new ArrayList<Object>(); + for (int i = 0; i < stack.size(); ++i) { + Object s = stack.get(i); + if (s instanceof LabelNode) { + s = labels.get(s); + } + clone.stack.add(s); + } + } + return clone; + } + + // ------------------------------------------------------------------------ + + private static List<Object> asList(final int n, final Object[] o) { + return Arrays.asList(o).subList(0, n); + } + + private static Object[] asArray(final List<Object> l) { + Object[] objs = new Object[l.size()]; + for (int i = 0; i < objs.length; ++i) { + Object o = l.get(i); + if (o instanceof LabelNode) { + o = ((LabelNode) o).getLabel(); + } + objs[i] = o; + } + return objs; + } +} diff --git a/src/compiler/scala/tools/asm/tree/IincInsnNode.java b/src/compiler/scala/tools/asm/tree/IincInsnNode.java new file mode 100644 index 0000000000..75ac40884d --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/IincInsnNode.java @@ -0,0 +1,80 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents an IINC instruction. + * + * @author Eric Bruneton + */ +public class IincInsnNode extends AbstractInsnNode { + + /** + * Index of the local variable to be incremented. + */ + public int var; + + /** + * Amount to increment the local variable by. + */ + public int incr; + + /** + * Constructs a new {@link IincInsnNode}. + * + * @param var index of the local variable to be incremented. + * @param incr increment amount to increment the local variable by. + */ + public IincInsnNode(final int var, final int incr) { + super(Opcodes.IINC); + this.var = var; + this.incr = incr; + } + + @Override + public int getType() { + return IINC_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitIincInsn(var, incr); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new IincInsnNode(var, incr); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/InnerClassNode.java b/src/compiler/scala/tools/asm/tree/InnerClassNode.java new file mode 100644 index 0000000000..4579488921 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/InnerClassNode.java @@ -0,0 +1,101 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import scala.tools.asm.ClassVisitor; + +/** + * A node that represents an inner class. + * + * @author Eric Bruneton + */ +public class InnerClassNode { + + /** + * The internal name of an inner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + */ + public String name; + + /** + * The internal name of the class to which the inner class belongs (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). May + * be <tt>null</tt>. + */ + public String outerName; + + /** + * The (simple) name of the inner class inside its enclosing class. May be + * <tt>null</tt> for anonymous inner classes. + */ + public String innerName; + + /** + * The access flags of the inner class as originally declared in the + * enclosing class. + */ + public int access; + + /** + * Constructs a new {@link InnerClassNode}. + * + * @param name the internal name of an inner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + * @param outerName the internal name of the class to which the inner class + * belongs (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + * May be <tt>null</tt>. + * @param innerName the (simple) name of the inner class inside its + * enclosing class. May be <tt>null</tt> for anonymous inner + * classes. + * @param access the access flags of the inner class as originally declared + * in the enclosing class. + */ + public InnerClassNode( + final String name, + final String outerName, + final String innerName, + final int access) + { + this.name = name; + this.outerName = outerName; + this.innerName = innerName; + this.access = access; + } + + /** + * Makes the given class visitor visit this inner class. + * + * @param cv a class visitor. + */ + public void accept(final ClassVisitor cv) { + cv.visitInnerClass(name, outerName, innerName, access); + } +} diff --git a/src/compiler/scala/tools/asm/tree/InsnList.java b/src/compiler/scala/tools/asm/tree/InsnList.java new file mode 100644 index 0000000000..dedd3bba73 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/InsnList.java @@ -0,0 +1,578 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import scala.tools.asm.MethodVisitor; + +/** + * A doubly linked list of {@link AbstractInsnNode} objects. <i>This + * implementation is not thread safe</i>. + */ +public class InsnList { + + /** + * The number of instructions in this list. + */ + private int size; + + /** + * The first instruction in this list. May be <tt>null</tt>. + */ + private AbstractInsnNode first; + + /** + * The last instruction in this list. May be <tt>null</tt>. + */ + private AbstractInsnNode last; + + /** + * A cache of the instructions of this list. This cache is used to improve + * the performance of the {@link #get} method. + */ + AbstractInsnNode[] cache; + + /** + * Returns the number of instructions in this list. + * + * @return the number of instructions in this list. + */ + public int size() { + return size; + } + + /** + * Returns the first instruction in this list. + * + * @return the first instruction in this list, or <tt>null</tt> if the + * list is empty. + */ + public AbstractInsnNode getFirst() { + return first; + } + + /** + * Returns the last instruction in this list. + * + * @return the last instruction in this list, or <tt>null</tt> if the list + * is empty. + */ + public AbstractInsnNode getLast() { + return last; + } + + /** + * Returns the instruction whose index is given. This method builds a cache + * of the instructions in this list to avoid scanning the whole list each + * time it is called. Once the cache is built, this method run in constant + * time. This cache is invalidated by all the methods that modify the list. + * + * @param index the index of the instruction that must be returned. + * @return the instruction whose index is given. + * @throws IndexOutOfBoundsException if (index < 0 || index >= size()). + */ + public AbstractInsnNode get(final int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); + } + if (cache == null) { + cache = toArray(); + } + return cache[index]; + } + + /** + * Returns <tt>true</tt> if the given instruction belongs to this list. + * This method always scans the instructions of this list until it finds the + * given instruction or reaches the end of the list. + * + * @param insn an instruction. + * @return <tt>true</tt> if the given instruction belongs to this list. + */ + public boolean contains(final AbstractInsnNode insn) { + AbstractInsnNode i = first; + while (i != null && i != insn) { + i = i.next; + } + return i != null; + } + + /** + * Returns the index of the given instruction in this list. This method + * builds a cache of the instruction indexes to avoid scanning the whole + * list each time it is called. Once the cache is built, this method run in + * constant time. The cache is invalidated by all the methods that modify + * the list. + * + * @param insn an instruction <i>of this list</i>. + * @return the index of the given instruction in this list. <i>The result of + * this method is undefined if the given instruction does not belong + * to this list</i>. Use {@link #contains contains} to test if an + * instruction belongs to an instruction list or not. + */ + public int indexOf(final AbstractInsnNode insn) { + if (cache == null) { + cache = toArray(); + } + return insn.index; + } + + /** + * Makes the given visitor visit all of the instructions in this list. + * + * @param mv the method visitor that must visit the instructions. + */ + public void accept(final MethodVisitor mv) { + AbstractInsnNode insn = first; + while (insn != null) { + insn.accept(mv); + insn = insn.next; + } + } + + /** + * Returns an iterator over the instructions in this list. + * + * @return an iterator over the instructions in this list. + */ + public ListIterator<AbstractInsnNode> iterator() { + return iterator(0); + } + + /** + * Returns an iterator over the instructions in this list. + * + * @return an iterator over the instructions in this list. + */ + @SuppressWarnings("unchecked") + public ListIterator<AbstractInsnNode> iterator(int index) { + return new InsnListIterator(index); + } + + /** + * Returns an array containing all of the instructions in this list. + * + * @return an array containing all of the instructions in this list. + */ + public AbstractInsnNode[] toArray() { + int i = 0; + AbstractInsnNode elem = first; + AbstractInsnNode[] insns = new AbstractInsnNode[size]; + while (elem != null) { + insns[i] = elem; + elem.index = i++; + elem = elem.next; + } + return insns; + } + + /** + * Replaces an instruction of this list with another instruction. + * + * @param location an instruction <i>of this list</i>. + * @param insn another instruction, <i>which must not belong to any + * {@link InsnList}</i>. + */ + public void set(final AbstractInsnNode location, final AbstractInsnNode insn) { + AbstractInsnNode next = location.next; + insn.next = next; + if (next != null) { + next.prev = insn; + } else { + last = insn; + } + AbstractInsnNode prev = location.prev; + insn.prev = prev; + if (prev != null) { + prev.next = insn; + } else { + first = insn; + } + if (cache != null) { + int index = location.index; + cache[index] = insn; + insn.index = index; + } else { + insn.index = 0; // insn now belongs to an InsnList + } + location.index = -1; // i no longer belongs to an InsnList + location.prev = null; + location.next = null; + } + + /** + * Adds the given instruction to the end of this list. + * + * @param insn an instruction, <i>which must not belong to any + * {@link InsnList}</i>. + */ + public void add(final AbstractInsnNode insn) { + ++size; + if (last == null) { + first = insn; + last = insn; + } else { + last.next = insn; + insn.prev = last; + } + last = insn; + cache = null; + insn.index = 0; // insn now belongs to an InsnList + } + + /** + * Adds the given instructions to the end of this list. + * + * @param insns an instruction list, which is cleared during the process. + * This list must be different from 'this'. + */ + public void add(final InsnList insns) { + if (insns.size == 0) { + return; + } + size += insns.size; + if (last == null) { + first = insns.first; + last = insns.last; + } else { + AbstractInsnNode elem = insns.first; + last.next = elem; + elem.prev = last; + last = insns.last; + } + cache = null; + insns.removeAll(false); + } + + /** + * Inserts the given instruction at the begining of this list. + * + * @param insn an instruction, <i>which must not belong to any + * {@link InsnList}</i>. + */ + public void insert(final AbstractInsnNode insn) { + ++size; + if (first == null) { + first = insn; + last = insn; + } else { + first.prev = insn; + insn.next = first; + } + first = insn; + cache = null; + insn.index = 0; // insn now belongs to an InsnList + } + + /** + * Inserts the given instructions at the begining of this list. + * + * @param insns an instruction list, which is cleared during the process. + * This list must be different from 'this'. + */ + public void insert(final InsnList insns) { + if (insns.size == 0) { + return; + } + size += insns.size; + if (first == null) { + first = insns.first; + last = insns.last; + } else { + AbstractInsnNode elem = insns.last; + first.prev = elem; + elem.next = first; + first = insns.first; + } + cache = null; + insns.removeAll(false); + } + + /** + * Inserts the given instruction after the specified instruction. + * + * @param location an instruction <i>of this list</i> after which insn must be + * inserted. + * @param insn the instruction to be inserted, <i>which must not belong to + * any {@link InsnList}</i>. + */ + public void insert(final AbstractInsnNode location, final AbstractInsnNode insn) { + ++size; + AbstractInsnNode next = location.next; + if (next == null) { + last = insn; + } else { + next.prev = insn; + } + location.next = insn; + insn.next = next; + insn.prev = location; + cache = null; + insn.index = 0; // insn now belongs to an InsnList + } + + /** + * Inserts the given instructions after the specified instruction. + * + * @param location an instruction <i>of this list</i> after which the + * instructions must be inserted. + * @param insns the instruction list to be inserted, which is cleared during + * the process. This list must be different from 'this'. + */ + public void insert(final AbstractInsnNode location, final InsnList insns) { + if (insns.size == 0) { + return; + } + size += insns.size; + AbstractInsnNode ifirst = insns.first; + AbstractInsnNode ilast = insns.last; + AbstractInsnNode next = location.next; + if (next == null) { + last = ilast; + } else { + next.prev = ilast; + } + location.next = ifirst; + ilast.next = next; + ifirst.prev = location; + cache = null; + insns.removeAll(false); + } + + /** + * Inserts the given instruction before the specified instruction. + * + * @param location an instruction <i>of this list</i> before which insn must be + * inserted. + * @param insn the instruction to be inserted, <i>which must not belong to + * any {@link InsnList}</i>. + */ + public void insertBefore(final AbstractInsnNode location, final AbstractInsnNode insn) { + ++size; + AbstractInsnNode prev = location.prev; + if (prev == null) { + first = insn; + } else { + prev.next = insn; + } + location.prev = insn; + insn.next = location; + insn.prev = prev; + cache = null; + insn.index = 0; // insn now belongs to an InsnList + } + + /** + * Inserts the given instructions before the specified instruction. + * + * @param location an instruction <i>of this list</i> before which the instructions + * must be inserted. + * @param insns the instruction list to be inserted, which is cleared during + * the process. This list must be different from 'this'. + */ + public void insertBefore(final AbstractInsnNode location, final InsnList insns) { + if (insns.size == 0) { + return; + } + size += insns.size; + AbstractInsnNode ifirst = insns.first; + AbstractInsnNode ilast = insns.last; + AbstractInsnNode prev = location .prev; + if (prev == null) { + first = ifirst; + } else { + prev.next = ifirst; + } + location .prev = ilast; + ilast.next = location ; + ifirst.prev = prev; + cache = null; + insns.removeAll(false); + } + + + + /** + * Removes the given instruction from this list. + * + * @param insn the instruction <i>of this list</i> that must be removed. + */ + public void remove(final AbstractInsnNode insn) { + --size; + AbstractInsnNode next = insn.next; + AbstractInsnNode prev = insn.prev; + if (next == null) { + if (prev == null) { + first = null; + last = null; + } else { + prev.next = null; + last = prev; + } + } else { + if (prev == null) { + first = next; + next.prev = null; + } else { + prev.next = next; + next.prev = prev; + } + } + cache = null; + insn.index = -1; // insn no longer belongs to an InsnList + insn.prev = null; + insn.next = null; + } + + /** + * Removes all of the instructions of this list. + * + * @param mark if the instructions must be marked as no longer belonging to + * any {@link InsnList}. + */ + void removeAll(final boolean mark) { + if (mark) { + AbstractInsnNode insn = first; + while (insn != null) { + AbstractInsnNode next = insn.next; + insn.index = -1; // insn no longer belongs to an InsnList + insn.prev = null; + insn.next = null; + insn = next; + } + } + size = 0; + first = null; + last = null; + cache = null; + } + + /** + * Removes all of the instructions of this list. + */ + public void clear() { + removeAll(false); + } + + /** + * Reset all labels in the instruction list. This method should be called + * before reusing same instructions list between several + * <code>ClassWriter</code>s. + */ + public void resetLabels() { + AbstractInsnNode insn = first; + while (insn != null) { + if (insn instanceof LabelNode) { + ((LabelNode) insn).resetLabel(); + } + insn = insn.next; + } + } + + // this class is not generified because it will create bridges + private final class InsnListIterator implements ListIterator/*<AbstractInsnNode>*/ { + + AbstractInsnNode next; + + AbstractInsnNode prev; + + InsnListIterator(int index) { + if(index==size()) { + next = null; + prev = getLast(); + } else { + next = get(index); + prev = next.prev; + } + } + + public boolean hasNext() { + return next != null; + } + + public Object next() { + if (next == null) { + throw new NoSuchElementException(); + } + AbstractInsnNode result = next; + prev = result; + next = result.next; + return result; + } + + public void remove() { + InsnList.this.remove(prev); + prev = prev.prev; + } + + public boolean hasPrevious() { + return prev != null; + } + + public Object previous() { + AbstractInsnNode result = prev; + next = result; + prev = result.prev; + return result; + } + + public int nextIndex() { + if (next == null) { + return size(); + } + if (cache == null) { + cache = toArray(); + } + return next.index; + } + + public int previousIndex() { + if (prev == null) { + return -1; + } + if (cache == null) { + cache = toArray(); + } + return prev.index; + } + + public void add(Object o) { + InsnList.this.insertBefore(next, (AbstractInsnNode) o); + prev = (AbstractInsnNode) o; + } + + public void set(Object o) { + InsnList.this.set(next.prev, (AbstractInsnNode) o); + prev = (AbstractInsnNode) o; + } + } +} diff --git a/src/compiler/scala/tools/asm/tree/InsnNode.java b/src/compiler/scala/tools/asm/tree/InsnNode.java new file mode 100644 index 0000000000..d4664d23c2 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/InsnNode.java @@ -0,0 +1,84 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a zero operand instruction. + * + * @author Eric Bruneton + */ +public class InsnNode extends AbstractInsnNode { + + /** + * Constructs a new {@link InsnNode}. + * + * @param opcode the opcode of the instruction to be constructed. This + * opcode must be NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, + * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, + * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, + * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, + * FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, + * DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, + * FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, + * LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, + * ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, + * LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, + * I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, + * FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, + * MONITORENTER, or MONITOREXIT. + */ + public InsnNode(final int opcode) { + super(opcode); + } + + @Override + public int getType() { + return INSN; + } + + /** + * Makes the given visitor visit this instruction. + * + * @param mv a method visitor. + */ + @Override + public void accept(final MethodVisitor mv) { + mv.visitInsn(opcode); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new InsnNode(opcode); + } +} diff --git a/src/compiler/scala/tools/asm/tree/IntInsnNode.java b/src/compiler/scala/tools/asm/tree/IntInsnNode.java new file mode 100644 index 0000000000..b61270c786 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/IntInsnNode.java @@ -0,0 +1,84 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents an instruction with a single int operand. + * + * @author Eric Bruneton + */ +public class IntInsnNode extends AbstractInsnNode { + + /** + * The operand of this instruction. + */ + public int operand; + + /** + * Constructs a new {@link IntInsnNode}. + * + * @param opcode the opcode of the instruction to be constructed. This + * opcode must be BIPUSH, SIPUSH or NEWARRAY. + * @param operand the operand of the instruction to be constructed. + */ + public IntInsnNode(final int opcode, final int operand) { + super(opcode); + this.operand = operand; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be BIPUSH, + * SIPUSH or NEWARRAY. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return INT_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitIntInsn(opcode, operand); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new IntInsnNode(opcode, operand); + } +} diff --git a/src/compiler/scala/tools/asm/tree/InvokeDynamicInsnNode.java b/src/compiler/scala/tools/asm/tree/InvokeDynamicInsnNode.java new file mode 100644 index 0000000000..d993b5a054 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/InvokeDynamicInsnNode.java @@ -0,0 +1,100 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.Handle; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents an invokedynamic instruction. + * + * @author Remi Forax + */ +public class InvokeDynamicInsnNode extends AbstractInsnNode { + + /** + * Invokedynamic name. + */ + public String name; + + /** + * Invokedynamic descriptor. + */ + public String desc; + + /** + * Bootstrap method + */ + public Handle bsm; + + /** + * Bootstrap constant arguments + */ + public Object[] bsmArgs; + + /** + * Constructs a new {@link InvokeDynamicInsnNode}. + * + * @param name invokedynamic name. + * @param desc invokedynamic descriptor (see {@link org.objectweb.asm.Type}). + * @param bsm the bootstrap method. + * @param bsmArgs the boostrap constant arguments. + */ + public InvokeDynamicInsnNode( + final String name, + final String desc, + final Handle bsm, + final Object... bsmArgs) + { + super(Opcodes.INVOKEDYNAMIC); + this.name = name; + this.desc = desc; + this.bsm = bsm; + this.bsmArgs = bsmArgs; + } + + @Override + public int getType() { + return INVOKE_DYNAMIC_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new InvokeDynamicInsnNode(name, desc, bsm, bsmArgs); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/JumpInsnNode.java b/src/compiler/scala/tools/asm/tree/JumpInsnNode.java new file mode 100644 index 0000000000..339ebbd2d0 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/JumpInsnNode.java @@ -0,0 +1,92 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a jump instruction. A jump instruction is an + * instruction that may jump to another instruction. + * + * @author Eric Bruneton + */ +public class JumpInsnNode extends AbstractInsnNode { + + /** + * The operand of this instruction. This operand is a label that designates + * the instruction to which this instruction may jump. + */ + public LabelNode label; + + /** + * Constructs a new {@link JumpInsnNode}. + * + * @param opcode the opcode of the type instruction to be constructed. This + * opcode must be IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, + * IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be constructed. This + * operand is a label that designates the instruction to which the + * jump instruction may jump. + */ + public JumpInsnNode(final int opcode, final LabelNode label) { + super(opcode); + this.label = label; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be IFEQ, IFNE, + * IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, + * IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, + * IFNULL or IFNONNULL. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return JUMP_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitJumpInsn(opcode, label.getLabel()); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new JumpInsnNode(opcode, clone(label, labels)); + } +} diff --git a/src/compiler/scala/tools/asm/tree/LabelNode.java b/src/compiler/scala/tools/asm/tree/LabelNode.java new file mode 100644 index 0000000000..523a8d6442 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/LabelNode.java @@ -0,0 +1,78 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; + +/** + * An {@link AbstractInsnNode} that encapsulates a {@link Label}. + */ +public class LabelNode extends AbstractInsnNode { + + private Label label; + + public LabelNode() { + super(-1); + } + + public LabelNode(final Label label) { + super(-1); + this.label = label; + } + + @Override + public int getType() { + return LABEL; + } + + public Label getLabel() { + if (label == null) { + label = new Label(); + } + return label; + } + + @Override + public void accept(final MethodVisitor cv) { + cv.visitLabel(getLabel()); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return labels.get(this); + } + + public void resetLabel() { + label = null; + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/LdcInsnNode.java b/src/compiler/scala/tools/asm/tree/LdcInsnNode.java new file mode 100644 index 0000000000..f8d115acd5 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/LdcInsnNode.java @@ -0,0 +1,77 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents an LDC instruction. + * + * @author Eric Bruneton + */ +public class LdcInsnNode extends AbstractInsnNode { + + /** + * The constant to be loaded on the stack. This parameter must be a non null + * {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a + * {@link String} or a {@link org.objectweb.asm.Type}. + */ + public Object cst; + + /** + * Constructs a new {@link LdcInsnNode}. + * + * @param cst the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String}. + */ + public LdcInsnNode(final Object cst) { + super(Opcodes.LDC); + this.cst = cst; + } + + @Override + public int getType() { + return LDC_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitLdcInsn(cst); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new LdcInsnNode(cst); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/LineNumberNode.java b/src/compiler/scala/tools/asm/tree/LineNumberNode.java new file mode 100644 index 0000000000..acc83c8d30 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/LineNumberNode.java @@ -0,0 +1,82 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a line number declaration. These nodes are pseudo + * instruction nodes in order to be inserted in an instruction list. + * + * @author Eric Bruneton + */ +public class LineNumberNode extends AbstractInsnNode { + + /** + * A line number. This number refers to the source file from which the class + * was compiled. + */ + public int line; + + /** + * The first instruction corresponding to this line number. + */ + public LabelNode start; + + /** + * Constructs a new {@link LineNumberNode}. + * + * @param line a line number. This number refers to the source file from + * which the class was compiled. + * @param start the first instruction corresponding to this line number. + */ + public LineNumberNode(final int line, final LabelNode start) { + super(-1); + this.line = line; + this.start = start; + } + + @Override + public int getType() { + return LINE; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitLineNumber(line, start.getLabel()); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new LineNumberNode(line, clone(start, labels)); + } +} diff --git a/src/compiler/scala/tools/asm/tree/LocalVariableNode.java b/src/compiler/scala/tools/asm/tree/LocalVariableNode.java new file mode 100644 index 0000000000..51cbd3ca00 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/LocalVariableNode.java @@ -0,0 +1,115 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a local variable declaration. + * + * @author Eric Bruneton + */ +public class LocalVariableNode { + + /** + * The name of a local variable. + */ + public String name; + + /** + * The type descriptor of this local variable. + */ + public String desc; + + /** + * The signature of this local variable. May be <tt>null</tt>. + */ + public String signature; + + /** + * The first instruction corresponding to the scope of this local variable + * (inclusive). + */ + public LabelNode start; + + /** + * The last instruction corresponding to the scope of this local variable + * (exclusive). + */ + public LabelNode end; + + /** + * The local variable's index. + */ + public int index; + + /** + * Constructs a new {@link LocalVariableNode}. + * + * @param name the name of a local variable. + * @param desc the type descriptor of this local variable. + * @param signature the signature of this local variable. May be + * <tt>null</tt>. + * @param start the first instruction corresponding to the scope of this + * local variable (inclusive). + * @param end the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index the local variable's index. + */ + public LocalVariableNode( + final String name, + final String desc, + final String signature, + final LabelNode start, + final LabelNode end, + final int index) + { + this.name = name; + this.desc = desc; + this.signature = signature; + this.start = start; + this.end = end; + this.index = index; + } + + /** + * Makes the given visitor visit this local variable declaration. + * + * @param mv a method visitor. + */ + public void accept(final MethodVisitor mv) { + mv.visitLocalVariable(name, + desc, + signature, + start.getLabel(), + end.getLabel(), + index); + } +} diff --git a/src/compiler/scala/tools/asm/tree/LookupSwitchInsnNode.java b/src/compiler/scala/tools/asm/tree/LookupSwitchInsnNode.java new file mode 100644 index 0000000000..6d0f971c29 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/LookupSwitchInsnNode.java @@ -0,0 +1,116 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a LOOKUPSWITCH instruction. + * + * @author Eric Bruneton + */ +public class LookupSwitchInsnNode extends AbstractInsnNode { + + /** + * Beginning of the default handler block. + */ + public LabelNode dflt; + + /** + * The values of the keys. This list is a list of {@link Integer} objects. + */ + public List<Integer> keys; + + /** + * Beginnings of the handler blocks. This list is a list of + * {@link LabelNode} objects. + */ + public List<LabelNode> labels; + + /** + * Constructs a new {@link LookupSwitchInsnNode}. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. <tt>labels[i]</tt> is + * the beginning of the handler block for the <tt>keys[i]</tt> key. + */ + public LookupSwitchInsnNode( + final LabelNode dflt, + final int[] keys, + final LabelNode[] labels) + { + super(Opcodes.LOOKUPSWITCH); + this.dflt = dflt; + this.keys = new ArrayList<Integer>(keys == null ? 0 : keys.length); + this.labels = new ArrayList<LabelNode>(labels == null ? 0 : labels.length); + if (keys != null) { + for (int i = 0; i < keys.length; ++i) { + this.keys.add(new Integer(keys[i])); + } + } + if (labels != null) { + this.labels.addAll(Arrays.asList(labels)); + } + } + + @Override + public int getType() { + return LOOKUPSWITCH_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + int[] keys = new int[this.keys.size()]; + for (int i = 0; i < keys.length; ++i) { + keys[i] = this.keys.get(i).intValue(); + } + Label[] labels = new Label[this.labels.size()]; + for (int i = 0; i < labels.length; ++i) { + labels[i] = this.labels.get(i).getLabel(); + } + mv.visitLookupSwitchInsn(dflt.getLabel(), keys, labels); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + LookupSwitchInsnNode clone = new LookupSwitchInsnNode(clone(dflt, + labels), null, clone(this.labels, labels)); + clone.keys.addAll(keys); + return clone; + } +} diff --git a/src/compiler/scala/tools/asm/tree/MethodInsnNode.java b/src/compiler/scala/tools/asm/tree/MethodInsnNode.java new file mode 100644 index 0000000000..c3036bc6b4 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/MethodInsnNode.java @@ -0,0 +1,107 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a method instruction. A method instruction is an + * instruction that invokes a method. + * + * @author Eric Bruneton + */ +public class MethodInsnNode extends AbstractInsnNode { + + /** + * The internal name of the method's owner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + */ + public String owner; + + /** + * The method's name. + */ + public String name; + + /** + * The method's descriptor (see {@link org.objectweb.asm.Type}). + */ + public String desc; + + /** + * Constructs a new {@link MethodInsnNode}. + * + * @param opcode the opcode of the type instruction to be constructed. This + * opcode must be INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see + * {@link org.objectweb.asm.Type#getInternalName() getInternalName}). + * @param name the method's name. + * @param desc the method's descriptor (see {@link org.objectweb.asm.Type}). + */ + public MethodInsnNode( + final int opcode, + final String owner, + final String name, + final String desc) + { + super(opcode); + this.owner = owner; + this.name = name; + this.desc = desc; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return METHOD_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new MethodInsnNode(opcode, owner, name, desc); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/MethodNode.java b/src/compiler/scala/tools/asm/tree/MethodNode.java new file mode 100644 index 0000000000..70ec39e058 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/MethodNode.java @@ -0,0 +1,645 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; + +/** + * A node that represents a method. + * + * @author Eric Bruneton + */ +public class MethodNode extends MethodVisitor { + + /** + * The method's access flags (see {@link Opcodes}). This field also + * indicates if the method is synthetic and/or deprecated. + */ + public int access; + + /** + * The method's name. + */ + public String name; + + /** + * The method's descriptor (see {@link Type}). + */ + public String desc; + + /** + * The method's signature. May be <tt>null</tt>. + */ + public String signature; + + /** + * The internal names of the method's exception classes (see + * {@link Type#getInternalName() getInternalName}). This list is a list of + * {@link String} objects. + */ + public List<String> exceptions; + + /** + * The runtime visible annotations of this method. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label visible + */ + public List<AnnotationNode> visibleAnnotations; + + /** + * The runtime invisible annotations of this method. This list is a list of + * {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label invisible + */ + public List<AnnotationNode> invisibleAnnotations; + + /** + * The non standard attributes of this method. This list is a list of + * {@link Attribute} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.Attribute + */ + public List<Attribute> attrs; + + /** + * The default value of this annotation interface method. This field must be + * a {@link Byte}, {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer}, {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type}, or an two elements String array (for + * enumeration values), a {@link AnnotationNode}, or a {@link List} of + * values of one of the preceding types. May be <tt>null</tt>. + */ + public Object annotationDefault; + + /** + * The runtime visible parameter annotations of this method. These lists are + * lists of {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label invisible parameters + */ + public List<AnnotationNode>[] visibleParameterAnnotations; + + /** + * The runtime invisible parameter annotations of this method. These lists + * are lists of {@link AnnotationNode} objects. May be <tt>null</tt>. + * + * @associates org.objectweb.asm.tree.AnnotationNode + * @label visible parameters + */ + public List<AnnotationNode>[] invisibleParameterAnnotations; + + /** + * The instructions of this method. This list is a list of + * {@link AbstractInsnNode} objects. + * + * @associates org.objectweb.asm.tree.AbstractInsnNode + * @label instructions + */ + public InsnList instructions; + + /** + * The try catch blocks of this method. This list is a list of + * {@link TryCatchBlockNode} objects. + * + * @associates org.objectweb.asm.tree.TryCatchBlockNode + */ + public List<TryCatchBlockNode> tryCatchBlocks; + + /** + * The maximum stack size of this method. + */ + public int maxStack; + + /** + * The maximum number of local variables of this method. + */ + public int maxLocals; + + /** + * The local variables of this method. This list is a list of + * {@link LocalVariableNode} objects. May be <tt>null</tt> + * + * @associates org.objectweb.asm.tree.LocalVariableNode + */ + public List<LocalVariableNode> localVariables; + + /** + * If the accept method has been called on this object. + */ + private boolean visited; + + /** + * Constructs an uninitialized {@link MethodNode}. <i>Subclasses must not + * use this constructor</i>. Instead, they must use the + * {@link #MethodNode(int)} version. + */ + public MethodNode() { + this(Opcodes.ASM4); + } + + /** + * Constructs an uninitialized {@link MethodNode}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + public MethodNode(final int api) { + super(api); + this.instructions = new InsnList(); + } + + /** + * Constructs a new {@link MethodNode}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the + * {@link #MethodNode(int, int, String, String, String, String[])} version. + * + * @param access the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be <tt>null</tt>. + * @param exceptions the internal names of the method's exception classes + * (see {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + */ + public MethodNode( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + this(Opcodes.ASM4, access, name, desc, signature, exceptions); + } + + /** + * Constructs a new {@link MethodNode}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param access the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be <tt>null</tt>. + * @param exceptions the internal names of the method's exception classes + * (see {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + */ + public MethodNode( + final int api, + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + super(api); + this.access = access; + this.name = name; + this.desc = desc; + this.signature = signature; + this.exceptions = new ArrayList<String>(exceptions == null + ? 0 + : exceptions.length); + boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0; + if (!isAbstract) { + this.localVariables = new ArrayList<LocalVariableNode>(5); + } + this.tryCatchBlocks = new ArrayList<TryCatchBlockNode>(); + if (exceptions != null) { + this.exceptions.addAll(Arrays.asList(exceptions)); + } + this.instructions = new InsnList(); + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotationDefault() { + return new AnnotationNode(new ArrayList<Object>(0) { + @Override + public boolean add(final Object o) { + annotationDefault = o; + return super.add(o); + } + }); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + AnnotationNode an = new AnnotationNode(desc); + if (visible) { + if (visibleAnnotations == null) { + visibleAnnotations = new ArrayList<AnnotationNode>(1); + } + visibleAnnotations.add(an); + } else { + if (invisibleAnnotations == null) { + invisibleAnnotations = new ArrayList<AnnotationNode>(1); + } + invisibleAnnotations.add(an); + } + return an; + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + AnnotationNode an = new AnnotationNode(desc); + if (visible) { + if (visibleParameterAnnotations == null) { + int params = Type.getArgumentTypes(this.desc).length; + visibleParameterAnnotations = (List<AnnotationNode>[])new List<?>[params]; + } + if (visibleParameterAnnotations[parameter] == null) { + visibleParameterAnnotations[parameter] = new ArrayList<AnnotationNode>(1); + } + visibleParameterAnnotations[parameter].add(an); + } else { + if (invisibleParameterAnnotations == null) { + int params = Type.getArgumentTypes(this.desc).length; + invisibleParameterAnnotations = (List<AnnotationNode>[])new List<?>[params]; + } + if (invisibleParameterAnnotations[parameter] == null) { + invisibleParameterAnnotations[parameter] = new ArrayList<AnnotationNode>(1); + } + invisibleParameterAnnotations[parameter].add(an); + } + return an; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attrs == null) { + attrs = new ArrayList<Attribute>(1); + } + attrs.add(attr); + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + instructions.add(new FrameNode(type, nLocal, local == null + ? null + : getLabelNodes(local), nStack, stack == null + ? null + : getLabelNodes(stack))); + } + + @Override + public void visitInsn(final int opcode) { + instructions.add(new InsnNode(opcode)); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + instructions.add(new IntInsnNode(opcode, operand)); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + instructions.add(new VarInsnNode(opcode, var)); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + instructions.add(new TypeInsnNode(opcode, type)); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + instructions.add(new FieldInsnNode(opcode, owner, name, desc)); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + instructions.add(new MethodInsnNode(opcode, owner, name, desc)); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs) + { + instructions.add(new InvokeDynamicInsnNode(name, desc, bsm, bsmArgs)); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + instructions.add(new JumpInsnNode(opcode, getLabelNode(label))); + } + + @Override + public void visitLabel(final Label label) { + instructions.add(getLabelNode(label)); + } + + @Override + public void visitLdcInsn(final Object cst) { + instructions.add(new LdcInsnNode(cst)); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + instructions.add(new IincInsnNode(var, increment)); + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + instructions.add(new TableSwitchInsnNode(min, + max, + getLabelNode(dflt), + getLabelNodes(labels))); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + instructions.add(new LookupSwitchInsnNode(getLabelNode(dflt), + keys, + getLabelNodes(labels))); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + instructions.add(new MultiANewArrayInsnNode(desc, dims)); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + tryCatchBlocks.add(new TryCatchBlockNode(getLabelNode(start), + getLabelNode(end), + getLabelNode(handler), + type)); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + localVariables.add(new LocalVariableNode(name, + desc, + signature, + getLabelNode(start), + getLabelNode(end), + index)); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + instructions.add(new LineNumberNode(line, getLabelNode(start))); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + + @Override + public void visitEnd() { + } + + /** + * Returns the LabelNode corresponding to the given Label. Creates a new + * LabelNode if necessary. The default implementation of this method uses + * the {@link Label#info} field to store associations between labels and + * label nodes. + * + * @param l a Label. + * @return the LabelNode corresponding to l. + */ + protected LabelNode getLabelNode(final Label l) { + if (!(l.info instanceof LabelNode)) { + l.info = new LabelNode(l); + } + return (LabelNode) l.info; + } + + private LabelNode[] getLabelNodes(final Label[] l) { + LabelNode[] nodes = new LabelNode[l.length]; + for (int i = 0; i < l.length; ++i) { + nodes[i] = getLabelNode(l[i]); + } + return nodes; + } + + private Object[] getLabelNodes(final Object[] objs) { + Object[] nodes = new Object[objs.length]; + for (int i = 0; i < objs.length; ++i) { + Object o = objs[i]; + if (o instanceof Label) { + o = getLabelNode((Label) o); + } + nodes[i] = o; + } + return nodes; + } + + // ------------------------------------------------------------------------ + // Accept method + // ------------------------------------------------------------------------ + + /** + * Checks that this method node is compatible with the given ASM API + * version. This methods checks that this node, and all its nodes + * recursively, do not contain elements that were introduced in more recent + * versions of the ASM API than the given version. + * + * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}. + */ + public void check(final int api) { + // nothing to do + } + + /** + * Makes the given class visitor visit this method. + * + * @param cv a class visitor. + */ + public void accept(final ClassVisitor cv) { + String[] exceptions = new String[this.exceptions.size()]; + this.exceptions.toArray(exceptions); + MethodVisitor mv = cv.visitMethod(access, + name, + desc, + signature, + exceptions); + if (mv != null) { + accept(mv); + } + } + + /** + * Makes the given method visitor visit this method. + * + * @param mv a method visitor. + */ + public void accept(final MethodVisitor mv) { + // visits the method attributes + int i, j, n; + if (annotationDefault != null) { + AnnotationVisitor av = mv.visitAnnotationDefault(); + AnnotationNode.accept(av, null, annotationDefault); + if (av != null) { + av.visitEnd(); + } + } + n = visibleAnnotations == null ? 0 : visibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = visibleAnnotations.get(i); + an.accept(mv.visitAnnotation(an.desc, true)); + } + n = invisibleAnnotations == null ? 0 : invisibleAnnotations.size(); + for (i = 0; i < n; ++i) { + AnnotationNode an = invisibleAnnotations.get(i); + an.accept(mv.visitAnnotation(an.desc, false)); + } + n = visibleParameterAnnotations == null + ? 0 + : visibleParameterAnnotations.length; + for (i = 0; i < n; ++i) { + List<?> l = visibleParameterAnnotations[i]; + if (l == null) { + continue; + } + for (j = 0; j < l.size(); ++j) { + AnnotationNode an = (AnnotationNode) l.get(j); + an.accept(mv.visitParameterAnnotation(i, an.desc, true)); + } + } + n = invisibleParameterAnnotations == null + ? 0 + : invisibleParameterAnnotations.length; + for (i = 0; i < n; ++i) { + List<?> l = invisibleParameterAnnotations[i]; + if (l == null) { + continue; + } + for (j = 0; j < l.size(); ++j) { + AnnotationNode an = (AnnotationNode) l.get(j); + an.accept(mv.visitParameterAnnotation(i, an.desc, false)); + } + } + if (visited) { + instructions.resetLabels(); + } + n = attrs == null ? 0 : attrs.size(); + for (i = 0; i < n; ++i) { + mv.visitAttribute(attrs.get(i)); + } + // visits the method's code + if (instructions.size() > 0) { + mv.visitCode(); + // visits try catch blocks + n = tryCatchBlocks == null ? 0 : tryCatchBlocks.size(); + for (i = 0; i < n; ++i) { + tryCatchBlocks.get(i).accept(mv); + } + // visits instructions + instructions.accept(mv); + // visits local variables + n = localVariables == null ? 0 : localVariables.size(); + for (i = 0; i < n; ++i) { + localVariables.get(i).accept(mv); + } + // visits maxs + mv.visitMaxs(maxStack, maxLocals); + visited = true; + } + mv.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/tree/MultiANewArrayInsnNode.java b/src/compiler/scala/tools/asm/tree/MultiANewArrayInsnNode.java new file mode 100644 index 0000000000..9dfba77335 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/MultiANewArrayInsnNode.java @@ -0,0 +1,81 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a MULTIANEWARRAY instruction. + * + * @author Eric Bruneton + */ +public class MultiANewArrayInsnNode extends AbstractInsnNode { + + /** + * An array type descriptor (see {@link org.objectweb.asm.Type}). + */ + public String desc; + + /** + * Number of dimensions of the array to allocate. + */ + public int dims; + + /** + * Constructs a new {@link MultiANewArrayInsnNode}. + * + * @param desc an array type descriptor (see {@link org.objectweb.asm.Type}). + * @param dims number of dimensions of the array to allocate. + */ + public MultiANewArrayInsnNode(final String desc, final int dims) { + super(Opcodes.MULTIANEWARRAY); + this.desc = desc; + this.dims = dims; + } + + @Override + public int getType() { + return MULTIANEWARRAY_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new MultiANewArrayInsnNode(desc, dims); + } + +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/TableSwitchInsnNode.java b/src/compiler/scala/tools/asm/tree/TableSwitchInsnNode.java new file mode 100644 index 0000000000..929ad9b32b --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/TableSwitchInsnNode.java @@ -0,0 +1,115 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A node that represents a TABLESWITCH instruction. + * + * @author Eric Bruneton + */ +public class TableSwitchInsnNode extends AbstractInsnNode { + + /** + * The minimum key value. + */ + public int min; + + /** + * The maximum key value. + */ + public int max; + + /** + * Beginning of the default handler block. + */ + public LabelNode dflt; + + /** + * Beginnings of the handler blocks. This list is a list of + * {@link LabelNode} objects. + */ + public List<LabelNode> labels; + + /** + * Constructs a new {@link TableSwitchInsnNode}. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. <tt>labels[i]</tt> is + * the beginning of the handler block for the <tt>min + i</tt> key. + */ + public TableSwitchInsnNode( + final int min, + final int max, + final LabelNode dflt, + final LabelNode... labels) + { + super(Opcodes.TABLESWITCH); + this.min = min; + this.max = max; + this.dflt = dflt; + this.labels = new ArrayList<LabelNode>(); + if (labels != null) { + this.labels.addAll(Arrays.asList(labels)); + } + } + + @Override + public int getType() { + return TABLESWITCH_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + Label[] labels = new Label[this.labels.size()]; + for (int i = 0; i < labels.length; ++i) { + labels[i] = this.labels.get(i).getLabel(); + } + mv.visitTableSwitchInsn(min, max, dflt.getLabel(), labels); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new TableSwitchInsnNode(min, + max, + clone(dflt, labels), + clone(this.labels, labels)); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/TryCatchBlockNode.java b/src/compiler/scala/tools/asm/tree/TryCatchBlockNode.java new file mode 100644 index 0000000000..375b4cfcb9 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/TryCatchBlockNode.java @@ -0,0 +1,94 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a try catch block. + * + * @author Eric Bruneton + */ +public class TryCatchBlockNode { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + public LabelNode start; + + /** + * End of the exception handler's scope (exclusive). + */ + public LabelNode end; + + /** + * Beginning of the exception handler's code. + */ + public LabelNode handler; + + /** + * Internal name of the type of exceptions handled by the handler. May be + * <tt>null</tt> to catch any exceptions (for "finally" blocks). + */ + public String type; + + /** + * Constructs a new {@link TryCatchBlockNode}. + * + * @param start beginning of the exception handler's scope (inclusive). + * @param end end of the exception handler's scope (exclusive). + * @param handler beginning of the exception handler's code. + * @param type internal name of the type of exceptions handled by the + * handler, or <tt>null</tt> to catch any exceptions (for "finally" + * blocks). + */ + public TryCatchBlockNode( + final LabelNode start, + final LabelNode end, + final LabelNode handler, + final String type) + { + this.start = start; + this.end = end; + this.handler = handler; + this.type = type; + } + + /** + * Makes the given visitor visit this try catch block. + * + * @param mv a method visitor. + */ + public void accept(final MethodVisitor mv) { + mv.visitTryCatchBlock(start.getLabel(), end.getLabel(), handler == null + ? null + : handler.getLabel(), type); + } +} diff --git a/src/compiler/scala/tools/asm/tree/TypeInsnNode.java b/src/compiler/scala/tools/asm/tree/TypeInsnNode.java new file mode 100644 index 0000000000..0b2666c498 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/TypeInsnNode.java @@ -0,0 +1,87 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a type instruction. A type instruction is an + * instruction that takes a type descriptor as parameter. + * + * @author Eric Bruneton + */ +public class TypeInsnNode extends AbstractInsnNode { + + /** + * The operand of this instruction. This operand is an internal name (see + * {@link org.objectweb.asm.Type}). + */ + public String desc; + + /** + * Constructs a new {@link TypeInsnNode}. + * + * @param opcode the opcode of the type instruction to be constructed. This + * opcode must be NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param desc the operand of the instruction to be constructed. This + * operand is an internal name (see {@link org.objectweb.asm.Type}). + */ + public TypeInsnNode(final int opcode, final String desc) { + super(opcode); + this.desc = desc; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be NEW, + * ANEWARRAY, CHECKCAST or INSTANCEOF. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return TYPE_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitTypeInsn(opcode, desc); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new TypeInsnNode(opcode, desc); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/VarInsnNode.java b/src/compiler/scala/tools/asm/tree/VarInsnNode.java new file mode 100644 index 0000000000..89f572db59 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/VarInsnNode.java @@ -0,0 +1,90 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree; + +import java.util.Map; + +import scala.tools.asm.MethodVisitor; + +/** + * A node that represents a local variable instruction. A local variable + * instruction is an instruction that loads or stores the value of a local + * variable. + * + * @author Eric Bruneton + */ +public class VarInsnNode extends AbstractInsnNode { + + /** + * The operand of this instruction. This operand is the index of a local + * variable. + */ + public int var; + + /** + * Constructs a new {@link VarInsnNode}. + * + * @param opcode the opcode of the local variable instruction to be + * constructed. This opcode must be ILOAD, LLOAD, FLOAD, DLOAD, + * ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var the operand of the instruction to be constructed. This operand + * is the index of a local variable. + */ + public VarInsnNode(final int opcode, final int var) { + super(opcode); + this.var = var; + } + + /** + * Sets the opcode of this instruction. + * + * @param opcode the new instruction opcode. This opcode must be ILOAD, + * LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE + * or RET. + */ + public void setOpcode(final int opcode) { + this.opcode = opcode; + } + + @Override + public int getType() { + return VAR_INSN; + } + + @Override + public void accept(final MethodVisitor mv) { + mv.visitVarInsn(opcode, var); + } + + @Override + public AbstractInsnNode clone(final Map<LabelNode, LabelNode> labels) { + return new VarInsnNode(opcode, var); + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/analysis/Analyzer.java b/src/compiler/scala/tools/asm/tree/analysis/Analyzer.java new file mode 100644 index 0000000000..df387b0b8e --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/Analyzer.java @@ -0,0 +1,549 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; +import scala.tools.asm.tree.IincInsnNode; +import scala.tools.asm.tree.InsnList; +import scala.tools.asm.tree.JumpInsnNode; +import scala.tools.asm.tree.LabelNode; +import scala.tools.asm.tree.LookupSwitchInsnNode; +import scala.tools.asm.tree.MethodNode; +import scala.tools.asm.tree.TableSwitchInsnNode; +import scala.tools.asm.tree.TryCatchBlockNode; +import scala.tools.asm.tree.VarInsnNode; + +/** + * A semantic bytecode analyzer. <i>This class does not fully check that JSR and + * RET instructions are valid.</i> + * + * @param <V> type of the Value used for the analysis. + * + * @author Eric Bruneton + */ +public class Analyzer<V extends Value> implements Opcodes { + + private final Interpreter<V> interpreter; + + private int n; + + private InsnList insns; + + private List<TryCatchBlockNode>[] handlers; + + private Frame<V>[] frames; + + private Subroutine[] subroutines; + + private boolean[] queued; + + private int[] queue; + + private int top; + + /** + * Constructs a new {@link Analyzer}. + * + * @param interpreter the interpreter to be used to symbolically interpret + * the bytecode instructions. + */ + public Analyzer(final Interpreter<V> interpreter) { + this.interpreter = interpreter; + } + + /** + * Analyzes the given method. + * + * @param owner the internal name of the class to which the method belongs. + * @param m the method to be analyzed. + * @return the symbolic state of the execution stack frame at each bytecode + * instruction of the method. The size of the returned array is + * equal to the number of instructions (and labels) of the method. A + * given frame is <tt>null</tt> if and only if the corresponding + * instruction cannot be reached (dead code). + * @throws AnalyzerException if a problem occurs during the analysis. + */ + public Frame<V>[] analyze(final String owner, final MethodNode m) + throws AnalyzerException + { + if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) { + frames = (Frame<V>[])new Frame<?>[0]; + return frames; + } + n = m.instructions.size(); + insns = m.instructions; + handlers = (List<TryCatchBlockNode>[])new List<?>[n]; + frames = (Frame<V>[])new Frame<?>[n]; + subroutines = new Subroutine[n]; + queued = new boolean[n]; + queue = new int[n]; + top = 0; + + // computes exception handlers for each instruction + for (int i = 0; i < m.tryCatchBlocks.size(); ++i) { + TryCatchBlockNode tcb = m.tryCatchBlocks.get(i); + int begin = insns.indexOf(tcb.start); + int end = insns.indexOf(tcb.end); + for (int j = begin; j < end; ++j) { + List<TryCatchBlockNode> insnHandlers = handlers[j]; + if (insnHandlers == null) { + insnHandlers = new ArrayList<TryCatchBlockNode>(); + handlers[j] = insnHandlers; + } + insnHandlers.add(tcb); + } + } + + // computes the subroutine for each instruction: + Subroutine main = new Subroutine(null, m.maxLocals, null); + List<AbstractInsnNode> subroutineCalls = new ArrayList<AbstractInsnNode>(); + Map<LabelNode, Subroutine> subroutineHeads = new HashMap<LabelNode, Subroutine>(); + findSubroutine(0, main, subroutineCalls); + while (!subroutineCalls.isEmpty()) { + JumpInsnNode jsr = (JumpInsnNode) subroutineCalls.remove(0); + Subroutine sub = subroutineHeads.get(jsr.label); + if (sub == null) { + sub = new Subroutine(jsr.label, m.maxLocals, jsr); + subroutineHeads.put(jsr.label, sub); + findSubroutine(insns.indexOf(jsr.label), sub, subroutineCalls); + } else { + sub.callers.add(jsr); + } + } + for (int i = 0; i < n; ++i) { + if (subroutines[i] != null && subroutines[i].start == null) { + subroutines[i] = null; + } + } + + // initializes the data structures for the control flow analysis + Frame<V> current = newFrame(m.maxLocals, m.maxStack); + Frame<V> handler = newFrame(m.maxLocals, m.maxStack); + current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))); + Type[] args = Type.getArgumentTypes(m.desc); + int local = 0; + if ((m.access & ACC_STATIC) == 0) { + Type ctype = Type.getObjectType(owner); + current.setLocal(local++, interpreter.newValue(ctype)); + } + for (int i = 0; i < args.length; ++i) { + current.setLocal(local++, interpreter.newValue(args[i])); + if (args[i].getSize() == 2) { + current.setLocal(local++, interpreter.newValue(null)); + } + } + while (local < m.maxLocals) { + current.setLocal(local++, interpreter.newValue(null)); + } + merge(0, current, null); + + init(owner, m); + + // control flow analysis + while (top > 0) { + int insn = queue[--top]; + Frame<V> f = frames[insn]; + Subroutine subroutine = subroutines[insn]; + queued[insn] = false; + + AbstractInsnNode insnNode = null; + try { + insnNode = m.instructions.get(insn); + int insnOpcode = insnNode.getOpcode(); + int insnType = insnNode.getType(); + + if (insnType == AbstractInsnNode.LABEL + || insnType == AbstractInsnNode.LINE + || insnType == AbstractInsnNode.FRAME) + { + merge(insn + 1, f, subroutine); + newControlFlowEdge(insn, insn + 1); + } else { + current.init(f).execute(insnNode, interpreter); + subroutine = subroutine == null ? null : subroutine.copy(); + + if (insnNode instanceof JumpInsnNode) { + JumpInsnNode j = (JumpInsnNode) insnNode; + if (insnOpcode != GOTO && insnOpcode != JSR) { + merge(insn + 1, current, subroutine); + newControlFlowEdge(insn, insn + 1); + } + int jump = insns.indexOf(j.label); + if (insnOpcode == JSR) { + merge(jump, current, new Subroutine(j.label, + m.maxLocals, + j)); + } else { + merge(jump, current, subroutine); + } + newControlFlowEdge(insn, jump); + } else if (insnNode instanceof LookupSwitchInsnNode) { + LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode; + int jump = insns.indexOf(lsi.dflt); + merge(jump, current, subroutine); + newControlFlowEdge(insn, jump); + for (int j = 0; j < lsi.labels.size(); ++j) { + LabelNode label = lsi.labels.get(j); + jump = insns.indexOf(label); + merge(jump, current, subroutine); + newControlFlowEdge(insn, jump); + } + } else if (insnNode instanceof TableSwitchInsnNode) { + TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode; + int jump = insns.indexOf(tsi.dflt); + merge(jump, current, subroutine); + newControlFlowEdge(insn, jump); + for (int j = 0; j < tsi.labels.size(); ++j) { + LabelNode label = tsi.labels.get(j); + jump = insns.indexOf(label); + merge(jump, current, subroutine); + newControlFlowEdge(insn, jump); + } + } else if (insnOpcode == RET) { + if (subroutine == null) { + throw new AnalyzerException(insnNode, "RET instruction outside of a sub routine"); + } + for (int i = 0; i < subroutine.callers.size(); ++i) { + JumpInsnNode caller = subroutine.callers.get(i); + int call = insns.indexOf(caller); + if (frames[call] != null) { + merge(call + 1, + frames[call], + current, + subroutines[call], + subroutine.access); + newControlFlowEdge(insn, call + 1); + } + } + } else if (insnOpcode != ATHROW + && (insnOpcode < IRETURN || insnOpcode > RETURN)) + { + if (subroutine != null) { + if (insnNode instanceof VarInsnNode) { + int var = ((VarInsnNode) insnNode).var; + subroutine.access[var] = true; + if (insnOpcode == LLOAD || insnOpcode == DLOAD + || insnOpcode == LSTORE + || insnOpcode == DSTORE) + { + subroutine.access[var + 1] = true; + } + } else if (insnNode instanceof IincInsnNode) { + int var = ((IincInsnNode) insnNode).var; + subroutine.access[var] = true; + } + } + merge(insn + 1, current, subroutine); + newControlFlowEdge(insn, insn + 1); + } + } + + List<TryCatchBlockNode> insnHandlers = handlers[insn]; + if (insnHandlers != null) { + for (int i = 0; i < insnHandlers.size(); ++i) { + TryCatchBlockNode tcb = insnHandlers.get(i); + Type type; + if (tcb.type == null) { + type = Type.getObjectType("java/lang/Throwable"); + } else { + type = Type.getObjectType(tcb.type); + } + int jump = insns.indexOf(tcb.handler); + if (newControlFlowExceptionEdge(insn, tcb)) { + handler.init(f); + handler.clearStack(); + handler.push(interpreter.newValue(type)); + merge(jump, handler, subroutine); + } + } + } + } catch (AnalyzerException e) { + throw new AnalyzerException(e.node, "Error at instruction " + insn + + ": " + e.getMessage(), e); + } catch (Exception e) { + throw new AnalyzerException(insnNode, "Error at instruction " + insn + + ": " + e.getMessage(), e); + } + } + + return frames; + } + + private void findSubroutine(int insn, final Subroutine sub, final List<AbstractInsnNode> calls) + throws AnalyzerException + { + while (true) { + if (insn < 0 || insn >= n) { + throw new AnalyzerException(null, "Execution can fall off end of the code"); + } + if (subroutines[insn] != null) { + return; + } + subroutines[insn] = sub.copy(); + AbstractInsnNode node = insns.get(insn); + + // calls findSubroutine recursively on normal successors + if (node instanceof JumpInsnNode) { + if (node.getOpcode() == JSR) { + // do not follow a JSR, it leads to another subroutine! + calls.add(node); + } else { + JumpInsnNode jnode = (JumpInsnNode) node; + findSubroutine(insns.indexOf(jnode.label), sub, calls); + } + } else if (node instanceof TableSwitchInsnNode) { + TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node; + findSubroutine(insns.indexOf(tsnode.dflt), sub, calls); + for (int i = tsnode.labels.size() - 1; i >= 0; --i) { + LabelNode l = tsnode.labels.get(i); + findSubroutine(insns.indexOf(l), sub, calls); + } + } else if (node instanceof LookupSwitchInsnNode) { + LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node; + findSubroutine(insns.indexOf(lsnode.dflt), sub, calls); + for (int i = lsnode.labels.size() - 1; i >= 0; --i) { + LabelNode l = lsnode.labels.get(i); + findSubroutine(insns.indexOf(l), sub, calls); + } + } + + // calls findSubroutine recursively on exception handler successors + List<TryCatchBlockNode> insnHandlers = handlers[insn]; + if (insnHandlers != null) { + for (int i = 0; i < insnHandlers.size(); ++i) { + TryCatchBlockNode tcb = insnHandlers.get(i); + findSubroutine(insns.indexOf(tcb.handler), sub, calls); + } + } + + // if insn does not falls through to the next instruction, return. + switch (node.getOpcode()) { + case GOTO: + case RET: + case TABLESWITCH: + case LOOKUPSWITCH: + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case ARETURN: + case RETURN: + case ATHROW: + return; + } + insn++; + } + } + + /** + * Returns the symbolic stack frame for each instruction of the last + * recently analyzed method. + * + * @return the symbolic state of the execution stack frame at each bytecode + * instruction of the method. The size of the returned array is + * equal to the number of instructions (and labels) of the method. A + * given frame is <tt>null</tt> if the corresponding instruction + * cannot be reached, or if an error occured during the analysis of + * the method. + */ + public Frame<V>[] getFrames() { + return frames; + } + + /** + * Returns the exception handlers for the given instruction. + * + * @param insn the index of an instruction of the last recently analyzed + * method. + * @return a list of {@link TryCatchBlockNode} objects. + */ + public List<TryCatchBlockNode> getHandlers(final int insn) { + return handlers[insn]; + } + + /** + * Initializes this analyzer. This method is called just before the + * execution of control flow analysis loop in #analyze. The default + * implementation of this method does nothing. + * + * @param owner the internal name of the class to which the method belongs. + * @param m the method to be analyzed. + * @throws AnalyzerException if a problem occurs. + */ + protected void init(String owner, MethodNode m) throws AnalyzerException { + } + + /** + * Constructs a new frame with the given size. + * + * @param nLocals the maximum number of local variables of the frame. + * @param nStack the maximum stack size of the frame. + * @return the created frame. + */ + protected Frame<V> newFrame(final int nLocals, final int nStack) { + return new Frame<V>(nLocals, nStack); + } + + /** + * Constructs a new frame that is identical to the given frame. + * + * @param src a frame. + * @return the created frame. + */ + protected Frame<V> newFrame(final Frame<? extends V> src) { + return new Frame<V>(src); + } + + /** + * Creates a control flow graph edge. The default implementation of this + * method does nothing. It can be overriden in order to construct the + * control flow graph of a method (this method is called by the + * {@link #analyze analyze} method during its visit of the method's code). + * + * @param insn an instruction index. + * @param successor index of a successor instruction. + */ + protected void newControlFlowEdge(final int insn, final int successor) { + } + + /** + * Creates a control flow graph edge corresponding to an exception handler. + * The default implementation of this method does nothing. It can be + * overridden in order to construct the control flow graph of a method (this + * method is called by the {@link #analyze analyze} method during its visit + * of the method's code). + * + * @param insn an instruction index. + * @param successor index of a successor instruction. + * @return true if this edge must be considered in the data flow analysis + * performed by this analyzer, or false otherwise. The default + * implementation of this method always returns true. + */ + protected boolean newControlFlowExceptionEdge( + final int insn, + final int successor) + { + return true; + } + + /** + * Creates a control flow graph edge corresponding to an exception handler. + * The default implementation of this method delegates to + * {@link #newControlFlowExceptionEdge(int, int) + * newControlFlowExceptionEdge(int, int)}. It can be overridden in order to + * construct the control flow graph of a method (this method is called by + * the {@link #analyze analyze} method during its visit of the method's + * code). + * + * @param insn an instruction index. + * @param tcb TryCatchBlockNode corresponding to this edge. + * @return true if this edge must be considered in the data flow analysis + * performed by this analyzer, or false otherwise. The default + * implementation of this method delegates to + * {@link #newControlFlowExceptionEdge(int, int) + * newControlFlowExceptionEdge(int, int)}. + */ + protected boolean newControlFlowExceptionEdge( + final int insn, + final TryCatchBlockNode tcb) + { + return newControlFlowExceptionEdge(insn, insns.indexOf(tcb.handler)); + } + + // ------------------------------------------------------------------------- + + private void merge( + final int insn, + final Frame<V> frame, + final Subroutine subroutine) throws AnalyzerException + { + Frame<V> oldFrame = frames[insn]; + Subroutine oldSubroutine = subroutines[insn]; + boolean changes; + + if (oldFrame == null) { + frames[insn] = newFrame(frame); + changes = true; + } else { + changes = oldFrame.merge(frame, interpreter); + } + + if (oldSubroutine == null) { + if (subroutine != null) { + subroutines[insn] = subroutine.copy(); + changes = true; + } + } else { + if (subroutine != null) { + changes |= oldSubroutine.merge(subroutine); + } + } + if (changes && !queued[insn]) { + queued[insn] = true; + queue[top++] = insn; + } + } + + private void merge( + final int insn, + final Frame<V> beforeJSR, + final Frame<V> afterRET, + final Subroutine subroutineBeforeJSR, + final boolean[] access) throws AnalyzerException + { + Frame<V> oldFrame = frames[insn]; + Subroutine oldSubroutine = subroutines[insn]; + boolean changes; + + afterRET.merge(beforeJSR, access); + + if (oldFrame == null) { + frames[insn] = newFrame(afterRET); + changes = true; + } else { + changes = oldFrame.merge(afterRET, interpreter); + } + + if (oldSubroutine != null && subroutineBeforeJSR != null) { + changes |= oldSubroutine.merge(subroutineBeforeJSR); + } + if (changes && !queued[insn]) { + queued[insn] = true; + queue[top++] = insn; + } + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/AnalyzerException.java b/src/compiler/scala/tools/asm/tree/analysis/AnalyzerException.java new file mode 100644 index 0000000000..a89bb3513f --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/AnalyzerException.java @@ -0,0 +1,64 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import scala.tools.asm.tree.AbstractInsnNode; + +/** + * Thrown if a problem occurs during the analysis of a method. + * + * @author Bing Ran + * @author Eric Bruneton + */ +public class AnalyzerException extends Exception { + + public final AbstractInsnNode node; + + public AnalyzerException(final AbstractInsnNode node, final String msg) { + super(msg); + this.node = node; + } + + public AnalyzerException(final AbstractInsnNode node, final String msg, final Throwable exception) { + super(msg, exception); + this.node = node; + } + + public AnalyzerException( + final AbstractInsnNode node, + final String msg, + final Object expected, + final Value encountered) + { + super((msg == null ? "Expected " : msg + ": expected ") + expected + + ", but found " + encountered); + this.node = node; + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/BasicInterpreter.java b/src/compiler/scala/tools/asm/tree/analysis/BasicInterpreter.java new file mode 100644 index 0000000000..64ddcc11e6 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/BasicInterpreter.java @@ -0,0 +1,365 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.List; + +import scala.tools.asm.Handle; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; +import scala.tools.asm.tree.FieldInsnNode; +import scala.tools.asm.tree.IntInsnNode; +import scala.tools.asm.tree.InvokeDynamicInsnNode; +import scala.tools.asm.tree.LdcInsnNode; +import scala.tools.asm.tree.MethodInsnNode; +import scala.tools.asm.tree.MultiANewArrayInsnNode; +import scala.tools.asm.tree.TypeInsnNode; + +/** + * An {@link Interpreter} for {@link BasicValue} values. + * + * @author Eric Bruneton + * @author Bing Ran + */ +public class BasicInterpreter extends Interpreter<BasicValue> implements + Opcodes +{ + + public BasicInterpreter() { + super(ASM4); + } + + protected BasicInterpreter(final int api) { + super(api); + } + + @Override + public BasicValue newValue(final Type type) { + if (type == null) { + return BasicValue.UNINITIALIZED_VALUE; + } + switch (type.getSort()) { + case Type.VOID: + return null; + case Type.BOOLEAN: + case Type.CHAR: + case Type.BYTE: + case Type.SHORT: + case Type.INT: + return BasicValue.INT_VALUE; + case Type.FLOAT: + return BasicValue.FLOAT_VALUE; + case Type.LONG: + return BasicValue.LONG_VALUE; + case Type.DOUBLE: + return BasicValue.DOUBLE_VALUE; + case Type.ARRAY: + case Type.OBJECT: + return BasicValue.REFERENCE_VALUE; + default: + throw new Error("Internal error"); + } + } + + @Override + public BasicValue newOperation(final AbstractInsnNode insn) + throws AnalyzerException + { + switch (insn.getOpcode()) { + case ACONST_NULL: + return newValue(Type.getObjectType("null")); + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + return BasicValue.INT_VALUE; + case LCONST_0: + case LCONST_1: + return BasicValue.LONG_VALUE; + case FCONST_0: + case FCONST_1: + case FCONST_2: + return BasicValue.FLOAT_VALUE; + case DCONST_0: + case DCONST_1: + return BasicValue.DOUBLE_VALUE; + case BIPUSH: + case SIPUSH: + return BasicValue.INT_VALUE; + case LDC: + Object cst = ((LdcInsnNode) insn).cst; + if (cst instanceof Integer) { + return BasicValue.INT_VALUE; + } else if (cst instanceof Float) { + return BasicValue.FLOAT_VALUE; + } else if (cst instanceof Long) { + return BasicValue.LONG_VALUE; + } else if (cst instanceof Double) { + return BasicValue.DOUBLE_VALUE; + } else if (cst instanceof String) { + return newValue(Type.getObjectType("java/lang/String")); + } else if (cst instanceof Type) { + int sort = ((Type) cst).getSort(); + if (sort == Type.OBJECT || sort == Type.ARRAY) { + return newValue(Type.getObjectType("java/lang/Class")); + } else if (sort == Type.METHOD) { + return newValue(Type.getObjectType("java/lang/invoke/MethodType")); + } else { + throw new IllegalArgumentException("Illegal LDC constant " + cst); + } + } else if (cst instanceof Handle) { + return newValue(Type.getObjectType("java/lang/invoke/MethodHandle")); + } else { + throw new IllegalArgumentException("Illegal LDC constant " + cst); + } + case JSR: + return BasicValue.RETURNADDRESS_VALUE; + case GETSTATIC: + return newValue(Type.getType(((FieldInsnNode) insn).desc)); + case NEW: + return newValue(Type.getObjectType(((TypeInsnNode) insn).desc)); + default: + throw new Error("Internal error."); + } + } + + @Override + public BasicValue copyOperation(final AbstractInsnNode insn, final BasicValue value) + throws AnalyzerException + { + return value; + } + + @Override + public BasicValue unaryOperation(final AbstractInsnNode insn, final BasicValue value) + throws AnalyzerException + { + switch (insn.getOpcode()) { + case INEG: + case IINC: + case L2I: + case F2I: + case D2I: + case I2B: + case I2C: + case I2S: + return BasicValue.INT_VALUE; + case FNEG: + case I2F: + case L2F: + case D2F: + return BasicValue.FLOAT_VALUE; + case LNEG: + case I2L: + case F2L: + case D2L: + return BasicValue.LONG_VALUE; + case DNEG: + case I2D: + case L2D: + case F2D: + return BasicValue.DOUBLE_VALUE; + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case TABLESWITCH: + case LOOKUPSWITCH: + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case ARETURN: + case PUTSTATIC: + return null; + case GETFIELD: + return newValue(Type.getType(((FieldInsnNode) insn).desc)); + case NEWARRAY: + switch (((IntInsnNode) insn).operand) { + case T_BOOLEAN: + return newValue(Type.getType("[Z")); + case T_CHAR: + return newValue(Type.getType("[C")); + case T_BYTE: + return newValue(Type.getType("[B")); + case T_SHORT: + return newValue(Type.getType("[S")); + case T_INT: + return newValue(Type.getType("[I")); + case T_FLOAT: + return newValue(Type.getType("[F")); + case T_DOUBLE: + return newValue(Type.getType("[D")); + case T_LONG: + return newValue(Type.getType("[J")); + default: + throw new AnalyzerException(insn, "Invalid array type"); + } + case ANEWARRAY: + String desc = ((TypeInsnNode) insn).desc; + return newValue(Type.getType("[" + Type.getObjectType(desc))); + case ARRAYLENGTH: + return BasicValue.INT_VALUE; + case ATHROW: + return null; + case CHECKCAST: + desc = ((TypeInsnNode) insn).desc; + return newValue(Type.getObjectType(desc)); + case INSTANCEOF: + return BasicValue.INT_VALUE; + case MONITORENTER: + case MONITOREXIT: + case IFNULL: + case IFNONNULL: + return null; + default: + throw new Error("Internal error."); + } + } + + @Override + public BasicValue binaryOperation( + final AbstractInsnNode insn, + final BasicValue value1, + final BasicValue value2) throws AnalyzerException + { + switch (insn.getOpcode()) { + case IALOAD: + case BALOAD: + case CALOAD: + case SALOAD: + case IADD: + case ISUB: + case IMUL: + case IDIV: + case IREM: + case ISHL: + case ISHR: + case IUSHR: + case IAND: + case IOR: + case IXOR: + return BasicValue.INT_VALUE; + case FALOAD: + case FADD: + case FSUB: + case FMUL: + case FDIV: + case FREM: + return BasicValue.FLOAT_VALUE; + case LALOAD: + case LADD: + case LSUB: + case LMUL: + case LDIV: + case LREM: + case LSHL: + case LSHR: + case LUSHR: + case LAND: + case LOR: + case LXOR: + return BasicValue.LONG_VALUE; + case DALOAD: + case DADD: + case DSUB: + case DMUL: + case DDIV: + case DREM: + return BasicValue.DOUBLE_VALUE; + case AALOAD: + return BasicValue.REFERENCE_VALUE; + case LCMP: + case FCMPL: + case FCMPG: + case DCMPL: + case DCMPG: + return BasicValue.INT_VALUE; + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + case PUTFIELD: + return null; + default: + throw new Error("Internal error."); + } + } + + @Override + public BasicValue ternaryOperation( + final AbstractInsnNode insn, + final BasicValue value1, + final BasicValue value2, + final BasicValue value3) throws AnalyzerException + { + return null; + } + + @Override + public BasicValue naryOperation(final AbstractInsnNode insn, final List<? extends BasicValue> values) + throws AnalyzerException + { + int opcode = insn.getOpcode(); + if (opcode == MULTIANEWARRAY) { + return newValue(Type.getType(((MultiANewArrayInsnNode) insn).desc)); + } else if (opcode == INVOKEDYNAMIC){ + return newValue(Type.getReturnType(((InvokeDynamicInsnNode) insn).desc)); + } else { + return newValue(Type.getReturnType(((MethodInsnNode) insn).desc)); + } + } + + @Override + public void returnOperation( + final AbstractInsnNode insn, + final BasicValue value, + final BasicValue expected) throws AnalyzerException + { + } + + @Override + public BasicValue merge(final BasicValue v, final BasicValue w) { + if (!v.equals(w)) { + return BasicValue.UNINITIALIZED_VALUE; + } + return v; + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/BasicValue.java b/src/compiler/scala/tools/asm/tree/analysis/BasicValue.java new file mode 100644 index 0000000000..6c449db9b0 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/BasicValue.java @@ -0,0 +1,108 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import scala.tools.asm.Type; + +/** + * A {@link Value} that is represented by its type in a seven types type system. + * This type system distinguishes the UNINITIALZED, INT, FLOAT, LONG, DOUBLE, + * REFERENCE and RETURNADDRESS types. + * + * @author Eric Bruneton + */ +public class BasicValue implements Value { + + public static final BasicValue UNINITIALIZED_VALUE = new BasicValue(null); + + public static final BasicValue INT_VALUE = new BasicValue(Type.INT_TYPE); + + public static final BasicValue FLOAT_VALUE = new BasicValue(Type.FLOAT_TYPE); + + public static final BasicValue LONG_VALUE = new BasicValue(Type.LONG_TYPE); + + public static final BasicValue DOUBLE_VALUE = new BasicValue(Type.DOUBLE_TYPE); + + public static final BasicValue REFERENCE_VALUE = new BasicValue(Type.getObjectType("java/lang/Object")); + + public static final BasicValue RETURNADDRESS_VALUE = new BasicValue(Type.VOID_TYPE); + + private final Type type; + + public BasicValue(final Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public int getSize() { + return type == Type.LONG_TYPE || type == Type.DOUBLE_TYPE ? 2 : 1; + } + + public boolean isReference() { + return type != null + && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY); + } + + @Override + public boolean equals(final Object value) { + if (value == this) { + return true; + } else if (value instanceof BasicValue) { + if (type == null) { + return ((BasicValue) value).type == null; + } else { + return type.equals(((BasicValue) value).type); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + return type == null ? 0 : type.hashCode(); + } + + @Override + public String toString() { + if (this == UNINITIALIZED_VALUE) { + return "."; + } else if (this == RETURNADDRESS_VALUE) { + return "A"; + } else if (this == REFERENCE_VALUE) { + return "R"; + } else { + return type.getDescriptor(); + } + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/BasicVerifier.java b/src/compiler/scala/tools/asm/tree/analysis/BasicVerifier.java new file mode 100644 index 0000000000..9297dd9294 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/BasicVerifier.java @@ -0,0 +1,459 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.List; + +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; +import scala.tools.asm.tree.FieldInsnNode; +import scala.tools.asm.tree.InvokeDynamicInsnNode; +import scala.tools.asm.tree.MethodInsnNode; + +/** + * An extended {@link BasicInterpreter} that checks that bytecode instructions + * are correctly used. + * + * @author Eric Bruneton + * @author Bing Ran + */ +public class BasicVerifier extends BasicInterpreter { + + public BasicVerifier() { + super(ASM4); + } + + protected BasicVerifier(final int api) { + super(api); + } + + @Override + public BasicValue copyOperation(final AbstractInsnNode insn, final BasicValue value) + throws AnalyzerException + { + Value expected; + switch (insn.getOpcode()) { + case ILOAD: + case ISTORE: + expected = BasicValue.INT_VALUE; + break; + case FLOAD: + case FSTORE: + expected = BasicValue.FLOAT_VALUE; + break; + case LLOAD: + case LSTORE: + expected = BasicValue.LONG_VALUE; + break; + case DLOAD: + case DSTORE: + expected = BasicValue.DOUBLE_VALUE; + break; + case ALOAD: + if (!value.isReference()) { + throw new AnalyzerException(insn, + null, + "an object reference", + value); + } + return value; + case ASTORE: + if (!value.isReference() + && !BasicValue.RETURNADDRESS_VALUE.equals(value)) + { + throw new AnalyzerException(insn, + null, + "an object reference or a return address", + value); + } + return value; + default: + return value; + } + if (!expected.equals(value)) { + throw new AnalyzerException(insn, null, expected, value); + } + return value; + } + + @Override + public BasicValue unaryOperation(final AbstractInsnNode insn, final BasicValue value) + throws AnalyzerException + { + BasicValue expected; + switch (insn.getOpcode()) { + case INEG: + case IINC: + case I2F: + case I2L: + case I2D: + case I2B: + case I2C: + case I2S: + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case TABLESWITCH: + case LOOKUPSWITCH: + case IRETURN: + case NEWARRAY: + case ANEWARRAY: + expected = BasicValue.INT_VALUE; + break; + case FNEG: + case F2I: + case F2L: + case F2D: + case FRETURN: + expected = BasicValue.FLOAT_VALUE; + break; + case LNEG: + case L2I: + case L2F: + case L2D: + case LRETURN: + expected = BasicValue.LONG_VALUE; + break; + case DNEG: + case D2I: + case D2F: + case D2L: + case DRETURN: + expected = BasicValue.DOUBLE_VALUE; + break; + case GETFIELD: + expected = newValue(Type.getObjectType(((FieldInsnNode) insn).owner)); + break; + case CHECKCAST: + if (!value.isReference()) { + throw new AnalyzerException(insn, + null, + "an object reference", + value); + } + return super.unaryOperation(insn, value); + case ARRAYLENGTH: + if (!isArrayValue(value)) { + throw new AnalyzerException(insn, + null, + "an array reference", + value); + } + return super.unaryOperation(insn, value); + case ARETURN: + case ATHROW: + case INSTANCEOF: + case MONITORENTER: + case MONITOREXIT: + case IFNULL: + case IFNONNULL: + if (!value.isReference()) { + throw new AnalyzerException(insn, + null, + "an object reference", + value); + } + return super.unaryOperation(insn, value); + case PUTSTATIC: + expected = newValue(Type.getType(((FieldInsnNode) insn).desc)); + break; + default: + throw new Error("Internal error."); + } + if (!isSubTypeOf(value, expected)) { + throw new AnalyzerException(insn, null, expected, value); + } + return super.unaryOperation(insn, value); + } + + @Override + public BasicValue binaryOperation( + final AbstractInsnNode insn, + final BasicValue value1, + final BasicValue value2) throws AnalyzerException + { + BasicValue expected1; + BasicValue expected2; + switch (insn.getOpcode()) { + case IALOAD: + expected1 = newValue(Type.getType("[I")); + expected2 = BasicValue.INT_VALUE; + break; + case BALOAD: + if (isSubTypeOf(value1, newValue(Type.getType("[Z")))) { + expected1 = newValue(Type.getType("[Z")); + } else { + expected1 = newValue(Type.getType("[B")); + } + expected2 = BasicValue.INT_VALUE; + break; + case CALOAD: + expected1 = newValue(Type.getType("[C")); + expected2 = BasicValue.INT_VALUE; + break; + case SALOAD: + expected1 = newValue(Type.getType("[S")); + expected2 = BasicValue.INT_VALUE; + break; + case LALOAD: + expected1 = newValue(Type.getType("[J")); + expected2 = BasicValue.INT_VALUE; + break; + case FALOAD: + expected1 = newValue(Type.getType("[F")); + expected2 = BasicValue.INT_VALUE; + break; + case DALOAD: + expected1 = newValue(Type.getType("[D")); + expected2 = BasicValue.INT_VALUE; + break; + case AALOAD: + expected1 = newValue(Type.getType("[Ljava/lang/Object;")); + expected2 = BasicValue.INT_VALUE; + break; + case IADD: + case ISUB: + case IMUL: + case IDIV: + case IREM: + case ISHL: + case ISHR: + case IUSHR: + case IAND: + case IOR: + case IXOR: + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + expected1 = BasicValue.INT_VALUE; + expected2 = BasicValue.INT_VALUE; + break; + case FADD: + case FSUB: + case FMUL: + case FDIV: + case FREM: + case FCMPL: + case FCMPG: + expected1 = BasicValue.FLOAT_VALUE; + expected2 = BasicValue.FLOAT_VALUE; + break; + case LADD: + case LSUB: + case LMUL: + case LDIV: + case LREM: + case LAND: + case LOR: + case LXOR: + case LCMP: + expected1 = BasicValue.LONG_VALUE; + expected2 = BasicValue.LONG_VALUE; + break; + case LSHL: + case LSHR: + case LUSHR: + expected1 = BasicValue.LONG_VALUE; + expected2 = BasicValue.INT_VALUE; + break; + case DADD: + case DSUB: + case DMUL: + case DDIV: + case DREM: + case DCMPL: + case DCMPG: + expected1 = BasicValue.DOUBLE_VALUE; + expected2 = BasicValue.DOUBLE_VALUE; + break; + case IF_ACMPEQ: + case IF_ACMPNE: + expected1 = BasicValue.REFERENCE_VALUE; + expected2 = BasicValue.REFERENCE_VALUE; + break; + case PUTFIELD: + FieldInsnNode fin = (FieldInsnNode) insn; + expected1 = newValue(Type.getObjectType(fin.owner)); + expected2 = newValue(Type.getType(fin.desc)); + break; + default: + throw new Error("Internal error."); + } + if (!isSubTypeOf(value1, expected1)) { + throw new AnalyzerException(insn, "First argument", expected1, value1); + } else if (!isSubTypeOf(value2, expected2)) { + throw new AnalyzerException(insn, "Second argument", expected2, value2); + } + if (insn.getOpcode() == AALOAD) { + return getElementValue(value1); + } else { + return super.binaryOperation(insn, value1, value2); + } + } + + @Override + public BasicValue ternaryOperation( + final AbstractInsnNode insn, + final BasicValue value1, + final BasicValue value2, + final BasicValue value3) throws AnalyzerException + { + BasicValue expected1; + BasicValue expected3; + switch (insn.getOpcode()) { + case IASTORE: + expected1 = newValue(Type.getType("[I")); + expected3 = BasicValue.INT_VALUE; + break; + case BASTORE: + if (isSubTypeOf(value1, newValue(Type.getType("[Z")))) { + expected1 = newValue(Type.getType("[Z")); + } else { + expected1 = newValue(Type.getType("[B")); + } + expected3 = BasicValue.INT_VALUE; + break; + case CASTORE: + expected1 = newValue(Type.getType("[C")); + expected3 = BasicValue.INT_VALUE; + break; + case SASTORE: + expected1 = newValue(Type.getType("[S")); + expected3 = BasicValue.INT_VALUE; + break; + case LASTORE: + expected1 = newValue(Type.getType("[J")); + expected3 = BasicValue.LONG_VALUE; + break; + case FASTORE: + expected1 = newValue(Type.getType("[F")); + expected3 = BasicValue.FLOAT_VALUE; + break; + case DASTORE: + expected1 = newValue(Type.getType("[D")); + expected3 = BasicValue.DOUBLE_VALUE; + break; + case AASTORE: + expected1 = value1; + expected3 = BasicValue.REFERENCE_VALUE; + break; + default: + throw new Error("Internal error."); + } + if (!isSubTypeOf(value1, expected1)) { + throw new AnalyzerException(insn, "First argument", "a " + expected1 + + " array reference", value1); + } else if (!BasicValue.INT_VALUE.equals(value2)) { + throw new AnalyzerException(insn, "Second argument", + BasicValue.INT_VALUE, + value2); + } else if (!isSubTypeOf(value3, expected3)) { + throw new AnalyzerException(insn, "Third argument", expected3, value3); + } + return null; + } + + @Override + public BasicValue naryOperation(final AbstractInsnNode insn, final List<? extends BasicValue> values) + throws AnalyzerException + { + int opcode = insn.getOpcode(); + if (opcode == MULTIANEWARRAY) { + for (int i = 0; i < values.size(); ++i) { + if (!BasicValue.INT_VALUE.equals(values.get(i))) { + throw new AnalyzerException(insn, + null, + BasicValue.INT_VALUE, + values.get(i)); + } + } + } else { + int i = 0; + int j = 0; + if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { + Type owner = Type.getObjectType(((MethodInsnNode) insn).owner); + if (!isSubTypeOf(values.get(i++), newValue(owner))) { + throw new AnalyzerException(insn, "Method owner", + newValue(owner), + values.get(0)); + } + } + String desc = (opcode == INVOKEDYNAMIC)? + ((InvokeDynamicInsnNode) insn).desc: + ((MethodInsnNode) insn).desc; + Type[] args = Type.getArgumentTypes(desc); + while (i < values.size()) { + BasicValue expected = newValue(args[j++]); + BasicValue encountered = values.get(i++); + if (!isSubTypeOf(encountered, expected)) { + throw new AnalyzerException(insn, + "Argument " + j, + expected, + encountered); + } + } + } + return super.naryOperation(insn, values); + } + + @Override + public void returnOperation( + final AbstractInsnNode insn, + final BasicValue value, + final BasicValue expected) throws AnalyzerException + { + if (!isSubTypeOf(value, expected)) { + throw new AnalyzerException(insn, + "Incompatible return type", + expected, + value); + } + } + + protected boolean isArrayValue(final BasicValue value) { + return value.isReference(); + } + + protected BasicValue getElementValue(final BasicValue objectArrayValue) + throws AnalyzerException + { + return BasicValue.REFERENCE_VALUE; + } + + protected boolean isSubTypeOf(final BasicValue value, final BasicValue expected) { + return value.equals(expected); + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/Frame.java b/src/compiler/scala/tools/asm/tree/analysis/Frame.java new file mode 100644 index 0000000000..fe19c2c9ae --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/Frame.java @@ -0,0 +1,709 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.ArrayList; +import java.util.List; + +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; +import scala.tools.asm.tree.IincInsnNode; +import scala.tools.asm.tree.InvokeDynamicInsnNode; +import scala.tools.asm.tree.MethodInsnNode; +import scala.tools.asm.tree.MultiANewArrayInsnNode; +import scala.tools.asm.tree.VarInsnNode; + +/** + * A symbolic execution stack frame. A stack frame contains a set of local + * variable slots, and an operand stack. Warning: long and double values are + * represented by <i>two</i> slots in local variables, and by <i>one</i> slot + * in the operand stack. + * + * @param <V> type of the Value used for the analysis. + * + * @author Eric Bruneton + */ +public class Frame<V extends Value> { + + /** + * The expected return type of the analyzed method, or <tt>null</tt> if the + * method returns void. + */ + private V returnValue; + + /** + * The local variables and operand stack of this frame. + */ + private V[] values; + + /** + * The number of local variables of this frame. + */ + private int locals; + + /** + * The number of elements in the operand stack. + */ + private int top; + + /** + * Constructs a new frame with the given size. + * + * @param nLocals the maximum number of local variables of the frame. + * @param nStack the maximum stack size of the frame. + */ + public Frame(final int nLocals, final int nStack) { + this.values = (V[]) new Value[nLocals + nStack]; + this.locals = nLocals; + } + + /** + * Constructs a new frame that is identical to the given frame. + * + * @param src a frame. + */ + public Frame(final Frame<? extends V> src) { + this(src.locals, src.values.length - src.locals); + init(src); + } + + /** + * Copies the state of the given frame into this frame. + * + * @param src a frame. + * @return this frame. + */ + public Frame<V> init(final Frame<? extends V> src) { + returnValue = src.returnValue; + System.arraycopy(src.values, 0, values, 0, values.length); + top = src.top; + return this; + } + + /** + * Sets the expected return type of the analyzed method. + * + * @param v the expected return type of the analyzed method, or + * <tt>null</tt> if the method returns void. + */ + public void setReturn(final V v) { + returnValue = v; + } + + /** + * Returns the maximum number of local variables of this frame. + * + * @return the maximum number of local variables of this frame. + */ + public int getLocals() { + return locals; + } + + /** + * Returns the value of the given local variable. + * + * @param i a local variable index. + * @return the value of the given local variable. + * @throws IndexOutOfBoundsException if the variable does not exist. + */ + public V getLocal(final int i) throws IndexOutOfBoundsException { + if (i >= locals) { + throw new IndexOutOfBoundsException("Trying to access an inexistant local variable"); + } + return values[i]; + } + + /** + * Sets the value of the given local variable. + * + * @param i a local variable index. + * @param value the new value of this local variable. + * @throws IndexOutOfBoundsException if the variable does not exist. + */ + public void setLocal(final int i, final V value) + throws IndexOutOfBoundsException + { + if (i >= locals) { + throw new IndexOutOfBoundsException("Trying to access an inexistant local variable "+i); + } + values[i] = value; + } + + /** + * Returns the number of values in the operand stack of this frame. Long and + * double values are treated as single values. + * + * @return the number of values in the operand stack of this frame. + */ + public int getStackSize() { + return top; + } + + /** + * Returns the value of the given operand stack slot. + * + * @param i the index of an operand stack slot. + * @return the value of the given operand stack slot. + * @throws IndexOutOfBoundsException if the operand stack slot does not + * exist. + */ + public V getStack(final int i) throws IndexOutOfBoundsException { + return values[i + locals]; + } + + /** + * Clears the operand stack of this frame. + */ + public void clearStack() { + top = 0; + } + + /** + * Pops a value from the operand stack of this frame. + * + * @return the value that has been popped from the stack. + * @throws IndexOutOfBoundsException if the operand stack is empty. + */ + public V pop() throws IndexOutOfBoundsException { + if (top == 0) { + throw new IndexOutOfBoundsException("Cannot pop operand off an empty stack."); + } + return values[--top + locals]; + } + + /** + * Pushes a value into the operand stack of this frame. + * + * @param value the value that must be pushed into the stack. + * @throws IndexOutOfBoundsException if the operand stack is full. + */ + public void push(final V value) throws IndexOutOfBoundsException { + if (top + locals >= values.length) { + throw new IndexOutOfBoundsException("Insufficient maximum stack size."); + } + values[top++ + locals] = value; + } + + public void execute( + final AbstractInsnNode insn, + final Interpreter<V> interpreter) throws AnalyzerException + { + V value1, value2, value3, value4; + List<V> values; + int var; + + switch (insn.getOpcode()) { + case Opcodes.NOP: + break; + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.LDC: + push(interpreter.newOperation(insn)); + break; + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + push(interpreter.copyOperation(insn, + getLocal(((VarInsnNode) insn).var))); + break; + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + value2 = pop(); + value1 = pop(); + push(interpreter.binaryOperation(insn, value1, value2)); + break; + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + value1 = interpreter.copyOperation(insn, pop()); + var = ((VarInsnNode) insn).var; + setLocal(var, value1); + if (value1.getSize() == 2) { + setLocal(var + 1, interpreter.newValue(null)); + } + if (var > 0) { + Value local = getLocal(var - 1); + if (local != null && local.getSize() == 2) { + setLocal(var - 1, interpreter.newValue(null)); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + value3 = pop(); + value2 = pop(); + value1 = pop(); + interpreter.ternaryOperation(insn, value1, value2, value3); + break; + case Opcodes.POP: + if (pop().getSize() == 2) { + throw new AnalyzerException(insn, "Illegal use of POP"); + } + break; + case Opcodes.POP2: + if (pop().getSize() == 1) { + if (pop().getSize() != 1) { + throw new AnalyzerException(insn, "Illegal use of POP2"); + } + } + break; + case Opcodes.DUP: + value1 = pop(); + if (value1.getSize() != 1) { + throw new AnalyzerException(insn, "Illegal use of DUP"); + } + push(value1); + push(interpreter.copyOperation(insn, value1)); + break; + case Opcodes.DUP_X1: + value1 = pop(); + value2 = pop(); + if (value1.getSize() != 1 || value2.getSize() != 1) { + throw new AnalyzerException(insn, "Illegal use of DUP_X1"); + } + push(interpreter.copyOperation(insn, value1)); + push(value2); + push(value1); + break; + case Opcodes.DUP_X2: + value1 = pop(); + if (value1.getSize() == 1) { + value2 = pop(); + if (value2.getSize() == 1) { + value3 = pop(); + if (value3.getSize() == 1) { + push(interpreter.copyOperation(insn, value1)); + push(value3); + push(value2); + push(value1); + break; + } + } else { + push(interpreter.copyOperation(insn, value1)); + push(value2); + push(value1); + break; + } + } + throw new AnalyzerException(insn, "Illegal use of DUP_X2"); + case Opcodes.DUP2: + value1 = pop(); + if (value1.getSize() == 1) { + value2 = pop(); + if (value2.getSize() == 1) { + push(value2); + push(value1); + push(interpreter.copyOperation(insn, value2)); + push(interpreter.copyOperation(insn, value1)); + break; + } + } else { + push(value1); + push(interpreter.copyOperation(insn, value1)); + break; + } + throw new AnalyzerException(insn, "Illegal use of DUP2"); + case Opcodes.DUP2_X1: + value1 = pop(); + if (value1.getSize() == 1) { + value2 = pop(); + if (value2.getSize() == 1) { + value3 = pop(); + if (value3.getSize() == 1) { + push(interpreter.copyOperation(insn, value2)); + push(interpreter.copyOperation(insn, value1)); + push(value3); + push(value2); + push(value1); + break; + } + } + } else { + value2 = pop(); + if (value2.getSize() == 1) { + push(interpreter.copyOperation(insn, value1)); + push(value2); + push(value1); + break; + } + } + throw new AnalyzerException(insn, "Illegal use of DUP2_X1"); + case Opcodes.DUP2_X2: + value1 = pop(); + if (value1.getSize() == 1) { + value2 = pop(); + if (value2.getSize() == 1) { + value3 = pop(); + if (value3.getSize() == 1) { + value4 = pop(); + if (value4.getSize() == 1) { + push(interpreter.copyOperation(insn, value2)); + push(interpreter.copyOperation(insn, value1)); + push(value4); + push(value3); + push(value2); + push(value1); + break; + } + } else { + push(interpreter.copyOperation(insn, value2)); + push(interpreter.copyOperation(insn, value1)); + push(value3); + push(value2); + push(value1); + break; + } + } + } else { + value2 = pop(); + if (value2.getSize() == 1) { + value3 = pop(); + if (value3.getSize() == 1) { + push(interpreter.copyOperation(insn, value1)); + push(value3); + push(value2); + push(value1); + break; + } + } else { + push(interpreter.copyOperation(insn, value1)); + push(value2); + push(value1); + break; + } + } + throw new AnalyzerException(insn, "Illegal use of DUP2_X2"); + case Opcodes.SWAP: + value2 = pop(); + value1 = pop(); + if (value1.getSize() != 1 || value2.getSize() != 1) { + throw new AnalyzerException(insn, "Illegal use of SWAP"); + } + push(interpreter.copyOperation(insn, value2)); + push(interpreter.copyOperation(insn, value1)); + break; + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + value2 = pop(); + value1 = pop(); + push(interpreter.binaryOperation(insn, value1, value2)); + break; + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + push(interpreter.unaryOperation(insn, pop())); + break; + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + value2 = pop(); + value1 = pop(); + push(interpreter.binaryOperation(insn, value1, value2)); + break; + case Opcodes.IINC: + var = ((IincInsnNode) insn).var; + setLocal(var, interpreter.unaryOperation(insn, getLocal(var))); + break; + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + push(interpreter.unaryOperation(insn, pop())); + break; + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + value2 = pop(); + value1 = pop(); + push(interpreter.binaryOperation(insn, value1, value2)); + break; + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + interpreter.unaryOperation(insn, pop()); + break; + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + value2 = pop(); + value1 = pop(); + interpreter.binaryOperation(insn, value1, value2); + break; + case Opcodes.GOTO: + break; + case Opcodes.JSR: + push(interpreter.newOperation(insn)); + break; + case Opcodes.RET: + break; + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + interpreter.unaryOperation(insn, pop()); + break; + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + value1 = pop(); + interpreter.unaryOperation(insn, value1); + interpreter.returnOperation(insn, value1, returnValue); + break; + case Opcodes.RETURN: + if (returnValue != null) { + throw new AnalyzerException(insn, "Incompatible return type"); + } + break; + case Opcodes.GETSTATIC: + push(interpreter.newOperation(insn)); + break; + case Opcodes.PUTSTATIC: + interpreter.unaryOperation(insn, pop()); + break; + case Opcodes.GETFIELD: + push(interpreter.unaryOperation(insn, pop())); + break; + case Opcodes.PUTFIELD: + value2 = pop(); + value1 = pop(); + interpreter.binaryOperation(insn, value1, value2); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: { + values = new ArrayList<V>(); + String desc = ((MethodInsnNode) insn).desc; + for (int i = Type.getArgumentTypes(desc).length; i > 0; --i) { + values.add(0, pop()); + } + if (insn.getOpcode() != Opcodes.INVOKESTATIC) { + values.add(0, pop()); + } + if (Type.getReturnType(desc) == Type.VOID_TYPE) { + interpreter.naryOperation(insn, values); + } else { + push(interpreter.naryOperation(insn, values)); + } + break; + } + case Opcodes.INVOKEDYNAMIC: { + values = new ArrayList<V>(); + String desc = ((InvokeDynamicInsnNode) insn).desc; + for (int i = Type.getArgumentTypes(desc).length; i > 0; --i) { + values.add(0, pop()); + } + if (Type.getReturnType(desc) == Type.VOID_TYPE) { + interpreter.naryOperation(insn, values); + } else { + push(interpreter.naryOperation(insn, values)); + } + break; + } + case Opcodes.NEW: + push(interpreter.newOperation(insn)); + break; + case Opcodes.NEWARRAY: + case Opcodes.ANEWARRAY: + case Opcodes.ARRAYLENGTH: + push(interpreter.unaryOperation(insn, pop())); + break; + case Opcodes.ATHROW: + interpreter.unaryOperation(insn, pop()); + break; + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + push(interpreter.unaryOperation(insn, pop())); + break; + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + interpreter.unaryOperation(insn, pop()); + break; + case Opcodes.MULTIANEWARRAY: + values = new ArrayList<V>(); + for (int i = ((MultiANewArrayInsnNode) insn).dims; i > 0; --i) { + values.add(0, pop()); + } + push(interpreter.naryOperation(insn, values)); + break; + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + interpreter.unaryOperation(insn, pop()); + break; + default: + throw new RuntimeException("Illegal opcode "+insn.getOpcode()); + } + } + + /** + * Merges this frame with the given frame. + * + * @param frame a frame. + * @param interpreter the interpreter used to merge values. + * @return <tt>true</tt> if this frame has been changed as a result of the + * merge operation, or <tt>false</tt> otherwise. + * @throws AnalyzerException if the frames have incompatible sizes. + */ + public boolean merge(final Frame<? extends V> frame, final Interpreter<V> interpreter) + throws AnalyzerException + { + if (top != frame.top) { + throw new AnalyzerException(null, "Incompatible stack heights"); + } + boolean changes = false; + for (int i = 0; i < locals + top; ++i) { + V v = interpreter.merge(values[i], frame.values[i]); + if (v != values[i]) { + values[i] = v; + changes = true; + } + } + return changes; + } + + /** + * Merges this frame with the given frame (case of a RET instruction). + * + * @param frame a frame + * @param access the local variables that have been accessed by the + * subroutine to which the RET instruction corresponds. + * @return <tt>true</tt> if this frame has been changed as a result of the + * merge operation, or <tt>false</tt> otherwise. + */ + public boolean merge(final Frame<? extends V> frame, final boolean[] access) { + boolean changes = false; + for (int i = 0; i < locals; ++i) { + if (!access[i] && !values[i].equals(frame.values[i])) { + values[i] = frame.values[i]; + changes = true; + } + } + return changes; + } + + /** + * Returns a string representation of this frame. + * + * @return a string representation of this frame. + */ + @Override + public String toString() { + StringBuffer b = new StringBuffer(); + for (int i = 0; i < getLocals(); ++i) { + b.append(getLocal(i)); + } + b.append(' '); + for (int i = 0; i < getStackSize(); ++i) { + b.append(getStack(i).toString()); + } + return b.toString(); + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/Interpreter.java b/src/compiler/scala/tools/asm/tree/analysis/Interpreter.java new file mode 100644 index 0000000000..930c8f4af8 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/Interpreter.java @@ -0,0 +1,204 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.List; + +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; + +/** + * A semantic bytecode interpreter. More precisely, this interpreter only + * manages the computation of values from other values: it does not manage the + * transfer of values to or from the stack, and to or from the local variables. + * This separation allows a generic bytecode {@link Analyzer} to work with + * various semantic interpreters, without needing to duplicate the code to + * simulate the transfer of values. + * + * @param <V> type of the Value used for the analysis. + * + * @author Eric Bruneton + */ +public abstract class Interpreter<V extends Value> { + + protected final int api; + + protected Interpreter(final int api) { + this.api = api; + } + + /** + * Creates a new value that represents the given type. + * + * Called for method parameters (including <code>this</code>), + * exception handler variable and with <code>null</code> type + * for variables reserved by long and double types. + * + * @param type a primitive or reference type, or <tt>null</tt> to + * represent an uninitialized value. + * @return a value that represents the given type. The size of the returned + * value must be equal to the size of the given type. + */ + public abstract V newValue(Type type); + + /** + * Interprets a bytecode instruction without arguments. This method is + * called for the following opcodes: + * + * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, + * ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, + * DCONST_1, BIPUSH, SIPUSH, LDC, JSR, GETSTATIC, NEW + * + * @param insn the bytecode instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V newOperation(AbstractInsnNode insn) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction that moves a value on the stack or to + * or from local variables. This method is called for the following opcodes: + * + * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, + * ASTORE, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP + * + * @param insn the bytecode instruction to be interpreted. + * @param value the value that must be moved by the instruction. + * @return the result of the interpretation of the given instruction. The + * returned value must be <tt>equal</tt> to the given value. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V copyOperation(AbstractInsnNode insn, V value) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction with a single argument. This method is + * called for the following opcodes: + * + * INEG, LNEG, FNEG, DNEG, IINC, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, + * F2D, D2I, D2L, D2F, I2B, I2C, I2S, IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, + * TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, + * PUTSTATIC, GETFIELD, NEWARRAY, ANEWARRAY, ARRAYLENGTH, ATHROW, CHECKCAST, + * INSTANCEOF, MONITORENTER, MONITOREXIT, IFNULL, IFNONNULL + * + * @param insn the bytecode instruction to be interpreted. + * @param value the argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V unaryOperation(AbstractInsnNode insn, V value) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction with two arguments. This method is + * called for the following opcodes: + * + * IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IADD, + * LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, + * LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, ISHL, LSHL, ISHR, LSHR, IUSHR, + * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, LCMP, FCMPL, FCMPG, DCMPL, + * DCMPG, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ, IF_ACMPNE, PUTFIELD + * + * @param insn the bytecode instruction to be interpreted. + * @param value1 the first argument of the instruction to be interpreted. + * @param value2 the second argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V binaryOperation(AbstractInsnNode insn, V value1, V value2) + throws AnalyzerException; + + /** + * Interprets a bytecode instruction with three arguments. This method is + * called for the following opcodes: + * + * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE + * + * @param insn the bytecode instruction to be interpreted. + * @param value1 the first argument of the instruction to be interpreted. + * @param value2 the second argument of the instruction to be interpreted. + * @param value3 the third argument of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V ternaryOperation( + AbstractInsnNode insn, + V value1, + V value2, + V value3) throws AnalyzerException; + + /** + * Interprets a bytecode instruction with a variable number of arguments. + * This method is called for the following opcodes: + * + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, + * MULTIANEWARRAY and INVOKEDYNAMIC + * + * @param insn the bytecode instruction to be interpreted. + * @param values the arguments of the instruction to be interpreted. + * @return the result of the interpretation of the given instruction. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract V naryOperation( + AbstractInsnNode insn, + List< ? extends V> values) throws AnalyzerException; + + /** + * Interprets a bytecode return instruction. This method is called for the + * following opcodes: + * + * IRETURN, LRETURN, FRETURN, DRETURN, ARETURN + * + * @param insn the bytecode instruction to be interpreted. + * @param value the argument of the instruction to be interpreted. + * @param expected the expected return type of the analyzed method. + * @throws AnalyzerException if an error occured during the interpretation. + */ + public abstract void returnOperation( + AbstractInsnNode insn, + V value, + V expected) throws AnalyzerException; + + /** + * Merges two values. The merge operation must return a value that + * represents both values (for instance, if the two values are two types, + * the merged value must be a common super type of the two types. If the two + * values are integer intervals, the merged value must be an interval that + * contains the previous ones. Likewise for other types of values). + * + * @param v a value. + * @param w another value. + * @return the merged value. If the merged value is equal to <tt>v</tt>, + * this method <i>must</i> return <tt>v</tt>. + */ + public abstract V merge(V v, V w); +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/SimpleVerifier.java b/src/compiler/scala/tools/asm/tree/analysis/SimpleVerifier.java new file mode 100644 index 0000000000..c4f515d328 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/SimpleVerifier.java @@ -0,0 +1,329 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.List; + +import scala.tools.asm.Type; + +/** + * An extended {@link BasicVerifier} that performs more precise verifications. + * This verifier computes exact class types, instead of using a single "object + * reference" type (as done in the {@link BasicVerifier}). + * + * @author Eric Bruneton + * @author Bing Ran + */ +public class SimpleVerifier extends BasicVerifier { + + /** + * The class that is verified. + */ + private final Type currentClass; + + /** + * The super class of the class that is verified. + */ + private final Type currentSuperClass; + + /** + * The interfaces implemented by the class that is verified. + */ + private final List<Type> currentClassInterfaces; + + /** + * If the class that is verified is an interface. + */ + private final boolean isInterface; + + /** + * The loader to use for referenced classes. + */ + private ClassLoader loader = getClass().getClassLoader(); + + /** + * Constructs a new {@link SimpleVerifier}. + */ + public SimpleVerifier() { + this(null, null, false); + } + + /** + * Constructs a new {@link SimpleVerifier} to verify a specific class. This + * class will not be loaded into the JVM since it may be incorrect. + * + * @param currentClass the class that is verified. + * @param currentSuperClass the super class of the class that is verified. + * @param isInterface if the class that is verified is an interface. + */ + public SimpleVerifier( + final Type currentClass, + final Type currentSuperClass, + final boolean isInterface) + { + this(currentClass, currentSuperClass, null, isInterface); + } + + /** + * Constructs a new {@link SimpleVerifier} to verify a specific class. This + * class will not be loaded into the JVM since it may be incorrect. + * + * @param currentClass the class that is verified. + * @param currentSuperClass the super class of the class that is verified. + * @param currentClassInterfaces the interfaces implemented by the class + * that is verified. + * @param isInterface if the class that is verified is an interface. + */ + public SimpleVerifier( + final Type currentClass, + final Type currentSuperClass, + final List<Type> currentClassInterfaces, + final boolean isInterface) + { + this(ASM4, + currentClass, + currentSuperClass, + currentClassInterfaces, + isInterface); + } + + protected SimpleVerifier( + final int api, + final Type currentClass, + final Type currentSuperClass, + final List<Type> currentClassInterfaces, + final boolean isInterface) + { + super(api); + this.currentClass = currentClass; + this.currentSuperClass = currentSuperClass; + this.currentClassInterfaces = currentClassInterfaces; + this.isInterface = isInterface; + } + + /** + * Set the <code>ClassLoader</code> which will be used to load referenced + * classes. This is useful if you are verifying multiple interdependent + * classes. + * + * @param loader a <code>ClassLoader</code> to use + */ + public void setClassLoader(final ClassLoader loader) { + this.loader = loader; + } + + @Override + public BasicValue newValue(final Type type) { + if (type == null) { + return BasicValue.UNINITIALIZED_VALUE; + } + + boolean isArray = type.getSort() == Type.ARRAY; + if (isArray) { + switch (type.getElementType().getSort()) { + case Type.BOOLEAN: + case Type.CHAR: + case Type.BYTE: + case Type.SHORT: + return new BasicValue(type); + } + } + + BasicValue v = super.newValue(type); + if (BasicValue.REFERENCE_VALUE.equals(v)) { + if (isArray) { + v = newValue(type.getElementType()); + String desc = v.getType().getDescriptor(); + for (int i = 0; i < type.getDimensions(); ++i) { + desc = '[' + desc; + } + v = new BasicValue(Type.getType(desc)); + } else { + v = new BasicValue(type); + } + } + return v; + } + + @Override + protected boolean isArrayValue(final BasicValue value) { + Type t = value.getType(); + return t != null + && ("Lnull;".equals(t.getDescriptor()) || t.getSort() == Type.ARRAY); + } + + @Override + protected BasicValue getElementValue(final BasicValue objectArrayValue) + throws AnalyzerException + { + Type arrayType = objectArrayValue.getType(); + if (arrayType != null) { + if (arrayType.getSort() == Type.ARRAY) { + return newValue(Type.getType(arrayType.getDescriptor() + .substring(1))); + } else if ("Lnull;".equals(arrayType.getDescriptor())) { + return objectArrayValue; + } + } + throw new Error("Internal error"); + } + + @Override + protected boolean isSubTypeOf(final BasicValue value, final BasicValue expected) { + Type expectedType = expected.getType(); + Type type = value.getType(); + switch (expectedType.getSort()) { + case Type.INT: + case Type.FLOAT: + case Type.LONG: + case Type.DOUBLE: + return type.equals(expectedType); + case Type.ARRAY: + case Type.OBJECT: + if ("Lnull;".equals(type.getDescriptor())) { + return true; + } else if (type.getSort() == Type.OBJECT + || type.getSort() == Type.ARRAY) + { + return isAssignableFrom(expectedType, type); + } else { + return false; + } + default: + throw new Error("Internal error"); + } + } + + @Override + public BasicValue merge(final BasicValue v, final BasicValue w) { + if (!v.equals(w)) { + Type t = v.getType(); + Type u = w.getType(); + if (t != null + && (t.getSort() == Type.OBJECT || t.getSort() == Type.ARRAY)) + { + if (u != null + && (u.getSort() == Type.OBJECT || u.getSort() == Type.ARRAY)) + { + if ("Lnull;".equals(t.getDescriptor())) { + return w; + } + if ("Lnull;".equals(u.getDescriptor())) { + return v; + } + if (isAssignableFrom(t, u)) { + return v; + } + if (isAssignableFrom(u, t)) { + return w; + } + // TODO case of array classes of the same dimension + // TODO should we look also for a common super interface? + // problem: there may be several possible common super + // interfaces + do { + if (t == null || isInterface(t)) { + return BasicValue.REFERENCE_VALUE; + } + t = getSuperClass(t); + if (isAssignableFrom(t, u)) { + return newValue(t); + } + } while (true); + } + } + return BasicValue.UNINITIALIZED_VALUE; + } + return v; + } + + protected boolean isInterface(final Type t) { + if (currentClass != null && t.equals(currentClass)) { + return isInterface; + } + return getClass(t).isInterface(); + } + + protected Type getSuperClass(final Type t) { + if (currentClass != null && t.equals(currentClass)) { + return currentSuperClass; + } + Class<?> c = getClass(t).getSuperclass(); + return c == null ? null : Type.getType(c); + } + + protected boolean isAssignableFrom(final Type t, final Type u) { + if (t.equals(u)) { + return true; + } + if (currentClass != null && t.equals(currentClass)) { + if (getSuperClass(u) == null) { + return false; + } else { + if (isInterface) { + return u.getSort() == Type.OBJECT || u.getSort() == Type.ARRAY; + } + return isAssignableFrom(t, getSuperClass(u)); + } + } + if (currentClass != null && u.equals(currentClass)) { + if (isAssignableFrom(t, currentSuperClass)) { + return true; + } + if (currentClassInterfaces != null) { + for (int i = 0; i < currentClassInterfaces.size(); ++i) { + Type v = currentClassInterfaces.get(i); + if (isAssignableFrom(t, v)) { + return true; + } + } + } + return false; + } + Class<?> tc = getClass(t); + if (tc.isInterface()) { + tc = Object.class; + } + return tc.isAssignableFrom(getClass(u)); + } + + protected Class<?> getClass(final Type t) { + try { + if (t.getSort() == Type.ARRAY) { + return Class.forName(t.getDescriptor().replace('/', '.'), + false, + loader); + } + return Class.forName(t.getClassName(), false, loader); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e.toString()); + } + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/SmallSet.java b/src/compiler/scala/tools/asm/tree/analysis/SmallSet.java new file mode 100644 index 0000000000..205878d18c --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/SmallSet.java @@ -0,0 +1,134 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.AbstractSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A set of at most two elements. + * + * @author Eric Bruneton + */ +class SmallSet<E> extends AbstractSet<E> implements Iterator<E> { + + // if e1 is null, e2 must be null; otherwise e2 must be different from e1 + + E e1, e2; + + static final <T> Set<T> emptySet() { + return new SmallSet<T>(null, null); + } + + SmallSet(final E e1, final E e2) { + this.e1 = e1; + this.e2 = e2; + } + + // ------------------------------------------------------------------------- + // Implementation of inherited abstract methods + // ------------------------------------------------------------------------- + + @Override + public Iterator<E> iterator() { + return new SmallSet<E>(e1, e2); + } + + @Override + public int size() { + return e1 == null ? 0 : (e2 == null ? 1 : 2); + } + + // ------------------------------------------------------------------------- + // Implementation of the Iterator interface + // ------------------------------------------------------------------------- + + public boolean hasNext() { + return e1 != null; + } + + public E next() { + if (e1 == null) { + throw new NoSuchElementException(); + } + E e = e1; + e1 = e2; + e2 = null; + return e; + } + + public void remove() { + } + + // ------------------------------------------------------------------------- + // Utility methods + // ------------------------------------------------------------------------- + + Set<E> union(final SmallSet<E> s) { + if ((s.e1 == e1 && s.e2 == e2) || (s.e1 == e2 && s.e2 == e1)) { + return this; // if the two sets are equal, return this + } + if (s.e1 == null) { + return this; // if s is empty, return this + } + if (e1 == null) { + return s; // if this is empty, return s + } + if (s.e2 == null) { // s contains exactly one element + if (e2 == null) { + return new SmallSet<E>(e1, s.e1); // necessarily e1 != s.e1 + } else if (s.e1 == e1 || s.e1 == e2) { // s is included in this + return this; + } + } + if (e2 == null) { // this contains exactly one element + // if (s.e2 == null) { // cannot happen + // return new SmallSet(e1, s.e1); // necessarily e1 != s.e1 + // } else + if (e1 == s.e1 || e1 == s.e2) { // this in included in s + return s; + } + } + // here we know that there are at least 3 distinct elements + HashSet<E> r = new HashSet<E>(4); + r.add(e1); + if (e2 != null) { + r.add(e2); + } + r.add(s.e1); + if (s.e2 != null) { + r.add(s.e2); + } + return r; + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/SourceInterpreter.java b/src/compiler/scala/tools/asm/tree/analysis/SourceInterpreter.java new file mode 100644 index 0000000000..067200b51e --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/SourceInterpreter.java @@ -0,0 +1,206 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.AbstractInsnNode; +import scala.tools.asm.tree.FieldInsnNode; +import scala.tools.asm.tree.InvokeDynamicInsnNode; +import scala.tools.asm.tree.LdcInsnNode; +import scala.tools.asm.tree.MethodInsnNode; + +/** + * An {@link Interpreter} for {@link SourceValue} values. + * + * @author Eric Bruneton + */ +public class SourceInterpreter extends Interpreter<SourceValue> implements + Opcodes +{ + + public SourceInterpreter() { + super(ASM4); + } + + protected SourceInterpreter(final int api) { + super(api); + } + + @Override + public SourceValue newValue(final Type type) { + if (type == Type.VOID_TYPE) { + return null; + } + return new SourceValue(type == null ? 1 : type.getSize()); + } + + @Override + public SourceValue newOperation(final AbstractInsnNode insn) { + int size; + switch (insn.getOpcode()) { + case LCONST_0: + case LCONST_1: + case DCONST_0: + case DCONST_1: + size = 2; + break; + case LDC: + Object cst = ((LdcInsnNode) insn).cst; + size = cst instanceof Long || cst instanceof Double ? 2 : 1; + break; + case GETSTATIC: + size = Type.getType(((FieldInsnNode) insn).desc).getSize(); + break; + default: + size = 1; + } + return new SourceValue(size, insn); + } + + @Override + public SourceValue copyOperation(final AbstractInsnNode insn, final SourceValue value) { + return new SourceValue(value.getSize(), insn); + } + + @Override + public SourceValue unaryOperation(final AbstractInsnNode insn, final SourceValue value) + { + int size; + switch (insn.getOpcode()) { + case LNEG: + case DNEG: + case I2L: + case I2D: + case L2D: + case F2L: + case F2D: + case D2L: + size = 2; + break; + case GETFIELD: + size = Type.getType(((FieldInsnNode) insn).desc).getSize(); + break; + default: + size = 1; + } + return new SourceValue(size, insn); + } + + @Override + public SourceValue binaryOperation( + final AbstractInsnNode insn, + final SourceValue value1, + final SourceValue value2) + { + int size; + switch (insn.getOpcode()) { + case LALOAD: + case DALOAD: + case LADD: + case DADD: + case LSUB: + case DSUB: + case LMUL: + case DMUL: + case LDIV: + case DDIV: + case LREM: + case DREM: + case LSHL: + case LSHR: + case LUSHR: + case LAND: + case LOR: + case LXOR: + size = 2; + break; + default: + size = 1; + } + return new SourceValue(size, insn); + } + + @Override + public SourceValue ternaryOperation( + final AbstractInsnNode insn, + final SourceValue value1, + final SourceValue value2, + final SourceValue value3) + { + return new SourceValue(1, insn); + } + + @Override + public SourceValue naryOperation(final AbstractInsnNode insn, final List<? extends SourceValue> values) { + int size; + int opcode = insn.getOpcode(); + if (opcode == MULTIANEWARRAY) { + size = 1; + } else { + String desc = (opcode == INVOKEDYNAMIC)? + ((InvokeDynamicInsnNode) insn).desc: + ((MethodInsnNode) insn).desc; + size = Type.getReturnType(desc).getSize(); + } + return new SourceValue(size, insn); + } + + @Override + public void returnOperation( + final AbstractInsnNode insn, + final SourceValue value, + final SourceValue expected) + { + } + + @Override + public SourceValue merge(final SourceValue d, final SourceValue w) { + if (d.insns instanceof SmallSet && w.insns instanceof SmallSet) { + Set<AbstractInsnNode> s = ((SmallSet<AbstractInsnNode>) d.insns).union((SmallSet<AbstractInsnNode>) w.insns); + if (s == d.insns && d.size == w.size) { + return d; + } else { + return new SourceValue(Math.min(d.size, w.size), s); + } + } + if (d.size != w.size || !d.insns.containsAll(w.insns)) { + HashSet<AbstractInsnNode> s = new HashSet<AbstractInsnNode>(); + s.addAll(d.insns); + s.addAll(w.insns); + return new SourceValue(Math.min(d.size, w.size), s); + } + return d; + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/SourceValue.java b/src/compiler/scala/tools/asm/tree/analysis/SourceValue.java new file mode 100644 index 0000000000..57ff212fb2 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/SourceValue.java @@ -0,0 +1,97 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.Set; + +import scala.tools.asm.tree.AbstractInsnNode; + +/** + * A {@link Value} that is represented by its type in a two types type system. + * This type system distinguishes the ONEWORD and TWOWORDS types. + * + * @author Eric Bruneton + */ +public class SourceValue implements Value { + + /** + * The size of this value. + */ + public final int size; + + /** + * The instructions that can produce this value. For example, for the Java + * code below, the instructions that can produce the value of <tt>i</tt> + * at line 5 are the txo ISTORE instructions at line 1 and 3: + * + * <pre> + * 1: i = 0; + * 2: if (...) { + * 3: i = 1; + * 4: } + * 5: return i; + * </pre> + * + * This field is a set of {@link AbstractInsnNode} objects. + */ + public final Set<AbstractInsnNode> insns; + + public SourceValue(final int size) { + this(size, SmallSet.<AbstractInsnNode>emptySet()); + } + + public SourceValue(final int size, final AbstractInsnNode insn) { + this.size = size; + this.insns = new SmallSet<AbstractInsnNode>(insn, null); + } + + public SourceValue(final int size, final Set<AbstractInsnNode> insns) { + this.size = size; + this.insns = insns; + } + + public int getSize() { + return size; + } + + @Override + public boolean equals(final Object value) { + if (!(value instanceof SourceValue)) { + return false; + } + SourceValue v = (SourceValue) value; + return size == v.size && insns.equals(v.insns); + } + + @Override + public int hashCode() { + return insns.hashCode(); + } +} diff --git a/src/compiler/scala/tools/asm/tree/analysis/Subroutine.java b/src/compiler/scala/tools/asm/tree/analysis/Subroutine.java new file mode 100644 index 0000000000..038880ddcd --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/Subroutine.java @@ -0,0 +1,93 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +import java.util.ArrayList; +import java.util.List; + +import scala.tools.asm.tree.JumpInsnNode; +import scala.tools.asm.tree.LabelNode; + +/** + * A method subroutine (corresponds to a JSR instruction). + * + * @author Eric Bruneton + */ +class Subroutine { + + LabelNode start; + + boolean[] access; + + List<JumpInsnNode> callers; + + private Subroutine() { + } + + Subroutine( + final LabelNode start, + final int maxLocals, + final JumpInsnNode caller) + { + this.start = start; + this.access = new boolean[maxLocals]; + this.callers = new ArrayList<JumpInsnNode>(); + callers.add(caller); + } + + public Subroutine copy() { + Subroutine result = new Subroutine(); + result.start = start; + result.access = new boolean[access.length]; + System.arraycopy(access, 0, result.access, 0, access.length); + result.callers = new ArrayList<JumpInsnNode>(callers); + return result; + } + + public boolean merge(final Subroutine subroutine) throws AnalyzerException { + boolean changes = false; + for (int i = 0; i < access.length; ++i) { + if (subroutine.access[i] && !access[i]) { + access[i] = true; + changes = true; + } + } + if (subroutine.start == start) { + for (int i = 0; i < subroutine.callers.size(); ++i) { + JumpInsnNode caller = subroutine.callers.get(i); + if (!callers.contains(caller)) { + callers.add(caller); + changes = true; + } + } + } + return changes; + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/asm/tree/analysis/Value.java b/src/compiler/scala/tools/asm/tree/analysis/Value.java new file mode 100644 index 0000000000..1edf475ce7 --- /dev/null +++ b/src/compiler/scala/tools/asm/tree/analysis/Value.java @@ -0,0 +1,45 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.tree.analysis; + +/** + * An immutable symbolic value for semantic interpretation of bytecode. + * + * @author Eric Bruneton + */ +public interface Value { + + /** + * Returns the size of this value in words. + * + * @return either 1 or 2. + */ + int getSize(); +} diff --git a/src/compiler/scala/tools/asm/util/ASMifiable.java b/src/compiler/scala/tools/asm/util/ASMifiable.java new file mode 100644 index 0000000000..6a31dd508f --- /dev/null +++ b/src/compiler/scala/tools/asm/util/ASMifiable.java @@ -0,0 +1,53 @@ +/** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.util.Map; + +import scala.tools.asm.Label; + +/** + * An {@link org.objectweb.asm.Attribute Attribute} that can print the ASM code + * to create an equivalent attribute. + * + * @author Eugene Kuleshov + */ +public interface ASMifiable { + + /** + * Prints the ASM code to create an attribute equal to this attribute. + * + * @param buf a buffer used for printing Java code. + * @param varName name of the variable in a printed code used to store + * attribute instance. + * @param labelNames map of label instances to their names. + */ + void asmify(StringBuffer buf, String varName, Map<Label, String> labelNames); +} diff --git a/src/compiler/scala/tools/asm/util/ASMifier.java b/src/compiler/scala/tools/asm/util/ASMifier.java new file mode 100644 index 0000000000..5967c877d1 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/ASMifier.java @@ -0,0 +1,1238 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassReader; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; + +/** + * A {@link Printer} that prints the ASM code to generate the classes if visits. + * + * @author Eric Bruneton + */ +public class ASMifier extends Printer { + + /** + * The name of the visitor variable in the produced code. + */ + protected final String name; + + /** + * Identifier of the annotation visitor variable in the produced code. + */ + protected final int id; + + /** + * The label names. This map associates String values to Label keys. It is + * used only in ASMifierMethodVisitor. + */ + protected Map<Label, String> labelNames; + + /** + * Pseudo access flag used to distinguish class access flags. + */ + private static final int ACCESS_CLASS = 262144; + + /** + * Pseudo access flag used to distinguish field access flags. + */ + private static final int ACCESS_FIELD = 524288; + + /** + * Pseudo access flag used to distinguish inner class flags. + */ + private static final int ACCESS_INNER = 1048576; + + /** + * Constructs a new {@link ASMifier}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the + * {@link #ASMifier(int, String, int)} version. + */ + public ASMifier() { + this(Opcodes.ASM4, "cw", 0); + } + + /** + * Constructs a new {@link ASMifier}. + * + * @param api the ASM API version implemented by this class. Must be one of + * {@link Opcodes#ASM4}. + * @param name the name of the visitor variable in the produced code. + * @param id identifier of the annotation visitor variable in the produced + * code. + */ + protected ASMifier(final int api, final String name, final int id) { + super(api); + this.name = name; + this.id = id; + } + + /** + * Prints the ASM source code to generate the given class to the standard + * output. <p> Usage: ASMifier [-debug] <binary + * class name or class file name> + * + * @param args the command line arguments. + * + * @throws Exception if the class cannot be found, or if an IO exception + * occurs. + */ + public static void main(final String[] args) throws Exception { + int i = 0; + int flags = ClassReader.SKIP_DEBUG; + + boolean ok = true; + if (args.length < 1 || args.length > 2) { + ok = false; + } + if (ok && "-debug".equals(args[0])) { + i = 1; + flags = 0; + if (args.length != 2) { + ok = false; + } + } + if (!ok) { + System.err.println("Prints the ASM code to generate the given class."); + System.err.println("Usage: ASMifier [-debug] " + + "<fully qualified class name or class file name>"); + return; + } + ClassReader cr; + if (args[i].endsWith(".class") || args[i].indexOf('\\') > -1 + || args[i].indexOf('/') > -1) + { + cr = new ClassReader(new FileInputStream(args[i])); + } else { + cr = new ClassReader(args[i]); + } + cr.accept(new TraceClassVisitor(null, + new ASMifier(), + new PrintWriter(System.out)), flags); + } + + // ------------------------------------------------------------------------ + // Classes + // ------------------------------------------------------------------------ + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + String simpleName; + int n = name.lastIndexOf('/'); + if (n == -1) { + simpleName = name; + } else { + text.add("package asm." + name.substring(0, n).replace('/', '.') + + ";\n"); + simpleName = name.substring(n + 1); + } + text.add("import java.util.*;\n"); + text.add("import org.objectweb.asm.*;\n"); + text.add("import org.objectweb.asm.attrs.*;\n"); + text.add("public class " + simpleName + "Dump implements Opcodes {\n\n"); + text.add("public static byte[] dump () throws Exception {\n\n"); + text.add("ClassWriter cw = new ClassWriter(0);\n"); + text.add("FieldVisitor fv;\n"); + text.add("MethodVisitor mv;\n"); + text.add("AnnotationVisitor av0;\n\n"); + + buf.setLength(0); + buf.append("cw.visit("); + switch (version) { + case Opcodes.V1_1: + buf.append("V1_1"); + break; + case Opcodes.V1_2: + buf.append("V1_2"); + break; + case Opcodes.V1_3: + buf.append("V1_3"); + break; + case Opcodes.V1_4: + buf.append("V1_4"); + break; + case Opcodes.V1_5: + buf.append("V1_5"); + break; + case Opcodes.V1_6: + buf.append("V1_6"); + break; + case Opcodes.V1_7: + buf.append("V1_7"); + break; + default: + buf.append(version); + break; + } + buf.append(", "); + appendAccess(access | ACCESS_CLASS); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(signature); + buf.append(", "); + appendConstant(superName); + buf.append(", "); + if (interfaces != null && interfaces.length > 0) { + buf.append("new String[] {"); + for (int i = 0; i < interfaces.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendConstant(interfaces[i]); + } + buf.append(" }"); + } else { + buf.append("null"); + } + buf.append(");\n\n"); + text.add(buf.toString()); + } + + @Override + public void visitSource(final String file, final String debug) { + buf.setLength(0); + buf.append("cw.visitSource("); + appendConstant(file); + buf.append(", "); + appendConstant(debug); + buf.append(");\n\n"); + text.add(buf.toString()); + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append("cw.visitOuterClass("); + appendConstant(owner); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(");\n\n"); + text.add(buf.toString()); + } + + @Override + public ASMifier visitClassAnnotation( + final String desc, + final boolean visible) + { + return visitAnnotation(desc, visible); + } + + @Override + public void visitClassAttribute(final Attribute attr) { + visitAttribute(attr); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + buf.setLength(0); + buf.append("cw.visitInnerClass("); + appendConstant(name); + buf.append(", "); + appendConstant(outerName); + buf.append(", "); + appendConstant(innerName); + buf.append(", "); + appendAccess(access | ACCESS_INNER); + buf.append(");\n\n"); + text.add(buf.toString()); + } + + @Override + public ASMifier visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + buf.setLength(0); + buf.append("{\n"); + buf.append("fv = cw.visitField("); + appendAccess(access | ACCESS_FIELD); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(", "); + appendConstant(signature); + buf.append(", "); + appendConstant(value); + buf.append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("fv", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public ASMifier visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + buf.setLength(0); + buf.append("{\n"); + buf.append("mv = cw.visitMethod("); + appendAccess(access); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(", "); + appendConstant(signature); + buf.append(", "); + if (exceptions != null && exceptions.length > 0) { + buf.append("new String[] {"); + for (int i = 0; i < exceptions.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendConstant(exceptions[i]); + } + buf.append(" }"); + } else { + buf.append("null"); + } + buf.append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("mv", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public void visitClassEnd() { + text.add("cw.visitEnd();\n\n"); + text.add("return cw.toByteArray();\n"); + text.add("}\n"); + text.add("}\n"); + } + + // ------------------------------------------------------------------------ + // Annotations + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + buf.setLength(0); + buf.append("av").append(id).append(".visit("); + appendConstant(buf, name); + buf.append(", "); + appendConstant(buf, value); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + buf.setLength(0); + buf.append("av").append(id).append(".visitEnum("); + appendConstant(buf, name); + buf.append(", "); + appendConstant(buf, desc); + buf.append(", "); + appendConstant(buf, value); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public ASMifier visitAnnotation( + final String name, + final String desc) + { + buf.setLength(0); + buf.append("{\n"); + buf.append("AnnotationVisitor av").append(id + 1).append(" = av"); + buf.append(id).append(".visitAnnotation("); + appendConstant(buf, name); + buf.append(", "); + appendConstant(buf, desc); + buf.append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", id + 1); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public ASMifier visitArray(final String name) { + buf.setLength(0); + buf.append("{\n"); + buf.append("AnnotationVisitor av").append(id + 1).append(" = av"); + buf.append(id).append(".visitArray("); + appendConstant(buf, name); + buf.append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", id + 1); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public void visitAnnotationEnd() { + buf.setLength(0); + buf.append("av").append(id).append(".visitEnd();\n"); + text.add(buf.toString()); + } + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + @Override + public ASMifier visitFieldAnnotation( + final String desc, + final boolean visible) + { + return visitAnnotation(desc, visible); + } + + @Override + public void visitFieldAttribute(final Attribute attr) { + visitAttribute(attr); + } + + @Override + public void visitFieldEnd() { + buf.setLength(0); + buf.append(name).append(".visitEnd();\n"); + text.add(buf.toString()); + } + + // ------------------------------------------------------------------------ + // Methods + // ------------------------------------------------------------------------ + + @Override + public ASMifier visitAnnotationDefault() { + buf.setLength(0); + buf.append("{\n") + .append("av0 = ") + .append(name) + .append(".visitAnnotationDefault();\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public ASMifier visitMethodAnnotation( + final String desc, + final boolean visible) + { + return visitAnnotation(desc, visible); + } + + @Override + public ASMifier visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + buf.setLength(0); + buf.append("{\n") + .append("av0 = ").append(name).append(".visitParameterAnnotation(") + .append(parameter) + .append(", "); + appendConstant(desc); + buf.append(", ").append(visible).append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + @Override + public void visitMethodAttribute(final Attribute attr) { + visitAttribute(attr); + } + + @Override + public void visitCode() { + text.add(name + ".visitCode();\n"); + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + buf.setLength(0); + switch (type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + declareFrameTypes(nLocal, local); + declareFrameTypes(nStack, stack); + if (type == Opcodes.F_NEW) { + buf.append(name).append(".visitFrame(Opcodes.F_NEW, "); + } else { + buf.append(name).append(".visitFrame(Opcodes.F_FULL, "); + } + buf.append(nLocal).append(", new Object[] {"); + appendFrameTypes(nLocal, local); + buf.append("}, ").append(nStack).append(", new Object[] {"); + appendFrameTypes(nStack, stack); + buf.append('}'); + break; + case Opcodes.F_APPEND: + declareFrameTypes(nLocal, local); + buf.append(name).append(".visitFrame(Opcodes.F_APPEND,") + .append(nLocal) + .append(", new Object[] {"); + appendFrameTypes(nLocal, local); + buf.append("}, 0, null"); + break; + case Opcodes.F_CHOP: + buf.append(name).append(".visitFrame(Opcodes.F_CHOP,") + .append(nLocal) + .append(", null, 0, null"); + break; + case Opcodes.F_SAME: + buf.append(name).append(".visitFrame(Opcodes.F_SAME, 0, null, 0, null"); + break; + case Opcodes.F_SAME1: + declareFrameTypes(1, stack); + buf.append(name).append(".visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"); + appendFrameTypes(1, stack); + buf.append('}'); + break; + } + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitInsn(final int opcode) { + buf.setLength(0); + buf.append(name).append(".visitInsn(").append(OPCODES[opcode]).append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + buf.setLength(0); + buf.append(name) + .append(".visitIntInsn(") + .append(OPCODES[opcode]) + .append(", ") + .append(opcode == Opcodes.NEWARRAY + ? TYPES[operand] + : Integer.toString(operand)) + .append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + buf.setLength(0); + buf.append(name) + .append(".visitVarInsn(") + .append(OPCODES[opcode]) + .append(", ") + .append(var) + .append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + buf.setLength(0); + buf.append(name).append(".visitTypeInsn(").append(OPCODES[opcode]).append(", "); + appendConstant(type); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append(this.name).append(".visitFieldInsn(").append(OPCODES[opcode]).append(", "); + appendConstant(owner); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append(this.name).append(".visitMethodInsn(").append(OPCODES[opcode]).append(", "); + appendConstant(owner); + buf.append(", "); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs) + { + buf.setLength(0); + buf.append(this.name).append(".visitInvokeDynamicInsn("); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(", "); + appendConstant(bsm); + buf.append(", new Object[]{"); + for (int i = 0; i < bsmArgs.length; ++i) { + appendConstant(bsmArgs[i]); + if (i != bsmArgs.length - 1) { + buf.append(", "); + } + } + buf.append("});\n"); + text.add(buf.toString()); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + buf.setLength(0); + declareLabel(label); + buf.append(name).append(".visitJumpInsn(").append(OPCODES[opcode]).append(", "); + appendLabel(label); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitLabel(final Label label) { + buf.setLength(0); + declareLabel(label); + buf.append(name).append(".visitLabel("); + appendLabel(label); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitLdcInsn(final Object cst) { + buf.setLength(0); + buf.append(name).append(".visitLdcInsn("); + appendConstant(cst); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + buf.setLength(0); + buf.append(name) + .append(".visitIincInsn(") + .append(var) + .append(", ") + .append(increment) + .append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + buf.setLength(0); + for (int i = 0; i < labels.length; ++i) { + declareLabel(labels[i]); + } + declareLabel(dflt); + + buf.append(name) + .append(".visitTableSwitchInsn(") + .append(min) + .append(", ") + .append(max) + .append(", "); + appendLabel(dflt); + buf.append(", new Label[] {"); + for (int i = 0; i < labels.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendLabel(labels[i]); + } + buf.append(" });\n"); + text.add(buf.toString()); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + buf.setLength(0); + for (int i = 0; i < labels.length; ++i) { + declareLabel(labels[i]); + } + declareLabel(dflt); + + buf.append(name).append(".visitLookupSwitchInsn("); + appendLabel(dflt); + buf.append(", new int[] {"); + for (int i = 0; i < keys.length; ++i) { + buf.append(i == 0 ? " " : ", ").append(keys[i]); + } + buf.append(" }, new Label[] {"); + for (int i = 0; i < labels.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendLabel(labels[i]); + } + buf.append(" });\n"); + text.add(buf.toString()); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + buf.setLength(0); + buf.append(name).append(".visitMultiANewArrayInsn("); + appendConstant(desc); + buf.append(", ").append(dims).append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + buf.setLength(0); + declareLabel(start); + declareLabel(end); + declareLabel(handler); + buf.append(name).append(".visitTryCatchBlock("); + appendLabel(start); + buf.append(", "); + appendLabel(end); + buf.append(", "); + appendLabel(handler); + buf.append(", "); + appendConstant(type); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + buf.setLength(0); + buf.append(this.name).append(".visitLocalVariable("); + appendConstant(name); + buf.append(", "); + appendConstant(desc); + buf.append(", "); + appendConstant(signature); + buf.append(", "); + appendLabel(start); + buf.append(", "); + appendLabel(end); + buf.append(", ").append(index).append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + buf.setLength(0); + buf.append(name).append(".visitLineNumber(").append(line).append(", "); + appendLabel(start); + buf.append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + buf.setLength(0); + buf.append(name) + .append(".visitMaxs(") + .append(maxStack) + .append(", ") + .append(maxLocals) + .append(");\n"); + text.add(buf.toString()); + } + + @Override + public void visitMethodEnd() { + buf.setLength(0); + buf.append(name).append(".visitEnd();\n"); + text.add(buf.toString()); + } + + // ------------------------------------------------------------------------ + // Common methods + // ------------------------------------------------------------------------ + + public ASMifier visitAnnotation( + final String desc, + final boolean visible) + { + buf.setLength(0); + buf.append("{\n") + .append("av0 = ") + .append(name) + .append(".visitAnnotation("); + appendConstant(desc); + buf.append(", ").append(visible).append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + + public void visitAttribute(final Attribute attr) { + buf.setLength(0); + buf.append("// ATTRIBUTE ").append(attr.type).append('\n'); + if (attr instanceof ASMifiable) { + if (labelNames == null) { + labelNames = new HashMap<Label, String>(); + } + buf.append("{\n"); + ((ASMifiable) attr).asmify(buf, "attr", labelNames); + buf.append(name).append(".visitAttribute(attr);\n"); + buf.append("}\n"); + } + text.add(buf.toString()); + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + protected ASMifier createASMifier(final String name, final int id) { + return new ASMifier(Opcodes.ASM4, name, id); + } + + /** + * Appends a string representation of the given access modifiers to {@link + * #buf buf}. + * + * @param access some access modifiers. + */ + void appendAccess(final int access) { + boolean first = true; + if ((access & Opcodes.ACC_PUBLIC) != 0) { + buf.append("ACC_PUBLIC"); + first = false; + } + if ((access & Opcodes.ACC_PRIVATE) != 0) { + buf.append("ACC_PRIVATE"); + first = false; + } + if ((access & Opcodes.ACC_PROTECTED) != 0) { + buf.append("ACC_PROTECTED"); + first = false; + } + if ((access & Opcodes.ACC_FINAL) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_FINAL"); + first = false; + } + if ((access & Opcodes.ACC_STATIC) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_STATIC"); + first = false; + } + if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) { + if (!first) { + buf.append(" + "); + } + if ((access & ACCESS_CLASS) == 0) { + buf.append("ACC_SYNCHRONIZED"); + } else { + buf.append("ACC_SUPER"); + } + first = false; + } + if ((access & Opcodes.ACC_VOLATILE) != 0 + && (access & ACCESS_FIELD) != 0) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_VOLATILE"); + first = false; + } + if ((access & Opcodes.ACC_BRIDGE) != 0 && (access & ACCESS_CLASS) == 0 + && (access & ACCESS_FIELD) == 0) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_BRIDGE"); + first = false; + } + if ((access & Opcodes.ACC_VARARGS) != 0 && (access & ACCESS_CLASS) == 0 + && (access & ACCESS_FIELD) == 0) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_VARARGS"); + first = false; + } + if ((access & Opcodes.ACC_TRANSIENT) != 0 + && (access & ACCESS_FIELD) != 0) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_TRANSIENT"); + first = false; + } + if ((access & Opcodes.ACC_NATIVE) != 0 && (access & ACCESS_CLASS) == 0 + && (access & ACCESS_FIELD) == 0) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_NATIVE"); + first = false; + } + if ((access & Opcodes.ACC_ENUM) != 0 + && ((access & ACCESS_CLASS) != 0 + || (access & ACCESS_FIELD) != 0 || (access & ACCESS_INNER) != 0)) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_ENUM"); + first = false; + } + if ((access & Opcodes.ACC_ANNOTATION) != 0 + && ((access & ACCESS_CLASS) != 0 || (access & ACCESS_INNER) != 0)) + { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_ANNOTATION"); + first = false; + } + if ((access & Opcodes.ACC_ABSTRACT) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_ABSTRACT"); + first = false; + } + if ((access & Opcodes.ACC_INTERFACE) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_INTERFACE"); + first = false; + } + if ((access & Opcodes.ACC_STRICT) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_STRICT"); + first = false; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_SYNTHETIC"); + first = false; + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_DEPRECATED"); + first = false; + } + if (first) { + buf.append('0'); + } + } + + /** + * Appends a string representation of the given constant to the given + * buffer. + * + * @param cst an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double} or {@link String} object. May be <tt>null</tt>. + */ + protected void appendConstant(final Object cst) { + appendConstant(buf, cst); + } + + /** + * Appends a string representation of the given constant to the given + * buffer. + * + * @param buf a string buffer. + * @param cst an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double} or {@link String} object. May be <tt>null</tt>. + */ + static void appendConstant(final StringBuffer buf, final Object cst) { + if (cst == null) { + buf.append("null"); + } else if (cst instanceof String) { + appendString(buf, (String) cst); + } else if (cst instanceof Type) { + buf.append("Type.getType(\""); + buf.append(((Type) cst).getDescriptor()); + buf.append("\")"); + } else if (cst instanceof Handle) { + buf.append("new Handle("); + Handle h = (Handle) cst; + buf.append("Opcodes.").append(HANDLE_TAG[h.getTag()]).append(", \""); + buf.append(h.getOwner()).append("\", \""); + buf.append(h.getName()).append("\", \""); + buf.append(h.getDesc()).append("\")"); + } else if (cst instanceof Byte) { + buf.append("new Byte((byte)").append(cst).append(')'); + } else if (cst instanceof Boolean) { + buf.append(((Boolean) cst).booleanValue() ? "Boolean.TRUE" : "Boolean.FALSE"); + } else if (cst instanceof Short) { + buf.append("new Short((short)").append(cst).append(')'); + } else if (cst instanceof Character) { + int c = ((Character) cst).charValue(); + buf.append("new Character((char)").append(c).append(')'); + } else if (cst instanceof Integer) { + buf.append("new Integer(").append(cst).append(')'); + } else if (cst instanceof Float) { + buf.append("new Float(\"").append(cst).append("\")"); + } else if (cst instanceof Long) { + buf.append("new Long(").append(cst).append("L)"); + } else if (cst instanceof Double) { + buf.append("new Double(\"").append(cst).append("\")"); + } else if (cst instanceof byte[]) { + byte[] v = (byte[]) cst; + buf.append("new byte[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]); + } + buf.append('}'); + } else if (cst instanceof boolean[]) { + boolean[] v = (boolean[]) cst; + buf.append("new boolean[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]); + } + buf.append('}'); + } else if (cst instanceof short[]) { + short[] v = (short[]) cst; + buf.append("new short[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append("(short)").append(v[i]); + } + buf.append('}'); + } else if (cst instanceof char[]) { + char[] v = (char[]) cst; + buf.append("new char[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",") + .append("(char)") + .append((int) v[i]); + } + buf.append('}'); + } else if (cst instanceof int[]) { + int[] v = (int[]) cst; + buf.append("new int[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]); + } + buf.append('}'); + } else if (cst instanceof long[]) { + long[] v = (long[]) cst; + buf.append("new long[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]).append('L'); + } + buf.append('}'); + } else if (cst instanceof float[]) { + float[] v = (float[]) cst; + buf.append("new float[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]).append('f'); + } + buf.append('}'); + } else if (cst instanceof double[]) { + double[] v = (double[]) cst; + buf.append("new double[] {"); + for (int i = 0; i < v.length; i++) { + buf.append(i == 0 ? "" : ",").append(v[i]).append('d'); + } + buf.append('}'); + } + } + + private void declareFrameTypes(final int n, final Object[] o) { + for (int i = 0; i < n; ++i) { + if (o[i] instanceof Label) { + declareLabel((Label) o[i]); + } + } + } + + private void appendFrameTypes(final int n, final Object[] o) { + for (int i = 0; i < n; ++i) { + if (i > 0) { + buf.append(", "); + } + if (o[i] instanceof String) { + appendConstant(o[i]); + } else if (o[i] instanceof Integer) { + switch (((Integer) o[i]).intValue()) { + case 0: + buf.append("Opcodes.TOP"); + break; + case 1: + buf.append("Opcodes.INTEGER"); + break; + case 2: + buf.append("Opcodes.FLOAT"); + break; + case 3: + buf.append("Opcodes.DOUBLE"); + break; + case 4: + buf.append("Opcodes.LONG"); + break; + case 5: + buf.append("Opcodes.NULL"); + break; + case 6: + buf.append("Opcodes.UNINITIALIZED_THIS"); + break; + } + } else { + appendLabel((Label) o[i]); + } + } + } + + /** + * Appends a declaration of the given label to {@link #buf buf}. This + * declaration is of the form "Label lXXX = new Label();". Does nothing if + * the given label has already been declared. + * + * @param l a label. + */ + protected void declareLabel(final Label l) { + if (labelNames == null) { + labelNames = new HashMap<Label, String>(); + } + String name = labelNames.get(l); + if (name == null) { + name = "l" + labelNames.size(); + labelNames.put(l, name); + buf.append("Label ").append(name).append(" = new Label();\n"); + } + } + + /** + * Appends the name of the given label to {@link #buf buf}. The given label + * <i>must</i> already have a name. One way to ensure this is to always + * call {@link #declareLabel declared} before calling this method. + * + * @param l a label. + */ + protected void appendLabel(final Label l) { + buf.append(labelNames.get(l)); + } +} diff --git a/src/compiler/scala/tools/asm/util/CheckAnnotationAdapter.java b/src/compiler/scala/tools/asm/util/CheckAnnotationAdapter.java new file mode 100644 index 0000000000..8030c14f2e --- /dev/null +++ b/src/compiler/scala/tools/asm/util/CheckAnnotationAdapter.java @@ -0,0 +1,142 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; + +/** + * An {@link AnnotationVisitor} that checks that its methods are properly used. + * + * @author Eric Bruneton + */ +public class CheckAnnotationAdapter extends AnnotationVisitor { + + private final boolean named; + + private boolean end; + + public CheckAnnotationAdapter(final AnnotationVisitor av) { + this(av, true); + } + + CheckAnnotationAdapter(final AnnotationVisitor av, final boolean named) { + super(Opcodes.ASM4, av); + this.named = named; + } + + @Override + public void visit(final String name, final Object value) { + checkEnd(); + checkName(name); + if (!(value instanceof Byte || value instanceof Boolean + || value instanceof Character || value instanceof Short + || value instanceof Integer || value instanceof Long + || value instanceof Float || value instanceof Double + || value instanceof String || value instanceof Type + || value instanceof byte[] || value instanceof boolean[] + || value instanceof char[] || value instanceof short[] + || value instanceof int[] || value instanceof long[] + || value instanceof float[] || value instanceof double[])) + { + throw new IllegalArgumentException("Invalid annotation value"); + } + if (value instanceof Type) { + int sort = ((Type) value).getSort(); + if (sort != Type.OBJECT && sort != Type.ARRAY) { + throw new IllegalArgumentException("Invalid annotation value"); + } + } + if (av != null) { + av.visit(name, value); + } + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + checkEnd(); + checkName(name); + CheckMethodAdapter.checkDesc(desc, false); + if (value == null) { + throw new IllegalArgumentException("Invalid enum value"); + } + if (av != null) { + av.visitEnum(name, desc, value); + } + } + + @Override + public AnnotationVisitor visitAnnotation( + final String name, + final String desc) + { + checkEnd(); + checkName(name); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(av == null + ? null + : av.visitAnnotation(name, desc)); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + checkEnd(); + checkName(name); + return new CheckAnnotationAdapter(av == null + ? null + : av.visitArray(name), false); + } + + @Override + public void visitEnd() { + checkEnd(); + end = true; + if (av != null) { + av.visitEnd(); + } + } + + private void checkEnd() { + if (end) { + throw new IllegalStateException("Cannot call a visit method after visitEnd has been called"); + } + } + + private void checkName(final String name) { + if (named && name == null) { + throw new IllegalArgumentException("Annotation value name must not be null"); + } + } +} diff --git a/src/compiler/scala/tools/asm/util/CheckClassAdapter.java b/src/compiler/scala/tools/asm/util/CheckClassAdapter.java new file mode 100644 index 0000000000..a455322531 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/CheckClassAdapter.java @@ -0,0 +1,603 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassReader; +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.ClassNode; +import scala.tools.asm.tree.MethodNode; +import scala.tools.asm.tree.analysis.Analyzer; +import scala.tools.asm.tree.analysis.BasicValue; +import scala.tools.asm.tree.analysis.Frame; +import scala.tools.asm.tree.analysis.SimpleVerifier; + +/** + * A {@link ClassVisitor} that checks that its methods are properly used. More + * precisely this class adapter checks each method call individually, based + * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i> + * of method calls. For example, the invalid sequence + * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC, + * "i", "D", null)</tt> + * will <i>not</i> be detected by this class adapter. + * + * <p><code>CheckClassAdapter</code> can be also used to verify bytecode + * transformations in order to make sure transformed bytecode is sane. For + * example: + * + * <pre> + * InputStream is = ...; // get bytes for the source class + * ClassReader cr = new ClassReader(is); + * ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); + * ClassVisitor cv = new <b>MyClassAdapter</b>(new CheckClassAdapter(cw)); + * cr.accept(cv, 0); + * + * StringWriter sw = new StringWriter(); + * PrintWriter pw = new PrintWriter(sw); + * CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw); + * assertTrue(sw.toString(), sw.toString().length()==0); + * </pre> + * + * Above code runs transformed bytecode trough the + * <code>CheckClassAdapter</code>. It won't be exactly the same verification + * as JVM does, but it run data flow analysis for the code of each method and + * checks that expectations are met for each method instruction. + * + * <p>If method bytecode has errors, assertion text will show the erroneous + * instruction number and dump of the failed method with information about + * locals and stack slot for each instruction. For example (format is - + * insnNumber locals : stack): + * + * <pre> + * org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found . + * at org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289) + * at org.objectweb.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135) + * ... + * remove()V + * 00000 LinkedBlockingQueue$Itr . . . . . . . . : + * ICONST_0 + * 00001 LinkedBlockingQueue$Itr . . . . . . . . : I + * ISTORE 2 + * 00001 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : + * ... + * + * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : + * ILOAD 1 + * 00072 <b>?</b> + * INVOKESPECIAL java/lang/Integer.<init> (I)V + * ... + * </pre> + * + * In the above output you can see that variable 1 loaded by + * <code>ILOAD 1</code> instruction at position <code>00071</code> is not + * initialized. You can also see that at the beginning of the method (code + * inserted by the transformation) variable 2 is initialized. + * + * <p>Note that when used like that, <code>CheckClassAdapter.verify()</code> + * can trigger additional class loading, because it is using + * <code>SimpleVerifier</code>. + * + * @author Eric Bruneton + */ +public class CheckClassAdapter extends ClassVisitor { + + /** + * The class version number. + */ + private int version; + + /** + * <tt>true</tt> if the visit method has been called. + */ + private boolean start; + + /** + * <tt>true</tt> if the visitSource method has been called. + */ + private boolean source; + + /** + * <tt>true</tt> if the visitOuterClass method has been called. + */ + private boolean outer; + + /** + * <tt>true</tt> if the visitEnd method has been called. + */ + private boolean end; + + /** + * The already visited labels. This map associate Integer values to Label + * keys. + */ + private Map<Label, Integer> labels; + + /** + * <tt>true</tt> if the method code must be checked with a BasicVerifier. + */ + private boolean checkDataFlow; + + /** + * Checks a given class. <p> Usage: CheckClassAdapter <binary + * class name or class file name> + * + * @param args the command line arguments. + * + * @throws Exception if the class cannot be found, or if an IO exception + * occurs. + */ + public static void main(final String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Verifies the given class."); + System.err.println("Usage: CheckClassAdapter " + + "<fully qualified class name or class file name>"); + return; + } + ClassReader cr; + if (args[0].endsWith(".class")) { + cr = new ClassReader(new FileInputStream(args[0])); + } else { + cr = new ClassReader(args[0]); + } + + verify(cr, false, new PrintWriter(System.err)); + } + + /** + * Checks a given class. + * + * @param cr a <code>ClassReader</code> that contains bytecode for the + * analysis. + * @param loader a <code>ClassLoader</code> which will be used to load + * referenced classes. This is useful if you are verifiying multiple + * interdependent classes. + * @param dump true if bytecode should be printed out not only when errors + * are found. + * @param pw write where results going to be printed + */ + public static void verify( + final ClassReader cr, + final ClassLoader loader, + final boolean dump, + final PrintWriter pw) + { + ClassNode cn = new ClassNode(); + cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG); + + Type syperType = cn.superName == null + ? null + : Type.getObjectType(cn.superName); + List<MethodNode> methods = cn.methods; + + List<Type> interfaces = new ArrayList<Type>(); + for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) { + interfaces.add(Type.getObjectType(i.next().toString())); + } + + for (int i = 0; i < methods.size(); ++i) { + MethodNode method = methods.get(i); + SimpleVerifier verifier = new SimpleVerifier(Type.getObjectType(cn.name), + syperType, + interfaces, + (cn.access & Opcodes.ACC_INTERFACE) != 0); + Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier); + if (loader != null) { + verifier.setClassLoader(loader); + } + try { + a.analyze(cn.name, method); + if (!dump) { + continue; + } + } catch (Exception e) { + e.printStackTrace(pw); + } + printAnalyzerResult(method, a, pw); + } + pw.flush(); + } + + /** + * Checks a given class + * + * @param cr a <code>ClassReader</code> that contains bytecode for the + * analysis. + * @param dump true if bytecode should be printed out not only when errors + * are found. + * @param pw write where results going to be printed + */ + public static void verify( + final ClassReader cr, + final boolean dump, + final PrintWriter pw) + { + verify(cr, null, dump, pw); + } + + static void printAnalyzerResult( + MethodNode method, + Analyzer<BasicValue> a, + final PrintWriter pw) + { + Frame<BasicValue>[] frames = a.getFrames(); + Textifier t = new Textifier(); + TraceMethodVisitor mv = new TraceMethodVisitor(t); + + pw.println(method.name + method.desc); + for (int j = 0; j < method.instructions.size(); ++j) { + method.instructions.get(j).accept(mv); + + StringBuffer s = new StringBuffer(); + Frame<BasicValue> f = frames[j]; + if (f == null) { + s.append('?'); + } else { + for (int k = 0; k < f.getLocals(); ++k) { + s.append(getShortName(f.getLocal(k).toString())) + .append(' '); + } + s.append(" : "); + for (int k = 0; k < f.getStackSize(); ++k) { + s.append(getShortName(f.getStack(k).toString())) + .append(' '); + } + } + while (s.length() < method.maxStack + method.maxLocals + 1) { + s.append(' '); + } + pw.print(Integer.toString(j + 100000).substring(1)); + pw.print(" " + s + " : " + t.text.get(t.text.size() - 1)); + } + for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { + method.tryCatchBlocks.get(j).accept(mv); + pw.print(" " + t.text.get(t.text.size() - 1)); + } + pw.println(); + } + + private static String getShortName(final String name) { + int n = name.lastIndexOf('/'); + int k = name.length(); + if (name.charAt(k - 1) == ';') { + k--; + } + return n == -1 ? name : name.substring(n + 1, k); + } + + /** + * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use + * this constructor</i>. Instead, they must use the + * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. + * + * @param cv the class visitor to which this adapter must delegate calls. + */ + public CheckClassAdapter(final ClassVisitor cv) { + this(cv, true); + } + + /** + * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use + * this constructor</i>. Instead, they must use the + * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. + * + * @param cv the class visitor to which this adapter must delegate calls. + * @param checkDataFlow <tt>true</tt> to perform basic data flow checks, or + * <tt>false</tt> to not perform any data flow check (see + * {@link CheckMethodAdapter}). This option requires valid maxLocals + * and maxStack values. + */ + public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) + { + this(Opcodes.ASM4, cv, checkDataFlow); + } + + /** + * Constructs a new {@link CheckClassAdapter}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param cv the class visitor to which this adapter must delegate calls. + * @param checkDataFlow <tt>true</tt> to perform basic data flow checks, or + * <tt>false</tt> to not perform any data flow check (see + * {@link CheckMethodAdapter}). This option requires valid maxLocals + * and maxStack values. + */ + protected CheckClassAdapter( + final int api, + final ClassVisitor cv, + final boolean checkDataFlow) + { + super(api, cv); + this.labels = new HashMap<Label, Integer>(); + this.checkDataFlow = checkDataFlow; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor interface + // ------------------------------------------------------------------------ + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + if (start) { + throw new IllegalStateException("visit must be called only once"); + } + start = true; + checkState(); + checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE + + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC + + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM + + Opcodes.ACC_DEPRECATED + + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + if (name == null || !name.endsWith("package-info")) { + CheckMethodAdapter.checkInternalName(name, "class name"); + } + if ("java/lang/Object".equals(name)) { + if (superName != null) { + throw new IllegalArgumentException("The super class name of the Object class must be 'null'"); + } + } else { + CheckMethodAdapter.checkInternalName(superName, "super class name"); + } + if (signature != null) { + CheckMethodAdapter.checkClassSignature(signature); + } + if ((access & Opcodes.ACC_INTERFACE) != 0) { + if (!"java/lang/Object".equals(superName)) { + throw new IllegalArgumentException("The super class name of interfaces must be 'java/lang/Object'"); + } + } + if (interfaces != null) { + for (int i = 0; i < interfaces.length; ++i) { + CheckMethodAdapter.checkInternalName(interfaces[i], + "interface name at index " + i); + } + } + this.version = version; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(final String file, final String debug) { + checkState(); + if (source) { + throw new IllegalStateException("visitSource can be called only once."); + } + source = true; + super.visitSource(file, debug); + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) + { + checkState(); + if (outer) { + throw new IllegalStateException("visitOuterClass can be called only once."); + } + outer = true; + if (owner == null) { + throw new IllegalArgumentException("Illegal outer class owner"); + } + if (desc != null) { + CheckMethodAdapter.checkMethodDesc(desc); + } + super.visitOuterClass(owner, name, desc); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + checkState(); + CheckMethodAdapter.checkInternalName(name, "class name"); + if (outerName != null) { + CheckMethodAdapter.checkInternalName(outerName, "outer class name"); + } + if (innerName != null) { + CheckMethodAdapter.checkIdentifier(innerName, "inner class name"); + } + checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE + + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC + + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE + + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC + + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM); + super.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + checkState(); + checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE + + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC + + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE + + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC + + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED + + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + CheckMethodAdapter.checkUnqualifiedName(version, name, "field name"); + CheckMethodAdapter.checkDesc(desc, false); + if (signature != null) { + CheckMethodAdapter.checkFieldSignature(signature); + } + if (value != null) { + CheckMethodAdapter.checkConstant(value); + } + FieldVisitor av = super.visitField(access, name, desc, signature, value); + return new CheckFieldAdapter(av); + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + checkState(); + checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE + + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC + + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED + + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE + + Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT + + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED + + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + CheckMethodAdapter.checkMethodIdentifier(version, name, "method name"); + CheckMethodAdapter.checkMethodDesc(desc); + if (signature != null) { + CheckMethodAdapter.checkMethodSignature(signature); + } + if (exceptions != null) { + for (int i = 0; i < exceptions.length; ++i) { + CheckMethodAdapter.checkInternalName(exceptions[i], + "exception name at index " + i); + } + } + CheckMethodAdapter cma; + if (checkDataFlow) { + cma = new CheckMethodAdapter(access, + name, + desc, + super.visitMethod(access, name, desc, signature, exceptions), + labels); + } else { + cma = new CheckMethodAdapter(super.visitMethod(access, + name, + desc, + signature, + exceptions), labels); + } + cma.version = version; + return cma; + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + checkState(); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); + } + + @Override + public void visitAttribute(final Attribute attr) { + checkState(); + if (attr == null) { + throw new IllegalArgumentException("Invalid attribute (must not be null)"); + } + super.visitAttribute(attr); + } + + @Override + public void visitEnd() { + checkState(); + end = true; + super.visitEnd(); + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Checks that the visit method has been called and that visitEnd has not + * been called. + */ + private void checkState() { + if (!start) { + throw new IllegalStateException("Cannot visit member before visit has been called."); + } + if (end) { + throw new IllegalStateException("Cannot visit member after visitEnd has been called."); + } + } + + /** + * Checks that the given access flags do not contain invalid flags. This + * method also checks that mutually incompatible flags are not set + * simultaneously. + * + * @param access the access flags to be checked + * @param possibleAccess the valid access flags. + */ + static void checkAccess(final int access, final int possibleAccess) { + if ((access & ~possibleAccess) != 0) { + throw new IllegalArgumentException("Invalid access flags: " + + access); + } + int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1; + int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1; + int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1; + if (pub + pri + pro > 1) { + throw new IllegalArgumentException("public private and protected are mutually exclusive: " + + access); + } + int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1; + int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1; + if (fin + abs > 1) { + throw new IllegalArgumentException("final and abstract are mutually exclusive: " + + access); + } + } +} diff --git a/src/compiler/scala/tools/asm/util/CheckFieldAdapter.java b/src/compiler/scala/tools/asm/util/CheckFieldAdapter.java new file mode 100644 index 0000000000..bdcbe14b16 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/CheckFieldAdapter.java @@ -0,0 +1,97 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.Opcodes; + +/** + * A {@link FieldVisitor} that checks that its methods are properly used. + */ +public class CheckFieldAdapter extends FieldVisitor { + + private boolean end; + + /** + * Constructs a new {@link CheckFieldAdapter}. <i>Subclasses must not use + * this constructor</i>. Instead, they must use the + * {@link #CheckFieldAdapter(int, FieldVisitor)} version. + * + * @param fv the field visitor to which this adapter must delegate calls. + */ + public CheckFieldAdapter(final FieldVisitor fv) { + this(Opcodes.ASM4, fv); + } + + /** + * Constructs a new {@link CheckFieldAdapter}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param fv the field visitor to which this adapter must delegate calls. + */ + protected CheckFieldAdapter(final int api, final FieldVisitor fv) { + super(api, fv); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + checkEnd(); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); + } + + @Override + public void visitAttribute(final Attribute attr) { + checkEnd(); + if (attr == null) { + throw new IllegalArgumentException("Invalid attribute (must not be null)"); + } + super.visitAttribute(attr); + } + + @Override + public void visitEnd() { + checkEnd(); + end = true; + super.visitEnd(); + } + + private void checkEnd() { + if (end) { + throw new IllegalStateException("Cannot call a visit method after visitEnd has been called"); + } + } +} diff --git a/src/compiler/scala/tools/asm/util/CheckMethodAdapter.java b/src/compiler/scala/tools/asm/util/CheckMethodAdapter.java new file mode 100644 index 0000000000..7549765421 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/CheckMethodAdapter.java @@ -0,0 +1,1668 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.tree.MethodNode; +import scala.tools.asm.tree.analysis.Analyzer; +import scala.tools.asm.tree.analysis.BasicValue; +import scala.tools.asm.tree.analysis.BasicVerifier; + +/** + * A {@link MethodVisitor} that checks that its methods are properly used. More + * precisely this method adapter checks each instruction individually, i.e., + * each visit method checks some preconditions based <i>only</i> on its + * arguments - such as the fact that the given opcode is correct for a given + * visit method. This adapter can also perform some basic data flow checks (more + * precisely those that can be performed without the full class hierarchy - see + * {@link org.objectweb.asm.tree.analysis.BasicVerifier}). For instance in a + * method whose signature is <tt>void m ()</tt>, the invalid instruction + * IRETURN, or the invalid sequence IADD L2I will be detected if the data flow + * checks are enabled. These checks are enabled by using the + * {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)} constructor. + * They are not performed if any other constructor is used. + * + * @author Eric Bruneton + */ +public class CheckMethodAdapter extends MethodVisitor { + + /** + * The class version number. + */ + public int version; + + /** + * <tt>true</tt> if the visitCode method has been called. + */ + private boolean startCode; + + /** + * <tt>true</tt> if the visitMaxs method has been called. + */ + private boolean endCode; + + /** + * <tt>true</tt> if the visitEnd method has been called. + */ + private boolean endMethod; + + /** + * Number of visited instructions. + */ + private int insnCount; + + /** + * The already visited labels. This map associate Integer values to pseudo + * code offsets. + */ + private final Map<Label, Integer> labels; + + /** + * The labels used in this method. Every used label must be visited with + * visitLabel before the end of the method (i.e. should be in #labels). + */ + private Set<Label> usedLabels; + + /** + * The exception handler ranges. Each pair of list element contains the + * start and end labels of an exception handler block. + */ + private List<Label> handlers; + + /** + * Code of the visit method to be used for each opcode. + */ + private static final int[] TYPE; + + /** + * The Label.status field. + */ + private static Field labelStatusField; + + static { + String s = "BBBBBBBBBBBBBBBBCCIAADDDDDAAAAAAAAAAAAAAAAAAAABBBBBBBBDD" + + "DDDAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + + "BBBBBBBBBBBBBBBBBBBJBBBBBBBBBBBBBBBBBBBBHHHHHHHHHHHHHHHHD" + + "KLBBBBBBFFFFGGGGAECEBBEEBBAMHHAA"; + TYPE = new int[s.length()]; + for (int i = 0; i < TYPE.length; ++i) { + TYPE[i] = s.charAt(i) - 'A' - 1; + } + } + + // code to generate the above string + // public static void main (String[] args) { + // int[] TYPE = new int[] { + // 0, //NOP + // 0, //ACONST_NULL + // 0, //ICONST_M1 + // 0, //ICONST_0 + // 0, //ICONST_1 + // 0, //ICONST_2 + // 0, //ICONST_3 + // 0, //ICONST_4 + // 0, //ICONST_5 + // 0, //LCONST_0 + // 0, //LCONST_1 + // 0, //FCONST_0 + // 0, //FCONST_1 + // 0, //FCONST_2 + // 0, //DCONST_0 + // 0, //DCONST_1 + // 1, //BIPUSH + // 1, //SIPUSH + // 7, //LDC + // -1, //LDC_W + // -1, //LDC2_W + // 2, //ILOAD + // 2, //LLOAD + // 2, //FLOAD + // 2, //DLOAD + // 2, //ALOAD + // -1, //ILOAD_0 + // -1, //ILOAD_1 + // -1, //ILOAD_2 + // -1, //ILOAD_3 + // -1, //LLOAD_0 + // -1, //LLOAD_1 + // -1, //LLOAD_2 + // -1, //LLOAD_3 + // -1, //FLOAD_0 + // -1, //FLOAD_1 + // -1, //FLOAD_2 + // -1, //FLOAD_3 + // -1, //DLOAD_0 + // -1, //DLOAD_1 + // -1, //DLOAD_2 + // -1, //DLOAD_3 + // -1, //ALOAD_0 + // -1, //ALOAD_1 + // -1, //ALOAD_2 + // -1, //ALOAD_3 + // 0, //IALOAD + // 0, //LALOAD + // 0, //FALOAD + // 0, //DALOAD + // 0, //AALOAD + // 0, //BALOAD + // 0, //CALOAD + // 0, //SALOAD + // 2, //ISTORE + // 2, //LSTORE + // 2, //FSTORE + // 2, //DSTORE + // 2, //ASTORE + // -1, //ISTORE_0 + // -1, //ISTORE_1 + // -1, //ISTORE_2 + // -1, //ISTORE_3 + // -1, //LSTORE_0 + // -1, //LSTORE_1 + // -1, //LSTORE_2 + // -1, //LSTORE_3 + // -1, //FSTORE_0 + // -1, //FSTORE_1 + // -1, //FSTORE_2 + // -1, //FSTORE_3 + // -1, //DSTORE_0 + // -1, //DSTORE_1 + // -1, //DSTORE_2 + // -1, //DSTORE_3 + // -1, //ASTORE_0 + // -1, //ASTORE_1 + // -1, //ASTORE_2 + // -1, //ASTORE_3 + // 0, //IASTORE + // 0, //LASTORE + // 0, //FASTORE + // 0, //DASTORE + // 0, //AASTORE + // 0, //BASTORE + // 0, //CASTORE + // 0, //SASTORE + // 0, //POP + // 0, //POP2 + // 0, //DUP + // 0, //DUP_X1 + // 0, //DUP_X2 + // 0, //DUP2 + // 0, //DUP2_X1 + // 0, //DUP2_X2 + // 0, //SWAP + // 0, //IADD + // 0, //LADD + // 0, //FADD + // 0, //DADD + // 0, //ISUB + // 0, //LSUB + // 0, //FSUB + // 0, //DSUB + // 0, //IMUL + // 0, //LMUL + // 0, //FMUL + // 0, //DMUL + // 0, //IDIV + // 0, //LDIV + // 0, //FDIV + // 0, //DDIV + // 0, //IREM + // 0, //LREM + // 0, //FREM + // 0, //DREM + // 0, //INEG + // 0, //LNEG + // 0, //FNEG + // 0, //DNEG + // 0, //ISHL + // 0, //LSHL + // 0, //ISHR + // 0, //LSHR + // 0, //IUSHR + // 0, //LUSHR + // 0, //IAND + // 0, //LAND + // 0, //IOR + // 0, //LOR + // 0, //IXOR + // 0, //LXOR + // 8, //IINC + // 0, //I2L + // 0, //I2F + // 0, //I2D + // 0, //L2I + // 0, //L2F + // 0, //L2D + // 0, //F2I + // 0, //F2L + // 0, //F2D + // 0, //D2I + // 0, //D2L + // 0, //D2F + // 0, //I2B + // 0, //I2C + // 0, //I2S + // 0, //LCMP + // 0, //FCMPL + // 0, //FCMPG + // 0, //DCMPL + // 0, //DCMPG + // 6, //IFEQ + // 6, //IFNE + // 6, //IFLT + // 6, //IFGE + // 6, //IFGT + // 6, //IFLE + // 6, //IF_ICMPEQ + // 6, //IF_ICMPNE + // 6, //IF_ICMPLT + // 6, //IF_ICMPGE + // 6, //IF_ICMPGT + // 6, //IF_ICMPLE + // 6, //IF_ACMPEQ + // 6, //IF_ACMPNE + // 6, //GOTO + // 6, //JSR + // 2, //RET + // 9, //TABLESWITCH + // 10, //LOOKUPSWITCH + // 0, //IRETURN + // 0, //LRETURN + // 0, //FRETURN + // 0, //DRETURN + // 0, //ARETURN + // 0, //RETURN + // 4, //GETSTATIC + // 4, //PUTSTATIC + // 4, //GETFIELD + // 4, //PUTFIELD + // 5, //INVOKEVIRTUAL + // 5, //INVOKESPECIAL + // 5, //INVOKESTATIC + // 5, //INVOKEINTERFACE + // -1, //INVOKEDYNAMIC + // 3, //NEW + // 1, //NEWARRAY + // 3, //ANEWARRAY + // 0, //ARRAYLENGTH + // 0, //ATHROW + // 3, //CHECKCAST + // 3, //INSTANCEOF + // 0, //MONITORENTER + // 0, //MONITOREXIT + // -1, //WIDE + // 11, //MULTIANEWARRAY + // 6, //IFNULL + // 6, //IFNONNULL + // -1, //GOTO_W + // -1 //JSR_W + // }; + // for (int i = 0; i < TYPE.length; ++i) { + // System.out.print((char)(TYPE[i] + 1 + 'A')); + // } + // System.out.println(); + // } + + /** + * Constructs a new {@link CheckMethodAdapter} object. This method adapter + * will not perform any data flow check (see + * {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). + * <i>Subclasses must not use this constructor</i>. Instead, they must use + * the {@link #CheckMethodAdapter(int, MethodVisitor, Map)} version. + * + * @param mv the method visitor to which this adapter must delegate calls. + */ + public CheckMethodAdapter(final MethodVisitor mv) { + this(mv, new HashMap<Label, Integer>()); + } + + /** + * Constructs a new {@link CheckMethodAdapter} object. This method adapter + * will not perform any data flow check (see + * {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). + * <i>Subclasses must not use this constructor</i>. Instead, they must use + * the {@link #CheckMethodAdapter(int, MethodVisitor, Map)} version. + * + * @param mv the method visitor to which this adapter must delegate calls. + * @param labels a map of already visited labels (in other methods). + */ + public CheckMethodAdapter( + final MethodVisitor mv, + final Map<Label, Integer> labels) + { + this(Opcodes.ASM4, mv, labels); + } + + /** + * Constructs a new {@link CheckMethodAdapter} object. This method adapter + * will not perform any data flow check (see + * {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). + * + * @param mv the method visitor to which this adapter must delegate calls. + * @param labels a map of already visited labels (in other methods). + */ + protected CheckMethodAdapter( + final int api, + final MethodVisitor mv, + final Map<Label, Integer> labels) + { + super(api, mv); + this.labels = labels; + this.usedLabels = new HashSet<Label>(); + this.handlers = new ArrayList<Label>(); + } + + /** + * Constructs a new {@link CheckMethodAdapter} object. This method adapter + * will perform basic data flow checks. For instance in a method whose + * signature is <tt>void m ()</tt>, the invalid instruction IRETURN, or the + * invalid sequence IADD L2I will be detected. + * + * @param access the method's access flags. + * @param name the method's name. + * @param desc the method's descriptor (see {@link Type Type}). + * @param cmv the method visitor to which this adapter must delegate calls. + * @param labels a map of already visited labels (in other methods). + */ + public CheckMethodAdapter( + final int access, + final String name, + final String desc, + final MethodVisitor cmv, + final Map<Label, Integer> labels) + { + this(new MethodNode(access, name, desc, null, null) { + @Override + public void visitEnd() { + Analyzer<BasicValue> a = new Analyzer<BasicValue>(new BasicVerifier()); + try { + a.analyze("dummy", this); + } catch (Exception e) { + if (e instanceof IndexOutOfBoundsException + && maxLocals == 0 && maxStack == 0) + { + throw new RuntimeException("Data flow checking option requires valid, non zero maxLocals and maxStack values."); + } + e.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + CheckClassAdapter.printAnalyzerResult(this, a, pw); + pw.close(); + throw new RuntimeException(e.getMessage() + ' ' + + sw.toString()); + } + accept(cmv); + } + }, + labels); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + checkEndMethod(); + checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + checkEndMethod(); + return new CheckAnnotationAdapter(super.visitAnnotationDefault(), false); + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + checkEndMethod(); + checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitParameterAnnotation(parameter, + desc, + visible)); + } + + @Override + public void visitAttribute(final Attribute attr) { + checkEndMethod(); + if (attr == null) { + throw new IllegalArgumentException("Invalid attribute (must not be null)"); + } + super.visitAttribute(attr); + } + + @Override + public void visitCode() { + startCode = true; + super.visitCode(); + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + int mLocal; + int mStack; + switch (type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + mLocal = Integer.MAX_VALUE; + mStack = Integer.MAX_VALUE; + break; + + case Opcodes.F_SAME: + mLocal = 0; + mStack = 0; + break; + + case Opcodes.F_SAME1: + mLocal = 0; + mStack = 1; + break; + + case Opcodes.F_APPEND: + case Opcodes.F_CHOP: + mLocal = 3; + mStack = 0; + break; + + default: + throw new IllegalArgumentException("Invalid frame type " + type); + } + + if (nLocal > mLocal) { + throw new IllegalArgumentException("Invalid nLocal=" + nLocal + + " for frame type " + type); + } + if (nStack > mStack) { + throw new IllegalArgumentException("Invalid nStack=" + nStack + + " for frame type " + type); + } + + if (type != Opcodes.F_CHOP) { + if (nLocal > 0 && (local == null || local.length < nLocal)) { + throw new IllegalArgumentException("Array local[] is shorter than nLocal"); + } + for (int i = 0; i < nLocal; ++i) { + checkFrameValue(local[i]); + } + } + if (nStack > 0 && (stack == null || stack.length < nStack)) { + throw new IllegalArgumentException("Array stack[] is shorter than nStack"); + } + for (int i = 0; i < nStack; ++i) { + checkFrameValue(stack[i]); + } + + super.visitFrame(type, nLocal, local, nStack, stack); + } + + @Override + public void visitInsn(final int opcode) { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 0); + super.visitInsn(opcode); + ++insnCount; + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 1); + switch (opcode) { + case Opcodes.BIPUSH: + checkSignedByte(operand, "Invalid operand"); + break; + case Opcodes.SIPUSH: + checkSignedShort(operand, "Invalid operand"); + break; + // case Constants.NEWARRAY: + default: + if (operand < Opcodes.T_BOOLEAN || operand > Opcodes.T_LONG) { + throw new IllegalArgumentException("Invalid operand (must be an array type code T_...): " + + operand); + } + } + super.visitIntInsn(opcode, operand); + ++insnCount; + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 2); + checkUnsignedShort(var, "Invalid variable index"); + super.visitVarInsn(opcode, var); + ++insnCount; + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 3); + checkInternalName(type, "type"); + if (opcode == Opcodes.NEW && type.charAt(0) == '[') { + throw new IllegalArgumentException("NEW cannot be used to create arrays: " + + type); + } + super.visitTypeInsn(opcode, type); + ++insnCount; + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 4); + checkInternalName(owner, "owner"); + checkUnqualifiedName(version, name, "name"); + checkDesc(desc, false); + super.visitFieldInsn(opcode, owner, name, desc); + ++insnCount; + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 5); + checkMethodIdentifier(version, name, "name"); + checkInternalName(owner, "owner"); + checkMethodDesc(desc); + super.visitMethodInsn(opcode, owner, name, desc); + ++insnCount; + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs) + { + checkStartCode(); + checkEndCode(); + checkMethodIdentifier(version, name, "name"); + checkMethodDesc(desc); + if (bsm.getTag() != Opcodes.H_INVOKESTATIC + && bsm.getTag() != Opcodes.H_NEWINVOKESPECIAL) + { + throw new IllegalArgumentException("invalid handle tag " + + bsm.getTag()); + } + for (int i = 0; i < bsmArgs.length; i++) { + checkLDCConstant(bsmArgs[i]); + } + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + ++insnCount; + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + checkStartCode(); + checkEndCode(); + checkOpcode(opcode, 6); + checkLabel(label, false, "label"); + checkNonDebugLabel(label); + super.visitJumpInsn(opcode, label); + usedLabels.add(label); + ++insnCount; + } + + @Override + public void visitLabel(final Label label) { + checkStartCode(); + checkEndCode(); + checkLabel(label, false, "label"); + if (labels.get(label) != null) { + throw new IllegalArgumentException("Already visited label"); + } + labels.put(label, new Integer(insnCount)); + super.visitLabel(label); + } + + @Override + public void visitLdcInsn(final Object cst) { + checkStartCode(); + checkEndCode(); + checkLDCConstant(cst); + super.visitLdcInsn(cst); + ++insnCount; + } + + @Override + public void visitIincInsn(final int var, final int increment) { + checkStartCode(); + checkEndCode(); + checkUnsignedShort(var, "Invalid variable index"); + checkSignedShort(increment, "Invalid increment"); + super.visitIincInsn(var, increment); + ++insnCount; + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + checkStartCode(); + checkEndCode(); + if (max < min) { + throw new IllegalArgumentException("Max = " + max + + " must be greater than or equal to min = " + min); + } + checkLabel(dflt, false, "default label"); + checkNonDebugLabel(dflt); + if (labels == null || labels.length != max - min + 1) { + throw new IllegalArgumentException("There must be max - min + 1 labels"); + } + for (int i = 0; i < labels.length; ++i) { + checkLabel(labels[i], false, "label at index " + i); + checkNonDebugLabel(labels[i]); + } + super.visitTableSwitchInsn(min, max, dflt, labels); + for (int i = 0; i < labels.length; ++i) { + usedLabels.add(labels[i]); + } + ++insnCount; + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + checkEndCode(); + checkStartCode(); + checkLabel(dflt, false, "default label"); + checkNonDebugLabel(dflt); + if (keys == null || labels == null || keys.length != labels.length) { + throw new IllegalArgumentException("There must be the same number of keys and labels"); + } + for (int i = 0; i < labels.length; ++i) { + checkLabel(labels[i], false, "label at index " + i); + checkNonDebugLabel(labels[i]); + } + super.visitLookupSwitchInsn(dflt, keys, labels); + usedLabels.add(dflt); + for (int i = 0; i < labels.length; ++i) { + usedLabels.add(labels[i]); + } + ++insnCount; + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + checkStartCode(); + checkEndCode(); + checkDesc(desc, false); + if (desc.charAt(0) != '[') { + throw new IllegalArgumentException("Invalid descriptor (must be an array type descriptor): " + + desc); + } + if (dims < 1) { + throw new IllegalArgumentException("Invalid dimensions (must be greater than 0): " + + dims); + } + if (dims > desc.lastIndexOf('[') + 1) { + throw new IllegalArgumentException("Invalid dimensions (must not be greater than dims(desc)): " + + dims); + } + super.visitMultiANewArrayInsn(desc, dims); + ++insnCount; + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + checkStartCode(); + checkEndCode(); + checkLabel(start, false, "start label"); + checkLabel(end, false, "end label"); + checkLabel(handler, false, "handler label"); + checkNonDebugLabel(start); + checkNonDebugLabel(end); + checkNonDebugLabel(handler); + if (labels.get(start) != null || labels.get(end) != null + || labels.get(handler) != null) + { + throw new IllegalStateException("Try catch blocks must be visited before their labels"); + } + if (type != null) { + checkInternalName(type, "type"); + } + super.visitTryCatchBlock(start, end, handler, type); + handlers.add(start); + handlers.add(end); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + checkStartCode(); + checkEndCode(); + checkUnqualifiedName(version, name, "name"); + checkDesc(desc, false); + checkLabel(start, true, "start label"); + checkLabel(end, true, "end label"); + checkUnsignedShort(index, "Invalid variable index"); + int s = labels.get(start).intValue(); + int e = labels.get(end).intValue(); + if (e < s) { + throw new IllegalArgumentException("Invalid start and end labels (end must be greater than start)"); + } + super.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + checkStartCode(); + checkEndCode(); + checkUnsignedShort(line, "Invalid line number"); + checkLabel(start, true, "start label"); + super.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + checkStartCode(); + checkEndCode(); + endCode = true; + for (Label l : usedLabels) { + if (labels.get(l) == null) { + throw new IllegalStateException("Undefined label used"); + } + } + for (int i = 0; i < handlers.size(); ) { + Integer start = labels.get(handlers.get(i++)); + Integer end = labels.get(handlers.get(i++)); + if (start == null || end == null) { + throw new IllegalStateException("Undefined try catch block labels"); + } + if (end.intValue() <= start.intValue()) { + throw new IllegalStateException("Emty try catch block handler range"); + } + } + checkUnsignedShort(maxStack, "Invalid max stack"); + checkUnsignedShort(maxLocals, "Invalid max locals"); + super.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + checkEndMethod(); + endMethod = true; + super.visitEnd(); + } + + // ------------------------------------------------------------------------- + + /** + * Checks that the visitCode method has been called. + */ + void checkStartCode() { + if (!startCode) { + throw new IllegalStateException("Cannot visit instructions before visitCode has been called."); + } + } + + /** + * Checks that the visitMaxs method has not been called. + */ + void checkEndCode() { + if (endCode) { + throw new IllegalStateException("Cannot visit instructions after visitMaxs has been called."); + } + } + + /** + * Checks that the visitEnd method has not been called. + */ + void checkEndMethod() { + if (endMethod) { + throw new IllegalStateException("Cannot visit elements after visitEnd has been called."); + } + } + + /** + * Checks a stack frame value. + * + * @param value the value to be checked. + */ + void checkFrameValue(final Object value) { + if (value == Opcodes.TOP || value == Opcodes.INTEGER + || value == Opcodes.FLOAT || value == Opcodes.LONG + || value == Opcodes.DOUBLE || value == Opcodes.NULL + || value == Opcodes.UNINITIALIZED_THIS) + { + return; + } + if (value instanceof String) { + checkInternalName((String) value, "Invalid stack frame value"); + return; + } + if (!(value instanceof Label)) { + throw new IllegalArgumentException("Invalid stack frame value: " + + value); + } else { + usedLabels.add((Label) value); + } + } + + /** + * Checks that the type of the given opcode is equal to the given type. + * + * @param opcode the opcode to be checked. + * @param type the expected opcode type. + */ + static void checkOpcode(final int opcode, final int type) { + if (opcode < 0 || opcode > 199 || TYPE[opcode] != type) { + throw new IllegalArgumentException("Invalid opcode: " + opcode); + } + } + + /** + * Checks that the given value is a signed byte. + * + * @param value the value to be checked. + * @param msg an message to be used in case of error. + */ + static void checkSignedByte(final int value, final String msg) { + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + throw new IllegalArgumentException(msg + + " (must be a signed byte): " + value); + } + } + + /** + * Checks that the given value is a signed short. + * + * @param value the value to be checked. + * @param msg an message to be used in case of error. + */ + static void checkSignedShort(final int value, final String msg) { + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + throw new IllegalArgumentException(msg + + " (must be a signed short): " + value); + } + } + + /** + * Checks that the given value is an unsigned short. + * + * @param value the value to be checked. + * @param msg an message to be used in case of error. + */ + static void checkUnsignedShort(final int value, final String msg) { + if (value < 0 || value > 65535) { + throw new IllegalArgumentException(msg + + " (must be an unsigned short): " + value); + } + } + + /** + * Checks that the given value is an {@link Integer}, a{@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * + * @param cst the value to be checked. + */ + static void checkConstant(final Object cst) { + if (!(cst instanceof Integer) && !(cst instanceof Float) + && !(cst instanceof Long) && !(cst instanceof Double) + && !(cst instanceof String)) + { + throw new IllegalArgumentException("Invalid constant: " + cst); + } + } + + void checkLDCConstant(final Object cst) { + if (cst instanceof Type) { + int s = ((Type) cst).getSort(); + if (s != Type.OBJECT && s != Type.ARRAY && s != Type.METHOD) { + throw new IllegalArgumentException("Illegal LDC constant value"); + } + if (s != Type.METHOD && (version & 0xFFFF) < Opcodes.V1_5) { + throw new IllegalArgumentException("ldc of a constant class requires at least version 1.5"); + } + if (s == Type.METHOD && (version & 0xFFFF) < Opcodes.V1_7) { + throw new IllegalArgumentException("ldc of a method type requires at least version 1.7"); + } + } else if (cst instanceof Handle) { + if ((version & 0xFFFF) < Opcodes.V1_7) { + throw new IllegalArgumentException("ldc of a handle requires at least version 1.7"); + } + int tag = ((Handle) cst).getTag(); + if (tag < Opcodes.H_GETFIELD || tag > Opcodes.H_INVOKEINTERFACE) { + throw new IllegalArgumentException("invalid handle tag " + + tag); + } + } else { + checkConstant(cst); + } + } + + /** + * Checks that the given string is a valid unqualified name. + * + * @param version the class version. + * @param name the string to be checked. + * @param msg a message to be used in case of error. + */ + static void checkUnqualifiedName( + int version, + final String name, + final String msg) + { + if ((version & 0xFFFF) < Opcodes.V1_5) { + checkIdentifier(name, msg); + } else { + for (int i = 0; i < name.length(); ++i) { + if (".;[/".indexOf(name.charAt(i)) != -1) { + throw new IllegalArgumentException("Invalid " + msg + + " (must be a valid unqualified name): " + name); + } + } + } + } + + /** + * Checks that the given string is a valid Java identifier. + * + * @param name the string to be checked. + * @param msg a message to be used in case of error. + */ + static void checkIdentifier(final String name, final String msg) { + checkIdentifier(name, 0, -1, msg); + } + + /** + * Checks that the given substring is a valid Java identifier. + * + * @param name the string to be checked. + * @param start index of the first character of the identifier (inclusive). + * @param end index of the last character of the identifier (exclusive). -1 + * is equivalent to <tt>name.length()</tt> if name is not + * <tt>null</tt>. + * @param msg a message to be used in case of error. + */ + static void checkIdentifier( + final String name, + final int start, + final int end, + final String msg) + { + if (name == null || (end == -1 ? name.length() <= start : end <= start)) + { + throw new IllegalArgumentException("Invalid " + msg + + " (must not be null or empty)"); + } + if (!Character.isJavaIdentifierStart(name.charAt(start))) { + throw new IllegalArgumentException("Invalid " + msg + + " (must be a valid Java identifier): " + name); + } + int max = end == -1 ? name.length() : end; + for (int i = start + 1; i < max; ++i) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + throw new IllegalArgumentException("Invalid " + msg + + " (must be a valid Java identifier): " + name); + } + } + } + + /** + * Checks that the given string is a valid Java identifier or is equal to + * '<init>' or '<clinit>'. + * + * @param version the class version. + * @param name the string to be checked. + * @param msg a message to be used in case of error. + */ + static void checkMethodIdentifier( + int version, + final String name, + final String msg) + { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Invalid " + msg + + " (must not be null or empty)"); + } + if ("<init>".equals(name) || "<clinit>".equals(name)) { + return; + } + if ((version & 0xFFFF) >= Opcodes.V1_5) { + for (int i = 0; i < name.length(); ++i) { + if (".;[/<>".indexOf(name.charAt(i)) != -1) { + throw new IllegalArgumentException("Invalid " + msg + + " (must be a valid unqualified name): " + name); + } + } + return; + } + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + throw new IllegalArgumentException("Invalid " + + msg + + " (must be a '<init>', '<clinit>' or a valid Java identifier): " + + name); + } + for (int i = 1; i < name.length(); ++i) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + throw new IllegalArgumentException("Invalid " + + msg + + " (must be '<init>' or '<clinit>' or a valid Java identifier): " + + name); + } + } + } + + /** + * Checks that the given string is a valid internal class name. + * + * @param name the string to be checked. + * @param msg a message to be used in case of error. + */ + static void checkInternalName(final String name, final String msg) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Invalid " + msg + + " (must not be null or empty)"); + } + if (name.charAt(0) == '[') { + checkDesc(name, false); + } else { + checkInternalName(name, 0, -1, msg); + } + } + + /** + * Checks that the given substring is a valid internal class name. + * + * @param name the string to be checked. + * @param start index of the first character of the identifier (inclusive). + * @param end index of the last character of the identifier (exclusive). -1 + * is equivalent to <tt>name.length()</tt> if name is not + * <tt>null</tt>. + * @param msg a message to be used in case of error. + */ + static void checkInternalName( + final String name, + final int start, + final int end, + final String msg) + { + int max = end == -1 ? name.length() : end; + try { + int begin = start; + int slash; + do { + slash = name.indexOf('/', begin + 1); + if (slash == -1 || slash > max) { + slash = max; + } + checkIdentifier(name, begin, slash, null); + begin = slash + 1; + } while (slash != max); + } catch (IllegalArgumentException _) { + throw new IllegalArgumentException("Invalid " + + msg + + " (must be a fully qualified class name in internal form): " + + name); + } + } + + /** + * Checks that the given string is a valid type descriptor. + * + * @param desc the string to be checked. + * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid. + */ + static void checkDesc(final String desc, final boolean canBeVoid) { + int end = checkDesc(desc, 0, canBeVoid); + if (end != desc.length()) { + throw new IllegalArgumentException("Invalid descriptor: " + desc); + } + } + + /** + * Checks that a the given substring is a valid type descriptor. + * + * @param desc the string to be checked. + * @param start index of the first character of the identifier (inclusive). + * @param canBeVoid <tt>true</tt> if <tt>V</tt> can be considered valid. + * @return the index of the last character of the type decriptor, plus one. + */ + static int checkDesc( + final String desc, + final int start, + final boolean canBeVoid) + { + if (desc == null || start >= desc.length()) { + throw new IllegalArgumentException("Invalid type descriptor (must not be null or empty)"); + } + int index; + switch (desc.charAt(start)) { + case 'V': + if (canBeVoid) { + return start + 1; + } else { + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + case 'F': + case 'J': + case 'D': + return start + 1; + case '[': + index = start + 1; + while (index < desc.length() && desc.charAt(index) == '[') { + ++index; + } + if (index < desc.length()) { + return checkDesc(desc, index, false); + } else { + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + case 'L': + index = desc.indexOf(';', start); + if (index == -1 || index - start < 2) { + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + try { + checkInternalName(desc, start + 1, index, null); + } catch (IllegalArgumentException _) { + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + return index + 1; + default: + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + } + + /** + * Checks that the given string is a valid method descriptor. + * + * @param desc the string to be checked. + */ + static void checkMethodDesc(final String desc) { + if (desc == null || desc.length() == 0) { + throw new IllegalArgumentException("Invalid method descriptor (must not be null or empty)"); + } + if (desc.charAt(0) != '(' || desc.length() < 3) { + throw new IllegalArgumentException("Invalid descriptor: " + desc); + } + int start = 1; + if (desc.charAt(start) != ')') { + do { + if (desc.charAt(start) == 'V') { + throw new IllegalArgumentException("Invalid descriptor: " + + desc); + } + start = checkDesc(desc, start, false); + } while (start < desc.length() && desc.charAt(start) != ')'); + } + start = checkDesc(desc, start + 1, true); + if (start != desc.length()) { + throw new IllegalArgumentException("Invalid descriptor: " + desc); + } + } + + /** + * Checks a class signature. + * + * @param signature a string containing the signature that must be checked. + */ + static void checkClassSignature(final String signature) { + // ClassSignature: + // FormalTypeParameters? ClassTypeSignature ClassTypeSignature* + + int pos = 0; + if (getChar(signature, 0) == '<') { + pos = checkFormalTypeParameters(signature, pos); + } + pos = checkClassTypeSignature(signature, pos); + while (getChar(signature, pos) == 'L') { + pos = checkClassTypeSignature(signature, pos); + } + if (pos != signature.length()) { + throw new IllegalArgumentException(signature + ": error at index " + + pos); + } + } + + /** + * Checks a method signature. + * + * @param signature a string containing the signature that must be checked. + */ + static void checkMethodSignature(final String signature) { + // MethodTypeSignature: + // FormalTypeParameters? ( TypeSignature* ) ( TypeSignature | V ) ( + // ^ClassTypeSignature | ^TypeVariableSignature )* + + int pos = 0; + if (getChar(signature, 0) == '<') { + pos = checkFormalTypeParameters(signature, pos); + } + pos = checkChar('(', signature, pos); + while ("ZCBSIFJDL[T".indexOf(getChar(signature, pos)) != -1) { + pos = checkTypeSignature(signature, pos); + } + pos = checkChar(')', signature, pos); + if (getChar(signature, pos) == 'V') { + ++pos; + } else { + pos = checkTypeSignature(signature, pos); + } + while (getChar(signature, pos) == '^') { + ++pos; + if (getChar(signature, pos) == 'L') { + pos = checkClassTypeSignature(signature, pos); + } else { + pos = checkTypeVariableSignature(signature, pos); + } + } + if (pos != signature.length()) { + throw new IllegalArgumentException(signature + ": error at index " + + pos); + } + } + + /** + * Checks a field signature. + * + * @param signature a string containing the signature that must be checked. + */ + static void checkFieldSignature(final String signature) { + int pos = checkFieldTypeSignature(signature, 0); + if (pos != signature.length()) { + throw new IllegalArgumentException(signature + ": error at index " + + pos); + } + } + + /** + * Checks the formal type parameters of a class or method signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkFormalTypeParameters(final String signature, int pos) + { + // FormalTypeParameters: + // < FormalTypeParameter+ > + + pos = checkChar('<', signature, pos); + pos = checkFormalTypeParameter(signature, pos); + while (getChar(signature, pos) != '>') { + pos = checkFormalTypeParameter(signature, pos); + } + return pos + 1; + } + + /** + * Checks a formal type parameter of a class or method signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkFormalTypeParameter(final String signature, int pos) + { + // FormalTypeParameter: + // Identifier : FieldTypeSignature? (: FieldTypeSignature)* + + pos = checkIdentifier(signature, pos); + pos = checkChar(':', signature, pos); + if ("L[T".indexOf(getChar(signature, pos)) != -1) { + pos = checkFieldTypeSignature(signature, pos); + } + while (getChar(signature, pos) == ':') { + pos = checkFieldTypeSignature(signature, pos + 1); + } + return pos; + } + + /** + * Checks a field type signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkFieldTypeSignature(final String signature, int pos) + { + // FieldTypeSignature: + // ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature + // + // ArrayTypeSignature: + // [ TypeSignature + + switch (getChar(signature, pos)) { + case 'L': + return checkClassTypeSignature(signature, pos); + case '[': + return checkTypeSignature(signature, pos + 1); + default: + return checkTypeVariableSignature(signature, pos); + } + } + + /** + * Checks a class type signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkClassTypeSignature(final String signature, int pos) + { + // ClassTypeSignature: + // L Identifier ( / Identifier )* TypeArguments? ( . Identifier + // TypeArguments? )* ; + + pos = checkChar('L', signature, pos); + pos = checkIdentifier(signature, pos); + while (getChar(signature, pos) == '/') { + pos = checkIdentifier(signature, pos + 1); + } + if (getChar(signature, pos) == '<') { + pos = checkTypeArguments(signature, pos); + } + while (getChar(signature, pos) == '.') { + pos = checkIdentifier(signature, pos + 1); + if (getChar(signature, pos) == '<') { + pos = checkTypeArguments(signature, pos); + } + } + return checkChar(';', signature, pos); + } + + /** + * Checks the type arguments in a class type signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkTypeArguments(final String signature, int pos) { + // TypeArguments: + // < TypeArgument+ > + + pos = checkChar('<', signature, pos); + pos = checkTypeArgument(signature, pos); + while (getChar(signature, pos) != '>') { + pos = checkTypeArgument(signature, pos); + } + return pos + 1; + } + + /** + * Checks a type argument in a class type signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkTypeArgument(final String signature, int pos) { + // TypeArgument: + // * | ( ( + | - )? FieldTypeSignature ) + + char c = getChar(signature, pos); + if (c == '*') { + return pos + 1; + } else if (c == '+' || c == '-') { + pos++; + } + return checkFieldTypeSignature(signature, pos); + } + + /** + * Checks a type variable signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkTypeVariableSignature( + final String signature, + int pos) + { + // TypeVariableSignature: + // T Identifier ; + + pos = checkChar('T', signature, pos); + pos = checkIdentifier(signature, pos); + return checkChar(';', signature, pos); + } + + /** + * Checks a type signature. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkTypeSignature(final String signature, int pos) { + // TypeSignature: + // Z | C | B | S | I | F | J | D | FieldTypeSignature + + switch (getChar(signature, pos)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + case 'F': + case 'J': + case 'D': + return pos + 1; + default: + return checkFieldTypeSignature(signature, pos); + } + } + + /** + * Checks an identifier. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkIdentifier(final String signature, int pos) { + if (!Character.isJavaIdentifierStart(getChar(signature, pos))) { + throw new IllegalArgumentException(signature + + ": identifier expected at index " + pos); + } + ++pos; + while (Character.isJavaIdentifierPart(getChar(signature, pos))) { + ++pos; + } + return pos; + } + + /** + * Checks a single character. + * + * @param signature a string containing the signature that must be checked. + * @param pos index of first character to be checked. + * @return the index of the first character after the checked part. + */ + private static int checkChar(final char c, final String signature, int pos) + { + if (getChar(signature, pos) == c) { + return pos + 1; + } + throw new IllegalArgumentException(signature + ": '" + c + + "' expected at index " + pos); + } + + /** + * Returns the signature car at the given index. + * + * @param signature a signature. + * @param pos an index in signature. + * @return the character at the given index, or 0 if there is no such + * character. + */ + private static char getChar(final String signature, int pos) { + return pos < signature.length() ? signature.charAt(pos) : (char) 0; + } + + /** + * Checks that the given label is not null. This method can also check that + * the label has been visited. + * + * @param label the label to be checked. + * @param checkVisited <tt>true</tt> to check that the label has been + * visited. + * @param msg a message to be used in case of error. + */ + void checkLabel( + final Label label, + final boolean checkVisited, + final String msg) + { + if (label == null) { + throw new IllegalArgumentException("Invalid " + msg + + " (must not be null)"); + } + if (checkVisited && labels.get(label) == null) { + throw new IllegalArgumentException("Invalid " + msg + + " (must be visited first)"); + } + } + + /** + * Checks that the given label is not a label used only for debug purposes. + * + * @param label the label to be checked. + */ + private static void checkNonDebugLabel(final Label label) { + Field f = getLabelStatusField(); + int status = 0; + try { + status = f == null ? 0 : ((Integer) f.get(label)).intValue(); + } catch (IllegalAccessException e) { + throw new Error("Internal error"); + } + if ((status & 0x01) != 0) { + throw new IllegalArgumentException("Labels used for debug info cannot be reused for control flow"); + } + } + + /** + * Returns the Field object corresponding to the Label.status field. + * + * @return the Field object corresponding to the Label.status field. + */ + private static Field getLabelStatusField() { + if (labelStatusField == null) { + labelStatusField = getLabelField("a"); + if (labelStatusField == null) { + labelStatusField = getLabelField("status"); + } + } + return labelStatusField; + } + + /** + * Returns the field of the Label class whose name is given. + * + * @param name a field name. + * @return the field of the Label class whose name is given, or null. + */ + private static Field getLabelField(final String name) { + try { + Field f = Label.class.getDeclaredField(name); + f.setAccessible(true); + return f; + } catch (NoSuchFieldException e) { + return null; + } + } +} diff --git a/src/compiler/scala/tools/asm/util/CheckSignatureAdapter.java b/src/compiler/scala/tools/asm/util/CheckSignatureAdapter.java new file mode 100644 index 0000000000..3a6c3e780f --- /dev/null +++ b/src/compiler/scala/tools/asm/util/CheckSignatureAdapter.java @@ -0,0 +1,329 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.Opcodes; +import scala.tools.asm.signature.SignatureVisitor; + +/** + * A {@link SignatureVisitor} that checks that its methods are properly used. + * + * @author Eric Bruneton + */ +public class CheckSignatureAdapter extends SignatureVisitor { + + /** + * Type to be used to check class signatures. See + * {@link #CheckSignatureAdapter(int, SignatureVisitor) CheckSignatureAdapter}. + */ + public static final int CLASS_SIGNATURE = 0; + + /** + * Type to be used to check method signatures. See + * {@link #CheckSignatureAdapter(int, SignatureVisitor) CheckSignatureAdapter}. + */ + public static final int METHOD_SIGNATURE = 1; + + /** + * Type to be used to check type signatures.See + * {@link #CheckSignatureAdapter(int, SignatureVisitor) CheckSignatureAdapter}. + */ + public static final int TYPE_SIGNATURE = 2; + + private static final int EMPTY = 1; + + private static final int FORMAL = 2; + + private static final int BOUND = 4; + + private static final int SUPER = 8; + + private static final int PARAM = 16; + + private static final int RETURN = 32; + + private static final int SIMPLE_TYPE = 64; + + private static final int CLASS_TYPE = 128; + + private static final int END = 256; + + /** + * Type of the signature to be checked. + */ + private final int type; + + /** + * State of the automaton used to check the order of method calls. + */ + private int state; + + /** + * <tt>true</tt> if the checked type signature can be 'V'. + */ + private boolean canBeVoid; + + /** + * The visitor to which this adapter must delegate calls. May be + * <tt>null</tt>. + */ + private final SignatureVisitor sv; + + /** + * Creates a new {@link CheckSignatureAdapter} object. <i>Subclasses must + * not use this constructor</i>. Instead, they must use the + * {@link #CheckSignatureAdapter(int, int, SignatureVisitor)} version. + * + * @param type the type of signature to be checked. See + * {@link #CLASS_SIGNATURE}, {@link #METHOD_SIGNATURE} and + * {@link #TYPE_SIGNATURE}. + * @param sv the visitor to which this adapter must delegate calls. May be + * <tt>null</tt>. + */ + public CheckSignatureAdapter(final int type, final SignatureVisitor sv) { + this(Opcodes.ASM4, type, sv); + } + + /** + * Creates a new {@link CheckSignatureAdapter} object. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + * @param type the type of signature to be checked. See + * {@link #CLASS_SIGNATURE}, {@link #METHOD_SIGNATURE} and + * {@link #TYPE_SIGNATURE}. + * @param sv the visitor to which this adapter must delegate calls. May be + * <tt>null</tt>. + */ + protected CheckSignatureAdapter( + final int api, + final int type, + final SignatureVisitor sv) + { + super(api); + this.type = type; + this.state = EMPTY; + this.sv = sv; + } + + // class and method signatures + + @Override + public void visitFormalTypeParameter(final String name) { + if (type == TYPE_SIGNATURE + || (state != EMPTY && state != FORMAL && state != BOUND)) + { + throw new IllegalStateException(); + } + CheckMethodAdapter.checkIdentifier(name, "formal type parameter"); + state = FORMAL; + if (sv != null) { + sv.visitFormalTypeParameter(name); + } + } + + @Override + public SignatureVisitor visitClassBound() { + if (state != FORMAL) { + throw new IllegalStateException(); + } + state = BOUND; + SignatureVisitor v = sv == null ? null : sv.visitClassBound(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + @Override + public SignatureVisitor visitInterfaceBound() { + if (state != FORMAL && state != BOUND) { + throw new IllegalArgumentException(); + } + SignatureVisitor v = sv == null ? null : sv.visitInterfaceBound(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + // class signatures + + @Override + public SignatureVisitor visitSuperclass() { + if (type != CLASS_SIGNATURE || (state & (EMPTY | FORMAL | BOUND)) == 0) + { + throw new IllegalArgumentException(); + } + state = SUPER; + SignatureVisitor v = sv == null ? null : sv.visitSuperclass(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + @Override + public SignatureVisitor visitInterface() { + if (state != SUPER) { + throw new IllegalStateException(); + } + SignatureVisitor v = sv == null ? null : sv.visitInterface(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + // method signatures + + @Override + public SignatureVisitor visitParameterType() { + if (type != METHOD_SIGNATURE + || (state & (EMPTY | FORMAL | BOUND | PARAM)) == 0) + { + throw new IllegalArgumentException(); + } + state = PARAM; + SignatureVisitor v = sv == null ? null : sv.visitParameterType(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + @Override + public SignatureVisitor visitReturnType() { + if (type != METHOD_SIGNATURE + || (state & (EMPTY | FORMAL | BOUND | PARAM)) == 0) + { + throw new IllegalArgumentException(); + } + state = RETURN; + SignatureVisitor v = sv == null ? null : sv.visitReturnType(); + CheckSignatureAdapter cv = new CheckSignatureAdapter(TYPE_SIGNATURE, v); + cv.canBeVoid = true; + return cv; + } + + @Override + public SignatureVisitor visitExceptionType() { + if (state != RETURN) { + throw new IllegalStateException(); + } + SignatureVisitor v = sv == null ? null : sv.visitExceptionType(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + // type signatures + + @Override + public void visitBaseType(final char descriptor) { + if (type != TYPE_SIGNATURE || state != EMPTY) { + throw new IllegalStateException(); + } + if (descriptor == 'V') { + if (!canBeVoid) { + throw new IllegalArgumentException(); + } + } else { + if ("ZCBSIFJD".indexOf(descriptor) == -1) { + throw new IllegalArgumentException(); + } + } + state = SIMPLE_TYPE; + if (sv != null) { + sv.visitBaseType(descriptor); + } + } + + @Override + public void visitTypeVariable(final String name) { + if (type != TYPE_SIGNATURE || state != EMPTY) { + throw new IllegalStateException(); + } + CheckMethodAdapter.checkIdentifier(name, "type variable"); + state = SIMPLE_TYPE; + if (sv != null) { + sv.visitTypeVariable(name); + } + } + + @Override + public SignatureVisitor visitArrayType() { + if (type != TYPE_SIGNATURE || state != EMPTY) { + throw new IllegalStateException(); + } + state = SIMPLE_TYPE; + SignatureVisitor v = sv == null ? null : sv.visitArrayType(); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + @Override + public void visitClassType(final String name) { + if (type != TYPE_SIGNATURE || state != EMPTY) { + throw new IllegalStateException(); + } + CheckMethodAdapter.checkInternalName(name, "class name"); + state = CLASS_TYPE; + if (sv != null) { + sv.visitClassType(name); + } + } + + @Override + public void visitInnerClassType(final String name) { + if (state != CLASS_TYPE) { + throw new IllegalStateException(); + } + CheckMethodAdapter.checkIdentifier(name, "inner class name"); + if (sv != null) { + sv.visitInnerClassType(name); + } + } + + @Override + public void visitTypeArgument() { + if (state != CLASS_TYPE) { + throw new IllegalStateException(); + } + if (sv != null) { + sv.visitTypeArgument(); + } + } + + @Override + public SignatureVisitor visitTypeArgument(final char wildcard) { + if (state != CLASS_TYPE) { + throw new IllegalStateException(); + } + if ("+-=".indexOf(wildcard) == -1) { + throw new IllegalArgumentException(); + } + SignatureVisitor v = sv == null ? null : sv.visitTypeArgument(wildcard); + return new CheckSignatureAdapter(TYPE_SIGNATURE, v); + } + + @Override + public void visitEnd() { + if (state != CLASS_TYPE) { + throw new IllegalStateException(); + } + state = END; + if (sv != null) { + sv.visitEnd(); + } + } +} diff --git a/src/compiler/scala/tools/asm/util/Printer.java b/src/compiler/scala/tools/asm/util/Printer.java new file mode 100644 index 0000000000..c39fd548ce --- /dev/null +++ b/src/compiler/scala/tools/asm/util/Printer.java @@ -0,0 +1,558 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import scala.tools.asm.Attribute; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.Opcodes; + +/** + * An abstract converter from visit events to text. + * + * @author Eric Bruneton + */ +public abstract class Printer { + + /** + * The names of the Java Virtual Machine opcodes. + */ + public static final String[] OPCODES; + + /** + * The names of the for <code>operand</code> parameter values of the + * {@link org.objectweb.asm.MethodVisitor#visitIntInsn} method when + * <code>opcode</code> is <code>NEWARRAY</code>. + */ + public static final String[] TYPES; + + /** + * The names of the <code>tag</code> field values for + * {@link org.objectweb.asm.Handle}. + */ + public static final String[] HANDLE_TAG; + + static { + String s = "NOP,ACONST_NULL,ICONST_M1,ICONST_0,ICONST_1,ICONST_2," + + "ICONST_3,ICONST_4,ICONST_5,LCONST_0,LCONST_1,FCONST_0," + + "FCONST_1,FCONST_2,DCONST_0,DCONST_1,BIPUSH,SIPUSH,LDC,,," + + "ILOAD,LLOAD,FLOAD,DLOAD,ALOAD,,,,,,,,,,,,,,,,,,,,,IALOAD," + + "LALOAD,FALOAD,DALOAD,AALOAD,BALOAD,CALOAD,SALOAD,ISTORE," + + "LSTORE,FSTORE,DSTORE,ASTORE,,,,,,,,,,,,,,,,,,,,,IASTORE," + + "LASTORE,FASTORE,DASTORE,AASTORE,BASTORE,CASTORE,SASTORE,POP," + + "POP2,DUP,DUP_X1,DUP_X2,DUP2,DUP2_X1,DUP2_X2,SWAP,IADD,LADD," + + "FADD,DADD,ISUB,LSUB,FSUB,DSUB,IMUL,LMUL,FMUL,DMUL,IDIV,LDIV," + + "FDIV,DDIV,IREM,LREM,FREM,DREM,INEG,LNEG,FNEG,DNEG,ISHL,LSHL," + + "ISHR,LSHR,IUSHR,LUSHR,IAND,LAND,IOR,LOR,IXOR,LXOR,IINC,I2L," + + "I2F,I2D,L2I,L2F,L2D,F2I,F2L,F2D,D2I,D2L,D2F,I2B,I2C,I2S,LCMP," + + "FCMPL,FCMPG,DCMPL,DCMPG,IFEQ,IFNE,IFLT,IFGE,IFGT,IFLE," + + "IF_ICMPEQ,IF_ICMPNE,IF_ICMPLT,IF_ICMPGE,IF_ICMPGT,IF_ICMPLE," + + "IF_ACMPEQ,IF_ACMPNE,GOTO,JSR,RET,TABLESWITCH,LOOKUPSWITCH," + + "IRETURN,LRETURN,FRETURN,DRETURN,ARETURN,RETURN,GETSTATIC," + + "PUTSTATIC,GETFIELD,PUTFIELD,INVOKEVIRTUAL,INVOKESPECIAL," + + "INVOKESTATIC,INVOKEINTERFACE,INVOKEDYNAMIC,NEW,NEWARRAY," + + "ANEWARRAY,ARRAYLENGTH,ATHROW,CHECKCAST,INSTANCEOF," + + "MONITORENTER,MONITOREXIT,,MULTIANEWARRAY,IFNULL,IFNONNULL,"; + OPCODES = new String[200]; + int i = 0; + int j = 0; + int l; + while ((l = s.indexOf(',', j)) > 0) { + OPCODES[i++] = j + 1 == l ? null : s.substring(j, l); + j = l + 1; + } + + s = "T_BOOLEAN,T_CHAR,T_FLOAT,T_DOUBLE,T_BYTE,T_SHORT,T_INT,T_LONG,"; + TYPES = new String[12]; + j = 0; + i = 4; + while ((l = s.indexOf(',', j)) > 0) { + TYPES[i++] = s.substring(j, l); + j = l + 1; + } + + s = "H_GETFIELD,H_GETSTATIC,H_PUTFIELD,H_PUTSTATIC," + + "H_INVOKEVIRTUAL,H_INVOKESTATIC,H_INVOKESPECIAL," + + "H_NEWINVOKESPECIAL,H_INVOKEINTERFACE,"; + HANDLE_TAG = new String[10]; + j = 0; + i = 1; + while ((l = s.indexOf(',', j)) > 0) { + HANDLE_TAG[i++] = s.substring(j, l); + j = l + 1; + } + } + + /** + * The ASM API version implemented by this class. The value of this field + * must be one of {@link Opcodes#ASM4}. + */ + protected final int api; + + /** + * A buffer that can be used to create strings. + */ + protected final StringBuffer buf; + + /** + * The text to be printed. Since the code of methods is not necessarily + * visited in sequential order, one method after the other, but can be + * interlaced (some instructions from method one, then some instructions + * from method two, then some instructions from method one again...), it is + * not possible to print the visited instructions directly to a sequential + * stream. A class is therefore printed in a two steps process: a string + * tree is constructed during the visit, and printed to a sequential stream + * at the end of the visit. This string tree is stored in this field, as a + * string list that can contain other string lists, which can themselves + * contain other string lists, and so on. + */ + public final List<Object> text; + + /** + * Constructs a new {@link Printer}. + */ + protected Printer(final int api) { + this.api = api; + this.buf = new StringBuffer(); + this.text = new ArrayList<Object>(); + } + + /** + * Class header. + * See {@link org.objectweb.asm.ClassVisitor#visit}. + */ + public abstract void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces); + + /** + * Class source. + * See {@link org.objectweb.asm.ClassVisitor#visitSource}. + */ + public abstract void visitSource(final String file, final String debug); + + /** + * Class outer class. + * See {@link org.objectweb.asm.ClassVisitor#visitOuterClass}. + */ + public abstract void visitOuterClass( + final String owner, + final String name, + final String desc); + + /** + * Class annotation. + * See {@link org.objectweb.asm.ClassVisitor#visitAnnotation}. + */ + public abstract Printer visitClassAnnotation( + final String desc, + final boolean visible); + + /** + * Class attribute. + * See {@link org.objectweb.asm.ClassVisitor#visitAttribute}. + */ + public abstract void visitClassAttribute(final Attribute attr); + + /** + * Class inner name. + * See {@link org.objectweb.asm.ClassVisitor#visitInnerClass}. + */ + public abstract void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access); + + /** + * Class field. + * See {@link org.objectweb.asm.ClassVisitor#visitField}. + */ + public abstract Printer visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value); + + /** + * Class method. + * See {@link org.objectweb.asm.ClassVisitor#visitMethod}. + */ + public abstract Printer visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions); + + /** + * Class end. + * See {@link org.objectweb.asm.ClassVisitor#visitEnd}. + */ + public abstract void visitClassEnd(); + + // ------------------------------------------------------------------------ + // Annotations + // ------------------------------------------------------------------------ + + /** + * Annotation value. + * See {@link org.objectweb.asm.AnnotationVisitor#visit}. + */ + public abstract void visit(final String name, final Object value); + + /** + * Annotation enum value. + * See {@link org.objectweb.asm.AnnotationVisitor#visitEnum}. + */ + public abstract void visitEnum( + final String name, + final String desc, + final String value); + + /** + * Nested annotation value. + * See {@link org.objectweb.asm.AnnotationVisitor#visitAnnotation}. + */ + public abstract Printer visitAnnotation( + final String name, + final String desc); + + /** + * Annotation array value. + * See {@link org.objectweb.asm.AnnotationVisitor#visitArray}. + */ + public abstract Printer visitArray(final String name); + + /** + * Annotation end. + * See {@link org.objectweb.asm.AnnotationVisitor#visitEnd}. + */ + public abstract void visitAnnotationEnd(); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * Field annotation. + * See {@link org.objectweb.asm.FieldVisitor#visitAnnotation}. + */ + public abstract Printer visitFieldAnnotation( + final String desc, + final boolean visible); + + /** + * Field attribute. + * See {@link org.objectweb.asm.FieldVisitor#visitAttribute}. + */ + public abstract void visitFieldAttribute(final Attribute attr); + + /** + * Field end. + * See {@link org.objectweb.asm.FieldVisitor#visitEnd}. + */ + public abstract void visitFieldEnd(); + + // ------------------------------------------------------------------------ + // Methods + // ------------------------------------------------------------------------ + + /** + * Method default annotation. + * See {@link org.objectweb.asm.MethodVisitor#visitAnnotationDefault}. + */ + public abstract Printer visitAnnotationDefault(); + + /** + * Method annotation. + * See {@link org.objectweb.asm.MethodVisitor#visitAnnotation}. + */ + public abstract Printer visitMethodAnnotation( + final String desc, + final boolean visible); + + /** + * Method parameter annotation. + * See {@link org.objectweb.asm.MethodVisitor#visitParameterAnnotation}. + */ + public abstract Printer visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible); + + /** + * Method attribute. + * See {@link org.objectweb.asm.MethodVisitor#visitAttribute}. + */ + public abstract void visitMethodAttribute(final Attribute attr); + + /** + * Method start. + * See {@link org.objectweb.asm.MethodVisitor#visitCode}. + */ + public abstract void visitCode(); + + /** + * Method stack frame. + * See {@link org.objectweb.asm.MethodVisitor#visitFrame}. + */ + public abstract void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitInsn}. + */ + public abstract void visitInsn(final int opcode); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitIntInsn}. + */ + public abstract void visitIntInsn(final int opcode, final int operand); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitVarInsn}. + */ + public abstract void visitVarInsn(final int opcode, final int var); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitTypeInsn}. + */ + public abstract void visitTypeInsn(final int opcode, final String type); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitFieldInsn}. + */ + public abstract void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitMethodInsn}. + */ + public abstract void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitInvokeDynamicInsn}. + */ + public abstract void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitJumpInsn}. + */ + public abstract void visitJumpInsn(final int opcode, final Label label); + + /** + * Method label. + * See {@link org.objectweb.asm.MethodVisitor#visitLabel}. + */ + public abstract void visitLabel(final Label label); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitLdcInsn}. + */ + public abstract void visitLdcInsn(final Object cst); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitIincInsn}. + */ + public abstract void visitIincInsn(final int var, final int increment); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitTableSwitchInsn}. + */ + public abstract void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitLookupSwitchInsn}. + */ + public abstract void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels); + + /** + * Method instruction. + * See {@link org.objectweb.asm.MethodVisitor#visitMultiANewArrayInsn}. + */ + public abstract void visitMultiANewArrayInsn( + final String desc, + final int dims); + + /** + * Method exception handler. + * See {@link org.objectweb.asm.MethodVisitor#visitTryCatchBlock}. + */ + public abstract void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type); + + /** + * Method debug info. + * See {@link org.objectweb.asm.MethodVisitor#visitLocalVariable}. + */ + public abstract void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index); + + /** + * Method debug info. + * See {@link org.objectweb.asm.MethodVisitor#visitLineNumber}. + */ + public abstract void visitLineNumber(final int line, final Label start); + + /** + * Method max stack and max locals. + * See {@link org.objectweb.asm.MethodVisitor#visitMaxs}. + */ + public abstract void visitMaxs(final int maxStack, final int maxLocals); + + /** + * Method end. + * See {@link org.objectweb.asm.MethodVisitor#visitEnd}. + */ + public abstract void visitMethodEnd(); + + /** + * Returns the text constructed by this visitor. + * + * @return the text constructed by this visitor. + */ + public List<Object> getText() { + return text; + } + + /** + * Prints the text constructed by this visitor. + * + * @param pw the print writer to be used. + */ + public void print(final PrintWriter pw) { + printList(pw, text); + } + + /** + * Appends a quoted string to a given buffer. + * + * @param buf the buffer where the string must be added. + * @param s the string to be added. + */ + public static void appendString(final StringBuffer buf, final String s) { + buf.append('\"'); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + if (c == '\n') { + buf.append("\\n"); + } else if (c == '\r') { + buf.append("\\r"); + } else if (c == '\\') { + buf.append("\\\\"); + } else if (c == '"') { + buf.append("\\\""); + } else if (c < 0x20 || c > 0x7f) { + buf.append("\\u"); + if (c < 0x10) { + buf.append("000"); + } else if (c < 0x100) { + buf.append("00"); + } else if (c < 0x1000) { + buf.append('0'); + } + buf.append(Integer.toString(c, 16)); + } else { + buf.append(c); + } + } + buf.append('\"'); + } + + /** + * Prints the given string tree. + * + * @param pw the writer to be used to print the tree. + * @param l a string tree, i.e., a string list that can contain other string + * lists, and so on recursively. + */ + static void printList(final PrintWriter pw, final List<?> l) { + for (int i = 0; i < l.size(); ++i) { + Object o = l.get(i); + if (o instanceof List) { + printList(pw, (List<?>) o); + } else { + pw.print(o.toString()); + } + } + } +} diff --git a/src/compiler/scala/tools/asm/util/SignatureChecker.java b/src/compiler/scala/tools/asm/util/SignatureChecker.java new file mode 100644 index 0000000000..7b7eea4383 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/SignatureChecker.java @@ -0,0 +1,47 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + */ + +package scala.tools.asm.util; + +import scala.tools.asm.util.CheckMethodAdapter; +import scala.tools.asm.MethodVisitor; + +/** + * A subclass of ASM's CheckMethodAdapter for the sole purpose of accessing some protected methods there. + * + */ +public class SignatureChecker extends CheckMethodAdapter { + + public SignatureChecker(final MethodVisitor mv) { + super(mv); + } + + /** + * Checks a class signature. + * + * @param signature a string containing the signature that must be checked. + */ + public static void checkClassSignature(final String signature) { + CheckMethodAdapter.checkClassSignature(signature); + } + + /** + * Checks a method signature. + * + * @param signature a string containing the signature that must be checked. + */ + public static void checkMethodSignature(final String signature) { + CheckMethodAdapter.checkMethodSignature(signature); + } + + /** + * Checks a field signature. + * + * @param signature a string containing the signature that must be checked. + */ + public static void checkFieldSignature(final String signature) { + CheckMethodAdapter.checkFieldSignature(signature); + } + +} diff --git a/src/compiler/scala/tools/asm/util/Textifiable.java b/src/compiler/scala/tools/asm/util/Textifiable.java new file mode 100644 index 0000000000..b80d0139db --- /dev/null +++ b/src/compiler/scala/tools/asm/util/Textifiable.java @@ -0,0 +1,54 @@ +/** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.util.Map; + +import scala.tools.asm.Label; + +/** + * An {@link org.objectweb.asm.Attribute Attribute} that can print a readable + * representation of itself. + * + * Implementations should construct readable output from an attribute data + * structure. Such representation could be used in unit test assertions. + * + * @author Eugene Kuleshov + */ +public interface Textifiable { + + /** + * Build a human readable representation of this attribute. + * + * @param buf a buffer used for printing Java code. + * @param labelNames map of label instances to their names. + */ + void textify(StringBuffer buf, Map<Label, String> labelNames); +} diff --git a/src/compiler/scala/tools/asm/util/Textifier.java b/src/compiler/scala/tools/asm/util/Textifier.java new file mode 100644 index 0000000000..8d40ebd026 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/Textifier.java @@ -0,0 +1,1286 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassReader; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.Opcodes; +import scala.tools.asm.Type; +import scala.tools.asm.signature.SignatureReader; + +/** + * A {@link Printer} that prints a disassembled view of the classes it visits. + * + * @author Eric Bruneton + */ +public class Textifier extends Printer { + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for internal + * type names in bytecode notation. + */ + public static final int INTERNAL_NAME = 0; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for field + * descriptors, formatted in bytecode notation + */ + public static final int FIELD_DESCRIPTOR = 1; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for field + * signatures, formatted in bytecode notation + */ + public static final int FIELD_SIGNATURE = 2; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for method + * descriptors, formatted in bytecode notation + */ + public static final int METHOD_DESCRIPTOR = 3; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for method + * signatures, formatted in bytecode notation + */ + public static final int METHOD_SIGNATURE = 4; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for class + * signatures, formatted in bytecode notation + */ + public static final int CLASS_SIGNATURE = 5; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for field or + * method return value signatures, formatted in default Java notation + * (non-bytecode) + */ + public static final int TYPE_DECLARATION = 6; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for class + * signatures, formatted in default Java notation (non-bytecode) + */ + public static final int CLASS_DECLARATION = 7; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for method + * parameter signatures, formatted in default Java notation (non-bytecode) + */ + public static final int PARAMETERS_DECLARATION = 8; + + /** + * Constant used in {@link #appendDescriptor appendDescriptor} for handle + * descriptors, formatted in bytecode notation + */ + public static final int HANDLE_DESCRIPTOR = 9; + + /** + * Tab for class members. + */ + protected String tab = " "; + + /** + * Tab for bytecode instructions. + */ + protected String tab2 = " "; + + /** + * Tab for table and lookup switch instructions. + */ + protected String tab3 = " "; + + /** + * Tab for labels. + */ + protected String ltab = " "; + + /** + * The label names. This map associate String values to Label keys. + */ + protected Map<Label, String> labelNames; + + private int valueNumber = 0; + + /** + * Constructs a new {@link Textifier}. <i>Subclasses must not use this + * constructor</i>. Instead, they must use the {@link #Textifier(int)} + * version. + */ + public Textifier() { + this(Opcodes.ASM4); + } + + /** + * Constructs a new {@link Textifier}. + * + * @param api the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4}. + */ + protected Textifier(final int api) { + super(api); + } + + /** + * Prints a disassembled view of the given class to the standard output. <p> + * Usage: Textifier [-debug] <binary class name or class + * file name > + * + * @param args the command line arguments. + * + * @throws Exception if the class cannot be found, or if an IO exception + * occurs. + */ + public static void main(final String[] args) throws Exception { + int i = 0; + int flags = ClassReader.SKIP_DEBUG; + + boolean ok = true; + if (args.length < 1 || args.length > 2) { + ok = false; + } + if (ok && "-debug".equals(args[0])) { + i = 1; + flags = 0; + if (args.length != 2) { + ok = false; + } + } + if (!ok) { + System.err.println("Prints a disassembled view of the given class."); + System.err.println("Usage: Textifier [-debug] " + + "<fully qualified class name or class file name>"); + return; + } + ClassReader cr; + if (args[i].endsWith(".class") || args[i].indexOf('\\') > -1 + || args[i].indexOf('/') > -1) + { + cr = new ClassReader(new FileInputStream(args[i])); + } else { + cr = new ClassReader(args[i]); + } + cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), + flags); + } + + // ------------------------------------------------------------------------ + // Classes + // ------------------------------------------------------------------------ + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + int major = version & 0xFFFF; + int minor = version >>> 16; + buf.setLength(0); + buf.append("// class version ") + .append(major) + .append('.') + .append(minor) + .append(" (") + .append(version) + .append(")\n"); + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + buf.append("// DEPRECATED\n"); + } + buf.append("// access flags 0x").append(Integer.toHexString(access).toUpperCase()).append('\n'); + + appendDescriptor(CLASS_SIGNATURE, signature); + if (signature != null) { + TraceSignatureVisitor sv = new TraceSignatureVisitor(access); + SignatureReader r = new SignatureReader(signature); + r.accept(sv); + buf.append("// declaration: ") + .append(name) + .append(sv.getDeclaration()) + .append('\n'); + } + + appendAccess(access & ~Opcodes.ACC_SUPER); + if ((access & Opcodes.ACC_ANNOTATION) != 0) { + buf.append("@interface "); + } else if ((access & Opcodes.ACC_INTERFACE) != 0) { + buf.append("interface "); + } else if ((access & Opcodes.ACC_ENUM) == 0) { + buf.append("class "); + } + appendDescriptor(INTERNAL_NAME, name); + + if (superName != null && !"java/lang/Object".equals(superName)) { + buf.append(" extends "); + appendDescriptor(INTERNAL_NAME, superName); + buf.append(' '); + } + if (interfaces != null && interfaces.length > 0) { + buf.append(" implements "); + for (int i = 0; i < interfaces.length; ++i) { + appendDescriptor(INTERNAL_NAME, interfaces[i]); + buf.append(' '); + } + } + buf.append(" {\n\n"); + + text.add(buf.toString()); + } + + @Override + public void visitSource(final String file, final String debug) { + buf.setLength(0); + if (file != null) { + buf.append(tab) + .append("// compiled from: ") + .append(file) + .append('\n'); + } + if (debug != null) { + buf.append(tab) + .append("// debug info: ") + .append(debug) + .append('\n'); + } + if (buf.length() > 0) { + text.add(buf.toString()); + } + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append(tab).append("OUTERCLASS "); + appendDescriptor(INTERNAL_NAME, owner); + buf.append(' '); + if (name != null) { + buf.append(name).append(' '); + } + appendDescriptor(METHOD_DESCRIPTOR, desc); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public Textifier visitClassAnnotation( + final String desc, + final boolean visible) + { + text.add("\n"); + return visitAnnotation(desc, visible); + } + + @Override + public void visitClassAttribute(final Attribute attr) { + text.add("\n"); + visitAttribute(attr); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + buf.setLength(0); + buf.append(tab).append("// access flags 0x"); + buf.append(Integer.toHexString(access & ~Opcodes.ACC_SUPER).toUpperCase()).append('\n'); + buf.append(tab); + appendAccess(access); + buf.append("INNERCLASS "); + appendDescriptor(INTERNAL_NAME, name); + buf.append(' '); + appendDescriptor(INTERNAL_NAME, outerName); + buf.append(' '); + appendDescriptor(INTERNAL_NAME, innerName); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public Textifier visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + buf.setLength(0); + buf.append('\n'); + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + buf.append(tab).append("// DEPRECATED\n"); + } + buf.append(tab).append("// access flags 0x").append(Integer.toHexString(access).toUpperCase()).append('\n'); + if (signature != null) { + buf.append(tab); + appendDescriptor(FIELD_SIGNATURE, signature); + + TraceSignatureVisitor sv = new TraceSignatureVisitor(0); + SignatureReader r = new SignatureReader(signature); + r.acceptType(sv); + buf.append(tab) + .append("// declaration: ") + .append(sv.getDeclaration()) + .append('\n'); + } + + buf.append(tab); + appendAccess(access); + + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append(' ').append(name); + if (value != null) { + buf.append(" = "); + if (value instanceof String) { + buf.append('\"').append(value).append('\"'); + } else { + buf.append(value); + } + } + + buf.append('\n'); + text.add(buf.toString()); + + Textifier t = createTextifier(); + text.add(t.getText()); + return t; + } + + @Override + public Textifier visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + buf.setLength(0); + buf.append('\n'); + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + buf.append(tab).append("// DEPRECATED\n"); + } + buf.append(tab).append("// access flags 0x").append(Integer.toHexString(access).toUpperCase()).append('\n'); + + if (signature != null) { + buf.append(tab); + appendDescriptor(METHOD_SIGNATURE, signature); + + TraceSignatureVisitor v = new TraceSignatureVisitor(0); + SignatureReader r = new SignatureReader(signature); + r.accept(v); + String genericDecl = v.getDeclaration(); + String genericReturn = v.getReturnType(); + String genericExceptions = v.getExceptions(); + + buf.append(tab) + .append("// declaration: ") + .append(genericReturn) + .append(' ') + .append(name) + .append(genericDecl); + if (genericExceptions != null) { + buf.append(" throws ").append(genericExceptions); + } + buf.append('\n'); + } + + buf.append(tab); + appendAccess(access); + if ((access & Opcodes.ACC_NATIVE) != 0) { + buf.append("native "); + } + if ((access & Opcodes.ACC_VARARGS) != 0) { + buf.append("varargs "); + } + if ((access & Opcodes.ACC_BRIDGE) != 0) { + buf.append("bridge "); + } + + buf.append(name); + appendDescriptor(METHOD_DESCRIPTOR, desc); + if (exceptions != null && exceptions.length > 0) { + buf.append(" throws "); + for (int i = 0; i < exceptions.length; ++i) { + appendDescriptor(INTERNAL_NAME, exceptions[i]); + buf.append(' '); + } + } + + buf.append('\n'); + text.add(buf.toString()); + + Textifier t = createTextifier(); + text.add(t.getText()); + return t; + } + + @Override + public void visitClassEnd() { + text.add("}\n"); + } + + // ------------------------------------------------------------------------ + // Annotations + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + buf.setLength(0); + appendComa(valueNumber++); + + if (name != null) { + buf.append(name).append('='); + } + + if (value instanceof String) { + visitString((String) value); + } else if (value instanceof Type) { + visitType((Type) value); + } else if (value instanceof Byte) { + visitByte(((Byte) value).byteValue()); + } else if (value instanceof Boolean) { + visitBoolean(((Boolean) value).booleanValue()); + } else if (value instanceof Short) { + visitShort(((Short) value).shortValue()); + } else if (value instanceof Character) { + visitChar(((Character) value).charValue()); + } else if (value instanceof Integer) { + visitInt(((Integer) value).intValue()); + } else if (value instanceof Float) { + visitFloat(((Float) value).floatValue()); + } else if (value instanceof Long) { + visitLong(((Long) value).longValue()); + } else if (value instanceof Double) { + visitDouble(((Double) value).doubleValue()); + } else if (value.getClass().isArray()) { + buf.append('{'); + if (value instanceof byte[]) { + byte[] v = (byte[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitByte(v[i]); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitBoolean(v[i]); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitShort(v[i]); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitChar(v[i]); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitInt(v[i]); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitLong(v[i]); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitFloat(v[i]); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + for (int i = 0; i < v.length; i++) { + appendComa(i); + visitDouble(v[i]); + } + } + buf.append('}'); + } + + text.add(buf.toString()); + } + + private void visitInt(final int value) { + buf.append(value); + } + + private void visitLong(final long value) { + buf.append(value).append('L'); + } + + private void visitFloat(final float value) { + buf.append(value).append('F'); + } + + private void visitDouble(final double value) { + buf.append(value).append('D'); + } + + private void visitChar(final char value) { + buf.append("(char)").append((int) value); + } + + private void visitShort(final short value) { + buf.append("(short)").append(value); + } + + private void visitByte(final byte value) { + buf.append("(byte)").append(value); + } + + private void visitBoolean(final boolean value) { + buf.append(value); + } + + private void visitString(final String value) { + appendString(buf, value); + } + + private void visitType(final Type value) { + buf.append(value.getClassName()).append(".class"); + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + buf.setLength(0); + appendComa(valueNumber++); + if (name != null) { + buf.append(name).append('='); + } + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('.').append(value); + text.add(buf.toString()); + } + + @Override + public Textifier visitAnnotation( + final String name, + final String desc) + { + buf.setLength(0); + appendComa(valueNumber++); + if (name != null) { + buf.append(name).append('='); + } + buf.append('@'); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + text.add(")"); + return t; + } + + @Override + public Textifier visitArray( + final String name) + { + buf.setLength(0); + appendComa(valueNumber++); + if (name != null) { + buf.append(name).append('='); + } + buf.append('{'); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + text.add("}"); + return t; + } + + @Override + public void visitAnnotationEnd() { + } + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + @Override + public Textifier visitFieldAnnotation( + final String desc, + final boolean visible) + { + return visitAnnotation(desc, visible); + } + + @Override + public void visitFieldAttribute(final Attribute attr) { + visitAttribute(attr); + } + + @Override + public void visitFieldEnd() { + } + + // ------------------------------------------------------------------------ + // Methods + // ------------------------------------------------------------------------ + + @Override + public Textifier visitAnnotationDefault() { + text.add(tab2 + "default="); + Textifier t = createTextifier(); + text.add(t.getText()); + text.add("\n"); + return t; + } + + @Override + public Textifier visitMethodAnnotation( + final String desc, + final boolean visible) + { + return visitAnnotation(desc, visible); + } + + @Override + public Textifier visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + buf.setLength(0); + buf.append(tab2).append('@'); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + text.add(visible ? ") // parameter " : ") // invisible, parameter "); + text.add(new Integer(parameter)); + text.add("\n"); + return t; + } + + @Override + public void visitMethodAttribute(final Attribute attr) { + buf.setLength(0); + buf.append(tab).append("ATTRIBUTE "); + appendDescriptor(-1, attr.type); + + if (attr instanceof Textifiable) { + ((Textifiable) attr).textify(buf, labelNames); + } else { + buf.append(" : unknown\n"); + } + + text.add(buf.toString()); + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + buf.setLength(0); + buf.append(ltab); + buf.append("FRAME "); + switch (type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + buf.append("FULL ["); + appendFrameTypes(nLocal, local); + buf.append("] ["); + appendFrameTypes(nStack, stack); + buf.append(']'); + break; + case Opcodes.F_APPEND: + buf.append("APPEND ["); + appendFrameTypes(nLocal, local); + buf.append(']'); + break; + case Opcodes.F_CHOP: + buf.append("CHOP ").append(nLocal); + break; + case Opcodes.F_SAME: + buf.append("SAME"); + break; + case Opcodes.F_SAME1: + buf.append("SAME1 "); + appendFrameTypes(1, stack); + break; + } + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitInsn(final int opcode) { + buf.setLength(0); + buf.append(tab2).append(OPCODES[opcode]).append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + buf.setLength(0); + buf.append(tab2) + .append(OPCODES[opcode]) + .append(' ') + .append(opcode == Opcodes.NEWARRAY + ? TYPES[operand] + : Integer.toString(operand)) + .append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + buf.setLength(0); + buf.append(tab2) + .append(OPCODES[opcode]) + .append(' ') + .append(var) + .append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + buf.setLength(0); + buf.append(tab2).append(OPCODES[opcode]).append(' '); + appendDescriptor(INTERNAL_NAME, type); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append(tab2).append(OPCODES[opcode]).append(' '); + appendDescriptor(INTERNAL_NAME, owner); + buf.append('.').append(name).append(" : "); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + buf.setLength(0); + buf.append(tab2).append(OPCODES[opcode]).append(' '); + appendDescriptor(INTERNAL_NAME, owner); + buf.append('.').append(name).append(' '); + appendDescriptor(METHOD_DESCRIPTOR, desc); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs) + { + buf.setLength(0); + buf.append(tab2).append("INVOKEDYNAMIC").append(' '); + buf.append(name); + appendDescriptor(METHOD_DESCRIPTOR, desc); + buf.append(" ["); + appendHandle(bsm); + buf.append(tab3).append("// arguments:"); + if(bsmArgs.length == 0) { + buf.append(" none"); + } else { + buf.append('\n').append(tab3); + for(int i = 0; i < bsmArgs.length; i++) { + Object cst = bsmArgs[i]; + if (cst instanceof String) { + Printer.appendString(buf, (String) cst); + } else if (cst instanceof Type) { + buf.append(((Type) cst).getDescriptor()).append(".class"); + } else if (cst instanceof Handle) { + appendHandle((Handle) cst); + } else { + buf.append(cst); + } + buf.append(", "); + } + buf.setLength(buf.length() - 2); + } + buf.append('\n'); + buf.append(tab2).append("]\n"); + text.add(buf.toString()); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + buf.setLength(0); + buf.append(tab2).append(OPCODES[opcode]).append(' '); + appendLabel(label); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitLabel(final Label label) { + buf.setLength(0); + buf.append(ltab); + appendLabel(label); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitLdcInsn(final Object cst) { + buf.setLength(0); + buf.append(tab2).append("LDC "); + if (cst instanceof String) { + Printer.appendString(buf, (String) cst); + } else if (cst instanceof Type) { + buf.append(((Type) cst).getDescriptor()).append(".class"); + } else { + buf.append(cst); + } + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + buf.setLength(0); + buf.append(tab2) + .append("IINC ") + .append(var) + .append(' ') + .append(increment) + .append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + buf.setLength(0); + buf.append(tab2).append("TABLESWITCH\n"); + for (int i = 0; i < labels.length; ++i) { + buf.append(tab3).append(min + i).append(": "); + appendLabel(labels[i]); + buf.append('\n'); + } + buf.append(tab3).append("default: "); + appendLabel(dflt); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + buf.setLength(0); + buf.append(tab2).append("LOOKUPSWITCH\n"); + for (int i = 0; i < labels.length; ++i) { + buf.append(tab3).append(keys[i]).append(": "); + appendLabel(labels[i]); + buf.append('\n'); + } + buf.append(tab3).append("default: "); + appendLabel(dflt); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + buf.setLength(0); + buf.append(tab2).append("MULTIANEWARRAY "); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append(' ').append(dims).append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + buf.setLength(0); + buf.append(tab2).append("TRYCATCHBLOCK "); + appendLabel(start); + buf.append(' '); + appendLabel(end); + buf.append(' '); + appendLabel(handler); + buf.append(' '); + appendDescriptor(INTERNAL_NAME, type); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + buf.setLength(0); + buf.append(tab2).append("LOCALVARIABLE ").append(name).append(' '); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append(' '); + appendLabel(start); + buf.append(' '); + appendLabel(end); + buf.append(' ').append(index).append('\n'); + + if (signature != null) { + buf.append(tab2); + appendDescriptor(FIELD_SIGNATURE, signature); + + TraceSignatureVisitor sv = new TraceSignatureVisitor(0); + SignatureReader r = new SignatureReader(signature); + r.acceptType(sv); + buf.append(tab2) + .append("// declaration: ") + .append(sv.getDeclaration()) + .append('\n'); + } + text.add(buf.toString()); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + buf.setLength(0); + buf.append(tab2).append("LINENUMBER ").append(line).append(' '); + appendLabel(start); + buf.append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + buf.setLength(0); + buf.append(tab2).append("MAXSTACK = ").append(maxStack).append('\n'); + text.add(buf.toString()); + + buf.setLength(0); + buf.append(tab2).append("MAXLOCALS = ").append(maxLocals).append('\n'); + text.add(buf.toString()); + } + + @Override + public void visitMethodEnd() { + } + + // ------------------------------------------------------------------------ + // Common methods + // ------------------------------------------------------------------------ + + /** + * Prints a disassembled view of the given annotation. + * + * @param desc the class descriptor of the annotation class. + * @param visible <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values. + */ + public Textifier visitAnnotation( + final String desc, + final boolean visible) + { + buf.setLength(0); + buf.append(tab).append('@'); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + text.add(visible ? ")\n" : ") // invisible\n"); + return t; + } + + /** + * Prints a disassembled view of the given attribute. + * + * @param attr an attribute. + */ + public void visitAttribute(final Attribute attr) { + buf.setLength(0); + buf.append(tab).append("ATTRIBUTE "); + appendDescriptor(-1, attr.type); + + if (attr instanceof Textifiable) { + ((Textifiable) attr).textify(buf, null); + } else { + buf.append(" : unknown\n"); + } + + text.add(buf.toString()); + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Creates a new TraceVisitor instance. + * + * @return a new TraceVisitor. + */ + protected Textifier createTextifier() { + return new Textifier(); + } + + /** + * Appends an internal name, a type descriptor or a type signature to + * {@link #buf buf}. + * + * @param type indicates if desc is an internal name, a field descriptor, a + * method descriptor, a class signature, ... + * @param desc an internal name, type descriptor, or type signature. May be + * <tt>null</tt>. + */ + protected void appendDescriptor(final int type, final String desc) { + if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE + || type == METHOD_SIGNATURE) + { + if (desc != null) { + buf.append("// signature ").append(desc).append('\n'); + } + } else { + buf.append(desc); + } + } + + /** + * Appends the name of the given label to {@link #buf buf}. Creates a new + * label name if the given label does not yet have one. + * + * @param l a label. + */ + protected void appendLabel(final Label l) { + if (labelNames == null) { + labelNames = new HashMap<Label, String>(); + } + String name = labelNames.get(l); + if (name == null) { + name = "L" + labelNames.size(); + labelNames.put(l, name); + } + buf.append(name); + } + + /** + * Appends the information about the given handle to {@link #buf buf}. + * + * @param h a handle, non null. + */ + protected void appendHandle(final Handle h) { + buf.append('\n').append(tab3); + int tag = h.getTag(); + buf.append("// handle kind 0x").append(Integer.toHexString(tag)).append(" : "); + switch (tag) { + case Opcodes.H_GETFIELD: + buf.append("GETFIELD"); + break; + case Opcodes.H_GETSTATIC: + buf.append("GETSTATIC"); + break; + case Opcodes.H_PUTFIELD: + buf.append("PUTFIELD"); + break; + case Opcodes.H_PUTSTATIC: + buf.append("PUTSTATIC"); + break; + case Opcodes.H_INVOKEINTERFACE: + buf.append("INVOKEINTERFACE"); + break; + case Opcodes.H_INVOKESPECIAL: + buf.append("INVOKESPECIAL"); + break; + case Opcodes.H_INVOKESTATIC: + buf.append("INVOKESTATIC"); + break; + case Opcodes.H_INVOKEVIRTUAL: + buf.append("INVOKEVIRTUAL"); + break; + case Opcodes.H_NEWINVOKESPECIAL: + buf.append("NEWINVOKESPECIAL"); + break; + } + buf.append('\n'); + buf.append(tab3); + appendDescriptor(INTERNAL_NAME, h.getOwner()); + buf.append('.'); + buf.append(h.getName()); + buf.append('('); + appendDescriptor(HANDLE_DESCRIPTOR, h.getDesc()); + buf.append(')').append('\n'); + } + + /** + * Appends a string representation of the given access modifiers to {@link + * #buf buf}. + * + * @param access some access modifiers. + */ + private void appendAccess(final int access) { + if ((access & Opcodes.ACC_PUBLIC) != 0) { + buf.append("public "); + } + if ((access & Opcodes.ACC_PRIVATE) != 0) { + buf.append("private "); + } + if ((access & Opcodes.ACC_PROTECTED) != 0) { + buf.append("protected "); + } + if ((access & Opcodes.ACC_FINAL) != 0) { + buf.append("final "); + } + if ((access & Opcodes.ACC_STATIC) != 0) { + buf.append("static "); + } + if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) { + buf.append("synchronized "); + } + if ((access & Opcodes.ACC_VOLATILE) != 0) { + buf.append("volatile "); + } + if ((access & Opcodes.ACC_TRANSIENT) != 0) { + buf.append("transient "); + } + if ((access & Opcodes.ACC_ABSTRACT) != 0) { + buf.append("abstract "); + } + if ((access & Opcodes.ACC_STRICT) != 0) { + buf.append("strictfp "); + } + if ((access & Opcodes.ACC_ENUM) != 0) { + buf.append("enum "); + } + } + + private void appendComa(final int i) { + if (i != 0) { + buf.append(", "); + } + } + + private void appendFrameTypes(final int n, final Object[] o) { + for (int i = 0; i < n; ++i) { + if (i > 0) { + buf.append(' '); + } + if (o[i] instanceof String) { + String desc = (String) o[i]; + if (desc.startsWith("[")) { + appendDescriptor(FIELD_DESCRIPTOR, desc); + } else { + appendDescriptor(INTERNAL_NAME, desc); + } + } else if (o[i] instanceof Integer) { + switch (((Integer) o[i]).intValue()) { + case 0: + appendDescriptor(FIELD_DESCRIPTOR, "T"); + break; + case 1: + appendDescriptor(FIELD_DESCRIPTOR, "I"); + break; + case 2: + appendDescriptor(FIELD_DESCRIPTOR, "F"); + break; + case 3: + appendDescriptor(FIELD_DESCRIPTOR, "D"); + break; + case 4: + appendDescriptor(FIELD_DESCRIPTOR, "J"); + break; + case 5: + appendDescriptor(FIELD_DESCRIPTOR, "N"); + break; + case 6: + appendDescriptor(FIELD_DESCRIPTOR, "U"); + break; + } + } else { + appendLabel((Label) o[i]); + } + } + } +} diff --git a/src/compiler/scala/tools/asm/util/TraceAnnotationVisitor.java b/src/compiler/scala/tools/asm/util/TraceAnnotationVisitor.java new file mode 100644 index 0000000000..f112609031 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/TraceAnnotationVisitor.java @@ -0,0 +1,96 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Opcodes; + +/** + * An {@link AnnotationVisitor} that prints the annotations it visits with a + * {@link Printer}. + * + * @author Eric Bruneton + */ +public final class TraceAnnotationVisitor extends AnnotationVisitor { + + private final Printer p; + + public TraceAnnotationVisitor(final Printer p) { + this(null, p); + } + + public TraceAnnotationVisitor(final AnnotationVisitor av, final Printer p) { + super(Opcodes.ASM4, av); + this.p = p; + } + + @Override + public void visit(final String name, final Object value) { + p.visit(name, value); + super.visit(name, value); + } + + @Override + public void visitEnum( + final String name, + final String desc, + final String value) + { + p.visitEnum(name, desc, value); + super.visitEnum(name, desc, value); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String name, + final String desc) + { + Printer p = this.p.visitAnnotation(name, desc); + AnnotationVisitor av = this.av == null + ? null + : this.av.visitAnnotation(name, desc); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + Printer p = this.p.visitArray(name); + AnnotationVisitor av = this.av == null + ? null + : this.av.visitArray(name); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public void visitEnd() { + p.visitAnnotationEnd(); + super.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/util/TraceClassVisitor.java b/src/compiler/scala/tools/asm/util/TraceClassVisitor.java new file mode 100644 index 0000000000..bb830b71ce --- /dev/null +++ b/src/compiler/scala/tools/asm/util/TraceClassVisitor.java @@ -0,0 +1,232 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import java.io.PrintWriter; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.ClassVisitor; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A {@link ClassVisitor} that prints the classes it visits with a + * {@link Printer}. This class visitor can be used in the middle of a class + * visitor chain to trace the class that is visited at a given point in this + * chain. This may be useful for debugging purposes. <p> The trace printed when + * visiting the <tt>Hello</tt> class is the following: <p> <blockquote> + * + * <pre> // class version 49.0 (49) // access flags 0x21 public class Hello { + * + * // compiled from: Hello.java + * + * // access flags 0x1 public <init> ()V ALOAD 0 INVOKESPECIAL + * java/lang/Object <init> ()V RETURN MAXSTACK = 1 MAXLOCALS = 1 + * + * // access flags 0x9 public static main ([Ljava/lang/String;)V GETSTATIC + * java/lang/System out Ljava/io/PrintStream; LDC "hello" + * INVOKEVIRTUAL java/io/PrintStream println (Ljava/lang/String;)V RETURN + * MAXSTACK = 2 MAXLOCALS = 1 } </pre> + * + * </blockquote> where <tt>Hello</tt> is defined by: <p> <blockquote> + * + * <pre> public class Hello { + * + * public static void main(String[] args) { + * System.out.println("hello"); } } </pre> + * + * </blockquote> + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public final class TraceClassVisitor extends ClassVisitor { + + /** + * The print writer to be used to print the class. May be null. + */ + private final PrintWriter pw; + + /** + * The object that actually converts visit events into text. + */ + public final Printer p; + + /** + * Constructs a new {@link TraceClassVisitor}. + * + * @param pw the print writer to be used to print the class. + */ + public TraceClassVisitor(final PrintWriter pw) { + this(null, pw); + } + + /** + * Constructs a new {@link TraceClassVisitor}. + * + * @param cv the {@link ClassVisitor} to which this visitor delegates calls. + * May be <tt>null</tt>. + * @param pw the print writer to be used to print the class. + */ + public TraceClassVisitor(final ClassVisitor cv, final PrintWriter pw) { + this(cv, new Textifier(), pw); + } + + /** + * Constructs a new {@link TraceClassVisitor}. + * + * @param cv the {@link ClassVisitor} to which this visitor delegates calls. + * May be <tt>null</tt>. + * @param p the object that actually converts visit events into text. + * @param pw the print writer to be used to print the class. May be null if + * you simply want to use the result via + * {@link Printer#getText()}, instead of printing it. + */ + public TraceClassVisitor( + final ClassVisitor cv, + final Printer p, + final PrintWriter pw) + { + super(Opcodes.ASM4, cv); + this.pw = pw; + this.p = p; + } + + @Override + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) + { + p.visit(version, access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(final String file, final String debug) { + p.visitSource(file, debug); + super.visitSource(file, debug); + } + + @Override + public void visitOuterClass( + final String owner, + final String name, + final String desc) + { + p.visitOuterClass(owner, name, desc); + super.visitOuterClass(owner, name, desc); + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + Printer p = this.p.visitClassAnnotation(desc, visible); + AnnotationVisitor av = cv == null ? null : cv.visitAnnotation(desc, + visible); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public void visitAttribute(final Attribute attr) { + p.visitClassAttribute(attr); + super.visitAttribute(attr); + } + + @Override + public void visitInnerClass( + final String name, + final String outerName, + final String innerName, + final int access) + { + p.visitInnerClass(name, outerName, innerName, access); + super.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public FieldVisitor visitField( + final int access, + final String name, + final String desc, + final String signature, + final Object value) + { + Printer p = this.p.visitField(access, + name, + desc, + signature, + value); + FieldVisitor fv = cv == null ? null : cv.visitField(access, + name, + desc, + signature, + value); + return new TraceFieldVisitor(fv, p); + } + + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) + { + Printer p = this.p.visitMethod(access, + name, + desc, + signature, + exceptions); + MethodVisitor mv = cv == null ? null : cv.visitMethod(access, + name, + desc, + signature, + exceptions); + return new TraceMethodVisitor(mv, p); + } + + @Override + public void visitEnd() { + p.visitClassEnd(); + if (pw != null) { + p.print(pw); + pw.flush(); + } + super.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/util/TraceFieldVisitor.java b/src/compiler/scala/tools/asm/util/TraceFieldVisitor.java new file mode 100644 index 0000000000..f537e83be1 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/TraceFieldVisitor.java @@ -0,0 +1,78 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.FieldVisitor; +import scala.tools.asm.Opcodes; + +/** + * A {@link FieldVisitor} that prints the fields it visits with a + * {@link Printer}. + * + * @author Eric Bruneton + */ +public final class TraceFieldVisitor extends FieldVisitor { + + public final Printer p; + + public TraceFieldVisitor(final Printer p) { + this(null, p); + } + + public TraceFieldVisitor(final FieldVisitor fv, final Printer p) { + super(Opcodes.ASM4, fv); + this.p = p; + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + Printer p = this.p.visitFieldAnnotation(desc, visible); + AnnotationVisitor av = fv == null ? null : fv.visitAnnotation(desc, + visible); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public void visitAttribute(final Attribute attr) { + p.visitFieldAttribute(attr); + super.visitAttribute(attr); + } + + @Override + public void visitEnd() { + p.visitFieldEnd(); + super.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/util/TraceMethodVisitor.java b/src/compiler/scala/tools/asm/util/TraceMethodVisitor.java new file mode 100644 index 0000000000..9aabf2079e --- /dev/null +++ b/src/compiler/scala/tools/asm/util/TraceMethodVisitor.java @@ -0,0 +1,264 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.AnnotationVisitor; +import scala.tools.asm.Attribute; +import scala.tools.asm.Handle; +import scala.tools.asm.Label; +import scala.tools.asm.MethodVisitor; +import scala.tools.asm.Opcodes; + +/** + * A {@link MethodVisitor} that prints the methods it visits with a + * {@link Printer}. + * + * @author Eric Bruneton + */ +public final class TraceMethodVisitor extends MethodVisitor { + + public final Printer p; + + public TraceMethodVisitor(final Printer p) { + this(null, p); + } + + public TraceMethodVisitor(final MethodVisitor mv, final Printer p) { + super(Opcodes.ASM4, mv); + this.p = p; + } + + @Override + public AnnotationVisitor visitAnnotation( + final String desc, + final boolean visible) + { + Printer p = this.p.visitMethodAnnotation(desc, visible); + AnnotationVisitor av = mv == null ? null : mv.visitAnnotation(desc, + visible); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public void visitAttribute(final Attribute attr) { + p.visitMethodAttribute(attr); + super.visitAttribute(attr); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + Printer p = this.p.visitAnnotationDefault(); + AnnotationVisitor av = mv == null ? null : mv.visitAnnotationDefault(); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, + final String desc, + final boolean visible) + { + Printer p = this.p.visitParameterAnnotation(parameter, + desc, + visible); + AnnotationVisitor av = mv == null + ? null + : mv.visitParameterAnnotation(parameter, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + + @Override + public void visitCode() { + p.visitCode(); + super.visitCode(); + } + + @Override + public void visitFrame( + final int type, + final int nLocal, + final Object[] local, + final int nStack, + final Object[] stack) + { + p.visitFrame(type, nLocal, local, nStack, stack); + super.visitFrame(type, nLocal, local, nStack, stack); + } + + @Override + public void visitInsn(final int opcode) { + p.visitInsn(opcode); + super.visitInsn(opcode); + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + p.visitIntInsn(opcode, operand); + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + p.visitVarInsn(opcode, var); + super.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + p.visitTypeInsn(opcode, type); + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + p.visitFieldInsn(opcode, owner, name, desc); + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String desc) + { + p.visitMethodInsn(opcode, owner, name, desc); + super.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String desc, + Handle bsm, + Object... bsmArgs) + { + p.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + p.visitJumpInsn(opcode, label); + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitLabel(final Label label) { + p.visitLabel(label); + super.visitLabel(label); + } + + @Override + public void visitLdcInsn(final Object cst) { + p.visitLdcInsn(cst); + super.visitLdcInsn(cst); + } + + @Override + public void visitIincInsn(final int var, final int increment) { + p.visitIincInsn(var, increment); + super.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn( + final int min, + final int max, + final Label dflt, + final Label... labels) + { + p.visitTableSwitchInsn(min, max, dflt, labels); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn( + final Label dflt, + final int[] keys, + final Label[] labels) + { + p.visitLookupSwitchInsn(dflt, keys, labels); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + p.visitMultiANewArrayInsn(desc, dims); + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitTryCatchBlock( + final Label start, + final Label end, + final Label handler, + final String type) + { + p.visitTryCatchBlock(start, end, handler, type); + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + p.visitLocalVariable(name, desc, signature, start, end, index); + super.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + p.visitLineNumber(line, start); + super.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + p.visitMaxs(maxStack, maxLocals); + super.visitMaxs(maxStack, maxLocals); + } + + @Override + public void visitEnd() { + p.visitMethodEnd(); + super.visitEnd(); + } +} diff --git a/src/compiler/scala/tools/asm/util/TraceSignatureVisitor.java b/src/compiler/scala/tools/asm/util/TraceSignatureVisitor.java new file mode 100644 index 0000000000..a37b759811 --- /dev/null +++ b/src/compiler/scala/tools/asm/util/TraceSignatureVisitor.java @@ -0,0 +1,318 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package scala.tools.asm.util; + +import scala.tools.asm.Opcodes; +import scala.tools.asm.signature.SignatureVisitor; + +/** + * A {@link SignatureVisitor} that prints a disassembled view of the signature + * it visits. + * + * @author Eugene Kuleshov + * @author Eric Bruneton + */ +public final class TraceSignatureVisitor extends SignatureVisitor { + + private final StringBuffer declaration; + + private boolean isInterface; + + private boolean seenFormalParameter; + + private boolean seenInterfaceBound; + + private boolean seenParameter; + + private boolean seenInterface; + + private StringBuffer returnType; + + private StringBuffer exceptions; + + /** + * Stack used to keep track of class types that have arguments. Each element + * of this stack is a boolean encoded in one bit. The top of the stack is + * the lowest order bit. Pushing false = *2, pushing true = *2+1, popping = + * /2. + */ + private int argumentStack; + + /** + * Stack used to keep track of array class types. Each element of this stack + * is a boolean encoded in one bit. The top of the stack is the lowest order + * bit. Pushing false = *2, pushing true = *2+1, popping = /2. + */ + private int arrayStack; + + private String separator = ""; + + public TraceSignatureVisitor(final int access) { + super(Opcodes.ASM4); + isInterface = (access & Opcodes.ACC_INTERFACE) != 0; + this.declaration = new StringBuffer(); + } + + private TraceSignatureVisitor(final StringBuffer buf) { + super(Opcodes.ASM4); + this.declaration = buf; + } + + @Override + public void visitFormalTypeParameter(final String name) { + declaration.append(seenFormalParameter ? ", " : "<").append(name); + seenFormalParameter = true; + seenInterfaceBound = false; + } + + @Override + public SignatureVisitor visitClassBound() { + separator = " extends "; + startType(); + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + separator = seenInterfaceBound ? ", " : " extends "; + seenInterfaceBound = true; + startType(); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + endFormals(); + separator = " extends "; + startType(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + separator = seenInterface ? ", " : isInterface + ? " extends " + : " implements "; + seenInterface = true; + startType(); + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + endFormals(); + if (seenParameter) { + declaration.append(", "); + } else { + seenParameter = true; + declaration.append('('); + } + startType(); + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + endFormals(); + if (seenParameter) { + seenParameter = false; + } else { + declaration.append('('); + } + declaration.append(')'); + returnType = new StringBuffer(); + return new TraceSignatureVisitor(returnType); + } + + @Override + public SignatureVisitor visitExceptionType() { + if (exceptions == null) { + exceptions = new StringBuffer(); + } else { + exceptions.append(", "); + } + // startType(); + return new TraceSignatureVisitor(exceptions); + } + + @Override + public void visitBaseType(final char descriptor) { + switch (descriptor) { + case 'V': + declaration.append("void"); + break; + case 'B': + declaration.append("byte"); + break; + case 'J': + declaration.append("long"); + break; + case 'Z': + declaration.append("boolean"); + break; + case 'I': + declaration.append("int"); + break; + case 'S': + declaration.append("short"); + break; + case 'C': + declaration.append("char"); + break; + case 'F': + declaration.append("float"); + break; + // case 'D': + default: + declaration.append("double"); + break; + } + endType(); + } + + @Override + public void visitTypeVariable(final String name) { + declaration.append(name); + endType(); + } + + @Override + public SignatureVisitor visitArrayType() { + startType(); + arrayStack |= 1; + return this; + } + + @Override + public void visitClassType(final String name) { + if ("java/lang/Object".equals(name)) { + // Map<java.lang.Object,java.util.List> + // or + // abstract public V get(Object key); (seen in Dictionary.class) + // should have Object + // but java.lang.String extends java.lang.Object is unnecessary + boolean needObjectClass = argumentStack % 2 != 0 || seenParameter; + if (needObjectClass) { + declaration.append(separator).append(name.replace('/', '.')); + } + } else { + declaration.append(separator).append(name.replace('/', '.')); + } + separator = ""; + argumentStack *= 2; + } + + @Override + public void visitInnerClassType(final String name) { + if (argumentStack % 2 != 0) { + declaration.append('>'); + } + argumentStack /= 2; + declaration.append('.'); + declaration.append(separator).append(name.replace('/', '.')); + separator = ""; + argumentStack *= 2; + } + + @Override + public void visitTypeArgument() { + if (argumentStack % 2 == 0) { + ++argumentStack; + declaration.append('<'); + } else { + declaration.append(", "); + } + declaration.append('?'); + } + + @Override + public SignatureVisitor visitTypeArgument(final char tag) { + if (argumentStack % 2 == 0) { + ++argumentStack; + declaration.append('<'); + } else { + declaration.append(", "); + } + + if (tag == EXTENDS) { + declaration.append("? extends "); + } else if (tag == SUPER) { + declaration.append("? super "); + } + + startType(); + return this; + } + + @Override + public void visitEnd() { + if (argumentStack % 2 != 0) { + declaration.append('>'); + } + argumentStack /= 2; + endType(); + } + + public String getDeclaration() { + return declaration.toString(); + } + + public String getReturnType() { + return returnType == null ? null : returnType.toString(); + } + + public String getExceptions() { + return exceptions == null ? null : exceptions.toString(); + } + + // ----------------------------------------------- + + private void endFormals() { + if (seenFormalParameter) { + declaration.append('>'); + seenFormalParameter = false; + } + } + + private void startType() { + arrayStack *= 2; + } + + private void endType() { + if (arrayStack % 2 == 0) { + arrayStack /= 2; + } else { + while (arrayStack % 2 != 0) { + arrayStack /= 2; + declaration.append("[]"); + } + } + } +} diff --git a/src/compiler/scala/tools/cmd/program/DumpClass.scala b/src/compiler/scala/tools/cmd/program/DumpClass.scala deleted file mode 100644 index a583f1d3ea..0000000000 --- a/src/compiler/scala/tools/cmd/program/DumpClass.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package cmd -package program - -import scala.reflect.internal.JvmClassInfo -import scala.tools.nsc.io.Directory - -object DumpClass { - private val usage = """ - |Usage: dump-class [options] <path> <path> ... - | - |Parses and dumps the bytecode of all classes found at the given paths. - |""".stripMargin - - private val unaryOps = List( - "signatures" -> "dump signatures" - ) - private val info = Simple.scalaProgramInfo("dump-class", usage) - private val spec = Simple(info, unaryOps, Nil, null) - - def deepInfos(dir: String) = { - val files = Directory(dir).deepFiles.toList filter (_ hasExtension "class") - files.sortBy(_.path) map (f => (f.path, JvmClassInfo fromPath f.path)) - } - - def main(args: Array[String]): Unit = { - val runner = spec instance args - import runner._ - - if (args.isEmpty) - println(usage) - else - (residualArgs flatMap deepInfos) sortBy (_._1) map (_._2) foreach println - } -} diff --git a/src/compiler/scala/tools/cmd/program/Scmp.scala b/src/compiler/scala/tools/cmd/program/Scmp.scala deleted file mode 100644 index 6f39c29a77..0000000000 --- a/src/compiler/scala/tools/cmd/program/Scmp.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package cmd -package program - -import nsc.io._ -import scala.sys.process._ - -object Scmp { - private val scmpUsage = """ - |Usage: scmp [options] <cmd line> - |Example: scmp --p1 '-no-specialization -Ydebug' scalac src/library/scala/Function1.scala - | - |Note: the command line must start with a path to scalac. - |""".stripMargin - private val scmpOptions = List( - "p1" -> "options for the first run only", - "p2" -> "options for the second run only" - ) - private val scmpInfo = Simple.scalaProgramInfo("scmp", scmpUsage) - lazy val ScmpSpec = Simple(scmpInfo, Nil, scmpOptions, x => returning(x)(_.onlyKnownOptions = false)) - - def main(args0: Array[String]): Unit = { - if (args0.isEmpty) - return println(scmpUsage) - - val runner = ScmpSpec instance args0 - import runner._ - - val p1args = parsed.getOrElse("--p1", "") - val p2args = parsed.getOrElse("--p2", "") - - if (p1args.isEmpty && p2args.isEmpty) - return println("At least one of --p1 and --p2 must be given.") - if (residualArgs.isEmpty) - return println("There is no command to run.") - - def createCmd(extras: String) = - fromArgs(residualArgs.patch(1, toArgs(extras), 0)) - - def runCmd(cmd: String) = { - val tmpfile = File.makeTemp() - (cmd #> tmpfile.jfile !) - tmpfile - } - - val cmds = List(p1args, p2args) map createCmd - println(cmds.mkString("Running command lines:\n ", "\n ", "")) - - val files = cmds map runCmd map (_.path) - val diff = "diff %s %s".format(files: _*).!! - - if (diff.isEmpty) println("No differences.") - else println(diff) - } -} diff --git a/src/compiler/scala/tools/cmd/program/Simple.scala b/src/compiler/scala/tools/cmd/program/Simple.scala deleted file mode 100644 index f2095d64b8..0000000000 --- a/src/compiler/scala/tools/cmd/program/Simple.scala +++ /dev/null @@ -1,81 +0,0 @@ -/* NEST (New Scala Test) - * Copyright 2007-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package cmd -package program - -import Spec.Info - -/** A boilerplate reducer for commands with simple requirements. For examples, - * see Scmp and Tokens in this package. - */ -object Simple { - type CommandLineTransform = SimpleCommandLine => SimpleCommandLine - - abstract class SimpleSpec(val programInfo: Info) extends Spec with Meta.StdOpts with Interpolation - - trait SimpleInstance extends SimpleSpec with Instance { - val parsed: CommandLine - } - - class SimpleReference( - programInfo: Info, - unary: List[(String, String)] = Nil, - binary: List[(String, String)] = Nil, - postCreation: CommandLineTransform = null - ) extends SimpleSpec(programInfo) with Reference { - - spec => - - if (programInfo.usage != "") help(programInfo.usage) - unary foreach { case (option, help) => option / help --? } - binary foreach { case (option, help) => option / help --| } - - type ThisCommandLine = SimpleCommandLine - - def creator(args: List[String]) = new SimpleCommandLine(spec, args) - def instance(args: Array[String]): SimpleInstance = instance(args.toList) - def instance(args: List[String]): SimpleInstance = - new { - val parsed = spec(args: _*) - } with SimpleSpec(programInfo) with SimpleInstance { - lazy val referenceSpec = spec - } - - lazy val referenceSpec = spec - } - - def apply(info: Info, unary: List[(String, String)], binary: List[(String, String)], postCreation: CommandLineTransform): SimpleReference = { - new SimpleReference(info, unary, binary, postCreation) { - override def creator(args: List[String]) = { - val obj = super.creator(args) - if (postCreation == null) obj - else postCreation(obj) - } - } - } - - def scalaProgramInfo(name: String, help: String) = - Spec.Info(name, help, "scala.tools.cmd.program." + name.capitalize) - - /** You can't override a def with a var unless a setter exists. We cleverly - * sidestep this by mixing in a trait with dummy setters which will be - * inaccessible due to the overriding var. - */ - trait Ticket2338WontFixWorkaround { - def enforceArity_=(x: Boolean): Unit = sys.error("unreachable") - def onlyKnownOptions_=(x: Boolean): Unit = sys.error("unreachable") - } - - /** Configurability simplicity achieved by turning defs into vars and letting - * the spec creator apply a transformation. This way there's no need to create - * custom subclasses of CommandLine. - */ - class SimpleCommandLine(spec: Reference, args: List[String]) extends CommandLine(spec, args) with Ticket2338WontFixWorkaround { - override var enforceArity: Boolean = true - override var onlyKnownOptions: Boolean = true - } -} diff --git a/src/compiler/scala/tools/cmd/program/Tokens.scala b/src/compiler/scala/tools/cmd/program/Tokens.scala deleted file mode 100644 index be494bba3a..0000000000 --- a/src/compiler/scala/tools/cmd/program/Tokens.scala +++ /dev/null @@ -1,106 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package cmd -package program - -import nsc._ -import scala.reflect.internal.Chars.char2uescape -import io._ -import ast.parser.Tokens._ - -/** Given paths on the command line, tokenizes any scala files found - * and prints one token per line. - */ -object Tokens { - private val tokensUsage = "Usage: tokens [options] <path1 path2 ...>\n\nOptions:" - private val tokensUnary = List( - "verbose" -> "be more verbose", - "freq" -> "combine token lists and sort by frequency", - "stats" -> "output some stats" - ) - private val tokensBinary = List( - "sliding" -> "print tokens in groups of given size" - ) - private val tokensInfo = Simple.scalaProgramInfo("tokens", tokensUsage) - private lazy val TokensSpec = Simple(tokensInfo, tokensUnary, tokensBinary, null) - - def sanitize(x: Any): String = sanitize(x.toString) - def sanitize(str: String): String = str flatMap (x => if (x.isControl) char2uescape(x) else x.toString) - - def main(args0: Array[String]): Unit = { - if (args0.isEmpty) - return println(TokensSpec.helpMsg) - - val runner = TokensSpec instance args0 - import runner._ - - val files = (residualArgs flatMap walk).distinct - if (parsed isSet "--verbose") - println("Tokenizing: " + (files map (_.name) mkString " ")) - - if (parsed isSet "--stats") - println("Stats not yet implemented.") - - def raw = files flatMap fromScalaSource - def tokens: List[Any] = - if (parsed isSet "--sliding") raw sliding parsed("--sliding").toInt map (_ map sanitize mkString " ") toList - else raw - - def output = - if (parsed isSet "--freq") - (tokens groupBy (x => x) mapValues (_.length)).toList sortBy (-_._2) map (x => x._2 + " " + x._1) - else - tokens - - output foreach println - } - - def fromPaths(paths: String*): List[Any] = - (paths.toList flatMap walk).distinct flatMap fromScalaSource - - /** Given a path, returns all .scala files underneath it. - */ - private def walk(arg: String): List[File] = { - def traverse = Path(arg) ifDirectory (_.deepList()) getOrElse Iterator(File(arg)) - - Path onlyFiles traverse filter (_ hasExtension "scala") toList - } - - def fromScalaString(code: String): List[Any] = { - val f = File.makeTemp("tokens") - f writeAll code - fromScalaSource(f) - } - - /** Tokenizes a single scala file. - */ - def fromScalaSource(file: Path): List[Any] = fromScalaSource(file.path) - def fromScalaSource(file: String): List[Any] = { - val global = new Global(new Settings()) - import global._ - import syntaxAnalyzer.{ UnitScanner, token2string } - - val in = new UnitScanner(new CompilationUnit(getSourceFile(file))) - in.init() - - Iterator continually { - val token = in.token match { - case IDENTIFIER | BACKQUOTED_IDENT => in.name - case CHARLIT | INTLIT | LONGLIT => in.intVal - case DOUBLELIT | FLOATLIT => in.floatVal - case STRINGLIT => "\"" + in.strVal + "\"" - case SEMI | NEWLINE => ";" - case NEWLINES => ";;" - case COMMA => "," - case EOF => null - case x => token2string(x) - } - in.nextToken() - token - } takeWhile (_ != null) toList - } -} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index dfab703843..c917ec9984 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -8,11 +8,11 @@ package scala.tools.nsc import java.io.{ File, FileOutputStream, PrintWriter, IOException, FileNotFoundException } import java.nio.charset.{ Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException } import compat.Platform.currentTime -import scala.tools.util.{ Profiling, PathResolver } +import scala.tools.util.PathResolver import scala.collection.{ mutable, immutable } import io.{ SourceReader, AbstractFile, Path } import reporters.{ Reporter, ConsoleReporter } -import util.{ NoPosition, Exceptional, ClassPath, SourceFile, NoSourceFile, Statistics, StatisticsInfo, BatchSourceFile, ScriptSourceFile, ShowPickled, ScalaClassLoader, returning } +import util.{ NoPosition, Exceptional, ClassPath, SourceFile, NoSourceFile, Statistics, StatisticsInfo, BatchSourceFile, ScriptSourceFile, ScalaClassLoader, returning } import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat } import settings.{ AestheticSettings } import symtab.{ Flags, SymbolTable, SymbolLoaders, SymbolTrackers } @@ -25,7 +25,7 @@ import typechecker._ import transform._ import backend.icode.{ ICodes, GenICode, ICodeCheckers } import backend.{ ScalaPrimitives, Platform, MSILPlatform, JavaPlatform } -import backend.jvm.GenJVM +import backend.jvm.{GenJVM, GenASM} import backend.opt.{ Inliners, InlineExceptionHandlers, ClosureElimination, DeadCodeElimination } import backend.icode.analysis._ import language.postfixOps @@ -130,7 +130,28 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb object nodePrinters extends { val global: Global.this.type = Global.this } with NodePrinters { + var lastPrintedPhase: Phase = NoPhase + var lastPrintedSource: String = "" infolevel = InfoLevel.Verbose + + def showUnit(unit: CompilationUnit) { + print(" // " + unit.source) + if (unit.body == null) println(": tree is null") + else { + val source = util.stringFromWriter(w => newTreePrinter(w) print unit.body) + + // treePrinter show unit.body + if (lastPrintedSource == source) + println(": tree is unchanged since " + lastPrintedPhase) + else { + lastPrintedPhase = phase.prev // since we're running inside "afterPhase" + lastPrintedSource = source + println("") + println(source) + println("") + } + } + } } def withInfoLevel[T](infolevel: nodePrinters.InfoLevel.Value)(op: => T) = { @@ -162,7 +183,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb /** Register new context; called for every created context */ - def registerContext(c: analyzer.Context) {} + def registerContext(c: analyzer.Context) { + lastSeenContext = c + } /** Register top level class (called on entering the class) */ @@ -243,9 +266,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } def logThrowable(t: Throwable): Unit = globalError(throwableAsString(t)) - override def throwableAsString(t: Throwable): String = - if (opt.richExes) Exceptional(t).force().context() - else util.stackTraceString(t) + override def throwableAsString(t: Throwable) = util.stackTraceString(t) // ------------ File interface ----------------------------------------- @@ -333,19 +354,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb def showTrees = settings.Xshowtrees.value || settings.XshowtreesCompact.value || settings.XshowtreesStringified.value val showClass = optSetting[String](settings.Xshowcls) map (x => splitClassAndPhase(x, false)) val showObject = optSetting[String](settings.Xshowobj) map (x => splitClassAndPhase(x, true)) - - // profiling - def profCPUPhase = isActive(settings.Yprofile) && !profileAll - def profileAll = settings.Yprofile.doAllPhases - def profileAny = !settings.Yprofile.isDefault || !settings.YprofileMem.isDefault - def profileClass = settings.YprofileClass.value - def profileMem = settings.YprofileMem.value - - // shortish-term property based options - def timings = (sys.props contains "scala.timings") - def inferDebug = (sys.props contains "scalac.debug.infer") || settings.Yinferdebug.value - def typerDebug = (sys.props contains "scalac.debug.typer") || settings.Ytyperdebug.value - def lubDebug = (sys.props contains "scalac.debug.lub") } // The current division between scala.reflect.* and scala.tools.nsc.* is pretty @@ -387,14 +395,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb def run() { echoPhaseSummary(this) - currentRun.units foreach { unit => - if (opt.timings) { - val start = System.nanoTime - try applyPhase(unit) - finally unitTimings(unit) += (System.nanoTime - start) - } - else applyPhase(unit) - } + currentRun.units foreach applyPhase } def apply(unit: CompilationUnit): Unit @@ -438,8 +439,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } /** Switch to turn on detailed type logs */ - var printTypings = opt.typerDebug - var printInfers = opt.inferDebug + var printTypings = settings.Ytyperdebug.value + var printInfers = settings.Yinferdebug.value // phaseName = "parser" object syntaxAnalyzer extends { @@ -541,7 +542,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb // phaseName = "lazyvals" object lazyVals extends { - final val FLAGS_PER_WORD = 32 val global: Global.this.type = Global.this val runsAfter = List("erasure") val runsRightAfter = None @@ -617,13 +617,20 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb val runsRightAfter = None } with DeadCodeElimination - // phaseName = "jvm" + // phaseName = "jvm", FJBG-based version object genJVM extends { val global: Global.this.type = Global.this val runsAfter = List("dce") val runsRightAfter = None } with GenJVM + // phaseName = "jvm", ASM-based version + object genASM extends { + val global: Global.this.type = Global.this + val runsAfter = List("dce") + val runsRightAfter = None + } with GenASM + // This phase is optional: only added if settings.make option is given. // phaseName = "dependencyAnalysis" object dependencyAnalysis extends { @@ -741,20 +748,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb protected lazy val phasesSet = new mutable.HashSet[SubComponent] protected lazy val phasesDescMap = new mutable.HashMap[SubComponent, String] withDefaultValue "" private lazy val phaseTimings = new Phases.TimingModel // tracking phase stats - private lazy val unitTimings = mutable.HashMap[CompilationUnit, Long]() withDefaultValue 0L // tracking time spent per unit - private def unitTimingsFormatted(): String = { - def toMillis(nanos: Long) = "%.3f" format nanos / 1000000d - - val formatter = new util.TableDef[(String, String)] { - >> ("ms" -> (_._1)) >+ " " - << ("path" -> (_._2)) - } - "" + ( - new formatter.Table(unitTimings.toList sortBy (-_._2) map { - case (unit, nanos) => (toMillis(nanos), unit.source.path) - }) - ) - } protected def addToPhasesSet(sub: SubComponent, descr: String) { phasesSet += sub @@ -888,6 +881,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb curRun = null } + object typeDeconstruct extends { + val global: Global.this.type = Global.this + } with interpreter.StructuredTypeStrings + /** There are common error conditions where when the exception hits * here, currentRun.currentUnit is null. This robs us of the knowledge * of what file was being compiled when it broke. Since I really @@ -895,6 +892,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb */ private var lastSeenSourceFile: SourceFile = NoSourceFile + /** Let's share a lot more about why we crash all over the place. + * People will be very grateful. + */ + private var lastSeenContext: analyzer.Context = null + /** The currently active run */ def currentRun: Run = curRun @@ -923,25 +925,64 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb @inline final def beforeTyper[T](op: => T): T = beforePhase(currentRun.typerPhase)(op) @inline final def beforeUncurry[T](op: => T): T = beforePhase(currentRun.uncurryPhase)(op) + def explainContext(c: analyzer.Context): String = ( + if (c == null) "" else ( + """| context owners: %s + | + |Enclosing block or template: + |%s""".format( + c.owner.ownerChain.takeWhile(!_.isPackageClass).mkString(" -> "), + nodePrinters.nodeToString(c.enclClassOrMethod.tree) + ) + ) + ) + // Owners up to and including the first package class. + private def ownerChainString(sym: Symbol): String = ( + if (sym == null) "" + else sym.ownerChain.span(!_.isPackageClass) match { + case (xs, pkg :: _) => (xs :+ pkg) mkString " -> " + case _ => sym.ownerChain mkString " -> " // unlikely + } + ) + private def formatExplain(pairs: (String, Any)*): String = ( + pairs.toList collect { case (k, v) if v != null => "%20s: %s".format(k, v) } mkString "\n" + ) + + def explainTree(t: Tree): String = formatExplain( + ) + /** Don't want to introduce new errors trying to report errors, * so swallow exceptions. */ override def supplementErrorMessage(errorMessage: String): String = try { - """| - | while compiling: %s - | current phase: %s - | library version: %s - | compiler version: %s - | reconstructed args: %s - | - |%s""".stripMargin.format( - currentSource.path, - phase, - scala.util.Properties.versionString, - Properties.versionString, - settings.recreateArgs.mkString(" "), - if (opt.debug) "Current unit body:\n" + currentUnit.body + "\n" + errorMessage else errorMessage + val tree = analyzer.lastTreeToTyper + val sym = tree.symbol + val tpe = tree.tpe + val enclosing = lastSeenContext.enclClassOrMethod.tree + + val info1 = formatExplain( + "while compiling" -> currentSource.path, + "during phase" -> ( if (globalPhase eq phase) phase else "global=%s, atPhase=%s".format(globalPhase, phase) ), + "library version" -> scala.util.Properties.versionString, + "compiler version" -> Properties.versionString, + "reconstructed args" -> settings.recreateArgs.mkString(" ") + ) + val info2 = formatExplain( + "last tree to typer" -> tree.summaryString, + "symbol" -> Option(sym).fold("null")(_.debugLocationString), + "symbol definition" -> Option(sym).fold("null")(_.defString), + "tpe" -> tpe, + "symbol owners" -> ownerChainString(sym), + "context owners" -> ownerChainString(lastSeenContext.owner) ) + val info3: List[String] = ( + ( List("== Enclosing template or block ==", nodePrinters.nodeToString(enclosing).trim) ) + ++ ( if (tpe eq null) Nil else List("== Expanded type of tree ==", typeDeconstruct.show(tpe)) ) + ++ ( if (!opt.debug) Nil else List("== Current unit body ==", nodePrinters.nodeToString(currentUnit.body)) ) + ++ ( List(errorMessage) ) + ) + + ("\n" + info1) :: info2 :: info3 mkString "\n\n" } catch { case x: Exception => errorMessage } @@ -1215,14 +1256,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb */ def units: Iterator[CompilationUnit] = unitbuf.iterator - def registerPickle(sym: Symbol): Unit = { - // Convert all names to the type name: objects don't store pickled data - if (opt.showPhase && (opt.showNames exists (x => findNamedMember(x.toTypeName, sym) != NoSymbol))) { - symData get sym foreach { pickle => - ShowPickled.show("\n<<-- " + sym.fullName + " -->>\n", pickle, false) - } - } - } + def registerPickle(sym: Symbol): Unit = () /** does this run compile given class, module, or case factory? */ def compiles(sym: Symbol): Boolean = @@ -1257,9 +1291,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb private def showMembers() = opt.showNames foreach (x => showDef(x, opt.declsOnly, globalPhase)) - // If -Yprofile isn't given this will never be triggered. - lazy val profiler = Class.forName(opt.profileClass).newInstance().asInstanceOf[Profiling] - // Similarly, this will only be created under -Yshow-syms. object trackerFactory extends SymbolTrackers { val global: Global.this.type = Global.this @@ -1323,10 +1354,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { units foreach addUnit - if (opt.profileAll) { - inform("starting CPU profiling on compilation run") - profiler.startProfiling() - } val startTime = currentTime reporter.reset() @@ -1336,16 +1363,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb while (globalPhase.hasNext && !reporter.hasErrors) { val startTime = currentTime phase = globalPhase - - if (opt.profCPUPhase) { - inform("starting CPU profiling on phase " + globalPhase.name) - profiler profile globalPhase.run - } - else globalPhase.run - - // Create a profiling generation for each phase's allocations - if (opt.profileAny) - profiler.advanceGeneration(globalPhase.name) + globalPhase.run // progress update informTime(globalPhase.description, startTime) @@ -1385,13 +1403,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb advancePhase } - if (opt.profileAll) - profiler.stopProfiling() - - if (opt.timings) { - inform(phaseTimings.formatted) - inform(unitTimingsFormatted) - } if (traceSymbolActivity) units map (_.body) foreach (traceSymbols recordSymbolsInTree _) @@ -1404,15 +1415,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb symSource.keys foreach (x => resetPackageClass(x.owner)) informTime("total", startTime) - // save heap snapshot if requested - if (opt.profileMem) { - inform("Saving heap snapshot, this could take a while...") - System.gc() - profiler.captureSnapshot() - inform("...done saving heap snapshot.") - specializeTypes.printSpecStats() - } - // record dependency data if (!dependencyAnalysis.off) dependencyAnalysis.saveDependencyAnalysis() @@ -1532,8 +1534,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } // class Run def printAllUnits() { - print("[[syntax trees at end of " + phase + "]]") - afterPhase(phase) { currentRun.units foreach (treePrinter.print(_)) } + print("[[syntax trees at end of %25s]]".format(phase)) + afterPhase(phase)(currentRun.units foreach { unit => + nodePrinters showUnit unit + }) } /** We resolve the class/object ambiguity by passing a type/term name. @@ -1618,35 +1622,5 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb } object Global { - /** If possible, instantiate the global specified via -Yglobal-class. - * This allows the use of a custom Global subclass with the software which - * wraps Globals, such as scalac, fsc, and the repl. - */ - def fromSettings(settings: Settings, reporter: Reporter): Global = { - // !!! The classpath isn't known until the Global is created, which is too - // late, so we have to duplicate it here. Classpath is too tightly coupled, - // it is a construct external to the compiler and should be treated as such. - val parentLoader = settings.explicitParentLoader getOrElse getClass.getClassLoader - val loader = ScalaClassLoader.fromURLs(new PathResolver(settings).result.asURLs, parentLoader) - val name = settings.globalClass.value - val clazz = Class.forName(name, true, loader) - val cons = clazz.getConstructor(classOf[Settings], classOf[Reporter]) - - cons.newInstance(settings, reporter).asInstanceOf[Global] - } - - /** A global instantiated this way honors -Yglobal-class setting, and - * falls back on calling the Global constructor directly. - */ - def apply(settings: Settings, reporter: Reporter): Global = { - val g = ( - if (settings.globalClass.isDefault) null - else try fromSettings(settings, reporter) catch { case x => - reporter.warning(NoPosition, "Failed to instantiate " + settings.globalClass.value + ": " + x) - null - } - ) - if (g != null) g - else new Global(settings, reporter) - } + def apply(settings: Settings, reporter: Reporter): Global = new Global(settings, reporter) } diff --git a/src/compiler/scala/tools/nsc/InterpreterCommand.scala b/src/compiler/scala/tools/nsc/InterpreterCommand.scala deleted file mode 100644 index e25a83e08b..0000000000 --- a/src/compiler/scala/tools/nsc/InterpreterCommand.scala +++ /dev/null @@ -1,8 +0,0 @@ -package scala.tools.nsc - -import interpreter._ - -/** A compatibility stub. - */ -@deprecated("Use a class in the scala.tools.nsc.interpreter package.", "2.9.0") -class InterpreterCommand(arguments: List[String], error: String => Unit) extends CommandLine(arguments, error) { }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/MainInterpreter.scala b/src/compiler/scala/tools/nsc/MainInterpreter.scala deleted file mode 100644 index 5d190bbe14..0000000000 --- a/src/compiler/scala/tools/nsc/MainInterpreter.scala +++ /dev/null @@ -1,13 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Lex Spoon - */ - -package scala.tools.nsc - -import interpreter._ - -@deprecated("Use a class in the scala.tools.nsc.interpreter package.", "2.9.0") -object MainInterpreter { - def main(args: Array[String]): Unit = new ILoop main args -} diff --git a/src/compiler/scala/tools/nsc/ToolBoxes.scala b/src/compiler/scala/tools/nsc/ToolBoxes.scala index 8bf977090b..f5eefa4e62 100644 --- a/src/compiler/scala/tools/nsc/ToolBoxes.scala +++ b/src/compiler/scala/tools/nsc/ToolBoxes.scala @@ -30,10 +30,10 @@ trait ToolBoxes { self: Global => // todo. implement this ??? - def resetAllAttrs[T <: Tree](tree: T): T = + def resetAllAttrs(tree: Tree): Tree = self.resetAllAttrs(tree) - def resetLocalAttrs[T <: Tree](tree: T): T = + def resetLocalAttrs(tree: Tree): Tree = self.resetLocalAttrs(tree) def runExpr(tree0: Tree, freeTypes: Map[FreeType, Type] = Map[FreeType, Type]()): Any = { diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala index 5d849b9622..ce3106ab29 100644 --- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala +++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala @@ -25,43 +25,12 @@ abstract class NodePrinters { } var infolevel = InfoLevel.Quiet - def nodeToString: Tree => String = - if (sys.props contains "scala.colors") nodeToColorizedString - else nodeToRegularString + def nodeToString: Tree => String = nodeToRegularString object nodeToRegularString extends DefaultPrintAST with (Tree => String) { def apply(tree: Tree) = stringify(tree) } - object nodeToColorizedString extends ColorPrintAST with (Tree => String) { - def apply(tree: Tree) = stringify(tree) - } - - trait ColorPrintAST extends DefaultPrintAST { - import scala.tools.util.color._ - - def keywordColor = Cyan - def typeColor = Yellow - def termColor = Blue - def flagColor = Red - def literalColor = Green - - override def showFlags(tree: MemberDef) = - super.showFlags(tree) in flagColor.bright - - override def showDefTreeName(tree: DefTree) = - if (tree.name.isTermName) tree.name.decode in termColor.bright - else tree.name.decode in typeColor.bright - - override def showName(name: Name) = - if (name == nme.EMPTY || name == tpnme.EMPTY) "<empty>" in keywordColor - else if (name.isTermName) name.decode in termColor - else name.decode in typeColor - - override def showLiteral(lit: Literal) = - super.showLiteral(lit) in literalColor.bright - } - trait DefaultPrintAST extends PrintAST { val printPos = settings.Xprintpos.value || settings.Yposdebug.value @@ -133,7 +102,6 @@ abstract class NodePrinters { buf.clear() if (settings.XshowtreesStringified.value) buf.append(tree.toString + EOL) if (settings.XshowtreesCompact.value) { - // todo. colors for compact representation buf.append(showRaw(tree)) } else { level = 0 diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala index 283bdecf26..91935854f4 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala @@ -95,6 +95,12 @@ trait TreeDSL { def INT_>= (other: Tree) = fn(target, getMember(IntClass, nme.GE), other) def INT_== (other: Tree) = fn(target, getMember(IntClass, nme.EQ), other) def INT_!= (other: Tree) = fn(target, getMember(IntClass, nme.NE), other) + + // generic operations on ByteClass, IntClass, LongClass + def GEN_| (other: Tree, kind: ClassSymbol) = fn(target, getMember(kind, nme.OR), other) + def GEN_& (other: Tree, kind: ClassSymbol) = fn(target, getMember(kind, nme.AND), other) + def GEN_== (other: Tree, kind: ClassSymbol) = fn(target, getMember(kind, nme.EQ), other) + def GEN_!= (other: Tree, kind: ClassSymbol) = fn(target, getMember(kind, nme.NE), other) def BOOL_&& (other: Tree) = fn(target, Boolean_and, other) def BOOL_|| (other: Tree) = fn(target, Boolean_or, other) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 978d83616b..23fcffd657 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -236,7 +236,7 @@ abstract class TreeGen extends reflect.internal.TreeGen with TreeDSL { mkMethodCall( PredefModule, wrapArrayMethodName(elemtp), - if (isScalaValueType(elemtp)) Nil else List(elemtp), + if (isPrimitiveValueType(elemtp)) Nil else List(elemtp), List(tree) ) } @@ -261,7 +261,7 @@ abstract class TreeGen extends reflect.internal.TreeGen with TreeDSL { * elem type elemtp to expected type pt. */ def mkCastArray(tree: Tree, elemtp: Type, pt: Type) = - if (elemtp.typeSymbol == AnyClass && isScalaValueType(tree.tpe.typeArgs.head)) + if (elemtp.typeSymbol == AnyClass && isPrimitiveValueType(tree.tpe.typeArgs.head)) mkCast(mkRuntimeCall(nme.toObjectArray, List(tree)), pt) else mkCast(tree, pt) @@ -352,23 +352,19 @@ abstract class TreeGen extends reflect.internal.TreeGen with TreeDSL { else Block(prefix, containing) setPos (prefix.head.pos union containing.pos) } - /** Return a double-checked locking idiom around the syncBody tree. It guards with `cond` and + /** Return the synchronized part of the double-checked locking idiom around the syncBody tree. It guards with `cond` and * synchronizez on `clazz.this`. Additional statements can be included after initialization, * (outside the synchronized block). * * The idiom works only if the condition is using a volatile field. * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html */ - def mkDoubleCheckedLocking(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = - mkDoubleCheckedLocking(mkAttributedThis(clazz), cond, syncBody, stats) - - def mkDoubleCheckedLocking(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = { - If(cond, - Block( - mkSynchronized( - attrThis, - If(cond, Block(syncBody: _*), EmptyTree)) :: - stats: _*), - EmptyTree) - } + def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = + mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats) + + def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = + Block(mkSynchronized( + attrThis, + If(cond, Block(syncBody: _*), EmptyTree)) :: + stats: _*) } diff --git a/src/compiler/scala/tools/nsc/ast/Trees.scala b/src/compiler/scala/tools/nsc/ast/Trees.scala index 4c509778e9..adc490c8e1 100644 --- a/src/compiler/scala/tools/nsc/ast/Trees.scala +++ b/src/compiler/scala/tools/nsc/ast/Trees.scala @@ -264,9 +264,9 @@ trait Trees extends reflect.internal.Trees { self: Global => // def resetAllAttrs[A<:Tree](x:A): A = { new ResetAttrsTraverser().traverse(x); x } // def resetLocalAttrs[A<:Tree](x:A): A = { new ResetLocalAttrsTraverser().traverse(x); x } - def resetAllAttrs[A <: Tree](x: A, leaveAlone: Tree => Boolean = null): A = new ResetAttrs(false, leaveAlone).transform(x) - def resetLocalAttrs[A <: Tree](x: A, leaveAlone: Tree => Boolean = null): A = new ResetAttrs(true, leaveAlone).transform(x) - def resetLocalAttrsKeepLabels[A<:Tree](x: A, leaveAlone: Tree => Boolean = null): A = new ResetAttrs(true, leaveAlone, true).transform(x) + def resetAllAttrs(x: Tree, leaveAlone: Tree => Boolean = null): Tree = new ResetAttrs(false, leaveAlone).transform(x) + def resetLocalAttrs(x: Tree, leaveAlone: Tree => Boolean = null): Tree = new ResetAttrs(true, leaveAlone).transform(x) + def resetLocalAttrsKeepLabels(x: Tree, leaveAlone: Tree => Boolean = null): Tree = new ResetAttrs(true, leaveAlone, true).transform(x) /** A transformer which resets symbol and tpe fields of all nodes in a given tree, * with special treatment of: @@ -351,7 +351,7 @@ trait Trees extends reflect.internal.Trees { self: Global => } } - def transform[T <: Tree](x: T): T = { + def transform(x: Tree): Tree = { if (localOnly) new MarkLocals().traverse(x) @@ -361,9 +361,7 @@ trait Trees extends reflect.internal.Trees { self: Global => trace("locals (%d total): %n".format(orderedLocals.size))(msg) } - val x1 = new Transformer().transform(x) - assert(x.getClass isInstance x1, x1.getClass) - x1.asInstanceOf[T] + new Transformer().transform(x) } } diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 314a3b45a0..7da7611ce2 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -33,9 +33,13 @@ trait JavaPlatform extends Platform { if (settings.make.isDefault) Nil else List(dependencyAnalysis) + private def classEmitPhase = + if (settings.target.value == "jvm-1.5") genJVM + else genASM + def platformPhases = List( - flatten, // get rid of inner classes - genJVM // generate .class files + flatten, // get rid of inner classes + classEmitPhase // generate .class files ) ++ depAnalysisPhase lazy val externalEquals = getDecl(BoxesRunTimeClass, nme.equals_) diff --git a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala index dcbabd6517..b8ecaf1b43 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/BasicBlocks.scala @@ -219,8 +219,8 @@ trait BasicBlocks { ///////////////////// Substitutions /////////////////////// /** - * Replace the instruction at the given position. Used by labels when - * they are anchored. It retains the position of the previous instruction. + * Replace the instruction at the given position. Used by labels when they are anchored. + * The replacing instruction is given the nsc.util.Position of the instruction it replaces. */ def replaceInstruction(pos: Int, instr: Instruction): Boolean = { assert(closed, "Instructions can be replaced only after the basic block is closed") @@ -233,7 +233,7 @@ trait BasicBlocks { /** * Replace the given instruction with the new one. * Returns `true` if it actually changed something. - * It retains the position of the previous instruction. + * The replacing instruction is given the nsc.util.Position of the instruction it replaces. */ def replaceInstruction(oldInstr: Instruction, newInstr: Instruction): Boolean = { assert(closed, "Instructions can be replaced only after the basic block is closed") diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 4aeb537f9b..f7541a4739 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -1208,7 +1208,7 @@ abstract class GenICode extends SubComponent { if (!tree.symbol.isPackageClass) tree.symbol else tree.symbol.info.member(nme.PACKAGE) match { case NoSymbol => assert(false, "Cannot use package as value: " + tree) ; NoSymbol - case s => Console.err.println("Bug: found package class where package object expected. Converting.") ; s.moduleClass + case s => debugwarn("Bug: found package class where package object expected. Converting.") ; s.moduleClass } ) debuglog("LOAD_MODULE from %s: %s".format(tree.shortClass, sym)) diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index ec6c631bd1..576cc72f82 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -435,6 +435,8 @@ trait Opcodes { self: ICodes => override def consumed = 1 override def produced = 0 + + def flatTagsCount: Int = { var acc = 0; var rest = tags; while(rest.nonEmpty) { acc += rest.head.length; rest = rest.tail }; acc } // a one-liner } /** This class represents a JUMP instruction @@ -593,48 +595,49 @@ trait Opcodes { self: ICodes => /** This class represents a method invocation style. */ sealed abstract class InvokeStyle { /** Is this a dynamic method call? */ - def isDynamic: Boolean = this match { - case Dynamic => true - case _ => false - } + def isDynamic: Boolean = false /** Is this a static method call? */ - def isStatic: Boolean = this match { - case Static(_) => true - case _ => false - } + def isStatic: Boolean = false - def isSuper: Boolean = this match { - case SuperCall(_) => true - case _ => false - } + def isSuper: Boolean = false /** Is this an instance method call? */ - def hasInstance: Boolean = this match { - case Static(false) => false - case _ => true - } + def hasInstance: Boolean = true /** Returns a string representation of this style. */ - override def toString(): String = this match { - case Dynamic => "dynamic" - case Static(false) => "static-class" - case Static(true) => "static-instance" - case SuperCall(mix) => "super(" + mix + ")" - } + override def toString(): String } - /** Virtual calls */ - case object Dynamic extends InvokeStyle + /** Virtual calls. + * On JVM, translated to either `invokeinterface` or `invokevirtual`. + */ + case object Dynamic extends InvokeStyle { + override def isDynamic = true + override def toString(): String = "dynamic" + } /** - * Special invoke. Static(true) is used for calls to private - * members. + * Special invoke: + * Static(true) is used for calls to private members, ie `invokespecial` on JVM. + * Static(false) is used for calls to class-level instance-less static methods, ie `invokestatic` on JVM. */ - case class Static(onInstance: Boolean) extends InvokeStyle + case class Static(onInstance: Boolean) extends InvokeStyle { + override def isStatic = true + override def hasInstance = onInstance + override def toString(): String = { + if(onInstance) "static-instance" + else "static-class" + } + } - /** Call through super[mix]. */ - case class SuperCall(mix: Name) extends InvokeStyle + /** Call through super[mix]. + * On JVM, translated to `invokespecial`. + */ + case class SuperCall(mix: Name) extends InvokeStyle { + override def isSuper = true + override def toString(): String = { "super(" + mix + ")" } + } // CLR backend diff --git a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala index f99ac28e9d..72220184d2 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Primitives.scala @@ -127,42 +127,61 @@ trait Primitives { self: ICodes => /** Returns a string representation of this operation. */ override def toString(): String + + /** used only from GenASM */ + def opcodeIF(): Int + + /** used only from GenASM */ + def opcodeIFICMP(): Int + } /** An equality test */ case object EQ extends TestOp { def negate() = NE override def toString() = "EQ" + override def opcodeIF() = scala.tools.asm.Opcodes.IFEQ + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPEQ } /** A non-equality test */ case object NE extends TestOp { def negate() = EQ override def toString() = "NE" + override def opcodeIF() = scala.tools.asm.Opcodes.IFNE + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPNE } /** A less-than test */ case object LT extends TestOp { def negate() = GE override def toString() = "LT" + override def opcodeIF() = scala.tools.asm.Opcodes.IFLT + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLT } /** A greater-than-or-equal test */ case object GE extends TestOp { def negate() = LT override def toString() = "GE" + override def opcodeIF() = scala.tools.asm.Opcodes.IFGE + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGE } /** A less-than-or-equal test */ case object LE extends TestOp { def negate() = GT override def toString() = "LE" + override def opcodeIF() = scala.tools.asm.Opcodes.IFLE + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPLE } /** A greater-than test */ case object GT extends TestOp { def negate() = LE override def toString() = "GT" + override def opcodeIF() = scala.tools.asm.Opcodes.IFGT + override def opcodeIFICMP() = scala.tools.asm.Opcodes.IF_ICMPGT } /** This class represents an arithmetic operation. */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 0bc41b51bb..ff68aba845 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -6,7 +6,6 @@ package scala.tools.nsc package backend.jvm -import ch.epfl.lamp.fjbg._ import java.io.{ DataOutputStream, FileOutputStream, OutputStream, File => JFile } import scala.tools.nsc.io._ import scala.tools.nsc.util.ScalaClassLoader @@ -26,19 +25,19 @@ trait BytecodeWriters { private def outputDirectory(sym: Symbol): AbstractFile = ( settings.outputDirs.outputDirFor(beforeFlatten(sym.sourceFile)) ) - private def getFile(base: AbstractFile, cls: JClass, suffix: String): AbstractFile = { + private def getFile(base: AbstractFile, /*cls.getName()*/ clsName: String, suffix: String): AbstractFile = { var dir = base - val pathParts = cls.getName().split("[./]").toList + val pathParts = clsName.split("[./]").toList for (part <- pathParts.init) { dir = dir.subdirectoryNamed(part) } dir.fileNamed(pathParts.last + suffix) } - private def getFile(sym: Symbol, cls: JClass, suffix: String): AbstractFile = - getFile(outputDirectory(sym), cls, suffix) + private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = + getFile(outputDirectory(sym), clsName, suffix) trait BytecodeWriter { - def writeClass(label: String, jclass: JClass, sym: Symbol): Unit + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol): Unit def close(): Unit = () } @@ -49,11 +48,11 @@ trait BytecodeWriters { ) val writer = new Jar(jfile).jarWriter(jarMainAttrs: _*) - def writeClass(label: String, jclass: JClass, sym: Symbol) { - val path = jclass.getName + ".class" + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + val path = jclassName + ".class" val out = writer.newOutputStream(path) - try jclass writeTo out + try out.write(jclassBytes, 0, jclassBytes.length) finally out.flush() informProgress("added " + label + path + " to jar") @@ -73,11 +72,11 @@ trait BytecodeWriters { try javap(Seq("-verbose", "dummy")) foreach (_.show()) finally pw.close() } - abstract override def writeClass(label: String, jclass: JClass, sym: Symbol) { - super.writeClass(label, jclass, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + super.writeClass(label, jclassName, jclassBytes, sym) - val bytes = getFile(sym, jclass, ".class").toByteArray - val segments = jclass.getName().split("[./]") + val bytes = getFile(sym, jclassName, ".class").toByteArray + val segments = jclassName.split("[./]") val javapFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "javap" toFile; javapFile.parent.createDirectory() @@ -86,11 +85,11 @@ trait BytecodeWriters { } trait ClassBytecodeWriter extends BytecodeWriter { - def writeClass(label: String, jclass: JClass, sym: Symbol) { - val outfile = getFile(sym, jclass, ".class") + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + val outfile = getFile(sym, jclassName, ".class") val outstream = new DataOutputStream(outfile.bufferedOutput) - try jclass writeTo outstream + try outstream.write(jclassBytes, 0, jclassBytes.length) finally outstream.close() informProgress("wrote '" + label + "' to " + outfile) } @@ -99,15 +98,15 @@ trait BytecodeWriters { trait DumpBytecodeWriter extends BytecodeWriter { val baseDir = Directory(settings.Ydumpclasses.value).createDirectory() - abstract override def writeClass(label: String, jclass: JClass, sym: Symbol) { - super.writeClass(label, jclass, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + super.writeClass(label, jclassName, jclassBytes, sym) - val pathName = jclass.getName() + val pathName = jclassName var dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _) changeExtension "class" toFile; dumpFile.parent.createDirectory() val outstream = new DataOutputStream(new FileOutputStream(dumpFile.path)) - try jclass writeTo outstream + try outstream.write(jclassBytes, 0, jclassBytes.length) finally outstream.close() } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala new file mode 100644 index 0000000000..81fe907a0c --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -0,0 +1,3289 @@ +/* NSC -- new Scala compiler
+ * Copyright 2005-2011 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+
+import java.nio.ByteBuffer
+import scala.collection.{ mutable, immutable }
+import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer }
+import scala.tools.nsc.symtab._
+import scala.tools.nsc.io.AbstractFile
+
+import scala.tools.asm
+import asm.Label
+
+/**
+ * @author Iulian Dragos (version 1.0, FJBG-based implementation)
+ * @author Miguel Garcia (version 2.0, ASM-based implementation)
+ *
+ * Documentation at http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/2012Q2/GenASM.pdf
+ */
+abstract class GenASM extends SubComponent with BytecodeWriters {
+ import global._
+ import icodes._
+ import icodes.opcodes._
+ import definitions._
+
+ val phaseName = "jvm"
+
+ /** Create a new phase */
+ override def newPhase(p: Phase): Phase = new AsmPhase(p)
+
+ private def outputDirectory(sym: Symbol): AbstractFile =
+ settings.outputDirs outputDirFor beforeFlatten(sym.sourceFile)
+
+ private def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
+ var dir = base
+ val pathParts = clsName.split("[./]").toList
+ for (part <- pathParts.init) {
+ dir = dir.subdirectoryNamed(part)
+ }
+ dir.fileNamed(pathParts.last + suffix)
+ }
+ private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile =
+ getFile(outputDirectory(sym), clsName, suffix)
+
+ /** JVM code generation phase
+ */
+ class AsmPhase(prev: Phase) extends ICodePhase(prev) {
+ def name = phaseName
+ override def erasedTypes = true
+ def apply(cls: IClass) = sys.error("no implementation")
+
+ val BeanInfoAttr = definitions.getRequiredClass("scala.beans.BeanInfo")
+
+ def isJavaEntryPoint(icls: IClass) = {
+ val sym = icls.symbol
+ def fail(msg: String, pos: Position = sym.pos) = {
+ icls.cunit.warning(sym.pos,
+ sym.name + " 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(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 == 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.
+ beforeErasure {
+ val companion = sym.linkedClassOfClass
+ val companionMain = companion.tpe.member(nme.main)
+
+ if (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 { 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
+ isJavaMainMethod(m) || fail("main method must have exact signature (Array[String])Unit", m.pos)
+ case tp =>
+ fail("don't know what this is: " + tp, m.pos)
+ }
+ }
+ }
+ }
+ }
+
+ private def initBytecodeWriter(entryPoints: List[IClass]): 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 (_.symbol fullName '.') match {
+ case Nil =>
+ log("No Main-Class designated or discovered.")
+ case name :: Nil =>
+ log("Unique entry point: setting Main-Class to " + name)
+ settings.mainClass.value = name
+ case names =>
+ log("No Main-Class due to multiple entry points:\n " + names.mkString("\n "))
+ }
+ }
+ else log("Main-Class was specified: " + settings.mainClass.value)
+
+ new DirectToJarfileWriter(f.file)
+
+ case _ =>
+ if (settings.Ygenjavap.isDefault) {
+ if(settings.Ydumpclasses.isDefault)
+ new ClassBytecodeWriter { }
+ else
+ new ClassBytecodeWriter with DumpBytecodeWriter { }
+ }
+ else new ClassBytecodeWriter with JavapBytecodeWriter { }
+
+ // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point.
+ // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are:
+ // (a) unreadable pickle;
+ // (b) two constant pools, while having identical contents, are displayed differently due to physical layout.
+ // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead.
+ }
+ }
+
+ override def run() {
+
+ if (settings.debug.value)
+ inform("[running phase " + name + " on icode]")
+
+ if (settings.Xdce.value)
+ for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym))
+ icodes.classes -= sym
+
+ // For predictably ordered error messages.
+ var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName)
+
+ debuglog("Created new bytecode generator for " + classes.size + " classes.")
+ val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint)
+ val plainCodeGen = new JPlainBuilder(bytecodeWriter)
+ val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter)
+ val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter)
+
+ while(!sortedClasses.isEmpty) {
+ val c = sortedClasses.head
+
+ if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) {
+ if (c.symbol.companionClass == NoSymbol) {
+ mirrorCodeGen.genMirrorClass(c.symbol, c.cunit)
+ } else {
+ log("No mirror class for module with linked class: " + c.symbol.fullName)
+ }
+ }
+
+ plainCodeGen.genClass(c)
+
+ if (c.symbol hasAnnotation BeanInfoAttr) {
+ beanInfoCodeGen.genBeanInfoClass(c)
+ }
+
+ sortedClasses = sortedClasses.tail
+ classes -= c.symbol // GC opportunity
+ }
+
+ bytecodeWriter.close()
+ classes.clear()
+ reverseJavaName.clear()
+
+ /* don't javaNameCache.clear() because that causes the following tests to fail:
+ * test/files/run/macro-repl-dontexpand.scala
+ * test/files/jvm/interpreter.scala
+ * TODO but why? what use could javaNameCache possibly see once GenJVM is over?
+ */
+
+ /* TODO After emitting all class files (e.g., in a separate compiler phase) ASM can perform bytecode verification:
+ *
+ * (1) call the asm.util.CheckAdapter.verify() overload:
+ * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw)
+ *
+ * (2) 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).
+ */
+
+ } // end of AsmPhase.run()
+
+ } // end of class AsmPhase
+
+ var pickledBytes = 0 // statistics
+
+ // Don't put this in per run caches. Contains entries for classes as well as members.
+ val javaNameCache = new mutable.WeakHashMap[Symbol, Name]() ++= List(
+ NothingClass -> binarynme.RuntimeNothing,
+ RuntimeNothingClass -> binarynme.RuntimeNothing,
+ NullClass -> binarynme.RuntimeNull,
+ RuntimeNullClass -> binarynme.RuntimeNull
+ )
+
+ // unlike javaNameCache, reverseJavaName contains entries only for class symbols and their internal names.
+ val reverseJavaName = mutable.Map.empty[String, Symbol] ++= List(
+ binarynme.RuntimeNothing.toString() -> NothingClass, // neither RuntimeNothingClass nor RuntimeNullClass belong to the co-domain of this map.
+ binarynme.RuntimeNull.toString() -> NullClass
+ )
+
+ private def mkFlags(args: Int*) = args.foldLeft(0)(_ | _)
+
+ @inline final private def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0)
+
+ @inline final private def isRemote(s: Symbol) = (s hasAnnotation 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.
+ */
+ 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.hasFlag(Flags.FINAL) || isTopLevelModule(sym))
+ && !sym.enclClass.isInterface
+ && !sym.isClassConstructor
+ && !sym.isMutable // lazy vals and vars both
+ )
+
+ 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) ACC_FINAL else 0,
+ if (sym.isStaticMember) ACC_STATIC else 0,
+ if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
+ if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
+ if (sym.isVarargsMethod) ACC_VARARGS else 0,
+ if (sym.hasFlag(Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
+ )
+ }
+
+ def javaFieldFlags(sym: Symbol) = {
+ javaFlags(sym) | mkFlags(
+ if (sym hasAnnotation TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
+ if (sym hasAnnotation VolatileAttr) asm.Opcodes.ACC_VOLATILE else 0,
+ if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
+ )
+ }
+
+ def isTopLevelModule(sym: Symbol): Boolean =
+ afterPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
+
+ def isStaticModule(sym: Symbol): Boolean = {
+ sym.isModuleClass && !sym.isImplClass && !sym.isLifted
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // 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
+ // -----------------------------------------------------------------------------------------
+
+ /**
+ * Given an internal name (eg "java/lang/Integer") returns the class symbol for it.
+ *
+ * Better not to need this method (an example where control flow arrives here is welcome).
+ * This method is invoked only upon both (1) and (2) below happening:
+ * (1) providing an asm.ClassWriter with an internal name by other means than javaName()
+ * (2) forgetting to track the corresponding class-symbol in reverseJavaName.
+ *
+ * (The first item is already unlikely because we rely on javaName()
+ * to do the bookkeeping for entries that should go in innerClassBuffer.)
+ *
+ * (We could do completely without this method at the expense of computing stack-map-frames ourselves and
+ * invoking visitFrame(), but that would require another pass over all instructions.)
+ *
+ * Right now I can't think of any invocation of visitSomething() on MethodVisitor
+ * where we hand an internal name not backed by a reverseJavaName.
+ * However, I'm leaving this note just in case any such oversight is discovered.
+ */
+ def inameToSymbol(iname: String): Symbol = {
+ val name = global.newTypeName(iname)
+ val res0 =
+ if (nme.isModuleName(name)) definitions.getModule(nme.stripModuleSuffix(name))
+ else definitions.getClassByName(name.replace('/', '.')) // TODO fails for inner classes (but this hasn't been tested).
+ assert(res0 != NoSymbol)
+ val res = jsymbol(res0)
+ res
+ }
+
+ def jsymbol(sym: Symbol): Symbol = {
+ if(sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass
+ else if(sym.isModule) sym.moduleClass
+ else sym // we track only module-classes and plain-classes
+ }
+
+ private def superClasses(s: Symbol): List[Symbol] = {
+ assert(!s.isInterface)
+ s.superClass match {
+ case NoSymbol => List(s)
+ case sc => s :: superClasses(sc)
+ }
+ }
+
+ private def firstCommonSuffix(as: List[Symbol], bs: List[Symbol]): Symbol = {
+ assert(!(as contains NoSymbol))
+ assert(!(bs contains NoSymbol))
+ var chainA = as
+ var chainB = bs
+ var fcs: Symbol = NoSymbol
+ 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 == NoSymbol)
+ fcs
+ }
+
+ @inline final private def jvmWiseLUB(a: Symbol, b: Symbol): Symbol = {
+
+ assert(a.isClass)
+ assert(b.isClass)
+
+ val res = Pair(a.isInterface, b.isInterface) match {
+ case (true, true) =>
+ global.lub(List(a.tpe, b.tpe)).typeSymbol // TODO assert == firstCommonSuffix of resp. parents
+ case (true, false) =>
+ if(b isSubClass a) a else ObjectClass
+ case (false, true) =>
+ if(a isSubClass b) b else ObjectClass
+ case _ =>
+ firstCommonSuffix(superClasses(a), superClasses(b))
+ }
+ assert(res != NoSymbol)
+ res
+ }
+
+ /* 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 */
+ def getCommonSuperClass(inameA: String, inameB: String): String = {
+ val a = reverseJavaName.getOrElseUpdate(inameA, inameToSymbol(inameA))
+ val b = reverseJavaName.getOrElseUpdate(inameB, inameToSymbol(inameB))
+
+ // global.lub(List(a.tpe, b.tpe)).typeSymbol.javaBinaryName.toString()
+ // icodes.lub(icodes.toTypeKind(a.tpe), icodes.toTypeKind(b.tpe)).toType
+ val lcaSym = jvmWiseLUB(a, b)
+ val lcaName = lcaSym.javaBinaryName.toString // don't call javaName because that side-effects innerClassBuffer.
+ val oldsym = reverseJavaName.put(lcaName, lcaSym)
+ assert(oldsym.isEmpty || (oldsym.get == lcaSym), "somehow we're not managing to compute common-super-class for ASM consumption")
+ assert(lcaName != "scala/Any")
+
+ lcaName // TODO ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Do some caching.
+ }
+
+ class CClassWriter(flags: Int) extends asm.ClassWriter(flags) {
+ override def getCommonSuperClass(iname1: String, iname2: String): String = {
+ GenASM.this.getCommonSuperClass(iname1, iname2)
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // constants
+ // -----------------------------------------------------------------------------------------
+
+ private val classfileVersion: Int = settings.target.value match {
+ case "jvm-1.5" => asm.Opcodes.V1_5
+ case "jvm-1.5-asm" => asm.Opcodes.V1_5
+ case "jvm-1.6" => asm.Opcodes.V1_6
+ case "jvm-1.7" => asm.Opcodes.V1_7
+ }
+
+ private val majorVersion: Int = (classfileVersion & 0xFF)
+
+ private val extraProc: Int = mkFlags(
+ asm.ClassWriter.COMPUTE_MAXS,
+ if(majorVersion >= 50) asm.ClassWriter.COMPUTE_FRAMES else 0
+ )
+
+ val JAVA_LANG_OBJECT = asm.Type.getObjectType("java/lang/Object")
+ val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String")
+
+ /** basic functionality for class file building */
+ abstract class JBuilder(bytecodeWriter: BytecodeWriter) {
+
+ val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type]
+ val EMPTY_STRING_ARRAY = Array.empty[String]
+
+ val mdesc_arglessvoid = "()V"
+
+ val CLASS_CONSTRUCTOR_NAME = "<clinit>"
+ val INSTANCE_CONSTRUCTOR_NAME = "<init>"
+
+ // -----------------------------------------------------------------------------------------
+ // factory methods
+ // -----------------------------------------------------------------------------------------
+
+ /**
+ * Returns a new ClassWriter for the class given by arguments.
+ *
+ * @param access the class's access flags. This parameter also indicates if the class is deprecated.
+ *
+ * @param name the internal name of the class.
+ *
+ * @param signature the signature of this class. May be <tt>null</tt> if
+ * the class is not a generic one, and does not extend or implement
+ * generic classes or interfaces.
+ *
+ * @param superName the internal of name of the super class. For interfaces,
+ * the super class is {@link Object}. May be <tt>null</tt>, but
+ * only for the {@link Object} class.
+ *
+ * @param interfaces the internal names of the class's interfaces (see
+ * {@link Type#getInternalName() getInternalName}). May be
+ * <tt>null</tt>.
+ */
+ def createJClass(access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): asm.ClassWriter = {
+ val cw = new CClassWriter(extraProc)
+ cw.visit(classfileVersion,
+ access, name, signature,
+ superName, interfaces)
+
+ cw
+ }
+
+ 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)
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // utitilies useful when emitting plain, mirror, and beaninfo classes.
+ // -----------------------------------------------------------------------------------------
+
+ def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) {
+ try {
+ val arr = jclass.toByteArray()
+ bytecodeWriter.writeClass(label, jclassName, arr, sym)
+ } catch {
+ case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") =>
+ // TODO check where ASM throws the equivalent of CodeSizeTooBigException
+ log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).")
+ }
+ }
+
+ /** Specialized array conversion to prevent calling
+ * java.lang.reflect.Array.newInstance via TraversableOnce.toArray
+ */
+ def mkArray(xs: Traversable[asm.Type]): Array[asm.Type] = { val a = new Array[asm.Type](xs.size); xs.copyToArray(a); a }
+ def mkArray(xs: Traversable[String]): Array[String] = { val a = new Array[String](xs.size); xs.copyToArray(a); a }
+
+ // -----------------------------------------------------------------------------------------
+ // Getters for (JVMS 4.2) internal and unqualified names (represented as JType instances).
+ // These getters track behind the scenes the inner classes referred to in the class being emitted,
+ // so as to build the InnerClasses attribute (JVMS 4.7.6) via `addInnerClasses()`
+ // (which also adds as member classes those inner classes that have been declared,
+ // thus also covering the case of inner classes declared but otherwise not referred).
+ // -----------------------------------------------------------------------------------------
+
+ val innerClassBuffer = mutable.LinkedHashSet[Symbol]()
+
+ /** 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.
+ */
+ def innerClassSymbolFor(s: Symbol): Symbol =
+ if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol
+
+ /** Return the a name of this symbol that can be used on the Java platform. It removes spaces from names.
+ *
+ * Special handling:
+ * scala.Nothing erases to scala.runtime.Nothing$
+ * scala.Null erases to scala.runtime.Null$
+ *
+ * This is needed because they are not real classes, and they mean
+ * 'abrupt termination upon evaluation of that expression' or null respectively.
+ * This handling is done already in GenICode, but here we need to remove
+ * references from method signatures to these types, because such classes
+ * cannot exist in the classpath: the type checker will be very confused.
+ */
+ def javaName(sym: Symbol): String = {
+
+ /**
+ * Checks if given symbol corresponds to inner class/object and add it to innerClassBuffer
+ *
+ * Note: This method is called recursively thus making sure that we add complete chain
+ * of inner class all until root class.
+ */
+ def collectInnerClass(s: Symbol): Unit = {
+ // TODO: some beforeFlatten { ... } which accounts for
+ // being nested in parameterized classes (if we're going to selectively flatten.)
+ val x = innerClassSymbolFor(s)
+ if(x ne NoSymbol) {
+ assert(x.isClass, "not an inner-class symbol")
+ val isInner = !x.rawowner.isPackageClass
+ if (isInner) {
+ innerClassBuffer += x
+ collectInnerClass(x.rawowner)
+ }
+ }
+ }
+
+ collectInnerClass(sym)
+
+ var hasInternalName = (sym.isClass || (sym.isModule && !sym.isMethod))
+ val cachedJN = javaNameCache.getOrElseUpdate(sym, {
+ if (hasInternalName) { sym.javaBinaryName }
+ else { sym.javaSimpleName }
+ })
+
+ if(hasInternalName) {
+ val internalName = cachedJN.toString()
+ val trackedSym = jsymbol(sym)
+ reverseJavaName.get(internalName) match {
+ case None =>
+ reverseJavaName.put(internalName, trackedSym)
+ case Some(oldsym) =>
+ assert(List(NothingClass, NullClass).contains(oldsym) || oldsym == trackedSym,
+ "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.")
+ }
+ }
+
+ cachedJN.toString
+ }
+
+ def descriptor(t: Type): String = { javaType(t).getDescriptor }
+ def descriptor(k: TypeKind): String = { javaType(k).getDescriptor }
+ def descriptor(s: Symbol): String = { javaType(s).getDescriptor }
+
+ def javaType(tk: TypeKind): asm.Type = {
+ if(tk.isValueType) {
+ if(tk.isIntSizedType) {
+ (tk: @unchecked) match {
+ case BOOL => asm.Type.BOOLEAN_TYPE
+ case BYTE => asm.Type.BYTE_TYPE
+ case SHORT => asm.Type.SHORT_TYPE
+ case CHAR => asm.Type.CHAR_TYPE
+ case INT => asm.Type.INT_TYPE
+ }
+ } else {
+ (tk: @unchecked) match {
+ case UNIT => asm.Type.VOID_TYPE
+ case LONG => asm.Type.LONG_TYPE
+ case FLOAT => asm.Type.FLOAT_TYPE
+ case DOUBLE => asm.Type.DOUBLE_TYPE
+ }
+ }
+ } else {
+ assert(!tk.isBoxedType, tk) // documentation (BOXED matches none below anyway)
+ (tk: @unchecked) match {
+ case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
+ case ARRAY(elem) => javaArrayType(javaType(elem))
+ }
+ }
+ }
+
+ def javaType(t: Type): asm.Type = javaType(toTypeKind(t))
+
+ def javaType(s: Symbol): asm.Type = {
+ if (s.isMethod) {
+ val resT: asm.Type = if (s.isClassConstructor) asm.Type.VOID_TYPE else javaType(s.tpe.resultType);
+ asm.Type.getMethodType( resT, (s.tpe.paramTypes map javaType): _* )
+ } else { javaType(s.tpe) }
+ }
+
+ def javaArrayType(elem: asm.Type): asm.Type = { asm.Type.getObjectType("[" + elem.getDescriptor) }
+
+ def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }
+
+ } // end of class JBuilder
+
+
+ /** functionality for building plain and mirror classes */
+ abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
+
+ // -----------------------------------------------------------------------------------------
+ // more constants
+ // -----------------------------------------------------------------------------------------
+
+ 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)
+
+ 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
+
+ // -----------------------------------------------------------------------------------------
+ // 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.)
+ // -----------------------------------------------------------------------------------------
+
+ 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
+ }
+
+ def pickleMarkerLocal = {
+ createJAttribute(tpnme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex)
+ }
+
+ 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.
+ *
+ */
+ 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, List((nme.bytes, sigBytes)))
+ }
+ pickledBytes += pickle.writeIndex
+ currentRun.symData -= sym
+ currentRun.symData -= sym.companionSymbol
+ Some(scalaAnnot)
+ case _ =>
+ None
+ }
+ }
+
+ /**
+ * 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.
+ *
+ */
+ def getExceptions(excs: List[AnnotationInfo]): List[String] = {
+ for (AnnotationInfo(tp, List(exc), _) <- excs.distinct if tp.typeSymbol == ThrowsClass)
+ yield {
+ val Literal(const) = exc
+ javaName(const.typeValue.typeSymbol)
+ }
+ }
+
+ /** Whether an annotation should be emitted as a Java annotation
+ * .initialize: if 'annot' is read from pickle, atp might be un-initialized
+ */
+ private def shouldEmitAnnotation(annot: AnnotationInfo) =
+ annot.symbol.initialize.isJavaDefined &&
+ annot.matches(ClassfileAnnotationClass) &&
+ annot.args.isEmpty &&
+ !annot.matches(DeprecatedAttr)
+
+ // @M don't generate java generics sigs for (members of) implementation
+ // classes, as they are monomorphic (TODO: ok?)
+ 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.
+ sym.isSynthetic
+ || 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
+ */
+ def getGenericSignature(sym: Symbol, owner: Symbol): String = {
+
+ if (!needsGenericSignature(sym)) { return null }
+
+ val memberTpe = beforeErasure(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 _ => false }
+ }
+
+ if (settings.Xverify.value) {
+ // 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.SignatureChecker
+ if (sym.isMethod) { SignatureChecker checkMethodSignature sig } // requires asm-util.jar
+ else if (sym.isTerm) { SignatureChecker checkFieldSignature sig }
+ else { SignatureChecker 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 = beforeErasure(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
+ }
+
+ def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = {
+ val ca = new Array[Char](bytes.size)
+ var idx = 0
+ while(idx < bytes.size) {
+ val b: Byte = bytes(idx)
+ assert((b & ~0x7f) == 0)
+ ca(idx) = b.asInstanceOf[Char]
+ idx += 1
+ }
+
+ ca
+ }
+
+ // TODO this method isn't exercised during bootstrapping. Open question: is it bug free?
+ 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 newEncLength = encLength.toLong + (if(bSeven(offset) == 0) 2 else 1)
+ if(newEncLength > 65535) {
+ val ba = bSeven.slice(prevOffset, offset)
+ strs ::= new java.lang.String(ubytesToCharArray(ba))
+ encLength = 0
+ prevOffset = offset
+ } else {
+ encLength += 1
+ 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?
+ strs.reverse.toArray
+ }
+
+ 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)
+ }
+
+ 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, javaType(const.typeValue))
+ 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.
+ val assocValue = (if(sb.fitsInOneString) strEncode(sb) else arrEncode(sb))
+ av.visit(name, assocValue)
+ // 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)
+ }
+ }
+
+ def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, ClassfileAnnotArg)]) {
+ for ((name, value) <- assocs) {
+ emitArgument(av, name.toString(), value)
+ }
+ av.visitEnd()
+ }
+
+ 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)
+ }
+ }
+
+ 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)
+ }
+ }
+
+ 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)
+ }
+ }
+
+ 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)
+ }
+ }
+
+ def addInnerClasses(csym: Symbol, jclass: asm.ClassVisitor) {
+ /** 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): String = {
+ if (innerSym.originalEnclosingMethod != NoSymbol)
+ null
+ else {
+ val outerName = javaName(innerSym.rawowner)
+ if (isTopLevelModule(innerSym.rawowner)) "" + nme.stripModuleSuffix(newTermName(outerName))
+ else outerName
+ }
+ }
+
+ def innerName(innerSym: Symbol): String =
+ if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction)
+ null
+ else
+ innerSym.rawname + innerSym.moduleSuffix
+
+ // add inner classes which might not have been referenced yet
+ afterErasure {
+ for (sym <- List(csym, csym.linkedClassOfClass); m <- sym.info.decls.map(innerClassSymbolFor) if m.isClass)
+ innerClassBuffer += m
+ }
+
+ val allInners: List[Symbol] = innerClassBuffer.toList
+ if (allInners.nonEmpty) {
+ debuglog(csym.fullName('.') + " contains " + allInners.size + " inner classes.")
+
+ // entries ready to be serialized into the classfile, used to detect duplicates.
+ val entries = mutable.Map.empty[String, String]
+
+ // sort them so inner classes succeed their enclosing class to satisfy the Eclipse Java compiler
+ for (innerSym <- allInners sortBy (_.name.length)) { // TODO why not sortBy (_.name.toString()) ??
+ val flags = 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 jname = javaName(innerSym) // never null
+ val oname = outerName(innerSym) // null when method-enclosed
+ val iname = innerName(innerSym) // null for anonymous inner class
+
+ // Mimicking javap inner class output
+ debuglog(
+ if (oname == null || iname == null) "//class " + jname
+ else "//%s=class %s of class %s".format(iname, jname, oname)
+ )
+
+ assert(jname != null, "javaName is broken.") // documentation
+ val doAdd = entries.get(jname) 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 == oname, "duplicate")
+ false
+ case None => true
+ }
+
+ if(doAdd) {
+ entries += (jname -> oname)
+ jclass.visitInnerClass(jname, oname, iname, flags)
+ }
+
+ /*
+ * TODO assert (JVMS 4.7.6 The InnerClasses attribute)
+ * If a class file has a version number that is greater than or equal to 51.0, and
+ * has an InnerClasses attribute in its attributes table, then for all entries in the
+ * classes array of the InnerClasses attribute, the value of the
+ * outer_class_info_index item must be zero if the value of the
+ * inner_name_index item is zero.
+ */
+
+ }
+ }
+ }
+
+ /** Adds a @remote annotation, actual use unknown.
+ *
+ * Invoked from genMethod() and addForwarder().
+ */
+ def addRemoteExceptionAnnot(isRemoteClass: Boolean, isJMethodPublic: Boolean, meth: Symbol) {
+ val needsAnnotation = (
+ ( isRemoteClass ||
+ isRemote(meth) && isJMethodPublic
+ ) && !(meth.throwsAnnotations contains RemoteExceptionClass)
+ )
+ if (needsAnnotation) {
+ val c = Constant(RemoteExceptionClass.tpe)
+ val arg = Literal(c) setType c.tpe
+ meth.addAnnotation(ThrowsClass, arg)
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // Static forwarders (related to mirror classes but also present in
+ // a plain class lacking companion module, for details see `isCandidateForForwarders`).
+ // -----------------------------------------------------------------------------------------
+
+ val ExcludedForwarderFlags = {
+ import Flags._
+ // Should include DEFERRED but this breaks findMember.
+ ( CASE | SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | BridgeAndPrivateFlags )
+ }
+
+ /** Add a forwarder for method m. Used only from addForwarders(). */
+ private def addForwarder(isRemoteClass: Boolean, jclass: asm.ClassVisitor, module: Symbol, m: Symbol) {
+ val moduleName = javaName(module)
+ val methodInfo = module.thisType.memberInfo(m)
+ val paramJavaTypes: List[asm.Type] = methodInfo.paramTypes map javaType
+ // 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 == ThrowsClass)
+ val thrownExceptions: List[String] = getExceptions(throws)
+
+ val jReturnType = javaType(methodInfo.resultType)
+ val mdesc = asm.Type.getMethodDescriptor(jReturnType, paramJavaTypes: _*)
+ val mirrorMethodName = javaName(m)
+ val mirrorMethod: asm.MethodVisitor = jclass.visitMethod(
+ flags,
+ mirrorMethodName,
+ mdesc,
+ jgensig,
+ mkArray(thrownExceptions)
+ )
+
+ // typestate: entering mode with valid call sequences:
+ // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
+
+ emitAnnotations(mirrorMethod, others)
+ emitParamAnnotations(mirrorMethod, m.info.params.map(_.annotations))
+
+ // typestate: entering mode with valid call sequences:
+ // visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
+
+ 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.getSort() != asm.Type.METHOD, jparamType)
+ index += jparamType.getSize()
+ }
+
+ mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, javaType(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.
+ */
+ def addForwarders(isRemoteClass: Boolean, jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol) {
+ assert(moduleClass.isModuleClass, moduleClass)
+ debuglog("Dumping mirror class for object: " + moduleClass)
+
+ val linkedClass = moduleClass.companionClass
+ val linkedModule = linkedClass.companionSymbol
+ lazy val conflictingNames: Set[Name] = {
+ linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet
+ }
+ debuglog("Potentially conflicting names for forwarders: " + conflictingNames)
+
+ for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) {
+ if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor)
+ debuglog("No forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass))
+ else if (conflictingNames(m.name))
+ log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name))
+ else {
+ log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass))
+ addForwarder(isRemoteClass, jclass, moduleClass, m)
+ }
+ }
+ }
+
+ } // end of class JCommonBuilder
+
+
+ trait JAndroidBuilder {
+ self: JPlainBuilder =>
+
+ /** 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.
+ */
+ private val androidFieldName = newTermName("CREATOR")
+
+ private lazy val AndroidParcelableInterface = definitions.getClassIfDefined("android.os.Parcelable")
+ private lazy val AndroidCreatorClass = definitions.getClassIfDefined("android.os.Parcelable$Creator")
+
+ def isAndroidParcelableClass(sym: Symbol) =
+ (AndroidParcelableInterface != NoSymbol) &&
+ (sym.parentSymbols contains AndroidParcelableInterface)
+
+ /* Typestate: should be called before emitting fields (because it adds an IField to the current IClass). */
+ def addCreatorCode(block: BasicBlock) {
+ val fieldSymbol = (
+ clasz.symbol.newValue(newTermName(androidFieldName), NoPosition, Flags.STATIC | Flags.FINAL)
+ setInfo AndroidCreatorClass.tpe
+ )
+ val methodSymbol = definitions.getMember(clasz.symbol.companionModule, androidFieldName)
+ clasz addField new IField(fieldSymbol)
+ block emit CALL_METHOD(methodSymbol, Static(false))
+ block emit STORE_FIELD(fieldSymbol, true)
+ }
+
+ def legacyAddCreatorCode(clinit: asm.MethodVisitor) {
+ val creatorType: asm.Type = javaType(AndroidCreatorClass)
+ val tdesc_creator = creatorType.getDescriptor
+
+ jclass.visitField(
+ PublicStaticFinal,
+ androidFieldName,
+ tdesc_creator,
+ null, // no java-generic-signature
+ null // no initial value
+ ).visitEnd()
+
+ val moduleName = javaName(clasz.symbol)+"$"
+
+ // GETSTATIC `moduleName`.MODULE$ : `moduleName`;
+ clinit.visitFieldInsn(
+ asm.Opcodes.GETSTATIC,
+ moduleName,
+ strMODULE_INSTANCE_FIELD,
+ asm.Type.getObjectType(moduleName).getDescriptor
+ )
+
+ // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator;
+ clinit.visitMethodInsn(
+ asm.Opcodes.INVOKEVIRTUAL,
+ moduleName,
+ androidFieldName,
+ asm.Type.getMethodDescriptor(creatorType, Array.empty[asm.Type]: _*)
+ )
+
+ // PUTSTATIC `thisName`.CREATOR;
+ clinit.visitFieldInsn(
+ asm.Opcodes.PUTSTATIC,
+ thisName,
+ androidFieldName,
+ tdesc_creator
+ )
+ }
+
+ } // end of trait JAndroidBuilder
+
+ /** Map from type kinds to the Java reference types.
+ * It is used to push class literals onto the operand stack.
+ * @see Predef.classOf
+ * @see genConstant()
+ */
+ private val classLiteral = immutable.Map[TypeKind, asm.Type](
+ UNIT -> asm.Type.getObjectType("java/lang/Void"),
+ BOOL -> asm.Type.getObjectType("java/lang/Boolean"),
+ BYTE -> asm.Type.getObjectType("java/lang/Byte"),
+ SHORT -> asm.Type.getObjectType("java/lang/Short"),
+ CHAR -> asm.Type.getObjectType("java/lang/Character"),
+ INT -> asm.Type.getObjectType("java/lang/Integer"),
+ LONG -> asm.Type.getObjectType("java/lang/Long"),
+ FLOAT -> asm.Type.getObjectType("java/lang/Float"),
+ DOUBLE -> asm.Type.getObjectType("java/lang/Double")
+ )
+
+ def isNonUnitValueTK(tk: TypeKind): Boolean = { tk.isValueType && tk != UNIT }
+
+ case class MethodNameAndType(mname: String, mdesc: String)
+
+ private val jBoxTo: Map[TypeKind, 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;" )
+ )
+ }
+
+ private val jUnboxTo: Map[TypeKind, 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")
+ )
+ }
+
+ case class BlockInteval(start: BasicBlock, end: BasicBlock)
+
+ /** builder of plain classes */
+ class JPlainBuilder(bytecodeWriter: BytecodeWriter)
+ extends JCommonBuilder(bytecodeWriter)
+ with JAndroidBuilder {
+
+ val MIN_SWITCH_DENSITY = 0.7
+
+ val StringBuilderClassName = javaName(definitions.StringBuilderClass)
+ val BoxesRunTime = "scala/runtime/BoxesRunTime"
+
+ val StringBuilderType = asm.Type.getObjectType(StringBuilderClassName)
+ val mdesc_toString = "()Ljava/lang/String;"
+ val mdesc_arrayClone = "()Ljava/lang/Object;"
+
+ val tdesc_long = asm.Type.LONG_TYPE.getDescriptor // ie. "J"
+
+ def isParcelableClass = isAndroidParcelableClass(clasz.symbol)
+
+ def serialVUID: Option[Long] = clasz.symbol getAnnotation SerialVersionUIDAttr collect {
+ case AnnotationInfo(_, Literal(const) :: _, _) => const.longValue
+ }
+
+ private def getSuperInterfaces(c: IClass): Array[String] = {
+
+ // Additional interface parents based on annotations and other cues
+ def newParentForAttr(attr: Symbol): Option[Symbol] = attr match {
+ case SerializableAttr => Some(SerializableClass)
+ case CloneableAttr => Some(JavaCloneableClass)
+ case RemoteAttr => Some(RemoteInterfaceClass)
+ case _ => None
+ }
+
+ /** Drop redundant interfaces (ones which are implemented by some other parent) from the immediate parents.
+ * This is important on Android because there is otherwise an interface explosion.
+ */
+ 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 ps = c.symbol.info.parents
+ val superInterfaces0: List[Symbol] = if(ps.isEmpty) Nil else c.symbol.mixinClasses;
+ val superInterfaces = superInterfaces0 ++ c.symbol.annotations.flatMap(ann => newParentForAttr(ann.symbol)) distinct
+
+ if(superInterfaces.isEmpty) EMPTY_STRING_ARRAY
+ else mkArray(minimizeInterfaces(superInterfaces) map javaName)
+ }
+
+ var clasz: IClass = _ // this var must be assigned only by genClass()
+ var jclass: asm.ClassWriter = _ // the classfile being emitted
+ var thisName: String = _ // the internal name of jclass
+
+ def thisDescr: String = {
+ assert(thisName != null, "thisDescr invoked too soon.")
+ asm.Type.getObjectType(thisName).getDescriptor
+ }
+
+ def getCurrentCUnit(): CompilationUnit = { clasz.cunit }
+
+ def genClass(c: IClass) {
+ clasz = c
+ innerClassBuffer.clear()
+
+ thisName = javaName(c.symbol) // the internal name of the class being emitted
+
+ val ps = c.symbol.info.parents
+ val superClass: String = if(ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else javaName(ps.head.typeSymbol);
+
+ val ifaces = getSuperInterfaces(c)
+
+ val thisSignature = getGenericSignature(c.symbol, c.symbol.owner)
+ val flags = mkFlags(
+ javaFlags(c.symbol),
+ if(isDeprecated(c.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
+ )
+ jclass = createJClass(flags,
+ thisName, thisSignature,
+ superClass, ifaces)
+
+ // typestate: entering mode with valid call sequences:
+ // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
+
+ jclass.visitSource(c.cunit.source.toString,
+ null /* SourceDebugExtension */)
+
+ val enclM = getEnclosingMethodAttribute()
+ if(enclM != null) {
+ val EnclMethodEntry(className, methodName, methodType) = enclM
+ jclass.visitOuterClass(className, methodName, methodType.getDescriptor)
+ }
+
+ // typestate: entering mode with valid call sequences:
+ // ( visitAnnotation | visitAttribute )*
+
+ val ssa = getAnnotPickle(thisName, c.symbol)
+ jclass.visitAttribute(if(ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
+ emitAnnotations(jclass, c.symbol.annotations ++ ssa)
+
+ // typestate: entering mode with valid call sequences:
+ // ( visitInnerClass | visitField | visitMethod )* visitEnd
+
+ if (isStaticModule(c.symbol) || serialVUID != None || isParcelableClass) {
+
+ if (isStaticModule(c.symbol)) { addModuleInstanceField() }
+ addStaticInit(c.lookupStaticCtor)
+
+ } else {
+
+ for (constructor <- c.lookupStaticCtor) {
+ addStaticInit(Some(constructor))
+ }
+ val skipStaticForwarders = (c.symbol.isInterface || settings.noForwarders.value)
+ if (!skipStaticForwarders) {
+ val lmoc = c.symbol.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 = {
+ afterPickler { !(lmoc.name.toString contains '$') && lmoc.hasModuleFlag && !lmoc.isImplClass && !lmoc.isNestedClass }
+ }
+ if (isCandidateForForwarders) {
+ log("Adding static forwarders from '%s' to implementations in '%s'".format(c.symbol, lmoc))
+ addForwarders(isRemote(clasz.symbol), jclass, thisName, lmoc.moduleClass)
+ }
+ }
+ }
+
+ }
+
+ clasz.fields foreach genField
+ clasz.methods foreach { im => genMethod(im, c.symbol.isInterface) }
+
+ addInnerClasses(clasz.symbol, jclass)
+ jclass.visitEnd()
+ writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol)
+
+ }
+
+ /**
+ * @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: asm.Type)
+
+ /**
+ * @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.
+ *
+ */
+ private def getEnclosingMethodAttribute(): EnclMethodEntry = { // JVMS 4.7.7
+ var res: EnclMethodEntry = null
+ val clazz = clasz.symbol
+ val sym = clazz.originalEnclosingMethod
+ if (sym.isMethod) {
+ debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, sym.enclClass))
+ res = EnclMethodEntry(javaName(sym.enclClass), javaName(sym), javaType(sym))
+ } else if (clazz.isAnonymousClass) {
+ val enclClass = clazz.rawowner
+ assert(enclClass.isClass, enclClass)
+ val sym = enclClass.primaryConstructor
+ if (sym == NoSymbol) {
+ log("Ran out of room looking for an enclosing method for %s: no constructor here.".format(enclClass, clazz))
+ } else {
+ debuglog("enclosing method for %s is %s (in %s)".format(clazz, sym, enclClass))
+ res = EnclMethodEntry(javaName(enclClass), javaName(sym), javaType(sym))
+ }
+ }
+
+ res
+ }
+
+ def genField(f: IField) {
+ debuglog("Adding field: " + f.symbol.fullName)
+
+ val javagensig = getGenericSignature(f.symbol, clasz.symbol)
+
+ val flags = mkFlags(
+ javaFieldFlags(f.symbol),
+ if(isDeprecated(f.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
+ )
+
+ val jfield: asm.FieldVisitor = jclass.visitField(
+ flags,
+ javaName(f.symbol),
+ javaType(f.symbol.tpe).getDescriptor(),
+ javagensig,
+ null // no initial value
+ )
+
+ emitAnnotations(jfield, f.symbol.annotations)
+ jfield.visitEnd()
+ }
+
+ def debugLevel = settings.debuginfo.indexOfChoice
+
+ // val emitSource = debugLevel >= 1
+ val emitLines = debugLevel >= 2
+ val emitVars = debugLevel >= 3
+
+ var method: IMethod = _
+ var jmethod: asm.MethodVisitor = _
+ var jMethodName: String = _
+
+ @inline final def emit(opc: Int) { jmethod.visitInsn(opc) }
+
+ def genMethod(m: IMethod, isJInterface: Boolean) {
+
+ def isClosureApply(sym: Symbol): Boolean = {
+ (sym.name == nme.apply) &&
+ sym.owner.isSynthetic &&
+ sym.owner.tpe.parents.exists { t =>
+ val TypeRef(_, sym, _) = t
+ FunctionClass contains sym
+ }
+ }
+
+ if (m.symbol.isStaticConstructor || definitions.isGetClass(m.symbol)) return
+
+ debuglog("Generating method " + m.symbol.fullName)
+ method = m
+ computeLocalVarsIndex(m)
+
+ var resTpe: asm.Type = javaType(m.symbol.tpe.resultType)
+ if (m.symbol.isClassConstructor)
+ resTpe = asm.Type.VOID_TYPE
+
+ val flags = mkFlags(
+ javaFlags(m.symbol),
+ if (isJInterface) asm.Opcodes.ACC_ABSTRACT else 0,
+ if (m.symbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0,
+ if (method.native) asm.Opcodes.ACC_NATIVE else 0, // native methods of objects are generated in mirror classes
+ if(isDeprecated(m.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
+ )
+
+ // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
+ val jgensig = getGenericSignature(m.symbol, clasz.symbol)
+ addRemoteExceptionAnnot(isRemote(clasz.symbol), hasPublicBitSet(flags), m.symbol)
+ val (excs, others) = m.symbol.annotations partition (_.symbol == ThrowsClass)
+ val thrownExceptions: List[String] = getExceptions(excs)
+
+ jMethodName = javaName(m.symbol)
+ val mdesc = asm.Type.getMethodDescriptor(resTpe, (m.params map (p => javaType(p.kind))): _*)
+ jmethod = jclass.visitMethod(
+ flags,
+ jMethodName,
+ mdesc,
+ jgensig,
+ mkArray(thrownExceptions)
+ )
+
+ // TODO param names: (m.params map (p => javaName(p.sym)))
+
+ // typestate: entering mode with valid call sequences:
+ // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
+
+ emitAnnotations(jmethod, others)
+ emitParamAnnotations(jmethod, m.params.map(_.sym.annotations))
+
+ // typestate: entering mode with valid call sequences:
+ // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
+ // In addition, the visitXInsn and visitLabel methods must be called in the sequential order of the bytecode instructions of the visited code,
+ // visitTryCatchBlock must be called before the labels passed as arguments have been visited, and
+ // the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited.
+
+ val hasAbstractBitSet = ((flags & asm.Opcodes.ACC_ABSTRACT) != 0)
+ val hasCodeAttribute = (!hasAbstractBitSet && !method.native)
+ if (hasCodeAttribute) {
+
+ jmethod.visitCode()
+
+ if (emitVars && isClosureApply(method.symbol)) {
+ // add a fake local for debugging purposes
+ val outerField = clasz.symbol.info.decl(nme.OUTER_LOCAL)
+ if (outerField != NoSymbol) {
+ log("Adding fake local to represent outer 'this' for closure " + clasz)
+ val _this =
+ new Local(method.symbol.newVariable(nme.FAKE_LOCAL_THIS),
+ toTypeKind(outerField.tpe),
+ false)
+ m.locals = m.locals ::: List(_this)
+ computeLocalVarsIndex(m) // since we added a new local, we need to recompute indexes
+ jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
+ jmethod.visitFieldInsn(asm.Opcodes.GETFIELD,
+ javaName(clasz.symbol), // field owner
+ javaName(outerField), // field name
+ descriptor(outerField) // field descriptor
+ )
+ assert(_this.kind.isReferenceType, _this.kind)
+ jmethod.visitVarInsn(asm.Opcodes.ASTORE, indexOf(_this))
+ }
+ }
+
+ assert( m.locals forall { local => (m.params contains local) == local.arg }, m.locals )
+
+ val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
+ genCode(m, emitVars, hasStaticBitSet)
+
+ jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
+ }
+
+ jmethod.visitEnd()
+
+ }
+
+ def addModuleInstanceField() {
+ val fv =
+ jclass.visitField(PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ strMODULE_INSTANCE_FIELD,
+ thisDescr,
+ null, // no java-generic-signature
+ null // no initial value
+ )
+
+ // typestate: entering mode with valid call sequences:
+ // ( visitAnnotation | visitAttribute )* visitEnd.
+
+ fv.visitEnd()
+ }
+
+
+ /* Typestate: should be called before being done with emitting fields (because it invokes addCreatorCode() which adds an IField to the current IClass). */
+ def addStaticInit(mopt: Option[IMethod]) {
+
+ val clinitMethod: asm.MethodVisitor = jclass.visitMethod(
+ PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
+ CLASS_CONSTRUCTOR_NAME,
+ mdesc_arglessvoid,
+ null, // no java-generic-signature
+ null // no throwable exceptions
+ )
+
+ mopt match {
+
+ case Some(m) =>
+
+ val oldLastBlock = m.lastBlock
+ val lastBlock = m.newBlock()
+ oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock))
+
+ if (isStaticModule(clasz.symbol)) {
+ // call object's private ctor from static ctor
+ lastBlock emit NEW(REFERENCE(m.symbol.enclClass))
+ lastBlock emit CALL_METHOD(m.symbol.enclClass.primaryConstructor, Static(true))
+ }
+
+ // add serialVUID code
+ serialVUID foreach { value =>
+ val fieldName = "serialVersionUID"
+ val fieldSymbol = clasz.symbol.newValue(newTermName(fieldName), NoPosition, Flags.STATIC | Flags.FINAL) setInfo LongClass.tpe
+ clasz addField new IField(fieldSymbol)
+ lastBlock emit CONSTANT(Constant(value))
+ lastBlock emit STORE_FIELD(fieldSymbol, true)
+ }
+
+ if (isParcelableClass) { addCreatorCode(lastBlock) }
+
+ lastBlock emit RETURN(UNIT)
+ lastBlock.close
+
+ method = m
+ jmethod = clinitMethod
+ jMethodName = CLASS_CONSTRUCTOR_NAME
+ jmethod.visitCode()
+ genCode(m, false, true)
+ jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
+ jmethod.visitEnd()
+
+ case None =>
+ clinitMethod.visitCode()
+ legacyStaticInitializer(clinitMethod)
+ clinitMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
+ clinitMethod.visitEnd()
+
+ }
+ }
+
+ /* used only from addStaticInit() */
+ private def legacyStaticInitializer(clinit: asm.MethodVisitor) {
+ if (isStaticModule(clasz.symbol)) {
+ clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
+ clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
+ thisName, INSTANCE_CONSTRUCTOR_NAME, mdesc_arglessvoid)
+ }
+
+ serialVUID foreach { value =>
+ val fieldName = "serialVersionUID"
+ jclass.visitField(
+ PublicStaticFinal,
+ fieldName,
+ tdesc_long,
+ null, // no java-generic-signature
+ value // TODO confirm whether initial value here is behaviorally equiv to fjbg's emitPUSH emitPUTSTATIC
+ ).visitEnd()
+ }
+
+ if (isParcelableClass) { legacyAddCreatorCode(clinit) }
+
+ clinit.visitInsn(asm.Opcodes.RETURN)
+ }
+
+ // -----------------------------------------------------------------------------------------
+ // Emitting bytecode instructions.
+ // -----------------------------------------------------------------------------------------
+
+ private def genConstant(mv: asm.MethodVisitor, const: Constant) {
+ const.tag match {
+
+ case BooleanTag => jcode.boolconst(const.booleanValue)
+
+ case ByteTag => jcode.iconst(const.byteValue)
+ case ShortTag => jcode.iconst(const.shortValue)
+ case CharTag => jcode.iconst(const.charValue)
+ case IntTag => jcode.iconst(const.intValue)
+
+ case LongTag => jcode.lconst(const.longValue)
+ case FloatTag => jcode.fconst(const.floatValue)
+ case DoubleTag => jcode.dconst(const.doubleValue)
+
+ case UnitTag => ()
+
+ case StringTag =>
+ assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
+ mv.visitLdcInsn(const.stringValue) // `stringValue` special-cases null, but not for a const with StringTag
+
+ case NullTag => mv.visitInsn(asm.Opcodes.ACONST_NULL)
+
+ case ClazzTag =>
+ val kind = toTypeKind(const.typeValue)
+ val toPush: asm.Type =
+ if (kind.isValueType) classLiteral(kind)
+ else javaType(kind);
+ mv.visitLdcInsn(toPush)
+
+ case EnumTag =>
+ val sym = const.symbolValue
+ mv.visitFieldInsn(
+ asm.Opcodes.GETSTATIC,
+ javaName(sym.owner),
+ javaName(sym),
+ javaType(sym.tpe.underlying).getDescriptor()
+ )
+
+ case _ => abort("Unknown constant value: " + const)
+ }
+ }
+
+ /** 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).
+ */
+ object jcode {
+
+ import asm.Opcodes;
+
+ def aconst(cst: AnyRef) {
+ if (cst == null) { jmethod.visitInsn(Opcodes.ACONST_NULL) }
+ else { jmethod.visitLdcInsn(cst) }
+ }
+
+ @inline final def boolconst(b: Boolean) { iconst(if(b) 1 else 0) }
+
+ def iconst(cst: Int) {
+ if (cst >= -1 && cst <= 5) {
+ jmethod.visitInsn(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))
+ }
+ }
+
+ def lconst(cst: Long) {
+ if (cst == 0L || cst == 1L) {
+ jmethod.visitInsn(Opcodes.LCONST_0 + cst.asInstanceOf[Int])
+ } else {
+ jmethod.visitLdcInsn(new java.lang.Long(cst))
+ }
+ }
+
+ def fconst(cst: Float) {
+ val bits: Int = java.lang.Float.floatToIntBits(cst)
+ if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2
+ jmethod.visitInsn(Opcodes.FCONST_0 + cst.asInstanceOf[Int])
+ } else {
+ jmethod.visitLdcInsn(new java.lang.Float(cst))
+ }
+ }
+
+ def dconst(cst: Double) {
+ val bits: Long = java.lang.Double.doubleToLongBits(cst)
+ if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d
+ jmethod.visitInsn(Opcodes.DCONST_0 + cst.asInstanceOf[Int])
+ } else {
+ jmethod.visitLdcInsn(new java.lang.Double(cst))
+ }
+ }
+
+ def newarray(elem: TypeKind) {
+ if(elem.isRefOrArrayType) {
+ jmethod.visitTypeInsn(Opcodes.ANEWARRAY, javaType(elem).getInternalName)
+ } else {
+ val rand = {
+ if(elem.isIntSizedType) {
+ (elem: @unchecked) match {
+ case BOOL => Opcodes.T_BOOLEAN
+ case BYTE => Opcodes.T_BYTE
+ case SHORT => Opcodes.T_SHORT
+ case CHAR => Opcodes.T_CHAR
+ case INT => Opcodes.T_INT
+ }
+ } else {
+ (elem: @unchecked) match {
+ case LONG => Opcodes.T_LONG
+ case FLOAT => Opcodes.T_FLOAT
+ case DOUBLE => Opcodes.T_DOUBLE
+ }
+ }
+ }
+ jmethod.visitIntInsn(Opcodes.NEWARRAY, rand)
+ }
+ }
+
+
+ @inline def load( idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ILOAD, idx, tk) }
+ @inline def store(idx: Int, tk: TypeKind) { emitVarInsn(Opcodes.ISTORE, idx, tk) }
+
+ @inline def aload( tk: TypeKind) { emitTypeBased(aloadOpcodes, tk) }
+ @inline def astore(tk: TypeKind) { emitTypeBased(astoreOpcodes, tk) }
+
+ @inline def neg(tk: TypeKind) { emitPrimitive(negOpcodes, tk) }
+ @inline def add(tk: TypeKind) { emitPrimitive(addOpcodes, tk) }
+ @inline def sub(tk: TypeKind) { emitPrimitive(subOpcodes, tk) }
+ @inline def mul(tk: TypeKind) { emitPrimitive(mulOpcodes, tk) }
+ @inline def div(tk: TypeKind) { emitPrimitive(divOpcodes, tk) }
+ @inline def rem(tk: TypeKind) { emitPrimitive(remOpcodes, tk) }
+
+ @inline def invokespecial(owner: String, name: String, desc: String) {
+ jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc)
+ }
+ @inline def invokestatic(owner: String, name: String, desc: String) {
+ jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc)
+ }
+ @inline def invokeinterface(owner: String, name: String, desc: String) {
+ jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc)
+ }
+ @inline def invokevirtual(owner: String, name: String, desc: String) {
+ jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc)
+ }
+
+ @inline def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
+ @inline def emitIF(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIF, label) }
+ @inline def emitIF_ICMP(cond: TestOp, label: asm.Label) { jmethod.visitJumpInsn(cond.opcodeIFICMP, label) }
+ @inline def emitIF_ACMP(cond: TestOp, label: asm.Label) {
+ assert((cond == EQ) || (cond == NE), cond)
+ val opc = (if(cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE)
+ jmethod.visitJumpInsn(opc, label)
+ }
+ @inline def emitIFNONNULL(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNONNULL, label) }
+ @inline def emitIFNULL (label: asm.Label) { jmethod.visitJumpInsn(Opcodes.IFNULL, label) }
+
+ @inline def emitRETURN(tk: TypeKind) {
+ if(tk == UNIT) { jmethod.visitInsn(Opcodes.RETURN) }
+ else { emitTypeBased(returnOpcodes, tk) }
+ }
+
+ /** Emits one of tableswitch or lookoupswitch. */
+ 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
+ }
+
+ 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
+
+ def emitVarInsn(opc: Int, idx: Int, tk: TypeKind) {
+ assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc)
+ jmethod.visitVarInsn(javaType(tk).getOpcode(opc), idx)
+ }
+
+ // ---------------- array load and store ----------------
+
+ val aloadOpcodes = { import Opcodes._; Array(AALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD) }
+ val astoreOpcodes = { import Opcodes._; Array(AASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE) }
+
+ val returnOpcodes = { import Opcodes._; Array(ARETURN, IRETURN, IRETURN, IRETURN, IRETURN, LRETURN, FRETURN, DRETURN) }
+
+ def emitTypeBased(opcs: Array[Int], tk: TypeKind) {
+ 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)
+ }
+ }
+ }
+ jmethod.visitInsn(opc)
+ }
+
+ // ---------------- primitive operations ----------------
+
+ val negOpcodes: Array[Int] = { import Opcodes._; Array(INEG, LNEG, FNEG, DNEG) }
+ val addOpcodes: Array[Int] = { import Opcodes._; Array(IADD, LADD, FADD, DADD) }
+ val subOpcodes: Array[Int] = { import Opcodes._; Array(ISUB, LSUB, FSUB, DSUB) }
+ val mulOpcodes: Array[Int] = { import Opcodes._; Array(IMUL, LMUL, FMUL, DMUL) }
+ val divOpcodes: Array[Int] = { import Opcodes._; Array(IDIV, LDIV, FDIV, DDIV) }
+ val remOpcodes: Array[Int] = { import Opcodes._; Array(IREM, LREM, FREM, DREM) }
+
+ def emitPrimitive(opcs: Array[Int], tk: TypeKind) {
+ val opc = {
+ if(tk.isIntSizedType) { opcs(0) }
+ else {
+ (tk: @unchecked) match {
+ case LONG => opcs(1)
+ case FLOAT => opcs(2)
+ case DOUBLE => opcs(3)
+ }
+ }
+ }
+ jmethod.visitInsn(opc)
+ }
+
+ }
+
+ /** Invoked from genMethod() and addStaticInit() */
+ def genCode(m: IMethod,
+ emitVars: Boolean, // this param name hides the instance-level var
+ isStatic: Boolean) {
+
+
+ newNormal.normalize(m)
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 1 of genCode(): setting up one-to-one correspondence between ASM Labels and BasicBlocks `linearization`
+ // ------------------------------------------------------------------------------------------------------------
+
+ val linearization: List[BasicBlock] = linearizer.linearize(m)
+ if(linearization.isEmpty) { return }
+
+ var isModuleInitialized = false
+
+ val labels: collection.Map[BasicBlock, asm.Label] = mutable.HashMap(linearization map (_ -> new asm.Label()) : _*)
+
+ val onePastLast = new asm.Label // token for the mythical instruction past the last instruction in the method being emitted
+
+ // maps a BasicBlock b to the Label that corresponds to b's successor in the linearization. The last BasicBlock is mapped to the onePastLast label.
+ val linNext: collection.Map[BasicBlock, asm.Label] = {
+ val result = mutable.HashMap.empty[BasicBlock, asm.Label]
+ var rest = linearization
+ var prev = rest.head
+ rest = rest.tail
+ while(!rest.isEmpty) {
+ result += (prev -> labels(rest.head))
+ prev = rest.head
+ rest = rest.tail
+ }
+ assert(!result.contains(prev))
+ result += (prev -> onePastLast)
+
+ result
+ }
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 2 of genCode(): demarcating exception handler boundaries (visitTryCatchBlock() must be invoked before visitLabel() in genBlock())
+ // ------------------------------------------------------------------------------------------------------------
+
+ /**Generate exception handlers for the current method.
+ *
+ * Quoting from the JVMS 4.7.3 The Code Attribute
+ * The items of the Code_attribute structure are as follows:
+ * . . .
+ * exception_table[]
+ * Each entry in the exception_table array describes one
+ * exception handler in the code array. The order of the handlers in
+ * the exception_table array is significant.
+ * Each exception_table entry contains the following four items:
+ * start_pc, end_pc:
+ * ... The value of end_pc either must be a valid index into
+ * the code array of the opcode of an instruction or must be equal to code_length,
+ * the length of the code array.
+ * handler_pc:
+ * The value of the handler_pc item indicates the start of the exception handler
+ * catch_type:
+ * ... If the value of the catch_type item is zero,
+ * this exception handler is called for all exceptions.
+ * This is used to implement finally
+ */
+ def genExceptionHandlers() {
+
+ /** Return a list of pairs of intervals where the handler is active.
+ * Each interval is closed on both ends, ie. inclusive both in the left and right endpoints: [start, end].
+ * Preconditions:
+ * - e.covered non-empty
+ * Postconditions for the result:
+ * - always non-empty
+ * - intervals are sorted as per `linearization`
+ * - the argument's `covered` blocks have been grouped into maximally contiguous intervals,
+ * ie. between any two intervals in the result there is a non-empty gap.
+ * - each of the `covered` blocks in the argument is contained in some interval in the result
+ */
+ def intervals(e: ExceptionHandler): List[BlockInteval] = {
+ assert(e.covered.nonEmpty, e)
+ var result: List[BlockInteval] = Nil
+ var rest = linearization
+
+ // find intervals
+ while(!rest.isEmpty) {
+ // find interval start
+ var start: BasicBlock = null
+ while(!rest.isEmpty && (start eq null)) {
+ if(e.covered(rest.head)) { start = rest.head }
+ rest = rest.tail
+ }
+ if(start ne null) {
+ // find interval end
+ var end = start // for the time being
+ while(!rest.isEmpty && (e.covered(rest.head))) {
+ end = rest.head
+ rest = rest.tail
+ }
+ result = BlockInteval(start, end) :: result
+ }
+ }
+
+ assert(result.nonEmpty, e)
+
+ result
+ }
+
+ /* TODO test/files/run/exceptions-2.scala displays an ExceptionHandler.covered that contains
+ * blocks not in the linearization (dead-code?). Is that well-formed or not?
+ * For now, we ignore those blocks (after all, that's what `genBlocks(linearization)` in effect does).
+ */
+ for (e <- this.method.exh) {
+ val ignore: Set[BasicBlock] = (e.covered filterNot { b => linearization contains b } )
+ // TODO someday assert(ignore.isEmpty, "an ExceptionHandler.covered contains blocks not in the linearization (dead-code?)")
+ if(ignore.nonEmpty) {
+ e.covered = e.covered filterNot ignore
+ }
+ }
+
+ // an ExceptionHandler lacking covered blocks doesn't get an entry in the Exceptions table.
+ // TODO in that case, ExceptionHandler.cls doesn't go through javaName(). What if cls is an inner class?
+ for (e <- this.method.exh ; if e.covered.nonEmpty ; p <- intervals(e)) {
+ debuglog("Adding exception handler " + e + "at block: " + e.startBlock + " for " + method +
+ " from: " + p.start + " to: " + p.end + " catching: " + e.cls);
+ val cls: String = if (e.cls == NoSymbol || e.cls == ThrowableClass) null
+ else javaName(e.cls)
+ jmethod.visitTryCatchBlock(labels(p.start), linNext(p.end), labels(e.startBlock), cls)
+ }
+ } // end of genCode()'s genExceptionHandlers()
+
+ if (m.exh.nonEmpty) { genExceptionHandlers() }
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 3 of genCode(): "Infrastructure" to later emit debug info for local variables and method params (LocalVariablesTable bytecode attribute).
+ // ------------------------------------------------------------------------------------------------------------
+
+ case class LocVarEntry(local: Local, start: asm.Label, end: asm.Label) // start is inclusive while end exclusive.
+
+ case class Interval(lstart: asm.Label, lend: asm.Label) {
+ @inline final def start = lstart.getOffset
+ @inline final def end = lend.getOffset
+
+ def precedes(that: Interval): Boolean = { this.end < that.start }
+
+ def overlaps(that: Interval): Boolean = { !(this.precedes(that) || that.precedes(this)) }
+
+ def mergeWith(that: Interval): Interval = {
+ val newStart = if(this.start <= that.start) this.lstart else that.lstart;
+ val newEnd = if(this.end <= that.end) that.lend else this.lend;
+ Interval(newStart, newEnd)
+ }
+
+ def repOK: Boolean = { start <= end }
+
+ }
+
+ /** Track those instruction ranges where certain locals are in scope. Used to later emit the LocalVariableTable attribute (JVMS 4.7.13) */
+ object scoping {
+
+ private val pending = mutable.Map.empty[Local, mutable.Stack[Label]]
+ private var seen: List[LocVarEntry] = Nil
+
+ private def fuse(ranges: List[Interval], added: Interval): List[Interval] = {
+ assert(added.repOK, added)
+ if(ranges.isEmpty) { return List(added) }
+ // precond: ranges is sorted by increasing start
+ var fused: List[Interval] = Nil
+ var done = false
+ var rest = ranges
+ while(!done && rest.nonEmpty) {
+ val current = rest.head
+ assert(current.repOK, current)
+ rest = rest.tail
+ if(added precedes current) {
+ fused = fused ::: ( added :: current :: rest )
+ done = true
+ } else if(current overlaps added) {
+ fused = fused ::: ( added.mergeWith(current) :: rest )
+ done = true
+ }
+ }
+ if(!done) { fused = fused ::: List(added) }
+ assert(repOK(fused), fused)
+
+ fused
+ }
+
+ def pushScope(lv: Local, start: Label) {
+ val st = pending.getOrElseUpdate(lv, mutable.Stack.empty[Label])
+ st.push(start)
+ }
+ def popScope(lv: Local, end: Label) {
+ val start = pending(lv).pop()
+ seen ::= LocVarEntry(lv, start, end)
+ }
+
+ def getMerged(): collection.Map[Local, List[Interval]] = {
+ // TODO should but isn't: unbalanced start(s) of scope(s)
+ val shouldBeEmpty = pending filter { p => val Pair(k, st) = p; st.nonEmpty };
+
+ val merged = mutable.Map.empty[Local, List[Interval]]
+
+ def addToMerged(lv: Local, start: Label, end: Label) {
+ val ranges = merged.getOrElseUpdate(lv, Nil)
+ val coalesced = fuse(ranges, Interval(start, end))
+ merged.update(lv, coalesced)
+ }
+
+ for(LocVarEntry(lv, start, end) <- seen) { addToMerged(lv, start, end) }
+
+ /* for each var with unbalanced start(s) of scope(s):
+ (a) take the earliest start (among unbalanced and balanced starts)
+ (b) take the latest end (onePastLast if none available)
+ (c) merge the thus made-up interval
+ */
+ for(Pair(k, st) <- shouldBeEmpty) {
+ var start = st.toList.sortBy(_.getOffset).head
+ if(merged.isDefinedAt(k)) {
+ val balancedStart = merged(k).head.lstart
+ if(balancedStart.getOffset < start.getOffset) {
+ start = balancedStart;
+ }
+ }
+ val endOpt: Option[Label] = for(ranges <- merged.get(k)) yield ranges.last.lend;
+ val end = endOpt.getOrElse(onePastLast)
+ addToMerged(k, start, end)
+ }
+
+ merged
+ }
+
+ private def repOK(fused: List[Interval]): Boolean = {
+ fused match {
+ case Nil => true
+ case h :: Nil => h.repOK
+ case h :: n :: rest =>
+ h.repOK && h.precedes(n) && !h.overlaps(n) && repOK(n :: rest)
+ }
+ }
+
+ }
+
+ def genLocalVariableTable() {
+ // adding `this` and method params.
+ if (!isStatic) {
+ jmethod.visitLocalVariable("this", thisDescr, null, labels(m.startBlock), onePastLast, 0)
+ }
+ for(lv <- m.params) {
+ jmethod.visitLocalVariable(javaName(lv.sym), descriptor(lv.kind), null, labels(m.startBlock), onePastLast, indexOf(lv))
+ }
+ // adding non-param locals
+ var anonCounter = 0
+ var fltnd: List[Triple[String, Local, Interval]] = Nil
+ for(Pair(local, ranges) <- scoping.getMerged()) {
+ var name = javaName(local.sym)
+ if (name == null) {
+ anonCounter += 1;
+ name = "<anon" + anonCounter + ">"
+ }
+ for(intrvl <- ranges) {
+ fltnd ::= Triple(name, local, intrvl)
+ }
+ }
+ // quest for deterministic output that Map.toList doesn't provide (so that ant test.stability doesn't complain).
+ val srtd = fltnd.sortBy { kr =>
+ val Triple(name: String, local: Local, intrvl: Interval) = kr
+
+ Triple(intrvl.start, intrvl.end - intrvl.start, name) // ie sort by (start, length, name)
+ }
+
+ for(Triple(name, local, Interval(start, end)) <- srtd) {
+ jmethod.visitLocalVariable(name, descriptor(local.kind), null, start, end, indexOf(local))
+ }
+ // "There may be no more than one LocalVariableTable attribute per local variable in the Code attribute"
+ }
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 4 of genCode(): Bookkeeping (to later emit debug info) of association between line-number and instruction position.
+ // ------------------------------------------------------------------------------------------------------------
+
+ case class LineNumberEntry(line: Int, start: asm.Label)
+ var lastLineNr: Int = -1
+ var lnEntries: List[LineNumberEntry] = Nil
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 5 of genCode(): "Utilities" to emit code proper (most prominently: genBlock()).
+ // ------------------------------------------------------------------------------------------------------------
+
+ var nextBlock: BasicBlock = linearization.head
+
+ def genBlocks(l: List[BasicBlock]): Unit = l match {
+ case Nil => ()
+ case x :: Nil => nextBlock = null; genBlock(x)
+ case x :: y :: ys => nextBlock = y; genBlock(x); genBlocks(y :: ys)
+ }
+
+ def isAccessibleFrom(target: Symbol, site: Symbol): Boolean = {
+ target.isPublic || target.isProtected && {
+ (site.enclClass isSubClass target.enclClass) ||
+ (site.enclosingPackage == target.privateWithin)
+ }
+ } // end of genCode()'s isAccessibleFrom()
+
+ def genCallMethod(call: CALL_METHOD) {
+ val CALL_METHOD(method, style) = call
+ val siteSymbol = clasz.symbol
+ val hostSymbol = call.hostClass
+ val methodOwner = method.owner
+ // info calls so that types are up to date; erasure may add lateINTERFACE to traits
+ hostSymbol.info ; methodOwner.info
+
+ def isInterfaceCall(sym: Symbol) = (
+ sym.isInterface && methodOwner != ObjectClass
+ || sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass)
+ )
+ // whether to reference the type of the receiver or
+ // the type of the method owner (if not an interface!)
+ val useMethodOwner = (
+ style != Dynamic
+ || !isInterfaceCall(hostSymbol) && isAccessibleFrom(methodOwner, siteSymbol)
+ || hostSymbol.isBottomClass
+ )
+ val receiver = if (useMethodOwner) methodOwner else hostSymbol
+ val jowner = javaName(receiver)
+ val jname = javaName(method)
+ val jtype = javaType(method).getDescriptor()
+
+ def dbg(invoke: String) {
+ debuglog("%s %s %s.%s:%s".format(invoke, receiver.accessString, jowner, jname, jtype))
+ }
+
+ def initModule() {
+ // we initialize the MODULE$ field immediately after the super ctor
+ if (isStaticModule(siteSymbol) && !isModuleInitialized &&
+ jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
+ jname == INSTANCE_CONSTRUCTOR_NAME) {
+ isModuleInitialized = true
+ jmethod.visitVarInsn(asm.Opcodes.ALOAD, 0)
+ jmethod.visitFieldInsn(asm.Opcodes.PUTSTATIC, thisName, strMODULE_INSTANCE_FIELD, thisDescr)
+ }
+ }
+
+ style match {
+ case Static(true) => dbg("invokespecial"); jcode.invokespecial (jowner, jname, jtype)
+ case Static(false) => dbg("invokestatic"); jcode.invokestatic (jowner, jname, jtype)
+ case Dynamic if isInterfaceCall(receiver) => dbg("invokinterface"); jcode.invokeinterface(jowner, jname, jtype)
+ case Dynamic => dbg("invokevirtual"); jcode.invokevirtual (jowner, jname, jtype)
+ case SuperCall(_) =>
+ dbg("invokespecial")
+ jcode.invokespecial(jowner, jname, jtype)
+ initModule()
+ }
+ } // end of genCode()'s genCallMethod()
+
+ def genBlock(b: BasicBlock) {
+ jmethod.visitLabel(labels(b))
+
+ import asm.Opcodes;
+
+ debuglog("Generating code for block: " + b)
+
+ // val lastInstr = b.lastInstruction
+
+ for (instr <- b) {
+
+ if(instr.pos.isDefined) {
+ val iPos = instr.pos
+ val currentLineNr = iPos.line
+ val skip = (currentLineNr == lastLineNr) // if(iPos.isRange) iPos.sameRange(lastPos) else
+ if(!skip) {
+ lastLineNr = currentLineNr
+ val lineLab = new asm.Label
+ jmethod.visitLabel(lineLab)
+ lnEntries ::= LineNumberEntry(currentLineNr, lineLab)
+ }
+ }
+
+ instr match {
+ case THIS(_) => jmethod.visitVarInsn(Opcodes.ALOAD, 0)
+
+ case CONSTANT(const) => genConstant(jmethod, const)
+
+ case LOAD_ARRAY_ITEM(kind) => jcode.aload(kind)
+
+ case LOAD_LOCAL(local) => jcode.load(indexOf(local), local.kind)
+
+ case lf @ LOAD_FIELD(field, isStatic) =>
+ var owner = javaName(lf.hostClass)
+ debuglog("LOAD_FIELD with owner: " + owner + " flags: " + Flags.flagsToString(field.owner.flags))
+ val fieldJName = javaName(field)
+ val fieldDescr = descriptor(field)
+ val opc = if (isStatic) Opcodes.GETSTATIC else Opcodes.GETFIELD
+ jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
+
+ case LOAD_MODULE(module) =>
+ // assert(module.isModule, "Expected module: " + module)
+ debuglog("generating LOAD_MODULE for: " + module + " flags: " + Flags.flagsToString(module.flags));
+ if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) {
+ jmethod.visitVarInsn(Opcodes.ALOAD, 0)
+ } else {
+ jmethod.visitFieldInsn(
+ Opcodes.GETSTATIC,
+ javaName(module) /* + "$" */ ,
+ strMODULE_INSTANCE_FIELD,
+ descriptor(module)
+ )
+ }
+
+ case STORE_ARRAY_ITEM(kind) => jcode.astore(kind)
+
+ case STORE_LOCAL(local) => jcode.store(indexOf(local), local.kind)
+
+ case STORE_THIS(_) =>
+ // this only works for impl classes because the self parameter comes first
+ // in the method signature. If that changes, this code has to be revisited.
+ jmethod.visitVarInsn(Opcodes.ASTORE, 0)
+
+ case STORE_FIELD(field, isStatic) =>
+ val owner = javaName(field.owner)
+ val fieldJName = javaName(field)
+ val fieldDescr = descriptor(field)
+ val opc = if (isStatic) Opcodes.PUTSTATIC else Opcodes.PUTFIELD
+ jmethod.visitFieldInsn(opc, owner, fieldJName, fieldDescr)
+
+ case CALL_PRIMITIVE(primitive) => genPrimitive(primitive, instr.pos)
+
+ /** Special handling to access native Array.clone() */
+ case call @ CALL_METHOD(definitions.Array_clone, Dynamic) =>
+ val target: String = javaType(call.targetTypeKind).getInternalName
+ jcode.invokevirtual(target, "clone", mdesc_arrayClone)
+
+ case call @ CALL_METHOD(method, style) => genCallMethod(call)
+
+ case BOX(kind) =>
+ val MethodNameAndType(mname, mdesc) = jBoxTo(kind)
+ jcode.invokestatic(BoxesRunTime, mname, mdesc)
+
+ case UNBOX(kind) =>
+ val MethodNameAndType(mname, mdesc) = jUnboxTo(kind)
+ jcode.invokestatic(BoxesRunTime, mname, mdesc)
+
+ case NEW(REFERENCE(cls)) =>
+ val className = javaName(cls)
+ jmethod.visitTypeInsn(Opcodes.NEW, className)
+
+ case CREATE_ARRAY(elem, 1) => jcode newarray elem
+
+ case CREATE_ARRAY(elem, dims) =>
+ jmethod.visitMultiANewArrayInsn(descriptor(ArrayN(elem, dims)), dims)
+
+ case IS_INSTANCE(tpe) =>
+ val jtyp: asm.Type =
+ tpe match {
+ case REFERENCE(cls) => asm.Type.getObjectType(javaName(cls))
+ case ARRAY(elem) => javaArrayType(javaType(elem))
+ case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
+ }
+ jmethod.visitTypeInsn(Opcodes.INSTANCEOF, jtyp.getInternalName)
+
+ case CHECK_CAST(tpe) =>
+ tpe match {
+
+ case REFERENCE(cls) =>
+ if (cls != ObjectClass) { // No need to checkcast for Objects
+ jmethod.visitTypeInsn(Opcodes.CHECKCAST, javaName(cls))
+ }
+
+ case ARRAY(elem) =>
+ val iname = javaArrayType(javaType(elem)).getInternalName
+ jmethod.visitTypeInsn(Opcodes.CHECKCAST, iname)
+
+ case _ => abort("Unknown reference type in IS_INSTANCE: " + tpe)
+ }
+
+ case sw @ SWITCH(tagss, branches) =>
+ assert(branches.length == tagss.length + 1, sw)
+ val flatSize = sw.flatTagsCount
+ val flatKeys = new Array[Int](flatSize)
+ val flatBranches = new Array[asm.Label](flatSize)
+
+ var restTagss = tagss
+ var restBranches = branches
+ var k = 0 // ranges over flatKeys and flatBranches
+ while(restTagss.nonEmpty) {
+ val currLabel = labels(restBranches.head)
+ for(cTag <- restTagss.head) {
+ flatKeys(k) = cTag;
+ flatBranches(k) = currLabel
+ k += 1
+ }
+ restTagss = restTagss.tail
+ restBranches = restBranches.tail
+ }
+ val defaultLabel = labels(restBranches.head)
+ assert(restBranches.tail.isEmpty)
+ debuglog("Emitting SWITCH:\ntags: " + tagss + "\nbranches: " + branches)
+ jcode.emitSWITCH(flatKeys, flatBranches, defaultLabel, MIN_SWITCH_DENSITY)
+
+ case JUMP(whereto) =>
+ if (nextBlock != whereto) {
+ jcode goTo labels(whereto)
+ }
+
+ case CJUMP(success, failure, cond, kind) =>
+ if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ if (nextBlock == success) {
+ jcode.emitIF_ICMP(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF_ICMP(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
+ if (nextBlock == success) {
+ jcode.emitIF_ACMP(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF_ACMP(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else {
+ (kind: @unchecked) match {
+ case LONG => emit(Opcodes.LCMP)
+ case FLOAT =>
+ if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
+ else emit(Opcodes.FCMPL)
+ case DOUBLE =>
+ if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
+ else emit(Opcodes.DCMPL)
+ }
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ // .. and fall through to success label
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ }
+
+ case CZJUMP(success, failure, cond, kind) =>
+ if(kind.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ } else if(kind.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_)
+ val Success = success
+ val Failure = failure
+ (cond, nextBlock) match {
+ case (EQ, Success) => jcode emitIFNONNULL labels(failure)
+ case (NE, Failure) => jcode emitIFNONNULL labels(success)
+ case (EQ, Failure) => jcode emitIFNULL labels(success)
+ case (NE, Success) => jcode emitIFNULL labels(failure)
+ case (EQ, _) =>
+ jcode emitIFNULL labels(success)
+ jcode goTo labels(failure)
+ case (NE, _) =>
+ jcode emitIFNONNULL labels(success)
+ jcode goTo labels(failure)
+ }
+ } else {
+ (kind: @unchecked) match {
+ case LONG =>
+ emit(Opcodes.LCONST_0)
+ emit(Opcodes.LCMP)
+ case FLOAT =>
+ emit(Opcodes.FCONST_0)
+ if (cond == LT || cond == LE) emit(Opcodes.FCMPG)
+ else emit(Opcodes.FCMPL)
+ case DOUBLE =>
+ emit(Opcodes.DCONST_0)
+ if (cond == LT || cond == LE) emit(Opcodes.DCMPG)
+ else emit(Opcodes.DCMPL)
+ }
+ if (nextBlock == success) {
+ jcode.emitIF(cond.negate, labels(failure))
+ } else {
+ jcode.emitIF(cond, labels(success))
+ if (nextBlock != failure) { jcode goTo labels(failure) }
+ }
+ }
+
+ case RETURN(kind) => jcode emitRETURN kind
+
+ case THROW(_) => emit(Opcodes.ATHROW)
+
+ case DROP(kind) =>
+ emit(if(kind.isWideType) Opcodes.POP2 else Opcodes.POP)
+
+ case DUP(kind) =>
+ emit(if(kind.isWideType) Opcodes.DUP2 else Opcodes.DUP)
+
+ case MONITOR_ENTER() => emit(Opcodes.MONITORENTER)
+
+ case MONITOR_EXIT() => emit(Opcodes.MONITOREXIT)
+
+ case SCOPE_ENTER(lv) =>
+ // locals removed by closelim (via CopyPropagation) may have left behind SCOPE_ENTER, SCOPE_EXIT that are to be ignored
+ val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
+ if(relevant) { // TODO check: does GenICode emit SCOPE_ENTER, SCOPE_EXIT for synthetic vars?
+ // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
+ // similarly, these labels aren't tracked in the `labels` map.
+ val start = new asm.Label
+ jmethod.visitLabel(start)
+ scoping.pushScope(lv, start)
+ }
+
+ case SCOPE_EXIT(lv) =>
+ val relevant = (!lv.sym.isSynthetic && m.locals.contains(lv))
+ if(relevant) {
+ // this label will have DEBUG bit set in its flags (ie ASM ignores it for dataflow purposes)
+ // similarly, these labels aren't tracked in the `labels` map.
+ val end = new asm.Label
+ jmethod.visitLabel(end)
+ scoping.popScope(lv, end)
+ }
+
+ case LOAD_EXCEPTION(_) =>
+ ()
+ }
+
+ }
+
+ } // end of genCode()'s genBlock()
+
+ /**
+ * 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.
+ */
+ def emitT2T(from: TypeKind, to: TypeKind) {
+ assert(isNonUnitValueTK(from), from)
+ assert(isNonUnitValueTK(to), to)
+
+ def pickOne(opcs: Array[Int]) {
+ 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 }
+ if((from == BOOL) || (to == BOOL)) {
+ // the only conversion involving BOOL that is allowed is (BOOL -> BOOL)
+ throw new Error("inconvertible types : " + from.toString() + " -> " + to.toString())
+ }
+
+ if(from.isIntSizedType) { // BYTE, CHAR, SHORT, and INT. (we're done with BOOL already)
+
+ val fromByte = { import asm.Opcodes._; Array( -1, -1, I2C, -1, I2L, I2F, I2D) } // do nothing for (BYTE -> SHORT) and for (BYTE -> INT)
+ val fromChar = { import asm.Opcodes._; Array(I2B, I2S, -1, -1, I2L, I2F, I2D) } // for (CHAR -> INT) do nothing
+ val fromShort = { import asm.Opcodes._; Array(I2B, -1, I2C, -1, I2L, I2F, I2D) } // for (SHORT -> INT) do nothing
+ val fromInt = { import asm.Opcodes._; Array(I2B, I2S, I2C, -1, I2L, I2F, I2D) }
+
+ (from: @unchecked) match {
+ case BYTE => pickOne(fromByte)
+ case SHORT => pickOne(fromShort)
+ case CHAR => pickOne(fromChar)
+ case INT => pickOne(fromInt)
+ }
+
+ } else { // FLOAT, LONG, DOUBLE
+
+ (from: @unchecked) match {
+ case FLOAT =>
+ import asm.Opcodes.{ F2L, F2D, F2I }
+ (to: @unchecked) match {
+ case LONG => emit(F2L)
+ case DOUBLE => emit(F2D)
+ case _ => emit(F2I); emitT2T(INT, to)
+ }
+
+ case LONG =>
+ import asm.Opcodes.{ L2F, L2D, L2I }
+ (to: @unchecked) match {
+ case FLOAT => emit(L2F)
+ case DOUBLE => emit(L2D)
+ case _ => emit(L2I); emitT2T(INT, to)
+ }
+
+ case DOUBLE =>
+ import asm.Opcodes.{ D2L, D2F, D2I }
+ (to: @unchecked) match {
+ case FLOAT => emit(D2F)
+ case LONG => emit(D2L)
+ case _ => emit(D2I); emitT2T(INT, to)
+ }
+ }
+ }
+ } // end of genCode()'s emitT2T()
+
+ def genPrimitive(primitive: Primitive, pos: Position) {
+
+ import asm.Opcodes;
+
+ primitive match {
+
+ case Negation(kind) => jcode.neg(kind)
+
+ case Arithmetic(op, kind) =>
+ op match {
+
+ case ADD => jcode.add(kind)
+ case SUB => jcode.sub(kind)
+ case MUL => jcode.mul(kind)
+ case DIV => jcode.div(kind)
+ case REM => jcode.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("Impossible to negate an " + kind)
+ }
+
+ case _ =>
+ abort("Unknown arithmetic primitive " + primitive)
+ }
+
+ // TODO Logical's 2nd elem should be declared ValueTypeKind, to better approximate its allowed values (isIntSized, its comments appears to convey)
+ // TODO GenICode uses `toTypeKind` to define that elem, `toValueTypeKind` would be needed instead.
+ // TODO How about adding some asserts to Logical and similar ones to capture the remaining constraint (UNIT not allowed).
+ case Logical(op, kind) => (op, kind) 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) }
+ }
+
+ case Shift(op, kind) => (op, kind) 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)
+ }
+
+ case Comparison(op, kind) => ((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) // TODO bug? why not DCMPG? http://docs.oracle.com/javase/specs/jvms/se5.0/html/Instructions2.doc3.html
+ }
+
+ case Conversion(src, dst) =>
+ debuglog("Converting from: " + src + " to: " + dst)
+ if (dst == BOOL) { println("Illegal conversion at: " + clasz + " at: " + pos.source + ":" + pos.line) }
+ else { emitT2T(src, dst) }
+
+ case ArrayLength(_) => emit(Opcodes.ARRAYLENGTH)
+
+ case StartConcat =>
+ jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
+ jmethod.visitInsn(Opcodes.DUP)
+ jcode.invokespecial(
+ StringBuilderClassName,
+ INSTANCE_CONSTRUCTOR_NAME,
+ mdesc_arglessvoid
+ )
+
+ case StringConcat(el) =>
+ val jtype = el match {
+ case REFERENCE(_) | ARRAY(_) => JAVA_LANG_OBJECT
+ case _ => javaType(el)
+ }
+ jcode.invokevirtual(
+ StringBuilderClassName,
+ "append",
+ asm.Type.getMethodDescriptor(StringBuilderType, Array(jtype): _*)
+ )
+
+ case EndConcat =>
+ jcode.invokevirtual(StringBuilderClassName, "toString", mdesc_toString)
+
+ case _ => abort("Unimplemented primitive " + primitive)
+ }
+ } // end of genCode()'s genPrimitive()
+
+ // ------------------------------------------------------------------------------------------------------------
+ // Part 6 of genCode(): the executable part of genCode() starts here.
+ // ------------------------------------------------------------------------------------------------------------
+
+ genBlocks(linearization)
+
+ jmethod.visitLabel(onePastLast)
+
+ if(emitLines) {
+ for(LineNumberEntry(line, start) <- lnEntries.sortBy(_.start.getOffset)) { jmethod.visitLineNumber(line, start) }
+ }
+ if(emitVars) { genLocalVariableTable() }
+
+ } // end of BytecodeGenerator.genCode()
+
+
+ ////////////////////// local vars ///////////////////////
+
+ // def sizeOf(sym: Symbol): Int = sizeOf(toTypeKind(sym.tpe))
+
+ def sizeOf(k: TypeKind): Int = if(k.isWideType) 2 else 1
+
+ // def indexOf(m: IMethod, sym: Symbol): Int = {
+ // val Some(local) = m lookupLocal sym
+ // indexOf(local)
+ // }
+
+ @inline final def indexOf(local: Local): Int = {
+ assert(local.index >= 0, "Invalid index for: " + local + "{" + local.## + "}: ")
+ local.index
+ }
+
+ /**
+ * Compute the indexes of each local variable of the given method.
+ * *Does not assume the parameters come first!*
+ */
+ def computeLocalVarsIndex(m: IMethod) {
+ var idx = if (m.symbol.isStaticMember) 0 else 1;
+
+ for (l <- m.params) {
+ debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
+ l.index = idx
+ idx += sizeOf(l.kind)
+ }
+
+ for (l <- m.locals if !l.arg) {
+ debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
+ l.index = idx
+ idx += sizeOf(l.kind)
+ }
+ }
+
+ } // end of class JPlainBuilder
+
+
+ /** builder of mirror classes */
+ class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) {
+
+ 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.
+ */
+ def genMirrorClass(modsym: Symbol, cunit: CompilationUnit) {
+ assert(modsym.companionClass == NoSymbol, modsym)
+ innerClassBuffer.clear()
+ this.cunit = cunit
+ val moduleName = javaName(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 = createJClass(flags,
+ mirrorName,
+ null /* no java-generic-signature */,
+ JAVA_LANG_OBJECT.getInternalName,
+ EMPTY_STRING_ARRAY)
+
+ log("Dumping mirror class for '%s'".format(mirrorName))
+
+ // typestate: entering mode with valid call sequences:
+ // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
+
+ 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)
+
+ // typestate: entering mode with valid call sequences:
+ // ( visitInnerClass | visitField | visitMethod )* visitEnd
+
+ addForwarders(isRemote(modsym), mirrorClass, mirrorName, modsym)
+
+ addInnerClasses(modsym, mirrorClass)
+ mirrorClass.visitEnd()
+ writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym)
+ }
+
+
+ } // end of class JMirrorBuilder
+
+
+ /** builder of bean info classes */
+ class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) {
+
+ /**
+ * Generate a bean info class that describes the given class.
+ *
+ * @author Ross Judson (ross.judson@soletta.com)
+ */
+ def genBeanInfoClass(clasz: IClass) {
+
+ // val BeanInfoSkipAttr = definitions.getRequiredClass("scala.beans.BeanInfoSkip")
+ // val BeanDisplayNameAttr = definitions.getRequiredClass("scala.beans.BeanDisplayName")
+ // val BeanDescriptionAttr = definitions.getRequiredClass("scala.beans.BeanDescription")
+ // val description = c.symbol getAnnotation BeanDescriptionAttr
+ // informProgress(description.toString)
+ innerClassBuffer.clear()
+
+ val flags = mkFlags(
+ javaFlags(clasz.symbol),
+ if(isDeprecated(clasz.symbol)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
+ )
+
+ val beanInfoName = (javaName(clasz.symbol) + "BeanInfo")
+ val beanInfoClass = createJClass(
+ flags,
+ beanInfoName,
+ null, // no java-generic-signature
+ "scala/beans/ScalaBeanInfo",
+ EMPTY_STRING_ARRAY
+ )
+
+ // beanInfoClass typestate: entering mode with valid call sequences:
+ // [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )*
+
+ beanInfoClass.visitSource(
+ clasz.cunit.source.toString,
+ null /* SourceDebugExtension */
+ )
+
+ var fieldList = List[String]()
+
+ for (f <- clasz.fields if f.symbol.hasGetter;
+ g = f.symbol.getter(clasz.symbol);
+ s = f.symbol.setter(clasz.symbol);
+ if g.isPublic && !(f.symbol.name startsWith "$")
+ ) {
+ // inserting $outer breaks the bean
+ fieldList = javaName(f.symbol) :: javaName(g) :: (if (s != NoSymbol) javaName(s) else null) :: fieldList
+ }
+
+ val methodList: List[String] =
+ for (m <- clasz.methods
+ if !m.symbol.isConstructor &&
+ m.symbol.isPublic &&
+ !(m.symbol.name startsWith "$") &&
+ !m.symbol.isGetter &&
+ !m.symbol.isSetter)
+ yield javaName(m.symbol)
+
+ // beanInfoClass typestate: entering mode with valid call sequences:
+ // ( visitInnerClass | visitField | visitMethod )* visitEnd
+
+ val constructor = beanInfoClass.visitMethod(
+ asm.Opcodes.ACC_PUBLIC,
+ INSTANCE_CONSTRUCTOR_NAME,
+ mdesc_arglessvoid,
+ null, // no java-generic-signature
+ EMPTY_STRING_ARRAY // no throwable exceptions
+ )
+
+ // constructor typestate: entering mode with valid call sequences:
+ // [ visitAnnotationDefault ] ( visitAnnotation | visitParameterAnnotation | visitAttribute )*
+
+ val stringArrayJType: asm.Type = javaArrayType(JAVA_LANG_STRING)
+ val conJType: asm.Type =
+ asm.Type.getMethodType(
+ asm.Type.VOID_TYPE,
+ Array(javaType(ClassClass), 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 typestate: entering mode with valid call sequences:
+ // [ visitCode ( visitFrame | visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable | visitLineNumber )* visitMaxs ] visitEnd
+
+ constructor.visitCode()
+
+ constructor.visitVarInsn(asm.Opcodes.ALOAD, 0)
+ // push the class
+ constructor.visitLdcInsn(javaType(clasz.symbol))
+
+ // 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()
+
+ // TODO no inner classes attribute is written. Confirm intent.
+ assert(innerClassBuffer.isEmpty, innerClassBuffer)
+
+ beanInfoClass.visitEnd()
+
+ writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol)
+ }
+
+ } // end of class JBeanInfoBuilder
+
+ /** A namespace for utilities to normalize the code of an IMethod, over and beyond what IMethod.normalize() strives for.
+ * In particualr, IMethod.normalize() doesn't collapseJumpChains().
+ *
+ * TODO Eventually, these utilities should be moved to IMethod and reused from normalize() (there's nothing JVM-specific about them).
+ */
+ object newNormal {
+
+ def startsWithJump(b: BasicBlock): Boolean = { assert(b.nonEmpty, "empty block"); b.firstInstruction.isInstanceOf[JUMP] }
+
+ /** Prune from an exception handler those covered blocks which are jump-only. */
+ private def coverWhatCountsOnly(m: IMethod): Boolean = {
+ assert(m.hasCode, "code-less method")
+
+ var wasReduced = false
+ for(h <- m.exh) {
+ val shouldntCover = (h.covered filter startsWithJump)
+ if(shouldntCover.nonEmpty) {
+ wasReduced = true
+ h.covered --= shouldntCover // not removing any block on purpose.
+ }
+ }
+
+ wasReduced
+ }
+
+ /** An exception handler is pruned provided any of the following holds:
+ * (1) it covers nothing (for example, this may result after removing unreachable blocks)
+ * (2) each block it covers is of the form: JUMP(_)
+ * Return true iff one or more ExceptionHandlers were removed.
+ *
+ * A caveat: removing an exception handler, for whatever reason, means that its handler code (even if unreachable)
+ * won't be able to cause a class-loading-exception. As a result, behavior can be different.
+ */
+ private def elimNonCoveringExh(m: IMethod): Boolean = {
+ assert(m.hasCode, "code-less method")
+
+ def isRedundant(eh: ExceptionHandler): Boolean = {
+ (eh.cls != NoSymbol) && ( // TODO `eh.isFinallyBlock` more readable than `eh.cls != NoSymbol`
+ eh.covered.isEmpty
+ || (eh.covered forall startsWithJump)
+ )
+ }
+
+ var wasReduced = false
+ val toPrune = (m.exh.toSet filter isRedundant)
+ if(toPrune.nonEmpty) {
+ wasReduced = true
+ for(h <- toPrune; r <- h.blocks) { m.code.removeBlock(r) } // TODO m.code.removeExh(h)
+ m.exh = (m.exh filterNot toPrune)
+ }
+
+ wasReduced
+ }
+
+ private def isJumpOnly(b: BasicBlock): Option[BasicBlock] = {
+ b.toList match {
+ case JUMP(whereto) :: rest =>
+ assert(rest.isEmpty, "A block contains instructions after JUMP (looks like enterIgnoreMode() was itself ignored.)")
+ Some(whereto)
+ case _ => None
+ }
+ }
+
+ private def directSuccStar(b: BasicBlock): List[BasicBlock] = { directSuccStar(List(b)) }
+
+ /** Transitive closure of successors potentially reachable due to normal (non-exceptional) control flow.
+ Those BBs in the argument are also included in the result */
+ private def directSuccStar(starters: Traversable[BasicBlock]): List[BasicBlock] = {
+ val result = new mutable.ListBuffer[BasicBlock]
+ var toVisit: List[BasicBlock] = starters.toList.distinct
+ while(toVisit.nonEmpty) {
+ val h = toVisit.head
+ toVisit = toVisit.tail
+ result += h
+ for(p <- h.directSuccessors; if !result.contains(p) && !toVisit.contains(p)) { toVisit = p :: toVisit }
+ }
+ result.toList
+ }
+
+ /** Returns:
+ * for single-block self-loops, the pair (start, Nil)
+ * for other cycles, the pair (backedge-target, basic-blocks-in-the-cycle-except-backedge-target)
+ * otherwise a pair consisting of:
+ * (a) the endpoint of a (single or multi-hop) chain of JUMPs
+ * (such endpoint does not start with a JUMP and therefore is not part of the chain); and
+ * (b) the chain (ie blocks to be removed when collapsing the chain of jumps).
+ * Precondition: the BasicBlock given as argument starts with an unconditional JUMP.
+ */
+ private def finalDestination(start: BasicBlock): (BasicBlock, List[BasicBlock]) = {
+ assert(startsWithJump(start), "not the start of a (single or multi-hop) chain of JUMPs.")
+ var hops: List[BasicBlock] = Nil
+ var prev = start
+ var done = false
+ do {
+ done = isJumpOnly(prev) match {
+ case Some(dest) =>
+ if (dest == start) { return (start, hops) } // leave infinite-loops in place
+ hops ::= prev
+ if (hops.contains(dest)) {
+ // leave infinite-loops in place
+ return (dest, hops filterNot (dest eq))
+ }
+ prev = dest;
+ false
+ case None => true
+ }
+ } while(!done)
+
+ (prev, hops)
+ }
+
+ /**
+ * Collapse a chain of "jump-only" blocks such as:
+ *
+ * JUMP b1;
+ * b1: JUMP b2;
+ * b2: JUMP ... etc.
+ *
+ * by re-wiring predecessors to target directly the "final destination".
+ * Even if covered by an exception handler, a "non-self-loop jump-only block" can always be removed.
+
+ * Returns true if any replacement was made, false otherwise.
+ *
+ * In more detail:
+ * Starting at each of the entry points (m.startBlock, the start block of each exception handler)
+ * rephrase those control-flow instructions targeting a jump-only block (which jumps to a final destination D) to target D.
+ * The blocks thus skipped are also removed from IMethod.blocks.
+ *
+ * Rationale for this normalization:
+ * test/files/run/private-inline.scala after -optimize is chock full of
+ * BasicBlocks containing just JUMP(whereTo), where no exception handler straddles them.
+ * They should be collapsed by IMethod.normalize() but aren't.
+ * That was fine in FJBG times when by the time the exception table was emitted,
+ * it already contained "anchored" labels (ie instruction offsets were known)
+ * and thus ranges with identical (start, end) (i.e, identical after GenJVM omitted the JUMPs in question)
+ * could be weeded out to avoid "java.lang.ClassFormatError: Illegal exception table range"
+ * Now that visitTryCatchBlock() must be called before Labels are resolved,
+ * this method gets rid of the BasicBlocks described above (to recap, consisting of just a JUMP).
+ */
+ private def collapseJumpOnlyBlocks(m: IMethod): Boolean = {
+ assert(m.hasCode, "code-less method")
+
+ /* "start" is relative in a cycle, but we call this helper with the "first" entry-point we found. */
+ def realTarget(jumpStart: BasicBlock): Map[BasicBlock, BasicBlock] = {
+ assert(startsWithJump(jumpStart), "not part of a jump-chain")
+ val Pair(dest, redundants) = finalDestination(jumpStart)
+ (for(skipOver <- redundants) yield Pair(skipOver, dest)).toMap
+ }
+
+ def rephraseGotos(detour: Map[BasicBlock, BasicBlock]) {
+ for(Pair(oldTarget, newTarget) <- detour.iterator) {
+ if(m.startBlock == oldTarget) {
+ m.code.startBlock = newTarget
+ }
+ for(eh <- m.exh; if eh.startBlock == oldTarget) {
+ eh.setStartBlock(newTarget)
+ }
+ for(b <- m.blocks; if !detour.isDefinedAt(b)) {
+ val idxLast = (b.size - 1)
+ b.lastInstruction match {
+ case JUMP(whereto) =>
+ if (whereto == oldTarget) {
+ b.replaceInstruction(idxLast, JUMP(newTarget))
+ }
+ case CJUMP(succ, fail, cond, kind) =>
+ if ((succ == oldTarget) || (fail == oldTarget)) {
+ b.replaceInstruction(idxLast, CJUMP(detour.getOrElse(succ, succ),
+ detour.getOrElse(fail, fail),
+ cond, kind))
+ }
+ case CZJUMP(succ, fail, cond, kind) =>
+ if ((succ == oldTarget) || (fail == oldTarget)) {
+ b.replaceInstruction(idxLast, CZJUMP(detour.getOrElse(succ, succ),
+ detour.getOrElse(fail, fail),
+ cond, kind))
+ }
+ case SWITCH(tags, labels) =>
+ if(labels exists (detour.isDefinedAt(_))) {
+ val newLabels = (labels map { lab => detour.getOrElse(lab, lab) })
+ b.replaceInstruction(idxLast, SWITCH(tags, newLabels))
+ }
+ case _ => ()
+ }
+ }
+ }
+ }
+
+ /* remove from all containers that may contain a reference to */
+ def elide(redu: BasicBlock) {
+ assert(m.startBlock != redu, "startBlock should have been re-wired by now")
+ m.code.removeBlock(redu);
+ }
+
+ var wasReduced = false
+ val entryPoints: List[BasicBlock] = m.startBlock :: (m.exh map (_.startBlock));
+
+ var elided = mutable.Set.empty[BasicBlock] // debug
+ var newTargets = mutable.Set.empty[BasicBlock] // debug
+
+ for (ep <- entryPoints) {
+ var reachable = directSuccStar(ep) // this list may contain blocks belonging to jump-chains that we'll skip over
+ while(reachable.nonEmpty) {
+ val h = reachable.head
+ reachable = reachable.tail
+ if(startsWithJump(h)) {
+ val detour = realTarget(h)
+ if(detour.nonEmpty) {
+ wasReduced = true
+ reachable = (reachable filterNot (detour.keySet.contains(_)))
+ rephraseGotos(detour)
+ detour.keySet foreach elide
+ elided ++= detour.keySet
+ newTargets ++= detour.values
+ }
+ }
+ }
+ }
+ assert(newTargets.intersect(elided).isEmpty, "contradiction: we just elided the final destionation of a jump-chain")
+
+ wasReduced
+ }
+
+ def normalize(m: IMethod) {
+ if(!m.hasCode) { return }
+ collapseJumpOnlyBlocks(m)
+ var wasReduced = false;
+ do {
+ wasReduced = false
+ // Prune from an exception handler those covered blocks which are jump-only.
+ wasReduced |= coverWhatCountsOnly(m); icodes.checkValid(m) // TODO should be unnecessary now that collapseJumpOnlyBlocks(m) is in place
+ // Prune exception handlers covering nothing.
+ wasReduced |= elimNonCoveringExh(m); icodes.checkValid(m)
+
+ // TODO see note in genExceptionHandlers about an ExceptionHandler.covered containing dead blocks (newNormal should remove them, but, where do those blocks come from?)
+ } while (wasReduced)
+
+ // TODO this would be a good time to remove synthetic local vars seeing no use, don't forget to call computeLocalVarsIndex() afterwards.
+ }
+
+ }
+
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index aace545ea5..17c244ee82 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -6,11 +6,10 @@ package scala.tools.nsc package backend.jvm -import java.io.{ DataOutputStream, OutputStream } +import java.io.{ByteArrayOutputStream, DataOutputStream, OutputStream } import java.nio.ByteBuffer import scala.collection.{ mutable, immutable } import scala.reflect.internal.pickling.{ PickleFormat, PickleBuffer } -import scala.tools.reflect.SigParser import scala.tools.nsc.symtab._ import scala.tools.nsc.util.{ SourceFile, NoSourceFile } import scala.reflect.internal.ClassfileConstants._ @@ -122,9 +121,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with if (settings.debug.value) inform("[running phase " + name + " on icode]") - if (settings.Xverify.value && !SigParser.isParserAvailable) - global.warning("signature verification requested by signature parser unavailable: signatures not checked") - if (settings.Xdce.value) for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym)) icodes.classes -= sym @@ -191,7 +187,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with val MIN_SWITCH_DENSITY = 0.7 val INNER_CLASSES_FLAGS = - (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT) + (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_INTERFACE | ACC_ABSTRACT) val PublicStatic = ACC_PUBLIC | ACC_STATIC val PublicStaticFinal = ACC_PUBLIC | ACC_STATIC | ACC_FINAL @@ -345,7 +341,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with */ def emitClass(jclass: JClass, sym: Symbol) { addInnerClasses(jclass) - writeClass("" + sym.name, jclass, sym) + writeClass("" + sym.name, jclass.getName(), toByteArray(jclass), sym) } /** Returns the ScalaSignature annotation if it must be added to this class, @@ -517,6 +513,14 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with } } + private def toByteArray(jc: JClass): Array[Byte] = { + val bos = new java.io.ByteArrayOutputStream() + val dos = new java.io.DataOutputStream(bos) + jc.writeTo(dos) + dos.close() + bos.toByteArray + } + /** * Generate a bean info class that describes the given class. * @@ -586,7 +590,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with jcode.emitRETURN() // write the bean information class file. - writeClass("BeanInfo ", beanInfoClass, c.symbol) + writeClass("BeanInfo ", beanInfoClass.getName(), toByteArray(beanInfoClass), c.symbol) } /** Add the given 'throws' attributes to jmethod */ @@ -714,14 +718,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with nannots } - /** Run the signature parser to catch bogus signatures. - */ - def isValidSignature(sym: Symbol, sig: String) = ( - if (sym.isMethod) SigParser verifyMethod sig - else if (sym.isTerm) SigParser verifyType sig - else SigParser verifyClass sig - ) - // @M don't generate java generics sigs for (members of) implementation // classes, as they are monomorphic (TODO: ok?) private def needsGenericSignature(sym: Symbol) = !( @@ -743,19 +739,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with erasure.javaSig(sym, memberTpe) foreach { sig => // This seems useful enough in the general case. log(sig) - /** Since we're using a sun internal class for signature validation, - * we have to allow for it not existing or otherwise malfunctioning: - * in which case we treat every signature as valid. Medium term we - * should certainly write independent signature validation. - */ - if (settings.Xverify.value && SigParser.isParserAvailable && !isValidSignature(sym, sig)) { - clasz.cunit.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 - } if (checkSignatures) { val normalizedTpe = beforeErasure(erasure.prepareSigMap(memberTpe)) val bytecodeTpe = owner.thisType.memberInfo(sym) @@ -1151,6 +1134,27 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with */ def generateMirrorClass(clasz: Symbol, sourceFile: SourceFile) { import JAccessFlags._ + /* We need to save inner classes buffer and create a new one to make sure + * that we do confuse inner classes of the class we mirror with inner + * classes of the class we are mirroring. These two sets can be different + * as seen in this case: + * + * class A { + * class B + * def b: B = new B + * } + * object C extends A + * + * Here mirror class of C has a static forwarder for (inherited) method `b` + * therefore it refers to class `B` and needs InnerClasses entry. However, + * the real class for `C` (named `C$`) is empty and does not refer to `B` + * thus does not need InnerClasses entry it. + * + * NOTE: This logic has been refactored in GenASM and everything is + * implemented in a much cleaner way by having two separate buffers. + */ + val savedInnerClasses = innerClassBuffer + innerClassBuffer = mutable.LinkedHashSet[Symbol]() val moduleName = javaName(clasz) // + "$" val mirrorName = moduleName.substring(0, moduleName.length() - 1) val mirrorClass = fjbgContext.JClass(ACC_SUPER | ACC_PUBLIC | ACC_FINAL, @@ -1164,6 +1168,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with val ssa = scalaSignatureAddingMarker(mirrorClass, clasz.companionSymbol) addAnnotations(mirrorClass, clasz.annotations ++ ssa) emitClass(mirrorClass, clasz) + innerClassBuffer = savedInnerClasses } var linearization: List[BasicBlock] = Nil @@ -1946,8 +1951,10 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with // 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 & (Flags.FINAL | Flags.MODULE)) != 0) + (sym.hasFlag(Flags.FINAL) || isTopLevelModule(sym)) && !sym.enclClass.isInterface && !sym.isClassConstructor && !sym.isMutable // lazy vals and vars both diff --git a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala index 05d8c632c0..08e059419a 100644 --- a/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala +++ b/src/compiler/scala/tools/nsc/backend/opt/Inliners.scala @@ -9,7 +9,7 @@ package backend.opt import scala.collection.mutable import scala.tools.nsc.symtab._ -import scala.tools.nsc.util.{ NoSourceFile } +import scala.tools.nsc.util.NoSourceFile /** * @author Iulian Dragos @@ -196,33 +196,35 @@ abstract class Inliners extends SubComponent { val staleIn = mutable.Set.empty[BasicBlock] /** - * A transformation local to the body of the argument. + * A transformation local to the body of the IMethod received as argument. * An linining decision consists in replacing a callsite with the body of the callee. * Please notice that, because `analyzeMethod()` itself may modify a method body, * the particular callee bodies that end up being inlined depend on the particular order in which methods are visited - * (no topological ordering over the call-graph is attempted). + * (no topological sorting over the call-graph is attempted). * * Making an inlining decision requires type-flow information for both caller and callee. * Regarding the caller, such information is needed only for basic blocks containing inlining candidates * (and their transitive predecessors). This observation leads to using a custom type-flow analysis (MTFAGrowable) - * that can be re-inited, i.e. that reuses lattice elements (type-flow information) computed in a previous iteration + * that can be re-inited, i.e. that reuses lattice elements (type-flow information computed in a previous iteration) * as starting point for faster convergence in a new iteration. * * The mechanics of inlining are iterative for a given invocation of `analyzeMethod(m)`, - * thus considering the basic blocks that successful inlining added in a previous iteration: + * and are affected by inlinings from previous iterations + * (ie, "heuristic" rules are based on statistics tracked for that purpose): * * (1) before the iterations proper start, so-called preinlining is performed. * Those callsites whose (receiver, concreteMethod) are both known statically * can be analyzed for inlining before computing a type-flow. Details in `preInline()` * * (2) the first iteration computes type-flow information for basic blocks containing inlining candidates - * (and their transitive predecessors), so called `relevantBBs`. + * (and their transitive predecessors), so called `relevantBBs` basic blocks. * The ensuing analysis of each candidate (performed by `analyzeInc()`) - * may result in a CFG isomorphic to that of the callee being inserted where the callsite was - * (i.e. a CALL_METHOD instruction is replaced with a single-entry single-exit CFG, which we call "successful inlining"). + * may result in a CFG isomorphic to that of the callee being inserted in place of the callsite + * (i.e. a CALL_METHOD instruction is replaced with a single-entry single-exit CFG, + * a situation we call "successful inlining"). * - * (3) following iterations have their relevant basic blocks updated to focus - * on the inlined basic blocks and their successors only. Details in `MTFAGrowable.reinit()` + * (3) following iterations have `relevantBBs` updated to focus on the inlined basic blocks and their successors only. + * Details in `MTFAGrowable.reinit()` * */ def analyzeMethod(m: IMethod): Unit = { // m.normalize @@ -372,7 +374,7 @@ abstract class Inliners extends SubComponent { * That's why preInline() is invoked twice: any inlinings downplayed by the heuristics during the first round get an opportunity to rank higher during the second. * * As a whole, both `preInline()` invocations amount to priming the inlining process, - * so that the first TFA run afterwards is able to gain more information as compared to a cold-start. + * so that the first TFA that is run afterwards is able to gain more information as compared to a cold-start. */ val totalPreInlines = { val firstRound = preInline(true) @@ -388,9 +390,10 @@ abstract class Inliners extends SubComponent { /* it's important not to inline in unreachable basic blocks. linearizedBlocks() returns only reachable ones. */ tfa.callerLin = caller.m.linearizedBlocks() - /* TODO Do we want to perform inlining in non-finally exception handlers? + /* TODO Do we really want to inline inside exception handlers? * Seems counterproductive (the larger the method the less likely it will be JITed). - * The alternative above would be `linearizer.linearizeAt(caller.m, caller.m.startBlock)`. + * The alternative would be `linearizer.linearizeAt(caller.m, caller.m.startBlock)`. + * And, we would cut down on TFA iterations, too. * See also comment on the same topic in TypeFlowAnalysis. */ tfa.reinit(m, staleOut.toList, splicedBlocks, staleIn) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 346780147e..f8fffdc726 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -44,7 +44,7 @@ class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { </div> { browser } <div id="content" class="ui-layout-center"> - <iframe name="template" src={ relativeLinkTo{List("package.html")} }/> + <iframe id="template" src={ relativeLinkTo{List("package.html")} }/> </div> </body> diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 4463857aa5..49cd17c176 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -87,7 +87,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage { memberToCommentHtml(tpl, true) } <div id="mbrsel"> - <div id='textfilter'><span class='pre'/><span class='input'><input type='text' accesskey='/'/></span><span class='post'/></div> + <div id='textfilter'><span class='pre'/><span class='input'><input id='mbrsel-input' type='text' accesskey='/'/></span><span class='post'/></div> { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else <div id="order"> <span class="filtertype">Ordering</span> @@ -511,7 +511,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage <dt>See also</dt> <dd>{ val seeXml:List[scala.xml.NodeSeq]=(for(see <- comment.see ) yield <span class="cmt">{bodyToHtml(see)}</span> ) - seeXml.reduceLeft(_ ++ Text(", ") ++ _) + seeXml.reduceLeft(_ ++ _) }</dd> } else NodeSeq.Empty diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css index c6136c508e..2a8f9b570a 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.css @@ -1,23 +1,32 @@ * { - color: inherit; - font-size: 10pt; - text-decoration: none; + color: inherit; + font-size: 10pt; + text-decoration: none; font-family: Arial, sans-serif; - border-width: 0px; - padding: 0px; - margin: 0px; + border-width: 0px; + padding: 0px; + margin: 0px; } a { - cursor: pointer; + cursor: pointer; } a:hover { - text-decoration: underline; + text-decoration: underline; } h1 { - display: none; + display: none; +} + +.selected { + -moz-box-shadow: inset 0px 5px 10px rgba(58, 88, 97, .36); + -webkit-box-shadow: inset 0px 5px 10px rgba(58, 88, 97, .36); + border-top: solid 1px rgba(119, 138, 153, 0.8); + border-bottom: solid 1px rgba(151, 173, 191, 0.4); + background-color: #ced2d9; + margin: -1px 0px; } /*.letters { @@ -33,81 +42,81 @@ h1 { } #browser { - top: 0px; - left: 0px; - bottom: 0px; - width: 100%; - display: block; - position: fixed; + top: 0px; + left: 0px; + bottom: 0px; + width: 100%; + display: block; + position: fixed; } #filter { - position: absolute; - display: block; -/* padding: 5px;*/ - right: 0; - left: 0; - top: 0; - background-image:url('filterbg.gif'); - background-repeat:repeat-x; - background-color: #ededee; /* light gray */ - /*background-color: #DADADA;*/ - border:1px solid #bbbbbb; - border-top:0; - border-left:0; - border-right:0; + position: absolute; + display: block; +/* padding: 5px;*/ + right: 0; + left: 0; + top: 0; + background-image:url('filterbg.gif'); + background-repeat:repeat-x; + background-color: #ededee; /* light gray */ + /*background-color: #DADADA;*/ + border:1px solid #bbbbbb; + border-top:0; + border-left:0; + border-right:0; } #textfilter { - position: relative; - display: block; - height: 20px; - margin-top: 5px; - margin-bottom: 5px; + position: relative; + display: block; + height: 20px; + margin-top: 5px; + margin-bottom: 5px; } #textfilter > .pre { - display: block; - position: absolute; - top: 0; - left: 0; - height: 23px; - width: 21px; - background: url("filter_box_left.png"); + display: block; + position: absolute; + top: 0; + left: 0; + height: 23px; + width: 21px; + background: url("filter_box_left.png"); } #textfilter > .input { - display: block; - position: absolute; - top: 0; - right: 20px; - left: 20px; + display: block; + position: absolute; + top: 0; + right: 20px; + left: 20px; } #textfilter > .input > input { - height: 20px; - padding: 1px; - font-weight: bold; - color: #000000; - background: #ffffff url("filterboxbarbg.png") repeat-x bottom left; - width: 100%; + height: 20px; + padding: 1px; + font-weight: bold; + color: #000000; + background: #ffffff url("filterboxbarbg.png") repeat-x bottom left; + width: 100%; } #textfilter > .post { - display: block; - position: absolute; - top: 0; - right: 0; - height: 23px; - width: 21px; - background: url("filter_box_right.png"); + display: block; + position: absolute; + top: 0; + right: 0; + height: 23px; + width: 21px; + background: url("filter_box_right.png"); } /*#textfilter { - position: relative; - display: block; + position: relative; + display: block; height: 20px; - margin-bottom: 5px; + margin-bottom: 5px; } #textfilter > .pre { @@ -121,7 +130,7 @@ h1 { } #textfilter > .input { - display: block; + display: block; position: absolute; top: 0; right: 20px; @@ -129,11 +138,11 @@ h1 { } #textfilter > .input > input { - height: 16px; - padding: 2px; - font-weight: bold; - color: darkblue; - background-color: white; + height: 16px; + padding: 2px; + font-weight: bold; + color: darkblue; + background-color: white; width: 100%; } @@ -148,22 +157,22 @@ h1 { }*/ #focusfilter { - position: relative; - text-align: center; - display: block; - padding: 5px; - background-color: #fffebd; /* light yellow*/ - text-shadow: #ffffff 0 1px 0; + position: relative; + text-align: center; + display: block; + padding: 5px; + background-color: #fffebd; /* light yellow*/ + text-shadow: #ffffff 0 1px 0; } #focusfilter .focuscoll { - font-weight: bold; - text-shadow: #ffffff 0 1px 0; + font-weight: bold; + text-shadow: #ffffff 0 1px 0; } #focusfilter img { - bottom: -2px; - position: relative; + bottom: -2px; + position: relative; } #kindfilter { @@ -182,10 +191,9 @@ h1 { } #kindfilter > a:hover { - color: #4C4C4C; - text-decoration: none; - text-shadow: #ffffff 0 1px 0; - + color: #4C4C4C; + text-decoration: none; + text-shadow: #ffffff 0 1px 0; } #letters { @@ -208,117 +216,117 @@ h1 { } #tpl { - display: block; - position: fixed; - overflow: auto; - right: 0; - left: 0; - bottom: 0; - top: 5px; - position: absolute; - display: block; + display: block; + position: fixed; + overflow: auto; + right: 0; + left: 0; + bottom: 0; + top: 5px; + position: absolute; + display: block; } #tpl .packhide { - display: block; - float: right; - font-weight: normal; - color: white; + display: block; + float: right; + font-weight: normal; + color: white; } #tpl .packfocus { - display: block; - float: right; - font-weight: normal; - color: white; + display: block; + float: right; + font-weight: normal; + color: white; } #tpl .packages > ol { - background-color: #dadfe6; - /*margin-bottom: 5px;*/ + background-color: #dadfe6; + /*margin-bottom: 5px;*/ } /*#tpl .packages > ol > li { - margin-bottom: 1px; + margin-bottom: 1px; }*/ #tpl .packages > li > a { - padding: 0px 5px; + padding: 0px 5px; } #tpl .packages > li > a.tplshow { - display: block; - color: white; - font-weight: bold; - display: block; - text-shadow: #000000 0 1px 0; + display: block; + color: white; + font-weight: bold; + display: block; + text-shadow: #000000 0 1px 0; } #tpl ol > li.pack { - padding: 3px 5px; - background: url("packagesbg.gif"); - background-repeat:repeat-x; - min-height: 14px; - background-color: #6e808e; + padding: 3px 5px; + background: url("packagesbg.gif"); + background-repeat:repeat-x; + min-height: 14px; + background-color: #6e808e; } #tpl ol > li { - display: block; + display: block; } #tpl .templates > li { - padding-left: 5px; - min-height: 18px; + padding-left: 5px; + min-height: 18px; } #tpl ol > li .icon { - padding-right: 5px; - bottom: -2px; - position: relative; + padding-right: 5px; + bottom: -2px; + position: relative; } #tpl .templates div.placeholder { - padding-right: 5px; - width: 13px; - display: inline-block; + padding-right: 5px; + width: 13px; + display: inline-block; } #tpl .templates span.tplLink { - padding-left: 5px; + padding-left: 5px; } #content { - border-left-width: 1px; - border-left-color: black; - border-left-style: white; - right: 0px; - left: 0px; - bottom: 0px; - top: 0px; - position: fixed; - margin-left: 300px; - display: block; + border-left-width: 1px; + border-left-color: black; + border-left-style: white; + right: 0px; + left: 0px; + bottom: 0px; + top: 0px; + position: fixed; + margin-left: 300px; + display: block; } #content > iframe { - display: block; - height: 100%; - width: 100%; + display: block; + height: 100%; + width: 100%; } .ui-layout-pane { - background: #FFF; - overflow: auto; + background: #FFF; + overflow: auto; } .ui-layout-resizer { - background-image:url('filterbg.gif'); - background-repeat:repeat-x; - background-color: #ededee; /* light gray */ - border:1px solid #bbbbbb; - border-top:0; - border-bottom:0; - border-left: 0; + background-image:url('filterbg.gif'); + background-repeat:repeat-x; + background-color: #ededee; /* light gray */ + border:1px solid #bbbbbb; + border-top:0; + border-bottom:0; + border-left: 0; } .ui-layout-toggler { diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js index b767722b8c..eb7f752440 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js @@ -15,10 +15,10 @@ var lastHash = ""; $(document).ready(function() { $('body').layout({ west__size: '20%' }); - $('#browser').layout({ - center__paneSelector: ".ui-west-center" + $('#browser').layout({ + center__paneSelector: ".ui-west-center" //,center__initClosed:true - ,north__paneSelector: ".ui-west-north" + ,north__paneSelector: ".ui-west-north" }); $('iframe').bind("load", function(){ var subtitle = $(this).contents().find('title').text(); @@ -260,18 +260,95 @@ function prepareEntityList() { .prepend("<a class='packfocus'>focus</a>"); } +/* Handles all key presses while scrolling around with keyboard shortcuts in left panel */ +function keyboardScrolldownLeftPane() { + scheduler.add("init", function() { + $("#textfilter input").blur(); + var $items = $("#tpl li"); + $items.first().addClass('selected'); + + $(window).bind("keydown", function(e) { + var $old = $items.filter('.selected'), + $new; + + switch ( e.keyCode ) { + + case 9: // tab + $old.removeClass('selected'); + break; + + case 13: // enter + $old.removeClass('selected'); + var $url = $old.children().filter('a:last').attr('href'); + $("#template").attr("src",$url); + break; + + case 27: // escape + $old.removeClass('selected'); + $(window).unbind(e); + $("#textfilter input").focus(); + + break; + + case 38: // up + $new = $old.prev(); + + if (!$new.length) { + $new = $old.parent().prev(); + } + + if ($new.is('ol') && $new.children(':last').is('ol')) { + $new = $new.children().children(':last'); + } else if ($new.is('ol')) { + $new = $new.children(':last'); + } + + break; + + case 40: // down + $new = $old.next(); + if (!$new.length) { + $new = $old.parent().parent().next(); + } + if ($new.is('ol')) { + $new = $new.children(':first'); + } + break; + } + + if ($new.is('li')) { + $old.removeClass('selected'); + $new.addClass('selected'); + } else if (e.keyCode == 38) { + $(window).unbind(e); + $("#textfilter input").focus(); + } + }); + }); +} + /* Configures the text filter */ function configureTextFilter() { scheduler.add("init", function() { - $("#filter").append("<div id='textfilter'><span class='pre'/><span class='input'><input type='text' accesskey='/'/></span><span class='post'/></div>"); + $("#filter").append("<div id='textfilter'><span class='pre'/><span class='input'><input id='index-input' type='text' accesskey='/'/></span><span class='post'/></div>"); printAlphabet(); var input = $("#textfilter input"); resizeFilterBlock(); - input.bind("keyup", function(event) { + input.bind("keydown", function(event) { if (event.keyCode == 27) { // escape input.attr("value", ""); } - textFilter(); + if (event.keyCode == 9) { // tab + $("#template").contents().find("#mbrsel-input").focus(); + input.attr("value", ""); + return false; + } + if (event.keyCode == 40) { // down arrow + $(window).unbind("keydown"); + keyboardScrolldownLeftPane(); + return false; + } + textFilter(); }); input.focus(function(event) { input.select(); }); }); diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js index fd5a981cb0..33fbd83bee 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js @@ -25,9 +25,37 @@ $(document).ready(function(){ // Member filter box var input = $("#textfilter input"); input.bind("keyup", function(event) { - if (event.keyCode == 27) - input.val(""); // escape key - filter(true); + + switch ( event.keyCode ) { + + case 27: // escape key + input.val(""); + filter(true); + break; + + case 38: // up + input.val(""); + filter(false); + window.scrollTo(0, $("body").offset().top); + input.focus(); + break; + + case 33: //page up + input.val(""); + filter(false); + break; + + case 34: //page down + input.val(""); + filter(false); + break; + + default: + window.scrollTo(0, $("#mbrsel").offset().top); + filter(true); + break; + + } }); input.focus(function(event) { input.select(); @@ -37,13 +65,13 @@ $(document).ready(function(){ filter(); }); $(document).keydown(function(event) { - if(!event.altKey && !event.ctrlKey && - (event.keyCode == 27 || (event.keyCode >= 48 && event.keyCode <= 90)) && - document.activeElement != $("#textfilter input")[0]) { - $("#textfilter input").focus(); + + if (event.keyCode == 9) { // tab + $("#index-input", window.parent.document).focus(); + input.attr("value", ""); + return false; } }); - $("#textfilter input").focus(); $("#linearization li").click(function(){ if ($(this).hasClass("in")) { @@ -251,7 +279,8 @@ function initInherit() { }); }; -function filter(scrollToMember) { +/* filter used to take boolean scrollToMember */ +function filter() { var query = $.trim($("#textfilter input").val()).toLowerCase(); query = query.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&").replace(/\s+/g, "|"); var queryRegExp = new RegExp(query, "i"); @@ -327,10 +356,6 @@ function filter(scrollToMember) { members.hide(); }; - if (scrollToMember) { - window.scrollTo(0, $("#mbrsel").offset().top); - } - return false; }; diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 61f3670f5f..c3525037cd 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -179,7 +179,7 @@ trait ModelFactoryImplicitSupport { hardcoded.arraySkipConversions.contains(conv.conversionQualifiedName)) // Filter out non-sensical conversions from value types - if (isScalaValueType(sym.tpe)) + if (isPrimitiveValueType(sym.tpe)) conversions = conversions.filter((ic: ImplicitConversion) => hardcoded.valueClassFilter(sym.nameString, ic.conversionQualifiedName)) diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index ec73af4dca..edf0108e58 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -352,6 +352,10 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") case item: WorkItem => Some(item.raiseMissing()) case _ => Some(()) } + + // don't forget to service interrupt requests + val iqs = scheduler.dequeueAllInterrupts(_.execute()) + debugLog("ShutdownReq: cleaning work queue (%d items)".format(units.size)) debugLog("Cleanup up responses (%d loadedType pending, %d parsedEntered pending)" .format(waitLoadedTypeResponses.size, getParsedEnteredResponses.size)) @@ -926,7 +930,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") val implicitlyAdded = viaView != NoSymbol members.add(sym, pre, implicitlyAdded) { (s, st) => new TypeMember(s, st, - context.isAccessible(s, pre, superAccess && !implicitlyAdded), + context.isAccessible(if (s.hasGetter) s.getter(s.owner) else s, pre, superAccess && !implicitlyAdded), inherited, viaView) } diff --git a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala index 72e5ee42ed..49ba9d0aeb 100644 --- a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala +++ b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala @@ -269,7 +269,8 @@ self: scala.tools.nsc.Global => protected def isEligible(t: Tree) = !t.pos.isTransparent override def traverse(t: Tree) { t match { - case tt : TypeTree if tt.original != null => traverse(tt.original) + case tt : TypeTree if tt.original != null && (tt.pos includes tt.original.pos) => + traverse(tt.original) case _ => if (t.pos includes pos) { if (isEligible(t)) last = t diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala deleted file mode 100644 index e2a5a3e3ee..0000000000 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/FindOccurrences.scala +++ /dev/null @@ -1,28 +0,0 @@ -package scala.tools.nsc -package interactive -package tests.core - -import scala.tools.nsc.util.Position -import scala.tools.nsc.util.SourceFile - -/** Find occurrences of `text` in the passed `sources`. */ -private[core] object FindOccurrences { - - def apply(sources: Seq[SourceFile])(text: String): Map[SourceFile, Seq[Position]] = - allPositionsOf(sources, text) - - /** All positions of the given string in all source files. */ - private def allPositionsOf(sources: Seq[SourceFile], str: String): Map[SourceFile, Seq[Position]] = - (for (s <- sources; p <- positionsOf(s, str)) yield p).groupBy(_.source) - - /** Return all positions of the given str in the given source file. */ - private def positionsOf(source: SourceFile, str: String): Seq[Position] = { - val buf = new collection.mutable.ListBuffer[Position] - var pos = source.content.indexOfSlice(str) - while (pos >= 0) { - buf += source.position(pos - 1) // we need the position before the first character of this marker - pos = source.content.indexOfSlice(str, pos + 1) - } - buf.toList - } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala index c274e13976..d2baaf32c6 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerRequestsWorkingMode.scala @@ -16,7 +16,7 @@ trait PresentationCompilerRequestsWorkingMode extends TestResources { * ask the type at all positions marked with `TypeMarker.marker` and println the result. */ private def askAllSourcesAsync[T](marker: TestMarker)(askAt: Position => Response[T])(f: (Position, T) => Unit) { - val positions = allPositionsOf(marker.marker).valuesIterator.toList.flatten + val positions = allPositionsOf(str = marker.marker) val responses = for (pos <- positions) yield askAt(pos) for ((pos, r) <- positions zip responses) withResponse(pos, r)(f) @@ -26,13 +26,25 @@ trait PresentationCompilerRequestsWorkingMode extends TestResources { * response before going to the next one. */ private def askAllSourcesSync[T](marker: TestMarker)(askAt: Position => Response[T])(f: (Position, T) => Unit) { - val positions = allPositionsOf(marker.marker).valuesIterator.toList.flatten + val positions = allPositionsOf(str = marker.marker) for (pos <- positions) withResponse(pos, askAt(pos))(f) } - private def allPositionsOf: String => Map[SourceFile, Seq[Position]] = - FindOccurrences(sourceFiles) _ - + /** All positions of the given string in all source files. */ + private def allPositionsOf(srcs: Seq[SourceFile] = sourceFiles, str: String): Seq[Position] = + for (s <- srcs; p <- positionsOf(s, str)) yield p + + /** Return all positions of the given str in the given source file. */ + private def positionsOf(source: SourceFile, str: String): Seq[Position] = { + val buf = new collection.mutable.ListBuffer[Position] + var pos = source.content.indexOfSlice(str) + while (pos >= 0) { + buf += source.position(pos - 1) // we need the position before the first character of this marker + pos = source.content.indexOfSlice(str, pos + 1) + } + buf.toList + } + private def withResponse[T](pos: Position, response: Response[T])(f: (Position, T) => Unit) { /** Return the filename:line:col version of this position. */ def showPos(pos: Position): String = diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala index 518cb7bd76..21e90fe57f 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/SourcesCollector.scala @@ -13,7 +13,7 @@ private[tests] object SourcesCollector { * */ def apply(base: Path, filter: SourceFilter): Array[SourceFile] = { assert(base.isDirectory) - base.walk.filter(filter).map(source).toArray + base.walk.filter(filter).map(source).toList.toArray.sortBy(_.file.name) } private def source(file: Path): SourceFile = source(AbstractFile.getFile(file.toFile)) diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala index 3a605975f4..605ecee6c7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -19,11 +19,6 @@ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent) with ScalaClassLoader { - // private val defined = mutable.Map[String, Class[_]]() - - override protected def trace = - sys.props contains "scala.debug.classloader" - protected def classNameToPath(name: String): String = if (name endsWith ".class") name else name.replace('.', '/') + ".class" @@ -68,29 +63,13 @@ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) case null => super.classBytes(name) case file => file.toByteArray } - override def loadClass(name: String, resolve: Boolean) = { - classLoaderLog("load " + name + ".") - super.loadClass(name, resolve) - } override def findClass(name: String): JClass = { val bytes = classBytes(name) - classLoaderLog("find %s: %s".format(name, - if (bytes.isEmpty) "failed." - else bytes.size + " bytes." - )) - if (bytes.isEmpty) + if (bytes.length == 0) throw new ClassNotFoundException(name) - else { - val clazz = defineClass(name, bytes, 0, bytes.length) - // defined(name) = clazz - clazz - } + else + defineClass(name, bytes, 0, bytes.length) } - // Don't know how to construct an URL for something which exists only in memory - // override def getResource(name: String): URL = findAbstractFile(name) match { - // case null => super.getResource(name) - // case file => new URL(...) - // } private val packages = mutable.Map[String, Package]() diff --git a/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala index 453b106808..fc68998225 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala @@ -17,29 +17,9 @@ import scala.util.control.ControlThrowable trait CodeHandlers[T] { self => -/* - // A declaration introduces names and assigns them types. - // It can form part of a class definition (§5.1) or of a refinement in a compound type (§3.2.7). - // (Ed: aka abstract members.) - // - // ‘val’ ValDcl | ‘var’ VarDcl | ‘def’ FunDcl | ‘type’ {nl} TypeDcl - def decl(code: String): T - - // A definition introduces names that denote terms or types. - // It can form part of an object or class definition or it can be local to a block. - // (Ed: aka concrete members.) - // - // ‘val’ PatDef | ‘var’ VarDef | ‘def’ FunDef | ‘type’ {nl} TypeDef | - // [‘case’] ‘class’ ClassDef | [‘case’] ‘object’ ObjectDef | ‘trait’ TraitDef - def defn(code: String): T -*/ - // Expressions are composed of operators and operands. def expr(code: String): T - // An import clause has the form import p.I where p is a stable identifier (§3.1) and I is an import expression. - def impt(code: String): T - // Statements occur as parts of blocks and templates. // A statement can be an import, a definition or an expression, or it can be empty. // Statements used in the template of a class definition can also be declarations. @@ -54,10 +34,7 @@ trait CodeHandlers[T] { case _: NoSuccess => Nil } - // def decl(code: String) = try Some(self.decl(code)) catch handler - // def defn(code: String) = try Some(self.defn(code)) catch handler def expr(code: String) = try Some(self.expr(code)) catch handler - def impt(code: String) = try Some(self.impt(code)) catch handler def stmt(code: String) = try Some(self.stmt(code)) catch handler def stmts(code: String) = try (self.stmts(code) map (x => Some(x))) catch handlerSeq } diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 86f48b9d69..7a3e1913b6 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -14,13 +14,11 @@ import Completion._ trait Completion { type ExecResult def resetVerbosity(): Unit - def execute(line: String): Option[ExecResult] def completer(): ScalaCompleter } object NoCompletion extends Completion { type ExecResult = Nothing def resetVerbosity() = () - def execute(line: String) = None def completer() = NullCompleter } @@ -44,8 +42,6 @@ object Completion { && !(code startsWith "./") && !(code startsWith "..") ) - private val pathStarts = """/ \ ./ ../ ~/""" split ' ' toSet - def looksLikePath(code: String) = (code != null) && (pathStarts exists (code startsWith _)) object Forwarder { def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index c33675a83a..b3bbeb3169 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -12,40 +12,15 @@ import scala.reflect.NameTransformer * will supply their own candidates and resolve their own paths. */ trait CompletionAware { - /** The delimiters which are meaningful when this CompletionAware - * object is in control. - */ - // TODO - // def delimiters(): List[Char] = List('.') - /** The complete list of unqualified Strings to which this * object will complete. */ def completions(verbosity: Int): List[String] - /** Default filter to apply to completions. - */ - def filterNotFunction(s: String): Boolean = false - - /** Default sort. - */ - def sortFunction(s1: String, s2: String): Boolean = s1 < s2 - - /** Default map. - */ - def mapFunction(s: String) = NameTransformer decode s - /** The next completor in the chain. */ def follow(id: String): Option[CompletionAware] = None - /** What to return if this completion is given as a command. It - * returns None by default, which means to allow the repl to interpret - * the line normally. Returning Some(_) means the line will never - * reach the scala interpreter. - */ - def execute(id: String): Option[Any] = None - /** A list of useful information regarding a specific uniquely * identified completion. This is specifically written for the * following situation, but should be useful elsewhere too: @@ -75,45 +50,13 @@ trait CompletionAware { else comps else follow(parsed.bufferHead) map (_ completionsFor parsed.bufferTail) getOrElse Nil - results filterNot filterNotFunction map mapFunction sortWith (sortFunction _) - } - - /** TODO - unify this and completionsFor under a common traverser. - */ - def executionFor(parsed: Parsed): Option[Any] = { - import parsed._ - - if (isUnqualified && !isLastDelimiter && (completions(verbosity) contains buffer)) execute(buffer) - else if (!isQualified) None - else follow(bufferHead) flatMap (_ executionFor bufferTail) + results.sorted } } object CompletionAware { val Empty = new CompletionAware { def completions(verbosity: Int) = Nil } - /** Artificial object demonstrating completion */ - // lazy val replVars = CompletionAware( - // Map[String, CompletionAware]( - // "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), - // "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), - // "types" -> CompletionAware(() => allSeenTypes map (_.toString)), - // "implicits" -> CompletionAware(() => allImplicits map (_.toString)) - // ) - // ) - - // class Forwarder(underlying: CompletionAware) extends CompletionAware { - // override def completions() = underlying.completions() - // override def filterNotFunction(s: String) = underlying.filterNotFunction(s) - // override def sortFunction(s1: String, s2: String) = underlying.sortFunction(s1, s2) - // override def mapFunction(s: String) = underlying.mapFunction(s) - // override def follow(id: String) = underlying.follow(id) - // override def execute(id: String) = underlying.execute(id) - // override def completionsFor(parsed: Parsed) = underlying.completionsFor(parsed) - // override def executionFor(parsed: Parsed) = underlying.executionFor(parsed) - // } - // - def unapply(that: Any): Option[CompletionAware] = that match { case x: CompletionAware => Some((x)) case _ => None diff --git a/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala b/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala deleted file mode 100644 index d889cadf47..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/Dossiers.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -// Coming soon -trait Dossiers { - val intp: IMain - - import intp._ - import intp.global._ - import definitions._ - - trait Dossier { - def symbol: Symbol - def staticType: Type - - def id = name.toString - def name = symbol.name - def normalizedType = staticType.typeSymbolDirect.tpe.normalize - def simpleNameOfType = staticType.typeSymbol.simpleName - def staticTypeString = staticType.toString - - override def toString = "Dossier on %s:\n static type %s (normalized %s)".format( - symbol, staticType, normalizedType - ) - } - - class TypeDossier(val symbol: TypeSymbol, val staticType: Type) extends Dossier { - override def toString = super.toString - } - - class TermDossier(val symbol: TermSymbol, val staticType: Type, val value: AnyRef) extends Dossier { - def runtimeClass: JClass = value.getClass - def runtimeSymbol: Symbol = getClassIfDefined(runtimeClass.getName) - def runtimeType: Type = runtimeSymbol.tpe - def runtimeTypeString = TypeStrings.fromClazz(runtimeClass) - - def runtimeTypedParam = NamedParamClass(id, runtimeTypeString, value) - def staticTypedParam = NamedParamClass(id, staticTypeString, value) - - def isRuntimeTypeTighter = runtimeSymbol.ancestors contains normalizedType.typeSymbol - - override def toString = super.toString + ( - "\n runtime type %s/%s\n value %s".format( - runtimeType, runtimeTypeString, value - ) - ) - } -} - diff --git a/src/compiler/scala/tools/nsc/interpreter/Eval.scala b/src/compiler/scala/tools/nsc/interpreter/Eval.scala deleted file mode 100644 index 6a59cbb6bf..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/Eval.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -trait Eval { - /** Executes code looking for an implicit conversion from the type - * of the given identifier to CompletionAware. - */ - // def completionAwareImplicit[T](id: String) = { - // val f1string = "%s => %s".format(typeForIdent(id).get, classOf[CompletionAware].getName) - // val code = """{ - // | def f(implicit x: (%s) = null): %s = x - // | val f1 = f - // | if (f1 == null) None else Some(f1(%s)) - // |}""".stripMargin.format(f1string, f1string, id) - // - // evalExpr[Option[CompletionAware]](code) - // } - - // Coming soon - // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) - // case class LiftedCode(code: String) { - // val lifted: String = { - // beQuietDuring { interpret(code) } - // eval2[String]("({ " + code + " }).toString") - // } - // def >> : String = lifted - // } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala index 0cd918b6a5..79b429e26a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala @@ -13,7 +13,6 @@ trait ExprTyper { val repl: IMain import repl._ - import replTokens.{ Tokenizer } import global.{ reporter => _, Import => _, _ } import definitions._ import syntaxAnalyzer.{ UnitParser, UnitScanner, token2name } @@ -30,17 +29,9 @@ trait ExprTyper { result } - def tokens(code: String) = { - reporter.reset() - val in = newUnitScanner(code) - in.init() - new Tokenizer(in) tokenIterator - } def defns(code: String) = stmts(code) collect { case x: DefTree => x } def expr(code: String) = applyRule(code, _.expr()) - def impt(code: String) = applyRule(code, _.importExpr()) - def impts(code: String) = applyRule(code, _.importClause()) def stmts(code: String) = applyRule(code, _.templateStats()) def stmt(code: String) = stmts(code).last // guaranteed nonempty } @@ -111,13 +102,4 @@ trait ExprTyper { } finally typeOfExpressionDepth -= 1 } - - def tokens(line: String) = beQuietDuring(codeParser.tokens(line)) - - // In the todo column - // - // def compileAndTypeExpr(expr: String): Option[Typer] = { - // class TyperRun extends Run { - // override def stopPhase(name: String) = name == "superaccessors" - // } } diff --git a/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala deleted file mode 100644 index e1eb938b3c..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -/** TODO - * Spaces, dots, and other things in filenames are not correctly handled. - * space-escaping, knowing when we're inside quotes, etc. would be nice. - */ - -import io.{ Directory, Path } - -/** This isn't 100% clean right now, but it works and is simple. Rather - * than delegate to new objects on each '/' in the path, we treat the - * buffer like a path and process it directly. - */ -object FileCompletion { - def executionFor(buffer: String): Option[Path] = { - Some(Directory.Home match { - case Some(d) if buffer startsWith "~" => d / buffer.tail - case _ => Path(buffer) - }) filter (_.exists) - } - - private def fileCompletionForwarder(buffer: String, where: Directory): List[String] = { - completionsFor(where.path + buffer) map (_ stripPrefix where.path) toList - } - - private def homeCompletions(buffer: String): List[String] = { - require(buffer startsWith "~/") - val home = Directory.Home getOrElse (return Nil) - fileCompletionForwarder(buffer.tail, home) map ("~" + _) - } - private def cwdCompletions(buffer: String): List[String] = { - require(buffer startsWith "./") - val cwd = Directory.Current getOrElse (return Nil) - fileCompletionForwarder(buffer.tail, cwd) map ("." + _) - } - - def completionsFor(buffer: String): List[String] = - if (buffer startsWith "~/") homeCompletions(buffer) - else if (buffer startsWith "./") cwdCompletions(buffer) - else { - val p = Path(buffer) - val (dir, stub) = - // don't want /foo/. expanding "." - if (p.name == ".") (p.parent, ".") - else if (p.isDirectory) (p.toDirectory, "") - else (p.parent, p.name) - - dir.list filter (_.name startsWith stub) map (_.path) toList - } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 297d6ad1b9..de778e7469 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -12,13 +12,13 @@ import java.util.concurrent.locks.ReentrantLock import scala.sys.process.Process import session._ import scala.util.Properties.{ jdkHome, javaVersion } -import scala.tools.util.{ Signallable, Javap } +import scala.tools.util.{ Javap } import scala.annotation.tailrec import scala.collection.mutable.ListBuffer import scala.concurrent.ops import util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream } import interpreter._ -import io.{ File, Sources, Directory } +import io.{ File, Directory } import scala.reflect.NameTransformer._ import util.ScalaClassLoader import ScalaClassLoader._ @@ -49,6 +49,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) var settings: Settings = _ var intp: IMain = _ + @deprecated("Use `intp` instead.", "2.9.0") def interpreter = intp + @deprecated("Use `intp` instead.", "2.9.0") def interpreter_= (i: Interpreter): Unit = intp = i + /** Having inherited the difficult "var-ness" of the repl instance, * I'm trying to work around it by moving operations into a class from * which it will appear a stable prefix. @@ -103,27 +106,11 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def isAsync = !settings.Yreplsync.value lazy val power = new Power(intp, new StdReplVals(this)) - - // TODO - // object opt extends AestheticSettings - // - @deprecated("Use `intp` instead.", "2.9.0") - def interpreter = intp - - @deprecated("Use `intp` instead.", "2.9.0") - def interpreter_= (i: Interpreter): Unit = intp = i - def history = in.history /** The context class loader at the time this object was created */ protected val originalClassLoader = Thread.currentThread.getContextClassLoader - // Install a signal handler so we can be prodded. - private val signallable = - if (isReplDebug) - Signallable("Dump repl state.")(dumpCommand()) - else null - // classpath entries added via :cp var addedClasspath: String = "" @@ -152,32 +139,15 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) if (intp ne null) { intp.close() intp = null - removeSigIntHandler() } } class ILoopInterpreter extends IMain(settings, out) { outer => - private class ThreadStoppingLineManager(classLoader: ClassLoader) extends Line.Manager(classLoader) { - override def onRunaway(line: Line[_]): Unit = { - val template = """ - |// She's gone rogue, captain! Have to take her out! - |// Calling Thread.stop on runaway %s with offending code: - |// scala> %s""".stripMargin - - echo(template.format(line.thread, line.code)) - // XXX no way to suppress the deprecation warning - line.thread.stop() - in.redrawLine() - } - } override lazy val formatting = new Formatting { def prompt = ILoop.this.prompt } - override protected def createLineManager(classLoader: ClassLoader): Line.Manager = - new ThreadStoppingLineManager(classLoader) - override protected def parentClassLoader = settings.explicitParentLoader.getOrElse( classOf[ILoop].getClassLoader ) } @@ -287,14 +257,12 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) cmd("imports", "[name name ...]", "show import history, identifying sources of names", importsCommand), cmd("implicits", "[-v]", "show the implicits in scope", implicitsCommand), cmd("javap", "<path|class>", "disassemble a file or class name", javapCommand), - nullary("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), cmd("load", "<path>", "load and interpret a Scala file", loadCommand), nullary("paste", "enter paste mode: all input up to ctrl-D compiled together", pasteCommand), nullary("power", "enable power user mode", powerCmd), nullary("quit", "exit the interpreter", () => Result(false, None)), nullary("replay", "reset execution and replay all previous commands", replay), nullary("reset", "reset the repl to its initial state, forgetting all session entries", resetCommand), - // nullary("reset", "reset the interpreter, forgetting session values but retaining session types", replay), shCommand, nullary("silent", "disable/enable automatic printing of results", verbosity), cmd("type", "[-v] <expr>", "display the type of an expression without evaluating it", typeCommand), @@ -303,31 +271,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) /** Power user commands */ lazy val powerCommands: List[LoopCommand] = List( - nullary("dump", "displays a view of the interpreter's internal state", dumpCommand), - nullary("vals", "gives information about the power mode repl vals", valsCommand), - cmd("phase", "<phase>", "set the implicit phase for power commands", phaseCommand), - cmd("wrap", "<method>", "name of method to wrap around each repl line", wrapCommand) withLongHelp (""" - |:wrap - |:wrap clear - |:wrap <method> - | - |Installs a wrapper around each line entered into the repl. - |Currently it must be the simple name of an existing method - |with the specific signature shown in the following example. - | - |def timed[T](body: => T): T = { - | val start = System.nanoTime - | try body - | finally println((System.nanoTime - start) + " nanos elapsed.") - |} - |:wrap timed - | - |If given no argument, :wrap names the wrapper installed. - |An argument of clear will remove the wrapper if any is active. - |Note that wrappers do not compose (a new one replaces the old - |one) and also that the :phase command uses the same machinery, - |so setting :wrap will clear any :phase setting. - """.stripMargin.trim) + cmd("phase", "<phase>", "set the implicit phase for power commands", phaseCommand) ) private def dumpCommand(): Result = { @@ -508,14 +452,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) else res.show() } } - private def keybindingsCommand(): Result = { - if (in.keyBindings.isEmpty) "Key bindings unavailable." - else { - echo("Reading jline properties for default key bindings.") - echo("Accuracy not guaranteed: treat this as a guideline only.\n") - in.keyBindings foreach (x => echo ("" + x)) - } - } + private def wrapCommand(line: String): Result = { def failMsg = "Argument to :wrap must be the name of a method with signature [T](=> T): T" onIntp { intp => @@ -539,10 +476,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) intp setExecutionWrapper intp.pathToTerm(wrapper) "Set wrapper to '" + wrapper + "'" case tp => - failMsg + ( - if (tp == NoType) "\nFound: <unknown>" - else "\nFound: <unknown>" - ) + failMsg + "\nFound: <unknown>" } case _ => failMsg } @@ -551,18 +485,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) private def pathToPhaseWrapper = intp.pathToTerm("$r") + ".phased.atCurrent" private def phaseCommand(name: String): Result = { - // This line crashes us in TreeGen: - // - // if (intp.power.phased set name) "..." - // - // Exception in thread "main" java.lang.AssertionError: assertion failed: ._7.type - // at scala.Predef$.assert(Predef.scala:99) - // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:69) - // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:44) - // at scala.tools.nsc.ast.TreeGen.mkAttributedRef(TreeGen.scala:101) - // at scala.tools.nsc.ast.TreeGen.mkAttributedStableRef(TreeGen.scala:143) - // - // But it works like so, type annotated. val phased: Phased = power.phased import phased.NoPhaseName @@ -603,14 +525,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) private val crashRecovery: PartialFunction[Throwable, Boolean] = { case ex: Throwable => - if (settings.YrichExes.value) { - val sources = implicitly[Sources] - echo("\n" + ex.getMessage) - echo( - if (isReplDebug) "[searching " + sources.path + " for exception contexts...]" - else "[searching for exception contexts...]" - ) - } echo(intp.global.throwableAsString(ex)) ex match { @@ -846,10 +760,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) }) } - def runCompletion = - try in.completion execute code map (intp bindSyntheticValue _) - catch { case ex: Exception => None } - /** Here we place ourselves between the user and the interpreter and examine * the input they are ostensibly submitting. We intervene in several cases: * @@ -871,25 +781,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) // line comment, do nothing None } - /** Due to my accidentally letting file completion execution sneak ahead - * of actual parsing this now operates in such a way that the scala - * interpretation always wins. However to avoid losing useful file - * completion I let it fail and then check the others. So if you - * type /tmp it will echo a failure and then give you a Directory object. - * It's not pretty: maybe I'll implement the silence bits I need to avoid - * echoing the failure. - */ - else if (intp isParseable code) { - val (code, result) = reallyInterpret - if (power != null && code == IR.Error) - runCompletion - - result - } - else runCompletion match { - case Some(_) => None // completion hit: avoid the latent error - case _ => reallyInterpret._2 // trigger the latent error - } + else + reallyInterpret._2 } // runs :load `file` on any files passed via -i @@ -968,7 +861,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) /** process command-line arguments and do as they request */ def process(args: Array[String]): Boolean = { - val command = new CommandLine(args.toList, msg => echo("scala: " + msg)) + val command = new CommandLine(args.toList, echo) def neededHelp(): String = (if (command.settings.help.value) command.usageMsg + "\n" else "") + (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "") @@ -981,13 +874,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } @deprecated("Use `process` instead", "2.9.0") - def main(args: Array[String]): Unit = { - if (isReplDebug) - System.out.println(new java.util.Date) - - process(args) - } - @deprecated("Use `process` instead", "2.9.0") def main(settings: Settings): Unit = process(settings) } @@ -1050,32 +936,4 @@ object ILoop { } } def run(lines: List[String]): String = run(lines map (_ + "\n") mkString) - - // provide the enclosing type T - // in order to set up the interpreter's classpath and parent class loader properly - def breakIf[T: ClassTag](assertion: => Boolean, args: NamedParam*): Unit = - if (assertion) break[T](args.toList) - - // start a repl, binding supplied args - def break[T: ClassTag](args: List[NamedParam]): Unit = savingContextLoader { - val msg = if (args.isEmpty) "" else " Binding " + args.size + " value%s.".format( - if (args.size == 1) "" else "s" - ) - echo("Debug repl starting." + msg) - val repl = new ILoop { - override def prompt = "\ndebug> " - } - repl.settings = new Settings(echo) - repl.settings.embeddedDefaults[T] - repl.createInterpreter() - repl.in = new JLineReader(new JLineCompletion(repl)) - - // rebind exit so people don't accidentally call sys.exit by way of predef - repl.quietRun("""def exit = println("Type :quit to resume program execution.")""") - args foreach (p => repl.bind(p.name, p.tpe, p.value)) - repl.loop() - - echo("\nDebug repl exiting.") - repl.closeInterpreter() - } } diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala index 6a9654732b..9072eaae46 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package interpreter import util.Position -import scala.tools.util.SignalManager import scala.util.control.Exception.ignoring /** @@ -33,37 +32,6 @@ trait ILoopInit { echoAndRefresh(msg) } - /** Try to install sigint handler: ignore failure. Signal handler - * will interrupt current line execution if any is in progress. - * - * Attempting to protect the repl from accidental exit, we only honor - * a single ctrl-C if the current buffer is empty: otherwise we look - * for a second one within a short time. - */ - protected def installSigIntHandler() { - def onExit() { - Console.println("") // avoiding "shell prompt in middle of line" syndrome - sys.exit(1) - } - ignoring(classOf[Exception]) { - SignalManager("INT") = { - if (intp == null || intp.lineManager == null) - onExit() - else if (intp.lineManager.running) - intp.lineManager.cancel() - else if (in.currentLine != "") { - // non-empty buffer, so make them hit ctrl-C a second time - SignalManager("INT") = onExit() - io.timer(5)(installSigIntHandler()) // and restore original handler if they don't - } - else onExit() - } - } - } - protected def removeSigIntHandler() { - squashAndLog("removeSigIntHandler")(SignalManager("INT") = null) - } - private val initLock = new java.util.concurrent.locks.ReentrantLock() private val initCompilerCondition = initLock.newCondition() // signal the compiler is initialized private val initLoopCondition = initLock.newCondition() // signal the whole repl is initialized @@ -105,9 +73,7 @@ trait ILoopInit { protected def postInitThunks = List[Option[() => Unit]]( Some(intp.setContextClassLoader _), - if (isReplPower) Some(() => enablePowerMode(true)) else None, - // do this last to avoid annoying uninterruptible startups - Some(installSigIntHandler _) + if (isReplPower) Some(() => enablePowerMode(true)) else None ).flatten // ++ ( // warningsThunks diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index a9c2ce0d09..956e282b26 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -16,9 +16,9 @@ import reporters._ import symtab.Flags import scala.reflect.internal.Names import scala.tools.util.PathResolver -import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional, Indenter } +import scala.tools.nsc.util.ScalaClassLoader import ScalaClassLoader.URLClassLoader -import Exceptional.unwrap +import scala.tools.nsc.util.Exceptional.unwrap import scala.collection.{ mutable, immutable } import scala.util.control.Exception.{ ultimately } import IMain._ @@ -92,7 +92,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends * on the future. */ private var _classLoader: AbstractFileClassLoader = null // active classloader - private var _lineManager: Line.Manager = null // logic for individual lines private val _compiler: Global = newCompiler(settings, reporter) // our private compiler private val nextReqId = { @@ -233,11 +232,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends val global: imain.global.type = imain.global } with StructuredTypeStrings - // object dossiers extends { - // val intp: imain.type = imain - // } with Dossiers { } - // import dossiers._ - lazy val memberHandlers = new { val intp: imain.type = imain } with MemberHandlers @@ -272,15 +266,10 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def executionWrapper = _executionWrapper def setExecutionWrapper(code: String) = _executionWrapper = code def clearExecutionWrapper() = _executionWrapper = "" - def lineManager = _lineManager /** interpreter settings */ lazy val isettings = new ISettings(this) - /** Create a line manager. Overridable. */ - protected def noLineManager = ReplPropsKludge.noThreadCreation(settings) - protected def createLineManager(classLoader: ClassLoader): Line.Manager = new Line.Manager(classLoader) - /** Instantiate a compiler. Overridable. */ protected def newCompiler(settings: Settings, reporter: Reporter): ReplGlobal = { settings.outputDirs setSingleOutput virtualDirectory @@ -311,19 +300,14 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends ensureClassLoader() } final def ensureClassLoader() { - if (_classLoader == null) { + if (_classLoader == null) _classLoader = makeClassLoader() - _lineManager = if (noLineManager) null else createLineManager(_classLoader) - } } def classLoader: AbstractFileClassLoader = { ensureClassLoader() _classLoader } private class TranslatingClassLoader(parent: ClassLoader) extends AbstractFileClassLoader(virtualDirectory, parent) { - private[IMain] var traceClassLoading = isReplTrace - override protected def trace = super.trace || traceClassLoading - /** Overridden here to try translating a simple name to the generated * class name if the original attempt fails. This method is used by * getResourceAsStream as well as findClass. @@ -596,6 +580,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def interpretSynthetic(line: String): IR.Result = interpret(line, true) def interpret(line: String, synthetic: Boolean): IR.Result = { def loadAndRunReq(req: Request) = { + classLoader.setAsContext() val (result, succeeded) = req.loadAndRun /** To our displeasure, ConsoleReporter offers only printMessage, @@ -725,33 +710,14 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends val printName = sessionNames.print val resultName = sessionNames.result - class LineExceptional(ex: Throwable) extends Exceptional(ex) { - private def showReplInternal = isettings.showInternalStackTraces - - override def spanFn(frame: JavaStackFrame) = - if (showReplInternal) super.spanFn(frame) - else !(frame.className startsWith evalPath) - - override def contextPrelude = super.contextPrelude + ( - if (showReplInternal) "" - else "/* The repl internal portion of the stack trace is elided. */\n" - ) - } def bindError(t: Throwable) = { if (!bindExceptions) // avoid looping if already binding throw t val unwrapped = unwrap(t) withLastExceptionLock[String]({ - if (opt.richExes) { - val ex = new LineExceptional(unwrapped) - directBind[Exceptional]("lastException", ex) - ex.contextHead + "\n(access lastException for the full trace)" - } - else { - directBind[Throwable]("lastException", unwrapped) - util.stackTraceString(unwrapped) - } + directBind[Throwable]("lastException", unwrapped) + util.stackTraceString(unwrapped) }, util.stackTraceString(unwrapped)) } @@ -1027,24 +993,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends /** load and run the code using reflection */ def loadAndRun: (String, Boolean) = { - if (lineManager == null) return { - try { ("" + (lineRep call sessionNames.print), true) } - catch { case ex => (lineRep.bindError(ex), false) } - } - import interpreter.Line._ - - try { - val execution = lineManager.set(originalLine)(lineRep call sessionNames.print) - execution.await() - - execution.state match { - case Done => ("" + execution.get(), true) - case Threw => (lineRep.bindError(execution.caught()), false) - case Cancelled => ("Execution interrupted by signal.\n", false) - case Running => ("Execution still running! Seems impossible.", false) - } - } - finally lineManager.clear() + try { ("" + (lineRep call sessionNames.print), true) } + catch { case ex => (lineRep.bindError(ex), false) } } override def toString = "Request(line=%s, %s trees)".format(line, trees.size) @@ -1138,10 +1088,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } } - object replTokens extends { - val global: imain.global.type = imain.global - } with ReplTokens { } - object exprTyper extends { val repl: IMain.this.type = imain } with ExprTyper { } @@ -1154,9 +1100,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def typeOfExpression(expr: String, silent: Boolean = true): Type = exprTyper.typeOfExpression(expr, silent) - def prettyPrint(code: String) = - replTokens.prettyPrint(exprTyper tokens code) - protected def onlyTerms(xs: List[Name]) = xs collect { case x: TermName => x } protected def onlyTypes(xs: List[Name]) = xs collect { case x: TypeName => x } @@ -1238,12 +1181,6 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends def isShow = code.lines exists (_.trim endsWith "// show") def isShowRaw = code.lines exists (_.trim endsWith "// raw") - // checking for various debug signals - if (isShowRaw) - replTokens withRawTokens prettyPrint(code) - else if (repllog.isTrace || isShow) - prettyPrint(code) - // old style beSilentDuring(parse(code)) foreach { ts => ts foreach { t => diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala index 55706f4fd2..241ba5fa4a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -22,7 +22,6 @@ trait InteractiveReader { def history: History def completion: Completion - def keyBindings: List[KeyBinding] def eraseLine(): Unit def redrawLine(): Unit def currentLine: String diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index 795b2e3678..b1e6a9d7d9 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -279,24 +279,6 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput if (parsed.isEmpty) xs map ("." + _) else xs } - // chasing down results which won't parse - // This used to work fine, now it reports a type error before any - // exception gets to us. See SI-5657. Don't have time to deal with - // it, so disabling everything. - def execute(line: String): Option[ExecResult] = { - return None // disabled - - val parsed = Parsed(line) - def noDotOrSlash = line forall (ch => ch != '.' && ch != '/') - - if (noDotOrSlash) None // we defer all unqualified ids to the repl. - else { - (ids executionFor parsed) orElse - (rootClass executionFor parsed) orElse - (FileCompletion executionFor line) - } - } - // generic interface for querying (e.g. interpreter loop, testing) def completions(buf: String): List[String] = topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) @@ -360,15 +342,9 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput if (!looksLikeInvocation(buf)) None else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) - def regularCompletion = tryCompletion(mkDotted, topLevelFor) - def fileCompletion = - if (!looksLikePath(buf)) None - else tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) - def tryAll = ( lastResultCompletion - orElse regularCompletion - orElse fileCompletion + orElse tryCompletion(mkDotted, topLevelFor) getOrElse Candidates(cursor, Nil) ) diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index 99f6b627eb..758f6e2abc 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -22,9 +22,6 @@ class JLineReader(_completion: => Completion) extends InteractiveReader { lazy val completion = _completion lazy val history: JLineHistory = JLineHistory() - lazy val keyBindings = - try KeyBinding parse slurp(term.getDefaultBindings) - catch { case _: Exception => Nil } private def term = consoleReader.getTerminal() def reset() = term.reset() diff --git a/src/compiler/scala/tools/nsc/interpreter/KeyBinding.scala b/src/compiler/scala/tools/nsc/interpreter/KeyBinding.scala deleted file mode 100644 index a7ca3a77f3..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/KeyBinding.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -case class KeyBinding(name: String, code: Int, aliases: List[String], description: String) { - def nameString = if (aliases.nonEmpty) aliases mkString ", " else name - override def toString = "%3d %s: %s".format(code, nameString, description) -} - -object KeyBinding { - def parse(bindings: String): List[KeyBinding] = { - def loop(xs: List[String]): List[KeyBinding] = { - val (comment, lines) = xs span (_ startsWith "#") - val description = comment map (_ drop 1 trim) mkString " " - val (aliases, desc) = description span (_ != ':') match { - case (x, y) => ( - x split ',' map (_.trim) toList, - if (y == "") "" else y.tail.trim - ) - } - lines match { - case Nil => Nil - case hd :: tl => - val kb = (hd indexOf '=') match { - case -1 => KeyBinding(hd, -1, aliases, desc) - case idx => KeyBinding(hd drop idx + 1, hd take idx toInt, aliases, desc) - } - kb :: loop(tl) - } - } - // This is verrrrrrrry specific to the current contents - // of the keybindings.properties in jline. - loop(bindings split "\\n" map (_.trim) dropWhile (_ != "") filterNot (_ == "") toList) sortBy (_.code) - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/Line.scala b/src/compiler/scala/tools/nsc/interpreter/Line.scala deleted file mode 100644 index 3062c95dae..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/Line.scala +++ /dev/null @@ -1,107 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import java.util.concurrent.locks.ReentrantLock -import scala.tools.nsc.util.Exceptional -import Exceptional.unwrap -import Line._ - -/** Encapsulation of a single line in the repl. The concurrency - * infrastructure arose to deal with signals so SIGINT could be - * trapped without losing the repl session, but it will be useful - * in ways beyond that. Each line obtains a thread and the repl - * waits on a condition indicating that either the line has - * completed or failed. - */ -class Line[+T](val code: String, classLoader: ClassLoader, body: => T) { - private var _state: State = Running - private var _result: Option[Any] = None - private var _caught: Option[Throwable] = None - private val lock = new ReentrantLock() - private val finished = lock.newCondition() - - private def withLock[T](body: => T) = { - lock.lock() - try body - finally lock.unlock() - } - private def setState(state: State) = withLock { - _state = state - finished.signal() - } - // private because it should be called by the manager. - private def cancel() = if (running) setState(Cancelled) - - private def runAndSetState[T](body: => T) { - try { _result = Some(body) ; setState(Done) } - catch { case t => _caught = Some(t) ; setState(Threw) } - } - - // This is where the line thread is created and started. - private val _thread: Thread = - io.newThread(_ setContextClassLoader classLoader)(runAndSetState(body)) - - def state = _state - def thread = _thread - def alive = thread.isAlive - def runaway = !success && alive - def success = _state == Done - def running = _state == Running - - def caught() = { await() ; _caught.orNull } - def get() = { - await() - _result getOrElse sys.error("Called get with no result. Code: " + code) - } - def await() = withLock { while (running) finished.await() } -} - -object Line { - // seconds to let a runaway thread live before calling stop() - private val HUNTER_KILLER_DELAY = 5 - - // A line opens in state Running, and will eventually - // transition to Threw (an exception was caught), Cancelled - // (the line was explicitly cancelled, presumably by SIGINT) - // or Done (success). - sealed abstract class State - case object Running extends State - case object Threw extends State - case object Cancelled extends State - case object Done extends State - - class Manager(classLoader: ClassLoader) { - /** Override to add behavior for runaway lines. This method will - * be called if a line thread is still running five seconds after - * it has been cancelled. - */ - def onRunaway(line: Line[_]): Unit = () - - private var _current: Option[Line[_]] = None - def current = _current - - def clear() = { - _current foreach (_.cancel()) - _current = None - } - def set[T](code: String)(body: => T) = { - val line = new Line(code, classLoader, body) - _current = Some(line) - line - } - def running = _current.isDefined - def cancel() = { - current foreach { line => - line.thread.interrupt() - line.cancel() - if (line.runaway) - io.timer(HUNTER_KILLER_DELAY) { if (line.alive) onRunaway(line) } - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index 659caad1e1..9c4c05f1ee 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -321,8 +321,6 @@ class Power[ReplValsImpl <: ReplVals : TypeTag](val intp: IMain, replVals: ReplV } class RichReplString(s: String) { - // pretty print the string - def pp() { intp.prettyPrint(s) } // make an url out of the string def u: URL = ( if (s contains ":") new URL(s) @@ -337,7 +335,6 @@ class Power[ReplValsImpl <: ReplVals : TypeTag](val intp: IMain, replVals: ReplV } class RichReplURL(url: URL)(implicit codec: Codec) { def slurp(): String = io.Streamable.slurp(url) - def pp() { intp prettyPrint slurp() } } class RichSymbolList(syms: List[Symbol]) { def sigs = syms map (_.defString) diff --git a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala deleted file mode 100644 index dc4582eb17..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -class SeqCompletion[T](elems: Seq[T]) extends CompletionAware { - lazy val completions = elems.indices.toList map ("(%d)" format _) - def completions(verbosity: Int) = completions - private def elemAt(name: String) = - if (completions contains name) Some(elems(name drop 1 dropRight 1 toInt)) else None - - override def execute(name: String) = elemAt(name) - override def follow(name: String) = elemAt(name) map (x => ProductCompletion(x)) -} - -/** TODO - deal with non-case products by giving them _1 _2 etc. */ -class ProductCompletion(root: Product) extends CompletionAware { - lazy val caseFields: List[Any] = root.productIterator.toList - lazy val caseNames: List[String] = ByteCode caseParamNamesForPath root.getClass.getName getOrElse Nil - private def isValid = caseFields.length == caseNames.length - - private def fieldForName(s: String) = (completions indexOf s) match { - case idx if idx > -1 && isValid => Some(caseFields(idx)) - case _ => None - } - - lazy val completions = caseNames - def completions(verbosity: Int) = completions - override def execute(name: String) = fieldForName(name) - override def follow(name: String) = fieldForName(name) map (x => ProductCompletion(x)) -} - -object ProductCompletion { - /** TODO: other traversables. */ - def apply(elem: Any): CompletionAware = elem match { - case x: Seq[_] => new SeqCompletion[Any](x) - case x: Product => new ProductCompletion(x) - // case x: Map[_, _] => - case _ => CompletionAware.Empty - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala b/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala index 99489b7c99..5eb1e0ae18 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala @@ -13,7 +13,6 @@ class ReplProps { val jlineDebug = bool("scala.tools.jline.internal.Log.debug") val jlineTrace = bool("scala.tools.jline.internal.Log.trace") - val noThreads = bool("scala.repl.no-threads") val info = bool("scala.repl.info") val debug = bool("scala.repl.debug") @@ -24,9 +23,3 @@ class ReplProps { val powerInitCode = Prop[JFile]("scala.repl.power.initcode") val powerBanner = Prop[JFile]("scala.repl.power.banner") } - -object ReplPropsKludge { - // !!! short term binary compatibility hack for 2.9.1 to put this - // here - needed a not previously existing object. - def noThreadCreation(settings: Settings) = replProps.noThreads || settings.Yreplsync.value -} diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala b/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala index 175f6263ad..0c9f4fcd47 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala @@ -11,15 +11,6 @@ import scala.PartialFunction.cond import scala.reflect.internal.Chars trait ReplStrings { - // Longest common prefix - def longestCommonPrefix(xs: List[String]): String = { - if (xs.isEmpty || xs.contains("")) "" - else xs.head.head match { - case ch => - if (xs.tail forall (_.head == ch)) "" + ch + longestCommonPrefix(xs map (_.tail)) - else "" - } - } /** Convert a string into code that can recreate the string. * This requires replacing all special characters by escape * codes. It does not add the surrounding " marks. */ diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplTokens.scala b/src/compiler/scala/tools/nsc/interpreter/ReplTokens.scala deleted file mode 100644 index e63fabf151..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/ReplTokens.scala +++ /dev/null @@ -1,286 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import util.{ BatchSourceFile, Indenter } -import scala.tools.nsc.ast.parser.Tokens._ -import java.lang.Integer.toOctalString -import language.{implicitConversions, existentials} - -/** This began as an attempt at a completely minimal - * pretty printer for a token stream, but as it turns out - * it's "minimal, pretty, scala: pick any two." So - * now it's an unattractive hybrid between minimalism - * and other things. Still, it's a big improvement on the - * way I was printing source in the repl, so in it goes. - * - * @author Paul Phillips - */ -abstract class ReplTokens { - val global: Global - - import global._ - import syntaxAnalyzer.{ UnitScanner, token2name } - - // Mostly, this means print <NL> so we can see where - // semicolon inference took place. - private var rawTokens: Boolean = false - def withRawTokens[T](body: => T): T = { - rawTokens = true - try body - finally rawTokens = false - } - // There's the seed of a good idea in here, but you wouldn't - // know it from the current implementation. The objects are - // trying to depict what feelings of coziness a given token - // might have toward its immediate neighbors. But it lacks - // sufficient granularity and a good resolution mechanism. - sealed abstract class Cozy(l: => Boolean, r: => Boolean) { - def left = l - def right = r - } - object Cozy { - def unapply(x: Cozy) = Some((x.left, x.right)) - } - case object |--*--| extends Cozy(false, false) - case object <*--| extends Cozy(true, false) - case object |--*> extends Cozy(false, true) - case object <*> extends Cozy(true, true) - - @annotation.switch def escapedChar(ch: Char): String = ch match { - case '\b' => "\\b" - case '\t' => "\\t" - case '\n' => "\\n" - case '\f' => "\\f" - case '\r' => "\\r" - case '"' => "\\\"" - case '\'' => "\\\'" - case '\\' => "\\\\" - case _ => String.valueOf(ch) - } - def escape(text: String): String = { - text map { ch => - if (ch.isControl) "\\0" + toOctalString(ch) - else escapedChar(ch) - } mkString "" - } - private class Arrow(code: Int) { - def ->(str: String): (Int, ReplToken) = (code, Token(code)(str)) - def ->(tok: ReplToken): (Int, ReplToken) = (code, tok) - } - private val symbolTokenMap = { - implicit def liftToken(code: Int): Arrow = new Arrow(code) - - Map[Int, ReplToken]( - AT -> At, - CASECLASS -> "case class", - CASEOBJECT -> "case object", - COLON -> Colon, - COMMA -> Comma, - DOT -> Dot, - EOF -> Eof, - ERROR -> "<error>", - FALSE -> False, - IMPORT -> Import, - LBRACE -> LBrace, - LBRACKET -> LBracket, - LPAREN -> LParen, - NEWLINE -> Newline, - NEWLINES -> Newlines, - NULL -> Null, - RBRACE -> RBrace, - RBRACKET -> RBracket, - RPAREN -> RParen, - SEMI -> Semi, - SUBTYPE -> Subtype, - SUPERTYPE -> Supertype, - TRUE -> True, - VIEWBOUND -> ViewBound, - XMLSTART -> "<xmlstart>" - ) - } - def isAlphaId(t: ReplToken) = t match { - case Id(name) => name forall (ch => ch.isDigit || ch.isLetter || ch == '_') - case _ => false - } - def isOperatorId(t: ReplToken) = t match { - case Id(name) => !isAlphaId(t) - case _ => false - } - - sealed abstract class ReplToken(val tokenString: String, val cozy: Cozy) { - def this(str: String) = this(str, |--*--| ) - - def insistsOnSpace = false - def cozyRight(other: ReplToken) = (cozy.right || other.cozy.left) - def cozyLeft(other: ReplToken) = (cozy.left || other.cozy.right) - - final def <--?-->(other: ReplToken) = { - !(insistsOnSpace || other.insistsOnSpace) && ( - (this cozyRight other) || - (other cozyLeft this) - ) - } - - // to show invisibles - def rawString = tokenString - override def toString = ( - if (rawTokens) rawString - else tokenString - ) - } - trait InsistCozyRight extends ReplToken { - final override def cozyRight(other: ReplToken) = true - } - trait InsistCozyLeft extends ReplToken { - final override def cozyLeft(other: ReplToken) = true - } - trait InsistCozy extends InsistCozyLeft with InsistCozyRight { } - trait InsistSpaced extends ReplToken { - final override def insistsOnSpace = true - } - trait CozyWithLetters extends ReplToken { - override def cozyRight(other: ReplToken) = isAlphaId(other) || super.cozyRight(other) - override def cozyLeft(other: ReplToken) = isAlphaId(other) || super.cozyLeft(other) - } - trait Brackets extends ReplToken { - private def isCozyToken(t: ReplToken) = t == LBracket || t == RBracket || isAlphaId(t) - override def cozyRight(other: ReplToken) = isCozyToken(other) || super.cozyRight(other) - override def cozyLeft(other: ReplToken) = isCozyToken(other) || super.cozyLeft(other) - } - - case class Token(value: Int)(str: String) extends ReplToken(str) { } - case class Id(name: String) extends ReplToken(name) { } - case class Lit[T](value: T) extends ReplToken(value match { - case s: String => "\"" + s + "\"" - case _ => "" + value - }) - case object At extends ReplToken("@") with InsistCozyRight { } - case object Colon extends ReplToken(":", <*--|) - case object Comma extends ReplToken(",", <*--|) with InsistCozyLeft { } - case object Dot extends ReplToken(".", <*>) with InsistCozy { } - case object Eof extends ReplToken("EOF") - case object ErrorToken extends ReplToken("<internal error>") - case object False extends ReplToken("false") - case object Import extends ReplToken("import") - case object LBrace extends ReplToken("{") with InsistSpaced { } - case object LBracket extends ReplToken("[") with Brackets { } - case object LParen extends ReplToken("(", |--*>) - case object Newline extends ReplToken("\n", <*>) with InsistCozy { override def rawString = "<NL>\n" } - case object Newlines extends ReplToken("\n\n", <*>) with InsistCozy { override def rawString = "<NLS>\n\n" } - case object Null extends ReplToken("null") - case object RBrace extends ReplToken("}", |--*>) with InsistSpaced { } - case object RBracket extends ReplToken("]") with Brackets { } - case object RParen extends ReplToken(")", <*--|) - case object Semi extends ReplToken(";", <*--|) - case object Subtype extends ReplToken("<:") with InsistSpaced { } - case object Supertype extends ReplToken(">:") with InsistSpaced { } - case object True extends ReplToken("true") - case object ViewBound extends ReplToken("<%") with InsistSpaced { } - - class Tokenizer(in: UnitScanner) { - private def translate(tokenCode: Int): ReplToken = tokenCode match { - case IDENTIFIER | BACKQUOTED_IDENT => Id("" + in.name) - case CHARLIT | INTLIT | LONGLIT => Lit(in.intVal) - case DOUBLELIT | FLOATLIT => Lit(in.floatVal) - case STRINGLIT => Lit(escape(in.strVal)) - case SYMBOLLIT => Lit(scala.Symbol(in.strVal)) - case _ => - symbolTokenMap.getOrElse( - tokenCode, - token2name get tokenCode match { - case Some(name) => Token(tokenCode)("" + name) - case _ => Token(tokenCode)("<unknown: " + tokenCode + ">") - } - ) - } - def tokenIterator: Iterator[ReplToken] = ( - Iterator continually { - try translate(in.token) - finally in.nextToken() - } takeWhile (_ ne Eof) - ) - } - - def prettyPrintRaw(tokens: TraversableOnce[ReplToken]) { - withRawTokens(prettyPrint(tokens)) - } - - def prettyPrint(tokens: TraversableOnce[ReplToken]) { - new TokenPrinter prettyPrint tokens - } - - private class TokenPrinter { - type TokenTriple = (ReplToken, ReplToken, ReplToken) - val writer = new Indenter - var prev: List[ReplToken] = Nil - - def isIdentPart(t: ReplToken) = t match { - case Dot | Id(_) => true - case _ => false - } - def prevNonIdent = prev dropWhile isIdentPart match { - case Nil => ErrorToken - case t :: _ => t - } - def inImport = prevNonIdent == Import - - def printToken(left: ReplToken, token: ReplToken) = token match { - case LBrace => - writer openIndent ( - if (writer.atStartOfLine) token - else " " + token - ) - case RBrace => - writer.closeIndent(token) - case tok @ (Newline | Newlines) => - writer.nextIndent(tok) - case _ => - writer print ( - if (writer.atStartOfLine) token - else if (left <--?--> token) token - else " " + token - ) - } - - def prettyPrint(tokens: TraversableOnce[ReplToken]) { - val it = Iterator(Newline) ++ tokens.toIterator ++ Iterator(Newline) sliding 3 map { x => - val List(x1, x2, x3) = x - ((x1, x2, x3)) - } - prettyPrint(it) - } - def prettyPrint(it: Iterator[TokenTriple]) { - while (it.hasNext) it.next match { - // special casing to avoid newline on empty blocks - case (left, LBrace, RBrace) => - it.next - writer print " { }" - // special casing to avoid newlines on import x.{ y, z, q } - case (left, LBrace, _) if inImport => - writer print LBrace - def loop() { - if (it.hasNext) { - val (_, tok, _) = it.next - if (tok != Comma) { - writer print " " - } - writer print tok - if (tok != RBrace) - loop() - } - } - loop() - case (left, token, right) => - printToken(left, token) - - if (it.hasNext) prev ::= token - else printToken(token, right) - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/Runner.scala b/src/compiler/scala/tools/nsc/interpreter/Runner.scala deleted file mode 100644 index f9f75da3c6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/Runner.scala +++ /dev/null @@ -1,11 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -object Runner { - def main(args: Array[String]): Unit = new ILoop process args -} diff --git a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala index 992bef8056..a57b047bc0 100644 --- a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala @@ -18,7 +18,6 @@ extends InteractiveReader { val history = NoHistory val completion = NoCompletion - val keyBindings: List[KeyBinding] = Nil def init() = () def reset() = () diff --git a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala deleted file mode 100644 index 9979814afb..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import xml.{ XML, Group, Node, NodeSeq } -import XMLCompletion._ -import scala.collection.{ mutable, immutable } - -class XMLCompletion(root: Node) extends CompletionAware { - private val nodeCache = new mutable.HashMap[String, Node] - private def getNode(s: String): Option[Node] = { - completions // make sure cache is populated - nodeCache get s - } - - lazy val completions: List[String] = { - def children = root.child.toList - def uniqueTags = children groupBy (_.label) filter (_._2.size == 1) map (_._1) - val uniqs = uniqueTags.toList - - children.foldLeft(List[String]())((res, node) => { - val name = node.label - def count = res filter (_ startsWith (name + "[")) size // ] - val suffix = if (uniqs contains name) "" else "[%d]" format (count + 1) - val s = name + suffix - - nodeCache(s) = node - - s :: res - }).sorted - } - def completions(verbosity: Int) = completions - - override def execute(id: String) = getNode(id) - override def follow(id: String) = getNode(id) map (x => new XMLCompletion(x)) -} - -object XMLCompletion { - def apply(x: Node) = new XMLCompletion(x) -} diff --git a/src/compiler/scala/tools/nsc/interpreter/session/JLineHistory.scala b/src/compiler/scala/tools/nsc/interpreter/session/JLineHistory.scala index a09182319c..795ca79668 100644 --- a/src/compiler/scala/tools/nsc/interpreter/session/JLineHistory.scala +++ b/src/compiler/scala/tools/nsc/interpreter/session/JLineHistory.scala @@ -45,11 +45,5 @@ object JLineHistory { override def toString = "History(size = " + size + ", index = " + index + ")" } - def apply(): JLineHistory = - try { new JLineFileHistory } - catch { case x: Exception => - Console.println("Error creating file history: memory history only. " + x) - util.Exceptional(x).show() - new SimpleHistory() - } -}
\ No newline at end of file + def apply(): JLineHistory = try new JLineFileHistory catch { case x: Exception => new SimpleHistory() } +} diff --git a/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala b/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala deleted file mode 100644 index c9ed535841..0000000000 --- a/src/compiler/scala/tools/nsc/io/ClassAndJarInfo.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package io - -import java.net.{ URL, URLClassLoader } -import java.io.IOException -import collection.JavaConverters._ - -/** A convenience class for finding the jar with the bytecode for - * a given Class object and similar common tasks. - */ -class ClassAndJarInfo[T: ClassTag] { - val tag = classTag[T] - def clazz = tag.erasure - def internalName = clazz.getName.replace('.', '/') - - def resourceURL = new URLClassLoader(Array[URL]()) getResource internalName + ".class" - - def baseOfPath(path: String) = path indexOf '!' match { - case -1 => path stripSuffix internalName + ".class" - case idx => path take idx - } - - def simpleClassName = clazz.getName split """[$.]""" last - def classUrl = clazz getResource simpleClassName + ".class" - def codeSource = protectionDomain.getCodeSource() - def jarManifest = ( - try new JManifest(jarManifestUrl.openStream()) - catch { case _: IOException => new JManifest() } - ) - def jarManifestMainAttrs = jarManifest.getMainAttributes().asScala - def jarManifestUrl = new URL(baseOfPath("" + classUrl) + "!/META-INF/MANIFEST.MF") - def locationFile = File(locationUrl.toURI.getPath()) - def locationUrl = if (codeSource == null) new URL("file:///") else codeSource.getLocation() - def protectionDomain = clazz.getProtectionDomain() - def rootClasspath = rootPossibles find (_.exists) - def rootFromLocation = Path(locationUrl.toURI.getPath()) - def rootFromResource = Path(baseOfPath(classUrl.getPath) stripPrefix "file:") - def rootPossibles = Iterator(rootFromResource, rootFromLocation) -} diff --git a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala b/src/compiler/scala/tools/nsc/io/NullPrintStream.scala deleted file mode 100644 index 52c7ddc74b..0000000000 --- a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package io - -import java.io.{ PrintStream, ByteArrayOutputStream } - -/** A sink for when you want to discard all output. - */ -class NullPrintStream extends PrintStream(new ByteArrayOutputStream()) { } - -object NullPrintStream extends NullPrintStream { - def setOut() = Console setOut this - def setErr() = Console setErr this - def setOutAndErr() = { setOut() ; setErr() } - def sinkingOutAndErr[T](body: => T): T = - Console.withOut(this) { - Console.withErr(this) { - body - } - } - - def sinkingSystemOutAndErr[T](body: => T): T = { - val savedOut = System.out - val savedErr = System.err - System setOut NullPrintStream - System setErr NullPrintStream - try body - finally { - System setOut savedOut - System setErr savedErr - } - } -} diff --git a/src/compiler/scala/tools/nsc/io/Sources.scala b/src/compiler/scala/tools/nsc/io/Sources.scala deleted file mode 100644 index 25d27acae8..0000000000 --- a/src/compiler/scala/tools/nsc/io/Sources.scala +++ /dev/null @@ -1,86 +0,0 @@ -package scala.tools.nsc -package io - -import util.ClassPath -import java.util.concurrent.{ Future, ConcurrentHashMap, ExecutionException } -import java.util.zip.ZipException -import collection.JavaConverters._ -import Properties.{ envOrElse, propOrElse } - -class Sources(val path: String) { - val expandedPath = ClassPath.join(ClassPath expandPath path: _*) - val cache = new ConcurrentHashMap[String, List[Fileish]] - def allNames = cache.keys.asScala.toList.sorted - def apply(name: String) = get(name) - def size = cache.asScala.values map (_.length) sum - def isEmpty = path == "" - - private var debug = false - private def dbg(msg: => Any) = if (debug) Console println msg - private val partitioned = ClassPath toPaths expandedPath partition (_.isDirectory) - - val dirs = partitioned._1 map (_.toDirectory) - val jars = partitioned._2 filter Jar.isJarOrZip map (_.toFile) - val (isDone, force) = ( - if (path == "") (() => true, () => ()) - else { - val f1 = spawn(calculateDirs()) - val f2 = spawn(calculateJars()) - val fn1 = () => { f1.isDone() && f2.isDone() } - val fn2 = () => { f1.get() ; f2.get() ; () } - - (fn1, fn2) - } - ) - - private def catchZip(body: => Unit): Unit = { - try body - catch { case x: ZipException => dbg("Caught: " + x) } - } - - private def calculateDirs() = - dirs foreach { d => dbg(d) ; catchZip(addSources(d.deepFiles map (x => Fileish(x)))) } - - private def calculateJars() = - jars foreach { j => dbg(j) ; catchZip(addSources(new Jar(j).fileishIterator)) } - - private def addSources(fs: TraversableOnce[Fileish]) = - fs foreach { f => if (f.isSourceFile) add(f.name, f) } - - private def get(key: String): List[Fileish] = - if (cache containsKey key) cache.get(key) else Nil - - private def add(key: String, value: Fileish) = { - if (cache containsKey key) cache.replace(key, value :: cache.get(key)) - else cache.put(key, List(value)) - } - override def toString = "Sources(%d dirs, %d jars, %d sources)".format( - dirs.size, jars.size, cache.asScala.values map (_.length) sum - ) -} - -trait LowPrioritySourcesImplicits { - self: Sources.type => - - implicit def fallbackSources: Sources = defaultSources -} - -object Sources extends LowPrioritySourcesImplicits { - val empty = new Sources("") - - private def libraryInits = ClassPath.scalaLibrary.toList flatMap (_.toAbsolute.parents) - private def librarySourceDir = libraryInits map (_ / "src") find (_.isDirectory) - private def expandedSourceDir = librarySourceDir.toList flatMap (ClassPath expandDir _.path) - - private val initialPath = sys.props.traceSourcePath.value - private val initialSources = apply(expandedSourceDir :+ initialPath: _*) - - def defaultSources = { - val path = sys.props.traceSourcePath.value - if (path == "") empty - else if (path == initialPath) initialSources - else apply(expandedSourceDir :+ path: _*) - } - - def apply(paths: String*): Sources = new Sources(ClassPath.join(paths: _*)) -} diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala index 52e6de0bed..d29030603e 100644 --- a/src/compiler/scala/tools/nsc/io/package.scala +++ b/src/compiler/scala/tools/nsc/io/package.scala @@ -23,9 +23,6 @@ package object io { def callable[T](body: => T): Callable[T] = new Callable[T] { override def call() = body } def spawn[T](body: => T): Future[T] = daemonThreadPool submit callable(body) def submit(runnable: Runnable) = daemonThreadPool submit runnable - def runnableFn(f: () => Unit): Runnable = runnable(f()) - def callableFn[T](f: () => T): Callable[T] = callable(f()) - def spawnFn[T](f: () => T): Future[T] = spawn(f()) // Create, start, and return a daemon thread def daemonize(body: => Unit): Thread = newThread(_ setDaemon true)(body) diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 5c413243e8..f71e067366 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -872,7 +872,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { skipAhead() accept(RBRACE) } - ValDef(Modifiers(Flags.JAVA | Flags.STATIC), name, enumType, blankExpr) + // The STABLE flag is to signal to namer that this was read from a + // java enum, and so should be given a Constant type (thereby making + // it usable in annotations.) + ValDef(Modifiers(Flags.STABLE | Flags.JAVA | Flags.STATIC), name, enumType, blankExpr) } } diff --git a/src/compiler/scala/tools/nsc/package.scala b/src/compiler/scala/tools/nsc/package.scala index a908062b2f..88d600c113 100644 --- a/src/compiler/scala/tools/nsc/package.scala +++ b/src/compiler/scala/tools/nsc/package.scala @@ -6,11 +6,6 @@ package scala.tools package object nsc { - @deprecated("Use a class in the scala.tools.nsc.interpreter package.", "2.9.0") - type InterpreterSettings = interpreter.ISettings - @deprecated("Use a class in the scala.tools.nsc.interpreter package.", "2.9.0") - val InterpreterResults = interpreter.Results - type Phase = scala.reflect.internal.Phase val NoPhase = scala.reflect.internal.NoPhase diff --git a/src/compiler/scala/tools/nsc/reporters/ReporterTimer.scala b/src/compiler/scala/tools/nsc/reporters/ReporterTimer.scala deleted file mode 100644 index f55d0684c8..0000000000 --- a/src/compiler/scala/tools/nsc/reporters/ReporterTimer.scala +++ /dev/null @@ -1,18 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2002-2011 LAMP/EPFL - * @author Martin Odersky - */ - -package scala.tools.nsc -package reporters - -import scala.tools.util.AbstractTimer - -/** - * This class implements a timer that uses a Reporter to issue - * timings. - */ -class ReporterTimer(reporter: Reporter) extends AbstractTimer { - def issue(msg: String, duration: Long) = - reporter.info(null, "[" + msg + " in " + duration + "ms]", false) -} diff --git a/src/compiler/scala/tools/nsc/settings/AestheticSettings.scala b/src/compiler/scala/tools/nsc/settings/AestheticSettings.scala index 63775ff1c5..2baff0bb1c 100644 --- a/src/compiler/scala/tools/nsc/settings/AestheticSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AestheticSettings.scala @@ -27,7 +27,6 @@ trait AestheticSettings { def future = settings.future.value def logClasspath = settings.Ylogcp.value def printStats = settings.Ystatistics.value - def richExes = settings.YrichExes.value || sys.props.traceSourcePath.isSet def target = settings.target.value def unchecked = settings.unchecked.value def verbose = settings.verbose.value diff --git a/src/compiler/scala/tools/nsc/settings/ImmutableSettings.scala b/src/compiler/scala/tools/nsc/settings/ImmutableSettings.scala deleted file mode 100644 index 4de0c2332c..0000000000 --- a/src/compiler/scala/tools/nsc/settings/ImmutableSettings.scala +++ /dev/null @@ -1,11 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package settings - -/** TODO. - */ -class ImmutableSettings diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 0a9d25af7e..4aa30038f6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -89,7 +89,7 @@ trait ScalaSettings extends AbsScalaSettings val Xmigration28 = BooleanSetting ("-Xmigration", "Warn about constructs whose behavior may have changed between 2.7 and 2.8.") val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") - val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode.") + val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") val plugin = MultiStringSetting("-Xplugin", "file", "Load one or more plugins from files.") val disable = MultiStringSetting("-Xplugin-disable", "plugin", "Disable the given plugin(s).") val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") @@ -146,9 +146,6 @@ trait ScalaSettings extends AbsScalaSettings val noimports = BooleanSetting ("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.") val nopredef = BooleanSetting ("-Yno-predef", "Compile without importing Predef.") val noAdaptedArgs = BooleanSetting ("-Yno-adapted-args", "Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.") - val Yprofile = PhasesSetting ("-Yprofile", "(Requires jvm -agentpath to contain yjgpagent) Profile CPU usage of") - val YprofileMem = BooleanSetting ("-Yprofile-memory", "Profile memory, get heap snapshot after each compiler run (requires yjpagent, see above).") - val YprofileClass = StringSetting ("-Yprofile-class", "class", "Name of profiler class.", "scala.tools.util.YourkitProfiling") val Yrecursion = IntSetting ("-Yrecursion", "Set recursion depth used when locking symbols.", 0, Some((0, Int.MaxValue)), (_: String) => None) val selfInAnnots = BooleanSetting ("-Yself-in-annots", "Include a \"self\" identifier inside of annotations.") val Xshowtrees = BooleanSetting ("-Yshow-trees", "(Requires -Xprint:) Print detailed ASTs in formatted form.") @@ -167,9 +164,7 @@ trait ScalaSettings extends AbsScalaSettings val stopBefore = PhasesSetting ("-Ystop-before", "Stop before") val refinementMethodDispatch = ChoiceSetting ("-Ystruct-dispatch", "policy", "structural method dispatch policy", List("no-cache", "mono-cache", "poly-cache", "invoke-dynamic"), "poly-cache") - val globalClass = StringSetting ("-Yglobal-class", "class", "subclass of scala.tools.nsc.Global to use for compiler", "") val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") - val YrichExes = BooleanSetting ("-Yrich-exceptions", "Fancier exceptions. Set source search path with -D" + sys.SystemProperties.traceSourcePath.key) val Ybuilderdebug = ChoiceSetting ("-Ybuilder-debug", "manager", "Compile using the specified build manager.", List("none", "refined", "simple"), "none") val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.") val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") @@ -180,7 +175,6 @@ trait ScalaSettings extends AbsScalaSettings val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() - val YnoProductN = BooleanSetting ("-Yno-productN", "Do not add ProductN to case classes") def stop = stopAfter diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index a69b88a5df..1bb0948168 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -40,7 +40,7 @@ trait StandardScalaSettings { val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.") val optimise: BooleanSetting // depends on post hook which mutates other settings val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.") - val target = ChoiceSetting ("-target", "target", "Target platform for object files.", List("jvm-1.5", "msil"), "jvm-1.5") + val target = ChoiceSetting ("-target", "target", "Target platform for object files.", List("jvm-1.5", "jvm-1.5-asm", "jvm-1.6", "jvm-1.7", "msil"), "jvm-1.5") val unchecked = BooleanSetting ("-unchecked", "Enable detailed unchecked (erasure) warnings.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index ef73d6db1a..7c61ec032e 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -13,7 +13,6 @@ import scala.collection.{ mutable, immutable } import scala.collection.mutable.{ ListBuffer, ArrayBuffer } import scala.annotation.switch import scala.reflect.internal.pickling.{PickleBuffer, ByteCodecs} -import scala.reflect.internal.{ JvmClassInfo, JvmMemberInfo } import scala.tools.nsc.io.AbstractFile /** This abstract class implements a class file parser. @@ -47,8 +46,6 @@ abstract class ClassfileParser { private def currentIsTopLevel = currentClass.toString.indexOf('$') < 0 - private var jvmInfo: JvmClassInfo = _ - private object unpickler extends scala.reflect.internal.pickling.UnPickler { val global: ClassfileParser.this.global.type = ClassfileParser.this.global } @@ -98,7 +95,6 @@ abstract class ClassfileParser { this.in = new AbstractFileReader(file) this.clazz = if (root.isModule) root.companionClass else root this.staticModule = clazz.companionModule - this.jvmInfo = JvmClassInfo.fromBytes(file.toByteArray) this.isScala = false parseHeader @@ -1245,9 +1241,6 @@ abstract class ClassfileParser { in.skip(2 * ifaces) } - protected def getOwner(info: JvmMemberInfo): Symbol = - if (info.isStatic) moduleClass else clazz - protected def getOwner(flags: Int): Symbol = if (isStatic(flags)) moduleClass else clazz diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 4f833c82d3..bc4483923a 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -362,9 +362,8 @@ abstract class Constructors extends Transform with ast.TreeDSL { val tree = If( Apply( - Select( - Apply(gen.mkAttributedRef(specializedFlag), List()), - definitions.getMember(definitions.BooleanClass, nme.UNARY_!)), + CODE.NOT ( + Apply(gen.mkAttributedRef(specializedFlag), List())), List()), Block(stats, Literal(Constant())), EmptyTree) diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index b8e43dfc50..ea66dbedd6 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -6,7 +6,6 @@ package scala.tools.nsc package transform -import scala.tools.reflect.SigParser import scala.reflect.internal.ClassfileConstants._ import scala.collection.{ mutable, immutable } import symtab._ @@ -66,16 +65,6 @@ abstract class Erasure extends AddInterfaces } } - // for debugging signatures: traces logic given system property - // performance: get the value here - val traceSignatures = (sys.BooleanProp keyExists "scalac.sigs.trace").value - private object traceSig extends util.Tracer(() => traceSignatures) { - override def stringify(x: Any) = x match { - case tp: Type => super.stringify(dropAllRefinements(tp)) - case _ => super.stringify(x) - } - } - override protected def verifyJavaErasure = settings.Xverify.value || settings.debug.value def needsJavaSig(tp: Type) = !settings.Ynogenericsig.value && NeedsSigCollector.collect(tp) @@ -173,21 +162,6 @@ abstract class Erasure extends AddInterfaces } } - /** Run the signature parser to catch bogus signatures. - */ - def isValidSignature(sym: Symbol, sig: String) = ( - /** Since we're using a sun internal class for signature validation, - * we have to allow for it not existing or otherwise malfunctioning: - * in which case we treat every signature as valid. Medium term we - * should certainly write independent signature validation. - */ - SigParser.isParserAvailable && ( - if (sym.isMethod) SigParser verifyMethod sig - else if (sym.isTerm) SigParser verifyType sig - else SigParser verifyClass sig - ) - ) - private def hiBounds(bounds: TypeBounds): List[Type] = bounds.hi.normalize match { case RefinedType(parents, _) => parents map (_.normalize) case tp => tp :: Nil @@ -199,7 +173,7 @@ abstract class Erasure extends AddInterfaces def javaSig(sym0: Symbol, info: Type): Option[String] = beforeErasure { val isTraitSignature = sym0.enclClass.isTrait - def superSig(parents: List[Type]) = traceSig("superSig", parents) { + def superSig(parents: List[Type]) = { val ps = ( if (isTraitSignature) { // java is unthrilled about seeing interfaces inherit from classes @@ -213,7 +187,7 @@ abstract class Erasure extends AddInterfaces (ps map boxedSig).mkString } def boxedSig(tp: Type) = jsig(tp, primitiveOK = false) - def boundsSig(bounds: List[Type]) = traceSig("boundsSig", bounds) { + def boundsSig(bounds: List[Type]) = { val (isTrait, isClass) = bounds partition (_.typeSymbol.isTrait) val classPart = isClass match { case Nil => ":" // + boxedSig(ObjectClass.tpe) @@ -222,7 +196,7 @@ abstract class Erasure extends AddInterfaces classPart :: (isTrait map boxedSig) mkString ":" } def paramSig(tsym: Symbol) = tsym.name + boundsSig(hiBounds(tsym.info.bounds)) - def polyParamSig(tparams: List[Symbol]) = traceSig("polyParamSig", tparams) ( + def polyParamSig(tparams: List[Symbol]) = ( if (tparams.isEmpty) "" else tparams map paramSig mkString ("<", "", ">") ) @@ -315,22 +289,11 @@ abstract class Erasure extends AddInterfaces else jsig(etp) } } - val result = traceSig("javaSig", (sym0, info)) { - if (needsJavaSig(info)) { - try Some(jsig(info, toplevel = true)) - catch { case ex: UnknownSig => None } - } - else None - } - // Debugging: immediately verify signatures when tracing. - if (traceSignatures) { - result foreach { sig => - if (!isValidSignature(sym0, sig)) - println("**** invalid signature for " + sym0 + ": " + sig) - } + if (needsJavaSig(info)) { + try Some(jsig(info, toplevel = true)) + catch { case ex: UnknownSig => None } } - - result + else None } class UnknownSig extends Exception @@ -480,16 +443,22 @@ abstract class Erasure extends AddInterfaces // TODO: should we do this for user-defined unapplies as well? // does the first argument list have exactly one argument -- for user-defined unapplies we can't be sure def maybeWrap(bridgingCall: Tree): Tree = { - val canReturnNone = ( // can't statically know which member is going to be selected, so don't let this depend on member.isSynthetic + val guardExtractor = ( // can't statically know which member is going to be selected, so don't let this depend on member.isSynthetic (member.name == nme.unapply || member.name == nme.unapplySeq) && !afterErasure((member.tpe <:< other.tpe))) // no static guarantees (TODO: is the subtype test ever true?) - if (canReturnNone) { - import CODE._ + import CODE._ + val _false = FALSE_typed + val pt = member.tpe.resultType + lazy val zero = + if (_false.tpe <:< pt) _false + else if (NoneModule.tpe <:< pt) REF(NoneModule) + else EmptyTree + + if (guardExtractor && (zero ne EmptyTree)) { val typeTest = gen.mkIsInstanceOf(REF(bridge.firstParam), member.tpe.params.head.tpe) - IF (typeTest) THEN bridgingCall ELSE REF(NoneModule) - } - else bridgingCall + IF (typeTest) THEN bridgingCall ELSE zero + } else bridgingCall } val rhs = member.tpe match { case MethodType(Nil, ConstantType(c)) => Literal(c) @@ -619,7 +588,7 @@ abstract class Erasure extends AddInterfaces tree.duplicate setType pt } else if (tree.tpe != null && tree.tpe.typeSymbol == ArrayClass && pt.typeSymbol == ArrayClass) { // See SI-2386 for one example of when this might be necessary. - val needsExtraCast = isScalaValueType(tree.tpe.typeArgs.head) && !isScalaValueType(pt.typeArgs.head) + val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree gen.mkAttributedCast(tree1, pt) } else gen.mkAttributedCast(tree, pt) @@ -643,7 +612,8 @@ abstract class Erasure extends AddInterfaces else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { adaptToType(box(tree, pt.toString), pt) } else if (tree.tpe.isInstanceOf[MethodType] && tree.tpe.params.isEmpty) { - assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) + // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val + //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) // } else if (pt <:< tree.tpe) // cast(tree, pt) diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 8daad8a2ac..9cffb6a1e1 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -149,7 +149,7 @@ abstract class ExplicitOuter extends InfoTransform if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5 sym.makeNotPrivate(sym.owner) } - if (sym.owner.isTrait) sym setNotFlag PROTECTED // 6 + if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6 if (sym.isClassConstructor && isInner(sym.owner)) { // 1 val p = sym.newValueParameter(innerClassConstructorParamName, sym.pos) .setInfo(sym.owner.outerClass.thisType) @@ -448,8 +448,8 @@ abstract class ExplicitOuter extends InfoTransform override def transform(tree: Tree): Tree = { val sym = tree.symbol if (sym != null && sym.isType) { //(9) - sym setNotFlag PRIVATE - sym setNotFlag PROTECTED + if (sym.isPrivate) sym setFlag notPRIVATE + if (sym.isProtected) sym setFlag notPROTECTED } tree match { case Template(parents, self, decls) => diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 618a1cbba4..718e58b855 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -108,28 +108,30 @@ abstract class LambdaLift extends InfoTransform { * } */ private def markFree(sym: Symbol, enclosure: Symbol): Boolean = { - debuglog("mark free: " + sym + " of " + sym.owner + " marked free in " + enclosure) - if (enclosure == sym.owner.logicallyEnclosingMember) true - else if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false - else { - val ss = symSet(free, enclosure) - if (!ss(sym)) { - ss addEntry sym - renamable addEntry sym - beforePickler { - // The param symbol in the MethodType should not be renamed, only the symbol in scope. This way, - // parameter names for named arguments are not changed. Example: without cloning the MethodType, - // def closure(x: Int) = { () => x } - // would have the signature - // closure: (x$1: Int)() => Int - if (sym.isParameter && sym.owner.info.paramss.exists(_ contains sym)) - sym.owner modifyInfo (_ cloneInfo sym.owner) + debuglog("mark free: " + sym.fullLocationString + " marked free in " + enclosure) + (enclosure == sym.owner.logicallyEnclosingMember) || { + debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember)) + if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false + else { + val ss = symSet(free, enclosure) + if (!ss(sym)) { + ss addEntry sym + renamable addEntry sym + beforePickler { + // The param symbol in the MethodType should not be renamed, only the symbol in scope. This way, + // parameter names for named arguments are not changed. Example: without cloning the MethodType, + // def closure(x: Int) = { () => x } + // would have the signature + // closure: (x$1: Int)() => Int + if (sym.isParameter && sym.owner.info.paramss.exists(_ contains sym)) + sym.owner modifyInfo (_ cloneInfo sym.owner) + } + changedFreeVars = true + debuglog("" + sym + " is free in " + enclosure); + if (sym.isVariable) sym setFlag CAPTURED } - changedFreeVars = true - debuglog("" + sym + " is free in " + enclosure); - if (sym.isVariable) sym setFlag CAPTURED + !enclosure.isClass } - !enclosure.isClass } } @@ -273,8 +275,11 @@ abstract class LambdaLift extends InfoTransform { if (ps.isEmpty) searchIn(enclosure.skipConstructor.owner) else ps.head } - debuglog("proxy " + sym + " in " + sym.owner + " from " + currentOwner.ownerChain.mkString(" -> ") + - " " + sym.owner.logicallyEnclosingMember) + debuglog("proxy %s from %s has logical enclosure %s".format( + sym.debugLocationString, + currentOwner.debugLocationString, + sym.owner.logicallyEnclosingMember.debugLocationString) + ) if (isSameOwnerEnclosure(sym)) sym else searchIn(currentOwner) diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index 85ba539993..e8387c80f5 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -12,7 +12,8 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD import CODE._ val phaseName: String = "lazyvals" - val FLAGS_PER_WORD: Int + private val FLAGS_PER_BYTE: Int = 8 // Byte + private def bitmapKind = ByteClass def newTransformer(unit: CompilationUnit): Transformer = new LazyValues(unit) @@ -53,7 +54,6 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 import symtab.Flags._ - import lazyVals._ /** Perform the following transformations: * - for a lazy accessor inside a method, make it check the initialization bitmap @@ -68,8 +68,20 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD curTree = tree tree match { + + case Block(_, _) => + val block1 = super.transform(tree) + val Block(stats, expr) = block1 + val stats1 = stats.flatMap(_ match { + case Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if (nme.newLazyValSlowComputeName(n2) == n1) => + List(d1, d2) + case stat => + List(stat) + }) + treeCopy.Block(block1, stats1, expr) + case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) { - val res = if (!sym.owner.isClass && sym.isLazy) { + val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) { val enclosingClassOrDummyOrMethod = { val enclMethod = sym.enclMethod @@ -84,13 +96,14 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD } val idx = lazyVals(enclosingClassOrDummyOrMethod) lazyVals(enclosingClassOrDummyOrMethod) = idx + 1 - val rhs1 = mkLazyDef(enclosingClassOrDummyOrMethod, super.transform(rhs), idx, sym) + val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym) sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR) - rhs1 - } else - super.transform(rhs) - - deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) + (rhs1, sDef) + } else + (transform(rhs), EmptyTree) + + val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) + if (slowPathDef != EmptyTree) Block(slowPathDef, ddef1) else ddef1 } case Template(_, _, body) => atOwner(currentOwner) { @@ -126,7 +139,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass => deriveValDef(tree) { rhs0 => - val rhs = super.transform(rhs0) + val rhs = transform(rhs0) if (LocalLazyValFinder.find(rhs)) typed(addBitmapDefs(sym, rhs)) else rhs } @@ -175,6 +188,24 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD case _ => prependStats(bmps, rhs) } } + + def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + stats: List[Tree], retVal: Tree): Tree = { + val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name), lzyVal.pos, STABLE | PRIVATE) + defSym setInfo MethodType(List(), lzyVal.tpe.resultType) + defSym.owner = lzyVal.owner + if (bitmaps.contains(lzyVal)) + bitmaps(lzyVal).map(_.owner = defSym) + val rhs: Tree = (gen.mkSynchronizedCheck(clazz, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) + DEF(defSym).mkTree(addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) setSymbol defSym + } + + + def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + stats: List[Tree], retVal: Tree): (Tree, Tree) = { + val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal) + (If(cond, Apply(ID(slowPathDef.symbol), List()), retVal), slowPathDef) + } /** return a 'lazified' version of rhs. Rhs should conform to the * following schema: @@ -185,33 +216,38 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD * <rhs> when the lazy value has type Unit (for which there is no field * to cache it's value. * - * The result will be a tree of the form - * { - * if ((bitmap$n & MASK) == 0) { + * Similarly as for normal lazy val members (see Mixin), the result will be a tree of the form + * { if ((bitmap&n & MASK) == 0) this.l$compute() + * else l$ + * + * def l$compute() = { synchronized(enclosing_class_or_dummy) { + * if ((bitmap$n & MASK) == 0) { * l$ = <rhs> * bitmap$n = bimap$n | MASK + * }} + * l$ * } - * l$ * } - * where bitmap$n is an int value acting as a bitmap of initialized values. It is - * the 'n' is (offset / 32), the MASK is (1 << (offset % 32)). If the value has type - * unit, no field is used to cache the value, so the resulting code is: + * where bitmap$n is a byte value acting as a bitmap of initialized values. It is + * the 'n' is (offset / 8), the MASK is (1 << (offset % 8)). If the value has type + * unit, no field is used to cache the value, so the l$compute will now look as following: * { - * if ((bitmap$n & MASK) == 0) { + * def l$compute() = { synchronized(enclosing_class_or_dummy) { + * if ((bitmap$n & MASK) == 0) { * <rhs>; * bitmap$n = bimap$n | MASK - * } + * }} * () + * } * } */ - private def mkLazyDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): Tree = { + private def mkLazyDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { val bitmapSym = getBitmapFor(methOrClass, offset) - val mask = LIT(1 << (offset % FLAGS_PER_WORD)) + val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) def mkBlock(stmt: Tree) = BLOCK(stmt, mkSetFlag(bitmapSym, mask, bitmapRef), UNIT) - val (block, res) = tree match { case Block(List(assignment), res) if !lazyUnit(lazyVal) => (mkBlock(assignment), res) @@ -219,16 +255,13 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD (mkBlock(rhs), UNIT) } - val cond = (bitmapRef INT_& mask) INT_== ZERO - - atPos(tree.pos)(localTyper.typed { - def body = gen.mkDoubleCheckedLocking(methOrClass.enclClass, cond, List(block), Nil) - BLOCK(body, res) - }) + val cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) + val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) + (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) } private def mkSetFlag(bmp: Symbol, mask: Tree, bmpRef: Tree): Tree = - bmpRef === (bmpRef INT_| mask) + bmpRef === (bmpRef GEN_| (mask, bitmapKind)) val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil @@ -236,12 +269,12 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD * given offset. */ private def getBitmapFor(meth: Symbol, offset: Int): Symbol = { - val n = offset / FLAGS_PER_WORD + val n = offset / FLAGS_PER_BYTE val bmps = bitmaps(meth) if (bmps.length > n) bmps(n) else { - val sym = meth.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), meth.pos).setInfo(IntClass.tpe) + val sym = meth.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), meth.pos).setInfo(ByteClass.tpe) beforeTyper { sym addAnnotation VolatileAttr } diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 19f32a5411..79b9317f20 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -29,9 +29,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { /** Map a lazy, mixedin field accessor to it's trait member accessor */ private val initializer = perRunCaches.newMap[Symbol, Symbol] - /** Deferred bitmaps that will be added during the transformation of a class */ - private val deferredBitmaps = perRunCaches.newMap[Symbol, List[Tree]]() withDefaultValue Nil - // --------- helper functions ----------------------------------------------- /** A member of a trait is implemented statically if its implementation after the @@ -495,6 +492,19 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * fields count as fields defined by the class itself. */ private val fieldOffset = perRunCaches.newMap[Symbol, Int]() + + private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() + + // ByteClass, IntClass, LongClass + private def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) + + private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { + case BooleanClass => 1 + case ByteClass => 8 + case IntClass => 32 + case LongClass => 64 + } + /** The first transform; called in a pre-order traversal at phase mixin * (that is, every node is processed before its children). @@ -613,12 +623,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (field.accessed hasAnnotation TransientAttr) { if (isNormal) BITMAP_TRANSIENT else BITMAP_CHECKINIT_TRANSIENT - } - else if (field hasFlag PRIVATE | notPRIVATE) { - if (isNormal) BITMAP_PRIVATE - else BITMAP_CHECKINIT - } - else { + } else { if (isNormal) BITMAP_NORMAL else BITMAP_CHECKINIT } @@ -686,11 +691,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else newDefs ::: (stats filter isNotDuplicate) } - def addDeferredBitmap(clazz: Symbol, tree: Tree) { - // Append the set of deferred defs - deferredBitmaps(clazz) ::= typedPos(clazz.pos)(tree) - } - /** If `stat` is a superaccessor, complete it by adding a right-hand side. * Note: superaccessors are always abstract until this point. * The method to call in a superaccessor is stored in the accessor symbol's alias field. @@ -708,108 +708,116 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { stat } - import lazyVals._ - - /** - * Private or transient lazy vals use bitmaps that are private for the class context, - * unlike public or protected vals, which can use inherited bitmaps. - * Similarly fields in the checkinit mode use private bitmaps. - */ - def isLocalBitmapField(field: Symbol) = ( - field.accessed.hasAnnotation(TransientAttr) - || field.hasFlag(PRIVATE | notPRIVATE) - || isCheckInitField(field) - ) - /** * Return the bitmap field for 'offset'. Depending on the hierarchy it is possible to reuse * the bitmap of its parents. If that does not exist yet we create one. */ - def bitmapFor(clazz0: Symbol, offset: Int, field: Symbol, searchParents: Boolean = true): Symbol = { + def bitmapFor(clazz0: Symbol, offset: Int, field: Symbol): Symbol = { val category = bitmapCategory(field) - val bitmapName = nme.newBitmapName(category, offset / FLAGS_PER_WORD) - val sym = clazz0.info.member(bitmapName) + val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)) + val sym = clazz0.info.decl(bitmapName) assert(!sym.isOverloaded, sym) - + def createBitmap: Symbol = { - val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo IntClass.tpe + val bitmapKind = bitmapKindForCategory(category) + val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo bitmapKind.tpe beforeTyper(sym addAnnotation VolatileAttr) category match { case nme.BITMAP_TRANSIENT | nme.BITMAP_CHECKINIT_TRANSIENT => sym addAnnotation TransientAttr case _ => } - category match { - case nme.BITMAP_NORMAL if field.isLazy => sym setFlag PROTECTED - case _ => sym setFlag PrivateLocal + val init = bitmapKind match { + case BooleanClass => VAL(sym) === FALSE + case _ => VAL(sym) === ZERO } - + + sym setFlag PrivateLocal clazz0.info.decls.enter(sym) - if (clazz0 == clazz) - addDef(clazz.pos, VAL(sym) === ZERO) - else { - //FIXME: the assertion below will not work because of the way bitmaps are added. - // They should be added during infoTransform, so that in separate compilation, bitmap - // is a member of clazz and doesn't fail the condition couple lines below. - // This works, as long as we assume that the previous classes were compiled correctly. - //assert(clazz0.sourceFile != null) - addDeferredBitmap(clazz0, VAL(sym) === ZERO) - } + addDef(clazz0.pos, init) sym } if (sym ne NoSymbol) sym - else if (searchParents && !isLocalBitmapField(field)) - bitmapForParents(clazz0, offset, field) getOrElse createBitmap else createBitmap } - - def bitmapForParents(clazz0: Symbol, offset: Int, valSym: Symbol): Option[Symbol] = { - def requiredBitmaps(fs: Int): Int = if (fs == 0) -1 else (fs - 1) / FLAGS_PER_WORD - val bitmapNum = offset / FLAGS_PER_WORD - - // filter private and transient - // since we do not inherit normal values (in checkinit mode) also filter them out - // !!! Not sure how that comment relates to this code... - superClassesToCheck(clazz0) foreach { cl => - val fields0 = usedBits(cl) - - if (requiredBitmaps(fields0) < bitmapNum) { - val fields1 = (cl.info.decls filter isNonLocalFieldWithBitmap).size - return { - if (requiredBitmaps(fields0 + fields1) >= bitmapNum) - Some(bitmapFor(cl, offset, valSym, false)) - else None // Don't waste time, since we won't find bitmap anyway - } - } - } - None + + def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { + def realOffset = offset % flagsPerBitmap(sym) + if (kind == LongClass ) LIT(1L << realOffset) else LIT(1 << realOffset) } /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ - def mkSetFlag(clazz: Symbol, offset: Int, valSym: Symbol): Tree = { - val bmp = bitmapFor(clazz, offset, valSym) - val mask = LIT(1 << (offset % FLAGS_PER_WORD)) - def x = This(clazz) DOT bmp + def mkSetFlag(clazz: Symbol, offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { + val bmp = bitmapFor(clazz, offset, valSym) + def mask = maskForOffset(offset, valSym, kind) + def x = This(clazz) DOT bmp + def newValue = if (kind == BooleanClass) TRUE else (x GEN_| (mask, kind)) - x === (x INT_| mask) + x === newValue } /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the * precise comparison operator depending on the value of 'equalToZero'. */ - def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean): Tree = { - def lhs = (This(clazz) DOT bitmapSym) INT_& mask - if (equalToZero) lhs INT_== ZERO - else lhs INT_!= ZERO + def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean, kind: ClassSymbol): Tree = { + val bitmapTree = (This(clazz) DOT bitmapSym) + def lhs = bitmapTree GEN_& (mask, kind) + kind match { + case BooleanClass => + if (equalToZero) NOT(bitmapTree) + else bitmapTree + case _ => + if (equalToZero) lhs GEN_== (ZERO, kind) + else lhs GEN_!= (ZERO, kind) + } + } + + def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Symbol = { + val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name), lzyVal.pos, PRIVATE) + val params = defSym newSyntheticValueParams args.map(_.symbol.tpe) + defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType) + val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) + val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params) + addDef(position(defSym), DEF(defSym).mkTree(strictSubst(BLOCK(rhs, retVal))) setSymbol defSym) + defSym + } + + def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + stats: List[Tree], retVal: Tree): Tree = { + mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List()) + } + + def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], + stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = { + val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args) + If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal) } + + + /** Always copy the tree if we are going to perform sym substitution, + * otherwise we will side-effect on the tree that is used in the fast path + */ + class TreeSymSubstituterWithCopying(from: List[Symbol], to: List[Symbol]) extends TreeSymSubstituter(from, to) { + override def transform(tree: Tree): Tree = + if (tree.hasSymbol && from.contains(tree.symbol)) + super.transform(tree.duplicate) + else super.transform(tree.duplicate) + + override def apply[T <: Tree](tree: T): T = if (from.isEmpty) tree else super.apply(tree) + } /** return a 'lazified' version of rhs. It uses double-checked locking to ensure - * initialization is performed at most once. Private fields used only in this - * initializer are subsequently set to null. + * initialization is performed at most once. For performance reasons the double-checked + * locking is split into two parts, the first (fast) path checks the bitmap without + * synchronizing, and if that fails it initializes the lazy val within the + * synchronization block (slow path). This way the inliner should optimize + * the fast path because the method body is small enough. + * Private fields used only in this initializer are subsequently set to null. * * @param clazz The class symbol * @param init The tree which initializes the field ( f = <rhs> ) @@ -817,56 +825,64 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * @param offset The offset of this field in the flags bitmap * * The result will be a tree of the form - * { - * if ((bitmap$n & MASK) == 0) { - * synchronized(this) { - * if ((bitmap$n & MASK) == 0) { - * init // l$ = <rhs> - * bitmap$n = bimap$n | MASK - * } - * } - * this.f1 = null - * ... this.fn = null + * { if ((bitmap&n & MASK) == 0) this.l$compute() + * else l$ + * + * ... + * def l$compute() = { synchronized(this) { + * if ((bitmap$n & MASK) == 0) { + * init // l$ = <rhs> + * bitmap$n = bimap$n | MASK + * }} + * l$ * } - * l$ + * + * ... + * this.f1 = null + * ... this.fn = null * } - * where bitmap$n is an int value acting as a bitmap of initialized values. It is - * the 'n' is (offset / 32), the MASK is (1 << (offset % 32)). + * where bitmap$n is a byte, int or long value acting as a bitmap of initialized values. + * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it. + * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32), + * the MASK is (1 << (offset % 32)). + * If the class contains only a single lazy val then the bitmap is represented + * as a Boolean and the condition checking is a simple bool test. */ def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) val bitmapSym = bitmapFor(clazz, offset, lzyVal) - val mask = LIT(1 << (offset % FLAGS_PER_WORD)) - def cond = mkTest(clazz, mask, bitmapSym, true) + val kind = bitmapKind(lzyVal) + val mask = maskForOffset(offset, lzyVal, kind) + def cond = mkTest(clazz, mask, bitmapSym, true, kind) val nulls = lazyValNullables(lzyVal).toList sortBy (_.id) map nullify - def syncBody = init ::: List(mkSetFlag(clazz, offset, lzyVal), UNIT) + def syncBody = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT) if (nulls.nonEmpty) log("nulling fields inside " + lzyVal + ": " + nulls) - val result = gen.mkDoubleCheckedLocking(clazz, cond, syncBody, nulls) - typedPos(init.head.pos)(BLOCK(result, retVal)) + typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal)) } - def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree): Tree = + def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree = rhs match { case Block(List(assign), returnTree) => val Assign(moduleVarRef, _) = assign val cond = Apply(Select(moduleVarRef, nme.eq), List(NULL)) - val doubleSynchrTree = gen.mkDoubleCheckedLocking(attrThis, cond, List(assign), Nil) - Block(List(doubleSynchrTree), returnTree) + mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args) case _ => assert(false, "Invalid getter " + rhs + " for module in class " + clazz) EmptyTree } def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val bitmapSym = bitmapFor(clazz, offset, fieldSym.getter(fieldSym.owner)) - val mask = LIT(1 << (offset % FLAGS_PER_WORD)) + val sym = fieldSym.getter(fieldSym.owner) + val bitmapSym = bitmapFor(clazz, offset, sym) + val kind = bitmapKind(sym) + val mask = maskForOffset(offset, sym, kind) val msg = "Uninitialized field: " + unit.source + ": " + pos.line val result = - IF (mkTest(clazz, mask, bitmapSym, false)) . + IF (mkTest(clazz, mask, bitmapSym, false, kind)) . THEN (retVal) . ELSE (THROW(UninitializedErrorClass, LIT(msg))) @@ -887,14 +903,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (sym.isLazy && !isEmpty && !clazz.isImplClass) { assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - if (isUnit) - mkLazyDef(clazz, sym, List(rhs), UNIT, fieldOffset(sym)) - else { - val Block(stats, res) = rhs + deriveDefDef(stat) { + case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) + + case Block(stats, res) => mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - } - ) + + case t => t // pass specialized lazy vals through + } } else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(IMPLCLASS | TRAIT)) { assert(fieldOffset contains sym, sym) @@ -911,7 +927,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else if (settings.checkInit.value && !clazz.isTrait && sym.isSetter) { val getter = sym.getter(clazz) if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) - deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter))), UNIT)) + deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) else stat } else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) { @@ -921,7 +937,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // Martin to Hubert: I think this can be replaced by selfRef(tree.pos) // @PP: It does not seem so, it crashes for me trying to bootstrap. if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz), - rhs + rhs, sym, stat.vparamss.head ) ) ) @@ -939,7 +955,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) if (needsInitAndHasOffset(sym)) { debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym)) + List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym))) } else Nil } @@ -964,23 +980,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def addInitBits(clazz: Symbol, rhs: Tree): Tree = new AddInitBitsTransformer(clazz) transform rhs - def isNonLocalFieldWithBitmap(field: Symbol) = - isFieldWithBitmap(field) && !isLocalBitmapField(field) - def isCheckInitField(field: Symbol) = needsInitFlag(field) && !field.isDeferred def superClassesToCheck(clazz: Symbol) = clazz.ancestors filterNot (_ hasFlag TRAIT | JAVA) - /** - * Return the number of bits used by superclass fields. - */ - def usedBits(clazz0: Symbol): Int = - superClassesToCheck(clazz0) flatMap (_.info.decls) count { f => - f.owner != clazz0 && isNonLocalFieldWithBitmap(f) - } - // begin addNewDefs /** Fill the map from fields to offset numbers. @@ -988,25 +993,27 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * code generation easier later. */ def buildBitmapOffsets() { - def fold(zero: Int, fields: List[Symbol]) = { - var idx = zero + def fold(fields: List[Symbol], category: Name) = { + var idx = 0 fields foreach { f => - idx += 1 fieldOffset(f) = idx + idx += 1 } + + if (idx == 0) () + else if (idx == 1) bitmapKindForCategory(category) = BooleanClass + else if (idx < 9) bitmapKindForCategory(category) = ByteClass + else if (idx < 33) bitmapKindForCategory(category) = IntClass + else bitmapKindForCategory(category) = LongClass } clazz.info.decls.toList groupBy bitmapCategory foreach { case (nme.NO_NAME, _) => () - case (nme.BITMAP_NORMAL, fields) => fold(usedBits(clazz), fields) - case (_, fields) => fold(0, fields) + case (category, fields) => fold(fields, category) } } buildBitmapOffsets() var stats1 = addCheckedGetters(clazz, stats) - // add deferred bitmaps - deferredBitmaps remove clazz foreach { d => stats1 = add(stats1, d) } - def accessedReference(sym: Symbol) = sym.tpe match { case MethodType(Nil, ConstantType(c)) => Literal(c) case _ => @@ -1052,7 +1059,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val getter = sym.getter(clazz) if (!needsInitFlag(getter)) init - else Block(init, mkSetFlag(clazz, fieldOffset(getter), getter), UNIT) + else Block(init, mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)), UNIT) } } else if (needsInitFlag(sym)) @@ -1069,7 +1076,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val rhs = gen.newModule(sym, vdef.symbol.tpe) val assignAndRet = gen.mkAssignAndReturn(vdef.symbol, rhs) val attrThis = gen.mkAttributedThis(clazz) - val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet) + val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List()) addDefDef(sym, rhs1) } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index e0f34b47c8..07a10ecb1f 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -67,7 +67,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { import definitions.{ RootClass, BooleanClass, UnitClass, ArrayClass, - ScalaValueClasses, isPrimitiveValueClass, isScalaValueType, + ScalaValueClasses, isPrimitiveValueClass, isPrimitiveValueType, SpecializedClass, UnspecializedClass, AnyRefClass, ObjectClass, AnyRefModule, GroupOfSpecializable, uncheckedVarianceClass, ScalaInlineClass } @@ -145,7 +145,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { def includes(t1: TypeEnv, t2: TypeEnv) = t1 forall { case (sym, tpe) => t2 get sym exists { t2tp => - (tpe == t2tp) || !(isScalaValueType(tpe) || isScalaValueType(t2tp)) // u.t.b. (t2tp <:< AnyRefClass.tpe) + (tpe == t2tp) || !(isPrimitiveValueType(tpe) || isPrimitiveValueType(t2tp)) // u.t.b. (t2tp <:< AnyRefClass.tpe) } } @@ -266,7 +266,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { * specialized type. */ def survivingArgs(sym: Symbol, args: List[Type]): List[Type] = - for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isScalaValueType(tpe)) + for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe)) yield tpe val specializedType = new TypeMap { @@ -448,7 +448,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { /** Type parameters that survive when specializing in the specified environment. */ def survivingParams(params: List[Symbol], env: TypeEnv) = - params.filter(p => !p.isSpecialized || !isScalaValueType(env(p))) + params.filter(p => !p.isSpecialized || !isPrimitiveValueType(env(p))) /** Produces the symbols from type parameters `syms` of the original owner, * in the given type environment `env`. The new owner is `nowner`. @@ -666,16 +666,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // log("other concrete " + m) forwardToOverload(m) - } else if (m.isValue && !m.isMethod) { // concrete value definition + } else if (m.isMethod && m.hasFlag(LAZY)) { + forwardToOverload(m) + + } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition def mkAccessor(field: Symbol, name: Name) = { - val newFlags = (SPECIALIZED | m.getter(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR | LAZY) + val newFlags = (SPECIALIZED | m.getter(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) // we rely on the super class to initialize param accessors val sym = sClass.newMethod(name, field.pos, newFlags) info(sym) = SpecializedAccessor(field) sym } def overrideIn(clazz: Symbol, sym: Symbol) = { - val newFlags = (sym.flags | OVERRIDE | SPECIALIZED) & ~(DEFERRED | CASEACCESSOR | PARAMACCESSOR | LAZY) + val newFlags = (sym.flags | OVERRIDE | SPECIALIZED) & ~(DEFERRED | CASEACCESSOR | PARAMACCESSOR) val sym1 = sym.cloneSymbol(clazz, newFlags) sym1 modifyInfo (_ asSeenFrom (clazz.tpe, sym1.owner)) } @@ -881,7 +884,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { /** Return the specialized overload of `m`, in the given environment. */ private def specializedOverload(owner: Symbol, sym: Symbol, env: TypeEnv): Symbol = { - val newFlags = (sym.flags | SPECIALIZED) & ~(DEFERRED | CASEACCESSOR | ACCESSOR | LAZY) + val newFlags = (sym.flags | SPECIALIZED) & ~(DEFERRED | CASEACCESSOR) // this method properly duplicates the symbol's info ( sym.cloneSymbol(owner, newFlags, specializedName(sym, env)) modifyInfo (info => subst(env, info.asSeenFrom(owner.thisType, sym.owner))) @@ -1322,7 +1325,15 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { else None } else None } - + + def reportError[T](body: =>T)(handler: TypeError => T): T = + try body + catch { + case te: TypeError => + reporter.error(tree.pos, te.msg) + handler(te) + } + curTree = tree tree match { case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => @@ -1331,11 +1342,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { if (found.typeSymbol ne tpt.tpe.typeSymbol) { // the ctor can be specialized debuglog("** instantiated specialized type: " + found) - try localTyper.typedPos(tree.pos)(New(found, transformTrees(args): _*)) - catch { - case te: TypeError => - reporter.error(tree.pos, te.msg) - super.transform(tree) + reportError { + localTyper.typedPos(tree.pos)(New(found, transformTrees(args): _*)) + } { + _ => super.transform(tree) } } else super.transform(tree) @@ -1493,13 +1503,21 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { }) debuglog("created special overload tree " + t) debuglog("created " + t) - localTyper.typed(t) + reportError { + localTyper.typed(t) + } { + _ => super.transform(tree) + } case fwd @ Forward(_) => debuglog("forward: " + fwd + ", " + ddef) val rhs1 = forwardCall(tree.pos, gen.mkAttributedRef(symbol.owner.thisType, fwd.target), vparamss) debuglog("-->d completed forwarder to specialized overload: " + fwd.target + ": " + rhs1) - localTyper.typed(deriveDefDef(tree)(_ => rhs1)) + reportError { + localTyper.typed(deriveDefDef(tree)(_ => rhs1)) + } { + _ => super.transform(tree) + } case SpecializedAccessor(target) => val rhs1 = if (symbol.isGetter) @@ -1573,7 +1591,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { // val (_, origtparams) = splitParams(source.typeParams) val env = typeEnv(symbol) val boundTvars = env.keySet - val origtparams = source.typeParams.filter(tparam => !boundTvars(tparam) || !isScalaValueType(env(tparam))) + val origtparams = source.typeParams.filter(tparam => !boundTvars(tparam) || !isPrimitiveValueType(env(tparam))) if (origtparams.nonEmpty || symbol.typeParams.nonEmpty) debuglog("substituting " + origtparams + " for " + symbol.typeParams) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index 9915f7e9fc..4c9d855413 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -154,7 +154,10 @@ abstract class TailCalls extends Transform { def isTransformed = isEligible && accessed(label) def tailrecFailure() = unit.error(failPos, "could not optimize @tailrec annotated " + method + ": " + failReason) - def newThis(pos: Position) = method.newValue(nme.THIS, pos, SYNTHETIC) setInfo currentClass.typeOfThis + def newThis(pos: Position) = logResult("Creating new `this` during tailcalls\n method: %s\n current class: %s".format( + method.ownerChain.mkString(" -> "), currentClass.ownerChain.mkString(" -> "))) { + method.newValue(nme.THIS, pos, SYNTHETIC) setInfo currentClass.typeOfThis + } override def toString(): String = ( "" + method.name + " tparams: " + tparams + " tailPos: " + tailPos + @@ -222,7 +225,7 @@ abstract class TailCalls extends Transform { if (!ctx.isEligible) fail("it is neither private nor final so can be overridden") else if (!isRecursiveCall) { - if (receiverIsSuper) failHere("it contains a recursive call targetting a supertype") + if (receiverIsSuper) failHere("it contains a recursive call targeting supertype " + receiver.tpe) else failHere(defaultReason) } else if (!matchesTypeArgs) failHere("it is called recursively with different type arguments") diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ef98935f9c..f01cbade18 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -473,7 +473,15 @@ abstract class UnCurry extends InfoTransform arg.pos.source.path + ":" + arg.pos.line, fun.fullName, if (fun.isPrivate) "private" else "") ) - newFunction0(arg) + + arg match { + // don't add a thunk for by-name argument if argument already is an application of + // a Function0. We can then remove the application and use the existing Function0. + case Apply(Select(recv, nme.apply), Nil) if recv.tpe.typeSymbol isSubClass FunctionClass(0) => + recv + case _ => + newFunction0(arg) + } } } } @@ -560,7 +568,12 @@ abstract class UnCurry extends InfoTransform if ((sym ne null) && (sym.elisionLevel.exists (_ < settings.elidebelow.value || settings.noassertions.value))) replaceElidableTree(tree) else translateSynchronized(tree) match { - case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => + case dd @ DefDef(mods, name, tparams, _, tpt, rhs) => + // Remove default argument trees from parameter ValDefs, SI-4812 + val vparamssNoRhs = dd.vparamss mapConserve (_ mapConserve {p => + treeCopy.ValDef(p, p.mods, p.name, p.tpt, EmptyTree) + }) + if (dd.symbol hasAnnotation VarargsClass) saveRepeatedParams(dd) withNeedLift(false) { @@ -578,10 +591,10 @@ abstract class UnCurry extends InfoTransform } treeCopy.DefDef( dd, mods, name, transformTypeDefs(tparams), - transformValDefss(vparamss), transform(tpt), rhs1) + transformValDefss(vparamssNoRhs), transform(tpt), rhs1) } } else { - super.transform(dd) + super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs)) } } case ValDef(_, _, _, rhs) => diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 08bf581256..cb01faf619 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -9,7 +9,6 @@ package typechecker import scala.collection.{ mutable, immutable } import scala.tools.util.StringOps.{ countElementsAsString, countAsString } import symtab.Flags.{ PRIVATE, PROTECTED } -import scala.tools.util.EditDistance.similarString trait ContextErrors { self: Analyzer => @@ -171,36 +170,7 @@ trait ContextErrors { NormalTypeError(tree, "reference to " + name + " is ambiguous;\n" + msg) def SymbolNotFoundError(tree: Tree, name: Name, owner: Symbol, startingIdentCx: Context) = { - /*** Disabled pending investigation of performance impact. - - // This laborious determination arrived at to keep the tests working. - val calcSimilar = ( - name.length > 2 && ( - startingIdentCx.reportErrors - || startingIdentCx.enclClassOrMethod.reportErrors - ) - ) - // avoid calculating if we're in "silent" mode. - // name length check to limit unhelpful suggestions for e.g. "x" and "b1" - val similar = { - if (!calcSimilar) "" - else { - val allowed = ( - startingIdentCx.enclosingContextChain - flatMap (ctx => ctx.scope.toList ++ ctx.imports.flatMap(_.allImportedSymbols)) - filter (sym => sym.isTerm == name.isTermName) - filterNot (sym => sym.isPackage || sym.isSynthetic || sym.hasMeaninglessName) - ) - val allowedStrings = ( - allowed.map("" + _.name).distinct.sorted - filterNot (s => (s contains '$') || (s contains ' ')) - ) - similarString("" + name, allowedStrings) - } - } - */ - val similar = "" - NormalTypeError(tree, "not found: "+decodeWithKind(name, owner) + similar) + NormalTypeError(tree, "not found: "+decodeWithKind(name, owner)) } // typedAppliedTypeTree @@ -1064,14 +1034,24 @@ trait ContextErrors { setError(arg) } else arg } + + def WarnAfterNonSilentRecursiveInference(param: Symbol, arg: Tree)(implicit context: Context) = { + val note = "type-checking the invocation of "+ param.owner +" checks if the named argument expression '"+ param.name + " = ...' is a valid assignment\n"+ + "in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for "+ param.name +"." + context.warning(arg.pos, note) + } def UnknownParameterNameNamesDefaultError(arg: Tree, name: Name)(implicit context: Context) = { issueNormalTypeError(arg, "unknown parameter name: " + name) setError(arg) } - def DoubleParamNamesDefaultError(arg: Tree, name: Name)(implicit context: Context) = { - issueNormalTypeError(arg, "parameter specified twice: "+ name) + def DoubleParamNamesDefaultError(arg: Tree, name: Name, pos: Int, otherName: Option[Name])(implicit context: Context) = { + val annex = otherName match { + case Some(oName) => "\nNote that that '"+ oName +"' is not a parameter name of the invoked method." + case None => "" + } + issueNormalTypeError(arg, "parameter '"+ name +"' is already specified at parameter position "+ pos + annex) setError(arg) } diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala index 92ce0e6de4..e1fb683aa9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala +++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala @@ -63,15 +63,18 @@ trait EtaExpansion { self: Analyzer => * @return ... */ def liftoutPrefix(tree: Tree): Tree = { - def liftout(tree: Tree): Tree = + def liftout(tree: Tree, byName: Boolean): Tree = if (treeInfo.isExprSafeToInline(tree)) tree else { val vname: Name = freshName() // Problem with ticket #2351 here defs += atPos(tree.pos) { - ValDef(Modifiers(SYNTHETIC), vname.toTermName, TypeTree(), tree) + val rhs = if (byName) Function(List(), tree) else tree + ValDef(Modifiers(SYNTHETIC), vname.toTermName, TypeTree(), rhs) + } + atPos(tree.pos.focus) { + if (byName) Apply(Ident(vname), List()) else Ident(vname) } - Ident(vname) setPos tree.pos.focus } val tree1 = tree match { // a partial application using named arguments has the following form: @@ -85,11 +88,14 @@ trait EtaExpansion { self: Analyzer => defs ++= stats liftoutPrefix(fun) case Apply(fn, args) => - treeCopy.Apply(tree, liftoutPrefix(fn), args mapConserve (liftout)) setType null + val byName = fn.tpe.params.map(p => definitions.isByNameParamType(p.tpe)) + // zipAll: with repeated params, there might be more args than params + val newArgs = args.zipAll(byName, EmptyTree, false) map { case (arg, byN) => liftout(arg, byN) } + treeCopy.Apply(tree, liftoutPrefix(fn), newArgs) setType null case TypeApply(fn, args) => treeCopy.TypeApply(tree, liftoutPrefix(fn), args) setType null case Select(qual, name) => - treeCopy.Select(tree, liftout(qual), name) setSymbol NoSymbol setType null + treeCopy.Select(tree, liftout(qual, false), name) setSymbol NoSymbol setType null case Ident(name) => tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 722440349a..8f025336bb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -764,12 +764,14 @@ trait Implicits { * so that if there is a best candidate it can still be selected. */ private var divergence = false - private val MaxDiverges = 1 // not sure if this should be > 1 - private val divergenceHandler = util.Exceptional.expiringHandler(MaxDiverges) { - case x: DivergentImplicit => - divergence = true - log("discarding divergent implicit during implicit search") - SearchFailure + private val divergenceHandler: PartialFunction[Throwable, SearchResult] = { + var remaining = 1; + { case x: DivergentImplicit if remaining > 0 => + remaining -= 1 + divergence = true + log("discarding divergent implicit during implicit search") + SearchFailure + } } /** Sorted list of eligible implicits. @@ -1133,16 +1135,27 @@ trait Implicits { * An EmptyTree is returned if materialization fails. */ private def tagOfType(pre: Type, tp: Type, tagClass: Symbol): SearchResult = { - def success(arg: Tree) = + def success(arg: Tree) = { + def isMacroException(msg: String): Boolean = + // [Eugene] very unreliable, ask Hubert about a better way + msg contains "exception during macro expansion" + + def processMacroExpansionError(pos: Position, msg: String): SearchResult = { + // giving up and reporting all macro exceptions regardless of their source + // this might lead to an avalanche of errors if one of your implicit macros misbehaves + if (isMacroException(msg)) context.error(pos, msg) + failure(arg, "failed to typecheck the materialized tag: %n%s".format(msg), pos) + } + try { val tree1 = typed(atPos(pos.focus)(arg)) - def isErroneous = tree exists (_.isErroneous) - if (context.hasErrors) failure(tp, "failed to typecheck the materialized typetag: %n%s".format(context.errBuffer.head.errMsg), context.errBuffer.head.errPos) + if (context.hasErrors) processMacroExpansionError(context.errBuffer.head.errPos, context.errBuffer.head.errMsg) else new SearchResult(tree1, EmptyTreeTypeSubstituter) } catch { case ex: TypeError => - failure(arg, "failed to typecheck the materialized typetag: %n%s".format(ex.msg), ex.pos) + processMacroExpansionError(ex.pos, ex.msg) } + } val prefix = ( // ClassTags only exist for scala.reflect.mirror, so their materializer diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index fb0616c890..1c1adee343 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -499,7 +499,8 @@ trait Infer { else Some( if (targ.typeSymbol == RepeatedParamClass) targ.baseType(SeqClass) else if (targ.typeSymbol == JavaRepeatedParamClass) targ.baseType(ArrayClass) - else if ((opt.experimental || opt.virtPatmat) && tvar.constr.avoidWiden) targ + // this infers Foo.type instead of "object Foo" (see also widenIfNecessary) + else if (targ.typeSymbol.isModuleClass || ((opt.experimental || opt.virtPatmat) && tvar.constr.avoidWiden)) targ else targ.widen ) )) @@ -616,7 +617,8 @@ trait Infer { } else if (argPos.contains(pos)) { // parameter specified twice namesOK = false } else { - positionalAllowed = false + if (index != pos) + positionalAllowed = false argPos(index) = pos } index += 1 diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 353514c397..c10901cdce 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -641,143 +641,181 @@ trait Macros extends Traces { */ private type MacroRuntime = List[Any] => Any private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, Option[MacroRuntime]] - private def macroRuntime(macroDef: Symbol): Option[MacroRuntime] = - macroRuntimesCache.getOrElseUpdate(macroDef, { - val runtime = { - macroTraceVerbose("looking for macro implementation: ")(macroDef) - macroTraceVerbose("macroDef is annotated with: ")(macroDef.annotations) - - val ann = macroDef.getAnnotation(MacroImplAnnotation) - if (ann == None) { - macroTraceVerbose("@macroImpl annotation is missing (this means that macro definition failed to typecheck)")(macroDef) - return None - } + private lazy val fastTrack: Map[Symbol, MacroRuntime] = { + import scala.reflect.api.Universe + import scala.reflect.makro.internal._ + Map( // challenge: how can we factor out the common code? Does not seem to be easy. + MacroInternal_materializeArrayTag -> (args => { + assert(args.length == 3, args) + val c = args(0).asInstanceOf[MacroContext] + materializeArrayTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) + }), + MacroInternal_materializeErasureTag -> (args => { + assert(args.length == 3, args) + val c = args(0).asInstanceOf[MacroContext] + materializeErasureTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) + }), + MacroInternal_materializeClassTag -> (args => { + assert(args.length == 3, args) + val c = args(0).asInstanceOf[MacroContext] + materializeClassTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) + }), + MacroInternal_materializeTypeTag -> (args => { + assert(args.length == 3, args) + val c = args(0).asInstanceOf[MacroContext] + materializeTypeTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) + }), + MacroInternal_materializeConcreteTypeTag -> (args => { + assert(args.length == 3, args) + val c = args(0).asInstanceOf[MacroContext] + materializeConcreteTypeTag_impl(c)(args(1).asInstanceOf[c.Expr[Universe]])(args(2).asInstanceOf[c.TypeTag[_]]) + }) + ) + } + private def macroRuntime(macroDef: Symbol): Option[MacroRuntime] = { + macroTraceVerbose("looking for macro implementation: ")(macroDef) + if (fastTrack contains macroDef) { + macroLogVerbose("macro expansion serviced by a fast track") + Some(fastTrack(macroDef)) + } else { + macroRuntimesCache.getOrElseUpdate(macroDef, { + val runtime = { + macroTraceVerbose("looking for macro implementation: ")(macroDef) + macroTraceVerbose("macroDef is annotated with: ")(macroDef.annotations) - val macroImpl = ann.get.args(0).symbol - if (macroImpl == NoSymbol) { - macroTraceVerbose("@macroImpl annotation is malformed (this means that macro definition failed to typecheck)")(macroDef) - return None - } + val ann = macroDef.getAnnotation(MacroImplAnnotation) + if (ann == None) { + macroTraceVerbose("@macroImpl annotation is missing (this means that macro definition failed to typecheck)")(macroDef) + return None + } - macroLogVerbose("resolved implementation %s at %s".format(macroImpl, macroImpl.pos)) - if (macroImpl.isErroneous) { - macroTraceVerbose("macro implementation is erroneous (this means that either macro body or macro implementation signature failed to typecheck)")(macroDef) - return None - } + val macroImpl = ann.get.args(0).symbol + if (macroImpl == NoSymbol) { + macroTraceVerbose("@macroImpl annotation is malformed (this means that macro definition failed to typecheck)")(macroDef) + return None + } - def loadMacroImpl(macroMirror: Mirror): Option[(Object, macroMirror.Symbol)] = { - try { - // this logic relies on the assumptions that were valid for the old macro prototype - // namely that macro implementations can only be defined in top-level classes and modules - // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different - // for example, a macro def could be defined in a trait that is implemented by an object - // there are some more clever cases when seemingly non-static method ends up being statically accessible - // however, the code below doesn't account for these guys, because it'd take a look of time to get it right - // for now I leave it as a todo and move along to more the important stuff - - macroTraceVerbose("loading implementation class from %s: ".format(macroMirror))(macroImpl.owner.fullName) - macroTraceVerbose("classloader is: ")("%s of type %s".format(macroMirror.classLoader, if (macroMirror.classLoader != null) macroMirror.classLoader.getClass.toString else "primordial classloader")) - def inferClasspath(cl: ClassLoader) = cl match { - case cl: java.net.URLClassLoader => "[" + (cl.getURLs mkString ",") + "]" - case null => "[" + scala.tools.util.PathResolver.Environment.javaBootClassPath + "]" - case _ => "<unknown>" - } - macroTraceVerbose("classpath is: ")(inferClasspath(macroMirror.classLoader)) - - // [Eugene] relies on the fact that macro implementations can only be defined in static classes - // [Martin to Eugene] There's similar logic buried in Symbol#flatname. Maybe we can refactor? - def classfile(sym: Symbol): String = { - def recur(sym: Symbol): String = sym match { - case sym if sym.owner.isPackageClass => - val suffix = if (sym.isModuleClass) "$" else "" - sym.fullName + suffix - case sym => - val separator = if (sym.owner.isModuleClass) "" else "$" - recur(sym.owner) + separator + sym.javaSimpleName.toString + macroLogVerbose("resolved implementation %s at %s".format(macroImpl, macroImpl.pos)) + if (macroImpl.isErroneous) { + macroTraceVerbose("macro implementation is erroneous (this means that either macro body or macro implementation signature failed to typecheck)")(macroDef) + return None + } + + def loadMacroImpl(macroMirror: Mirror): Option[(Object, macroMirror.Symbol)] = { + try { + // this logic relies on the assumptions that were valid for the old macro prototype + // namely that macro implementations can only be defined in top-level classes and modules + // with the new prototype that materialized in a SIP, macros need to be statically accessible, which is different + // for example, a macro def could be defined in a trait that is implemented by an object + // there are some more clever cases when seemingly non-static method ends up being statically accessible + // however, the code below doesn't account for these guys, because it'd take a look of time to get it right + // for now I leave it as a todo and move along to more the important stuff + + macroTraceVerbose("loading implementation class from %s: ".format(macroMirror))(macroImpl.owner.fullName) + macroTraceVerbose("classloader is: ")("%s of type %s".format(macroMirror.classLoader, if (macroMirror.classLoader != null) macroMirror.classLoader.getClass.toString else "primordial classloader")) + def inferClasspath(cl: ClassLoader) = cl match { + case cl: java.net.URLClassLoader => "[" + (cl.getURLs mkString ",") + "]" + case null => "[" + scala.tools.util.PathResolver.Environment.javaBootClassPath + "]" + case _ => "<unknown>" } + macroTraceVerbose("classpath is: ")(inferClasspath(macroMirror.classLoader)) + + // [Eugene] relies on the fact that macro implementations can only be defined in static classes + // [Martin to Eugene] There's similar logic buried in Symbol#flatname. Maybe we can refactor? + def classfile(sym: Symbol): String = { + def recur(sym: Symbol): String = sym match { + case sym if sym.owner.isPackageClass => + val suffix = if (sym.isModuleClass) "$" else "" + sym.fullName + suffix + case sym => + val separator = if (sym.owner.isModuleClass) "" else "$" + recur(sym.owner) + separator + sym.javaSimpleName.toString + } - if (sym.isClass || sym.isModule) recur(sym) - else recur(sym.enclClass) - } + if (sym.isClass || sym.isModule) recur(sym) + else recur(sym.enclClass) + } - // [Eugene] this doesn't work for inner classes - // neither does macroImpl.owner.javaClassName, so I had to roll my own implementation - //val receiverName = macroImpl.owner.fullName - val implClassName = classfile(macroImpl.owner) - val implClassSymbol: macroMirror.Symbol = macroMirror.symbolForName(implClassName) + // [Eugene] this doesn't work for inner classes + // neither does macroImpl.owner.javaClassName, so I had to roll my own implementation + //val receiverName = macroImpl.owner.fullName + val implClassName = classfile(macroImpl.owner) + val implClassSymbol: macroMirror.Symbol = macroMirror.symbolForName(implClassName) - if (macroDebugVerbose) { - println("implClassSymbol is: " + implClassSymbol.fullNameString) + if (macroDebugVerbose) { + println("implClassSymbol is: " + implClassSymbol.fullNameString) - if (implClassSymbol != macroMirror.NoSymbol) { - val implClass = macroMirror.classToJava(implClassSymbol) - val implSource = implClass.getProtectionDomain.getCodeSource - println("implClass is %s from %s".format(implClass, implSource)) - println("implClassLoader is %s with classpath %s".format(implClass.getClassLoader, inferClasspath(implClass.getClassLoader))) + if (implClassSymbol != macroMirror.NoSymbol) { + val implClass = macroMirror.classToJava(implClassSymbol) + val implSource = implClass.getProtectionDomain.getCodeSource + println("implClass is %s from %s".format(implClass, implSource)) + println("implClassLoader is %s with classpath %s".format(implClass.getClassLoader, inferClasspath(implClass.getClassLoader))) + } } - } - val implObjSymbol = implClassSymbol.companionModule - macroTraceVerbose("implObjSymbol is: ")(implObjSymbol.fullNameString) + val implObjSymbol = implClassSymbol.companionModule + macroTraceVerbose("implObjSymbol is: ")(implObjSymbol.fullNameString) - if (implObjSymbol == macroMirror.NoSymbol) None - else { - // yet another reflection method that doesn't work for inner classes - //val receiver = macroMirror.companionInstance(receiverClass) - val implObj = try { - val implObjClass = java.lang.Class.forName(implClassName, true, macroMirror.classLoader) - implObjClass getField "MODULE$" get null - } catch { - case ex: NoSuchFieldException => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: NoClassDefFoundError => macroTraceVerbose("exception when loading implObj: ")(ex); null - case ex: ClassNotFoundException => macroTraceVerbose("exception when loading implObj: ")(ex); null - } - - if (implObj == null) None + if (implObjSymbol == macroMirror.NoSymbol) None else { - val implMethSymbol = implObjSymbol.info.member(macroMirror.newTermName(macroImpl.name.toString)) - macroLogVerbose("implMethSymbol is: " + implMethSymbol.fullNameString) - macroLogVerbose("jimplMethSymbol is: " + macroMirror.methodToJava(implMethSymbol)) + // yet another reflection method that doesn't work for inner classes + //val receiver = macroMirror.companionInstance(receiverClass) + val implObj = try { + val implObjClass = java.lang.Class.forName(implClassName, true, macroMirror.classLoader) + implObjClass getField "MODULE$" get null + } catch { + case ex: NoSuchFieldException => macroTraceVerbose("exception when loading implObj: ")(ex); null + case ex: NoClassDefFoundError => macroTraceVerbose("exception when loading implObj: ")(ex); null + case ex: ClassNotFoundException => macroTraceVerbose("exception when loading implObj: ")(ex); null + } - if (implMethSymbol == macroMirror.NoSymbol) None + if (implObj == null) None else { - macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMethSymbol)) - Some((implObj, implMethSymbol)) + val implMethSymbol = implObjSymbol.info.member(macroMirror.newTermName(macroImpl.name.toString)) + macroLogVerbose("implMethSymbol is: " + implMethSymbol.fullNameString) + macroLogVerbose("jimplMethSymbol is: " + macroMirror.methodToJava(implMethSymbol)) + + if (implMethSymbol == macroMirror.NoSymbol) None + else { + macroLogVerbose("successfully loaded macro impl as (%s, %s)".format(implObj, implMethSymbol)) + Some((implObj, implMethSymbol)) + } } } + } catch { + case ex: ClassNotFoundException => + macroTraceVerbose("implementation class failed to load: ")(ex.toString) + None } - } catch { - case ex: ClassNotFoundException => - macroTraceVerbose("implementation class failed to load: ")(ex.toString) - None } - } - val primary = loadMacroImpl(primaryMirror) - primary match { - case Some((implObj, implMethSymbol)) => - def runtime(args: List[Any]) = primaryMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] - Some(runtime _) - case None => - if (settings.XmacroFallbackClasspath.value != "") { - macroLogVerbose("trying to load macro implementation from the fallback mirror: %s".format(settings.XmacroFallbackClasspath.value)) - val fallback = loadMacroImpl(fallbackMirror) - fallback match { - case Some((implObj, implMethSymbol)) => - def runtime(args: List[Any]) = fallbackMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] - Some(runtime _) - case None => - None + val primary = loadMacroImpl(primaryMirror) + primary match { + case Some((implObj, implMethSymbol)) => + def runtime(args: List[Any]) = primaryMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] + Some(runtime _) + case None => + if (settings.XmacroFallbackClasspath.value != "") { + macroLogVerbose("trying to load macro implementation from the fallback mirror: %s".format(settings.XmacroFallbackClasspath.value)) + val fallback = loadMacroImpl(fallbackMirror) + fallback match { + case Some((implObj, implMethSymbol)) => + def runtime(args: List[Any]) = fallbackMirror.invoke(implObj, implMethSymbol)(args: _*).asInstanceOf[Any] + Some(runtime _) + case None => + None + } + } else { + None } - } else { - None - } + } } - } - if (runtime == None) macroDef setFlag IS_ERROR - runtime - }) + if (runtime == None) macroDef setFlag IS_ERROR + runtime + }) + } + } /** Should become private again once we're done with migrating typetag generation from implicits */ def macroContext(typer: Typer, prefixTree: Tree, expandeeTree: Tree): MacroContext { val mirror: global.type } = @@ -1203,8 +1241,10 @@ trait Macros extends Traces { if (relevancyThreshold == -1) None else { var relevantElements = realex.getStackTrace().take(relevancyThreshold + 1) - var framesTillReflectiveInvocationOfMacroImpl = relevantElements.reverse.indexWhere(_.isNativeMethod) + 1 - relevantElements = relevantElements dropRight framesTillReflectiveInvocationOfMacroImpl + def isMacroInvoker(este: StackTraceElement) = este.isNativeMethod || (este.getClassName != null && (este.getClassName contains "fastTrack")) + var threshold = relevantElements.reverse.indexWhere(isMacroInvoker) + 1 + while (threshold != relevantElements.length && isMacroInvoker(relevantElements(relevantElements.length - threshold - 1))) threshold += 1 + relevantElements = relevantElements dropRight threshold realex.setStackTrace(relevantElements) val message = new java.io.StringWriter() diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 6620ef7347..2f2278251b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -567,7 +567,15 @@ trait Namers extends MethodSynthesis { assignAndEnterFinishedSymbol(tree) else enterGetterSetter(tree) + + // When java enums are read from bytecode, they are known to have + // constant types by the jvm flag and assigned accordingly. When + // they are read from source, the java parser marks them with the + // STABLE flag, and now we receive that signal. + if (tree.symbol hasAllFlags STABLE | JAVA) + tree.symbol setInfo ConstantType(Constant(tree.symbol)) } + def enterLazyVal(tree: ValDef, lazyAccessor: Symbol): TermSymbol = { // If the owner is not a class, this is a lazy val from a method, // with no associated field. It has an accessor with $lzy appended to its name and @@ -765,7 +773,10 @@ trait Namers extends MethodSynthesis { val tpe1 = dropRepeatedParamType(tpe.deconst) val tpe2 = tpe1.widen - if (sym.isVariable || sym.isMethod && !sym.hasAccessorFlag) + // This infers Foo.type instead of "object Foo" + // See Infer#adjustTypeArgs for the polymorphic case. + if (tpe.typeSymbolDirect.isModuleClass) tpe1 + else if (sym.isVariable || sym.isMethod && !sym.hasAccessorFlag) if (tpe2 <:< pt) tpe2 else tpe1 else if (isHidden(tpe)) tpe2 // In an attempt to make pattern matches involving method local vals @@ -784,8 +795,8 @@ trait Namers extends MethodSynthesis { val typedBody = if (tree.symbol.isTermMacro) defnTyper.computeMacroDefType(tree, pt) else defnTyper.computeType(tree.rhs, pt) - val sym = if (owner.isMethod) owner else tree.symbol - val typedDefn = widenIfNecessary(sym, typedBody, pt) + + val typedDefn = widenIfNecessary(tree.symbol, typedBody, pt) assignTypeToTree(tree, typedDefn) } @@ -1412,6 +1423,8 @@ trait Namers extends MethodSynthesis { fail(LazyAndEarlyInit) if (sym.info.typeSymbol == FunctionClass(0) && sym.isValueParameter && sym.owner.isCaseClass) fail(ByNameParameter) + if (sym.isTrait && sym.isFinal && !sym.isSubClass(AnyValClass)) + checkNoConflict(ABSTRACT, FINAL) if (sym.isDeferred) { // Is this symbol type always allowed the deferred flag? diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 90e388c30a..88e464a1f4 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -158,20 +158,20 @@ trait NamesDefaults { self: Analyzer => // it stays in Vegas: SI-5720, SI-5727 qual changeOwner (blockTyper.context.owner -> sym) + val newQual = atPos(qual.pos.focus)(blockTyper.typedQualifier(Ident(sym.name))) var baseFunTransformed = atPos(baseFun.pos.makeTransparent) { - // don't use treeCopy: it would assign opaque position. - val f = Select(gen.mkAttributedRef(sym), selected) - .setType(baseFun1.tpe).setSymbol(baseFun1.symbol) + // setSymbol below is important because the 'selected' function might be overloaded. by + // assigning the correct method symbol, typedSelect will just assign the type. the reason + // to still call 'typed' is to correctly infer singleton types, SI-5259. + val f = blockTyper.typedOperator(Select(newQual, selected).setSymbol(baseFun1.symbol)) if (funTargs.isEmpty) f else TypeApply(f, funTargs).setType(baseFun.tpe) } val b = Block(List(vd), baseFunTransformed) .setType(baseFunTransformed.tpe).setPos(baseFun.pos) - - val defaultQual = Some(atPos(qual.pos.focus)(gen.mkAttributedRef(sym))) context.namedApplyBlockInfo = - Some((b, NamedApplyInfo(defaultQual, defaultTargs, Nil, blockTyper))) + Some((b, NamedApplyInfo(Some(newQual), defaultTargs, Nil, blockTyper))) b } @@ -338,7 +338,7 @@ trait NamesDefaults { self: Analyzer => // cannot call blockTyper.typedBlock here, because the method expr might be partially applied only val res = blockTyper.doTypedApply(tree, expr, refArgs, mode, pt) res.setPos(res.pos.makeTransparent) - val block = Block(stats ::: valDefs, res).setType(res.tpe).setPos(tree.pos) + val block = Block(stats ::: valDefs, res).setType(res.tpe).setPos(tree.pos.makeTransparent) context.namedApplyBlockInfo = Some((block, NamedApplyInfo(qual, targs, vargss :+ refArgs, blockTyper))) block @@ -478,7 +478,14 @@ trait NamesDefaults { self: Analyzer => // instead of arg, but can't do that because eventually setType(ErrorType) // is called, and EmptyTree can only be typed NoType. Thus we need to // disable conforms as a view... - try typer.silent(_.typed(arg, subst(paramtpe))) match { + val errsBefore = reporter.ERROR.count + try typer.silent { tpr => + val res = tpr.typed(arg, subst(paramtpe)) + // better warning for SI-5044: if `silent` was not actually silent give a hint to the user + if (errsBefore < reporter.ERROR.count) + WarnAfterNonSilentRecursiveInference(param, arg)(context) + res + } match { case SilentResultValue(t) => !t.isErroneous // #4041 case _ => false } @@ -487,7 +494,7 @@ trait NamesDefaults { self: Analyzer => // CyclicReferences. Fix for #3685 case cr @ CyclicReference(sym, _) => (sym.name == param.name) && sym.accessedOrSelf.isVariable && { - NameClashError(sym, arg)(typer.context) + NameClashError(sym, arg)(context) true } } @@ -507,7 +514,7 @@ trait NamesDefaults { self: Analyzer => // maps indices from (order written by user) to (order of definition) val argPos = Array.fill(args.length)(-1) var positionalAllowed = true - val namelessArgs = mapWithIndex(args) { (arg, index) => + val namelessArgs = mapWithIndex(args) { (arg, argIndex) => arg match { case arg @ AssignOrNamedArg(Ident(name), rhs) => def matchesName(param: Symbol) = !param.isSynthetic && ( @@ -519,30 +526,35 @@ trait NamesDefaults { self: Analyzer => case _ => false }) ) - val pos = params indexWhere matchesName - if (pos == -1) { + val paramPos = params indexWhere matchesName + if (paramPos == -1) { if (positionalAllowed) { - argPos(index) = index + argPos(argIndex) = argIndex // prevent isNamed from being true when calling doTypedApply recursively, // treat the arg as an assignment of type Unit Assign(arg.lhs, rhs) setPos arg.pos } else UnknownParameterNameNamesDefaultError(arg, name) } - else if (argPos contains pos) - DoubleParamNamesDefaultError(arg, name) - else if (isAmbiguousAssignment(typer, params(pos), arg)) + else if (argPos contains paramPos) { + val existingArgIndex = argPos.indexWhere(_ == paramPos) + val otherName = args(paramPos) match { + case AssignOrNamedArg(Ident(oName), rhs) if oName != name => Some(oName) + case _ => None + } + DoubleParamNamesDefaultError(arg, name, existingArgIndex+1, otherName) + } else if (isAmbiguousAssignment(typer, params(paramPos), arg)) AmbiguousReferenceInNamesDefaultError(arg, name) else { // if the named argument is on the original parameter // position, positional after named is allowed. - if (index != pos) + if (argIndex != paramPos) positionalAllowed = false - argPos(index) = pos + argPos(argIndex) = paramPos rhs } case _ => - argPos(index) = index + argPos(argIndex) = argIndex if (positionalAllowed) arg else PositionalAfterNamedNamesDefaultError(arg) } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index e60cda6af7..4e578e3f0d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -386,8 +386,9 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R overrideError("cannot be used here - classes can only override abstract types"); } else if (other.isEffectivelyFinal) { // (1.2) overrideError("cannot override final member"); - // synthetic exclusion needed for (at least) default getters. - } else if (!other.isDeferred && !member.isAnyOverride && !member.isSynthetic) { + } else if (!other.isDeferred && !member.isAnyOverride && !member.isSynthetic) { // (*) + // (*) Synthetic exclusion for (at least) default getters, fixes SI-5178. We cannot assign the OVERRIDE flag to + // the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket. if (isNeitherInClass && !(other.owner isSubClass member.owner)) emitOverrideError( clazz + " inherits conflicting members:\n " @@ -1278,6 +1279,15 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R } def transformStat(tree: Tree, index: Int): List[Tree] = tree match { + case t if treeInfo.isSelfConstrCall(t) => + assert(index == 0, index) + val t = transform(tree) + if (currentLevel.maxindex > 0) { + // An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 + debuglog("refsym = " + currentLevel.refsym) + unit.error(currentLevel.refpos, "forward reference not allowed from self constructor invocation") + } + List(t) case ModuleDef(_, _, _) => eliminateModuleDefs(tree) case ValDef(_, _, _, _) => val tree1 @ ValDef(_, _, _, rhs) = transform(tree) // important to do before forward reference check @@ -1478,8 +1488,23 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R private def transformCaseApply(tree: Tree, ifNot: => Unit) = { val sym = tree.symbol - - if (sym.isSourceMethod && sym.isCase && sym.name == nme.apply) + + def isClassTypeAccessible(tree: Tree): Boolean = tree match { + case TypeApply(fun, targs) => + isClassTypeAccessible(fun) + case Select(module, apply) => + // Fixes SI-5626. Classes in refinement types cannot be constructed with `new`. In this case, + // the companion class is actually not a ClassSymbol, but a reference to an abstract type. + module.symbol.companionClass.isClass + } + + val doTransform = + sym.isSourceMethod && + sym.isCase && + sym.name == nme.apply && + isClassTypeAccessible(tree) + + if (doTransform) toConstructor(tree.pos, tree.tpe) else { ifNot diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index 57e82ed706..6faa9a3cb7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -89,6 +89,11 @@ trait SyntheticMethods extends ast.TreeDSL { def forwardToRuntime(method: Symbol): Tree = forwardMethod(method, getMember(ScalaRunTimeModule, method.name prepend "_"))(mkThis :: _) + def callStaticsMethod(name: String)(args: Tree*): Tree = { + val method = termMember(RuntimeStaticsModule, name) + Apply(gen.mkAttributedRef(method), args.toList) + } + // Any member, including private def hasConcreteImpl(name: Name) = clazz.info.member(name).alternatives exists (m => !m.isDeferred && !m.isSynthetic) @@ -222,13 +227,47 @@ trait SyntheticMethods extends ast.TreeDSL { ) } + def hashcodeImplementation(sym: Symbol): Tree = { + sym.tpe.finalResultType.typeSymbol match { + case UnitClass | NullClass => Literal(Constant(0)) + case BooleanClass => If(Ident(sym), Literal(Constant(1231)), Literal(Constant(1237))) + case IntClass | ShortClass | ByteClass | CharClass => Ident(sym) + case LongClass => callStaticsMethod("longHash")(Ident(sym)) + case DoubleClass => callStaticsMethod("doubleHash")(Ident(sym)) + case FloatClass => callStaticsMethod("floatHash")(Ident(sym)) + case _ => callStaticsMethod("anyHash")(Ident(sym)) + } + } + + def specializedHashcode = { + createMethod(nme.hashCode_, Nil, IntClass.tpe) { m => + val accumulator = m.newVariable(newTermName("acc"), m.pos, SYNTHETIC) setInfo IntClass.tpe + val valdef = ValDef(accumulator, Literal(Constant(0xcafebabe))) + val mixes = accessors map (acc => + Assign( + Ident(accumulator), + callStaticsMethod("mix")(Ident(accumulator), hashcodeImplementation(acc)) + ) + ) + val finish = callStaticsMethod("finalizeHash")(Ident(accumulator), Literal(Constant(arity))) + + Block(valdef :: mixes, finish) + } + } + def chooseHashcode = { + if (accessors exists (x => isPrimitiveValueType(x.tpe.finalResultType))) + specializedHashcode + else + forwardToRuntime(Object_hashCode) + } + def valueClassMethods = List( Any_hashCode -> (() => hashCodeDerivedValueClassMethod), Any_equals -> (() => equalsDerivedValueClassMethod) ) def caseClassMethods = productMethods ++ productNMethods ++ Seq( - Object_hashCode -> (() => forwardToRuntime(Object_hashCode)), + Object_hashCode -> (() => chooseHashcode), Object_toString -> (() => forwardToRuntime(Object_toString)), Object_equals -> (() => equalsCaseClassMethod) ) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8c2eae1c86..e40a567f1d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -31,7 +31,6 @@ trait Typers extends Modes with Adaptations with Taggings { import global._ import definitions._ - import patmat.DefaultOverrideMatchAttachment final def forArgMode(fun: Tree, mode: Int) = @@ -85,6 +84,12 @@ trait Typers extends Modes with Adaptations with Taggings { private def isPastTyper = phase.id > currentRun.typerPhase.id + // To enable decent error messages when the typer crashes. + // TODO - this only catches trees which go through def typed, + // but there are all kinds of back ways - typedClassDef, etc. etc. + // Funnel everything through one doorway. + var lastTreeToTyper: Tree = EmptyTree + // when true: // - we may virtualize matches (if -Xexperimental and there's a suitable __match in scope) // - we synthesize PartialFunction implementations for `x => x match {...}` and `match {...}` when the expected type is PartialFunction @@ -109,6 +114,8 @@ trait Typers extends Modes with Adaptations with Taggings { case MethodType(params, _) => val argResultsBuff = new ListBuffer[SearchResult]() val argBuff = new ListBuffer[Tree]() + // paramFailed cannot be initialized with params.exists(_.tpe.isError) because that would + // hide some valid errors for params preceding the erroneous one. var paramFailed = false def mkPositionalArg(argTree: Tree, paramName: Name) = argTree @@ -124,7 +131,7 @@ trait Typers extends Modes with Adaptations with Taggings { for(ar <- argResultsBuff) paramTp = paramTp.subst(ar.subst.from, ar.subst.to) - val res = if (paramFailed) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, false, context) + val res = if (paramFailed || (paramTp.isError && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, false, context) argResultsBuff += res if (res != SearchFailure) { @@ -921,7 +928,7 @@ trait Typers extends Modes with Adaptations with Taggings { KindArityMismatchError(tree, pt) } else tree match { // (6) case TypeTree() => tree - case _ => TypeTree(tree.tpe) setOriginal (tree) setPos (tree.pos) + case _ => TypeTree(tree.tpe) setOriginal tree } } @@ -953,9 +960,14 @@ trait Typers extends Modes with Adaptations with Taggings { * see test/files/../t5189*.scala */ def adaptConstrPattern(): Tree = { // (5) - val extractor = tree.symbol.filter(sym => reallyExists(unapplyMember(sym.tpe))) + def isExtractor(sym: Symbol) = reallyExists(unapplyMember(sym.tpe)) + val extractor = tree.symbol filter isExtractor if (extractor != NoSymbol) { tree setSymbol extractor + tree.tpe match { + case OverloadedType(pre, alts) => tree.tpe = overloadedType(pre, alts filter isExtractor) + case _ => + } val unapply = unapplyMember(extractor.tpe) val clazz = unapplyParameterType(unapply) @@ -2297,7 +2309,7 @@ trait Typers extends Modes with Adaptations with Taggings { import CODE._ // need to duplicate the cases before typing them to generate the apply method, or the symbols will be all messed up - val casesTrue = if (isPartial) cases map (c => deriveCaseDef(c)(x => TRUE_typed).duplicate) else Nil + val casesTrue = if (isPartial) cases map (c => deriveCaseDef(c)(x => atPos(x.pos.focus)(TRUE_typed)).duplicate) else Nil // println("casesTrue "+ casesTrue) def parentsPartial(targs: List[Type]) = addSerializable(appliedType(AbstractPartialFunctionClass.typeConstructor, targs)) @@ -2372,7 +2384,7 @@ trait Typers extends Modes with Adaptations with Taggings { } def isDefinedAtMethod = { - val methodSym = anonClass.newMethod(nme.isDefinedAt, tree.pos, FINAL) + val methodSym = anonClass.newMethod(nme.isDefinedAt, tree.pos.makeTransparent, FINAL) val paramSyms = mkParams(methodSym) val selector = mkSel(paramSyms) @@ -2398,7 +2410,7 @@ trait Typers extends Modes with Adaptations with Taggings { def translated = if (members.head eq EmptyTree) setError(tree) - else typed(Block(List(ClassDef(anonClass, NoMods, List(List()), List(List()), members, tree.pos)), New(anonClass.tpe)), mode, pt) + else typed(atPos(tree.pos)(Block(List(ClassDef(anonClass, NoMods, List(List()), List(List()), members, tree.pos.focus)), atPos(tree.pos.focus)(New(anonClass.tpe)))), mode, pt) } // Function(params, Match(sel, cases)) ==> new <Partial>Function { def apply<OrElse>(params) = `translateMatch('sel match { cases }')` } @@ -3230,13 +3242,15 @@ trait Typers extends Modes with Adaptations with Taggings { } if (hasError) annotationError - else AnnotationInfo(annType, List(), nvPairs map {p => (p._1, p._2.get)}).setOriginal(Apply(typedFun, args)).setPos(ann.pos) + else AnnotationInfo(annType, List(), nvPairs map {p => (p._1.asInstanceOf[Name], p._2.get)}).setOriginal(Apply(typedFun, args).setPos(ann.pos)) // [Eugene+] why do we need this cast? } } else if (requireJava) { reportAnnotationError(NestedAnnotationError(ann, annType)) } else { val typedAnn = if (selfsym == NoSymbol) { - typed(ann, mode, annClass.tpe) + // local dummy fixes SI-5544 + val localTyper = newTyper(context.make(ann, context.owner.newLocalDummy(ann.pos))) + localTyper.typed(ann, mode, annClass.tpe) } else { // Since a selfsym is supplied, the annotation should have // an extra "self" identifier in scope for type checking. @@ -4972,6 +4986,7 @@ trait Typers extends Modes with Adaptations with Taggings { * @return ... */ def typed(tree: Tree, mode: Int, pt: Type): Tree = { + lastTreeToTyper = tree indentTyping() var alreadyTyped = false diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 1ebcea4a07..3d9453d8cd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -210,13 +210,25 @@ trait Unapplies extends ast.TreeDSL // and re-added in ``finishWith'' in the namer. def paramWithDefault(vd: ValDef) = treeCopy.ValDef(vd, vd.mods | DEFAULTPARAM, vd.name, atPos(vd.pos.focus)(TypeTree() setOriginal vd.tpt), toIdent(vd)) - - val paramss = mmap(cparamss)(paramWithDefault) - val classTpe = classType(cdef, tparams) + + val (copyParamss, funParamss) = cparamss match { + case Nil => (Nil, Nil) + case ps :: pss => + (List(ps.map(paramWithDefault)), mmap(pss)(p => copyUntyped[ValDef](p).copy(rhs = EmptyTree))) + } + + val classTpe = classType(cdef, tparams) + val bodyTpe = funParamss.foldRight(classTpe)((params, restp) => gen.scalaFunctionConstr(params.map(_.tpt), restp)) + + val argss = copyParamss match { + case Nil => Nil + case ps :: Nil => mmap(ps :: funParamss)(toIdent) + } + val body = funParamss.foldRight(New(classTpe, argss): Tree)(Function) Some(atPos(cdef.pos.focus)( - DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, classTpe, - New(classTpe, mmap(paramss)(toIdent))) + DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, copyParamss, bodyTpe, + body) )) } } diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 48ec941b50..404490bd49 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -9,7 +9,7 @@ package util import java.net.URL import scala.collection.{ mutable, immutable } -import io.{ File, Directory, Path, Jar, AbstractFile, ClassAndJarInfo } +import io.{ File, Directory, Path, Jar, AbstractFile } import scala.tools.util.StringOps.splitWhere import Jar.isJarOrZip import File.pathSeparator @@ -23,16 +23,6 @@ import java.net.MalformedURLException * @author Stepan Koltsov */ object ClassPath { - def scalaLibrary = locate[Option[_]] - def scalaCompiler = locate[Global] - - def infoFor[T](value: T) = info(value.getClass) - def info[T](clazz: Class[T]) = new ClassAndJarInfo()(ClassTag[T](clazz)) - def info[T: ClassTag] = new ClassAndJarInfo[T] - def locate[T: ClassTag] = info[T].rootClasspath - def locateJar[T: ClassTag] = info[T].rootPossibles find (x => isJarOrZip(x)) map (x => File(x)) - def locateDir[T: ClassTag] = info[T].rootPossibles find (_.isDirectory) map (_.toDirectory) - /** Expand single path entry */ private def expandS(pattern: String): List[String] = { val wildSuffix = File.separator + "*" @@ -54,26 +44,6 @@ object ClassPath { else List(pattern) } - /** Return duplicated classpath entries as - * (name, list of origins) - * in the order they occur on the path. - */ - // def findDuplicates(cp: ClassPath[_]) = { - // def toFullName(x: (String, _, cp.AnyClassRep)) = x._1 + "." + x._3.name - // def toOriginString(x: ClassPath[_]) = x.origin getOrElse x.name - // - // /** Flatten everything into tuples, recombine grouped by name, filter down to 2+ entries. */ - // val flattened = ( - // for ((pkgName, pkg) <- cp.allPackagesWithNames ; clazz <- pkg.classes) yield - // (pkgName, pkg, clazz) - // ) - // val multipleAppearingEntries = flattened groupBy toFullName filter (_._2.size > 1) - // - // /** Extract results. */ - // for (name <- flattened map toFullName distinct ; dups <- multipleAppearingEntries get name) yield - // (name, dups map { case (_, cp, _) => toOriginString(cp) }) - // } - /** Split classpath using platform-dependent path separator */ def split(path: String): List[String] = (path split pathSeparator).toList filterNot (_ == "") distinct @@ -240,26 +210,6 @@ abstract class ClassPath[T] { def packages: IndexedSeq[ClassPath[T]] def sourcepaths: IndexedSeq[AbstractFile] - /** Information which entails walking the tree. This is probably only - * necessary for tracking down problems - it's normally not used. - */ - // def allPackages: List[ClassPath[T]] = packages ::: (packages flatMap (_.allPackages)) - // def allPackageNames: List[String] = { - // def subpackages(prefix: String, cp: ClassPath[T]): List[String] = ( - // (cp.packages map (prefix + _.name)) ::: - // (cp.packages flatMap (x => subpackages(prefix + x.name + ".", x))) - // ) - // subpackages("", this) - // } - // def allPackagesWithNames: List[(String, ClassPath[T])] = { - // val root = packages map (p => p.name -> p) - // val subs = - // for ((prefix, p) <- root ; (k, v) <- p.allPackagesWithNames) yield - // (prefix + "." + k, v) - // - // root ::: subs - // } - /** * Represents classes which can be loaded with a ClassfileLoader/MsilFileLoader * and / or a SourcefileLoader. @@ -431,41 +381,10 @@ extends ClassPath[T] { } new MergedClassPath[T](newEntries, context) } - // - // override def allPackages: List[ClassPath[T]] = entries flatMap (_.allPackages) - // override def allPackageNames = entries flatMap (_.allPackageNames) - // override def allPackagesWithNames = entries flatMap (_.allPackagesWithNames) - // - // def duplicatedClasses = { - // def toFullName(x: (String, _, AnyClassRep)) = x._1 + "." + x._3.name - // - // /** Flatten everything into tuples, recombine grouped by name, filter down to 2+ entries. */ - // val flattened = ( - // for ((pkgName, pkg) <- allPackagesWithNames ; clazz <- pkg.classes) yield - // (pkgName, pkg, clazz) - // ) - // val multipleAppearingEntries = flattened groupBy toFullName filter (_._2.size > 1) - // - // /** Using original name list as reference point, return duplicated entries as - // * (name, list of origins) - // * in the order they occur on the path. - // */ - // for (name <- flattened map toFullName distinct ; dups <- multipleAppearingEntries get name) yield - // (name, dups map { - // case (_, cp, _) if cp.origin.isDefined => cp.origin.get - // case (_, cp, _) => cp.asURLs.mkString - // }) - // } - // def show() { println("ClassPath %s has %d entries and results in:\n".format(name, entries.size)) asClasspathString split ':' foreach (x => println(" " + x)) } - // def showDuplicates() = - // ClassPath findDuplicates this foreach { - // case (name, xs) => println(xs.mkString(name + ":\n ", "\n ", "\n")) - // } - // override def toString() = "merged classpath "+ entries.mkString("(", "\n", ")") } diff --git a/src/compiler/scala/tools/nsc/util/Exceptional.scala b/src/compiler/scala/tools/nsc/util/Exceptional.scala index 7452aa1b67..7c093b7866 100644 --- a/src/compiler/scala/tools/nsc/util/Exceptional.scala +++ b/src/compiler/scala/tools/nsc/util/Exceptional.scala @@ -3,141 +3,10 @@ package util import java.util.concurrent.ExecutionException import java.lang.reflect.{ InvocationTargetException, UndeclaredThrowableException } -import io.{ Sources, Fileish } import scala.tools.util.StringOps._ import language.implicitConversions -/** A simple throwable wrapper so it looks more like a parade of - * glittering frame-shaped beauties than the other thing. - */ -class Exceptional(val ex: Throwable)(implicit prefs: ScalaPrefs) { - val formatter = prefs.exceptionFormatter(ex) - val unwrapped = Exceptional.unwrap(ex) - val table = formatter.newTable(unwrapped) - def rawTrace() = unwrapped.printStackTrace() - def isScanDone = prefs.codeSources.isDone() - - /** Block until the scanning is complete. */ - def force(): this.type = { - prefs.codeSources.force() - this - } - - /** Stack frame contexts are only shown as long as this is true. */ - def spanFn(frame: JavaStackFrame): Boolean = true - - /** The result of this will be printed before a context trace. */ - def contextPrelude: String = - if (isScanDone || prefs.codeSources.isEmpty) "" - else "/* Still scanning source path: there may be more momentarily. */\n" - - /** Frames with surrounding context. */ - private def contextFrames = toList takeWhile spanFn - def contextHead(): String = contextElems.headOption getOrElse "" - def contextElems() = contextFrames map formatter.inContext - def context(): String = context(length) - def context(num: Int): String = contextPrelude + ojoinOr(contextFrames take num map formatter.inContext, "\n", "No stack trace.") - - /** Exceptional doesn't extend Seq because it turns out to be super - * annoying in the repl: tab-completion sees all the Seq methods. - */ - def length = toList.length - def toList = table.toList - def iterator = table.iterator - def apply(index: Int) = table(index) - - def causes = Exceptional.causes(ex) - def summary = unwrapped.toString + "\n at " + apply(0).shortNameString - - private def println(msg: Any) = { - Console println msg - Console.flush() - } - - def show(): Unit = println(context()) - def show(num: Int): Unit = println(context(num)) - def showCauses() = println((ex :: causes).mkString("", "\n caused by -> ", "")) - def showTable() = println(table) - def showSummary() = println(summary) - - override def toString = summary -} - - object Exceptional { - type Catcher[+T] = PartialFunction[Throwable, T] - - /** Creates an exception handler which will only ever catch the given - * number of exceptions (if the given pf is defined there) and after - * that will disable itself. - */ - def expiringHandler[T](numCatches: Int)(pf: Catcher[T]): Catcher[T] = { - var remaining = numCatches; - { case ex: Throwable if remaining > 0 && pf.isDefinedAt(ex) => - remaining -= 1 - pf(ex) - } - } - - /** The Throwable => Exceptional implicit plus the associated factory. */ - implicit def throwableToExceptional(ex: Throwable)(implicit prefs: ScalaPrefs): Exceptional = apply(ex)(prefs) - def apply(ex: Throwable)(implicit prefs: ScalaPrefs) = new Exceptional(ex)(prefs) - - /** Some handy functions. */ - def stack() = JavaStackFrame frames ((new Throwable).getStackTrace dropWhile isLocal) - def showme() = apply(new Throwable).show() - def showstack() = apply(new Throwable).showTable() - - /** A frame formatter with more refined aesthetics than the default. - * Come, let us be civilized. - */ - object ScalaFormat extends TableDef[JavaStackFrame] { - >> ("file" -> (_.fileName)) >+ ":" - << ("line" -> (_.line)) - >> ("class" -> (_.shortestName)) >+ "." - << ("method" -> (_.methodName)) - } - - trait Calibrated { - def newTable(ex: Throwable): TableDef[JavaStackFrame]#Table - def inContext(frame: JavaStackFrame): String - } - trait Formatter extends (Throwable => Calibrated) { - def apply(ex: Throwable): Calibrated - } - object Formatter { - def apply(implicit prefs: ScalaPrefs): Formatter = new Formatter { - def apply(ex: Throwable) = new Calibrated { - def newTable(ex: Throwable) = new ScalaFormat.Table(JavaStackFrame frames ex) - def inContext(frame: JavaStackFrame) = new FrameContext(frame, prefs.codeSources) toString - } - } - } - - /** Java stack traces have the interesting property of using only the name - * of the file, no paths. This makes it a bit of a gamble to try to associate - * a stack frame with a specific file. Witness the heuristic. - */ - def locateSources(sources: Sources, frame: JavaStackFrame): List[Fileish] = { - // if only one has a matching path, that's fairly sure to be the one - val matches = sources(frame.fileName) filter (_.pkgFromPath endsWith frame.pkgName) - if (matches.isEmpty || matches.tail.isEmpty) - return matches - - // otherwise we'll drink them in and look for a package name - matches filter (_.pkgFromSource endsWith frame.pkgName) - } - - /** Right now this punts if more than one match and it accepts the first at random. - */ - def locateSource(sources: Sources, frame: JavaStackFrame): Option[Fileish] = - locateSources(sources, frame).headOption - - def isLocal(ste: StackTraceElement) = ste.getClassName startsWith this.getClass.getName - def causes(x: Throwable): List[Throwable] = x.getCause match { - case null => Nil - case ex => x :: causes(ex) - } def unwrap(x: Throwable): Throwable = x match { case _: InvocationTargetException | _: ExceptionInInitializerError | diff --git a/src/compiler/scala/tools/nsc/util/FlagsUtil.scala b/src/compiler/scala/tools/nsc/util/FlagsUtil.scala deleted file mode 100644 index b615775468..0000000000 --- a/src/compiler/scala/tools/nsc/util/FlagsUtil.scala +++ /dev/null @@ -1,233 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -// Overloading invariants: these are "pseudoinvariants" because many of the -// methods do not exist on Modifiers, only on Symbol, not to mention it is only -// speculative that they are mutually exclusive: but is here to provide a -// starting point for further refinement. -// -// 16: BYNAMEPARAM CAPTURED COVARIANT -// x.isParameter ==> BYNAMEPARAM -// x.isMutable ==> CAPTURED -// x.isType ==> COVARIANT -// -// 17: CONTRAVARIANT INCONSTRUCTOR LABEL -// x.isType ==> CONTRAVARIANT -// x.isClass ==> INCONSTRUCTOR -// x.isMethod ==> LABEL -// -// 25: DEFAULTPARAM TRAIT -// x.isParameter ==> DEFAULTPARAM -// x.isClass ==> TRAIT -// -// 35: EXISTENTIAL MIXEDIN -// x.isType ==> EXISTENTIAL -// x.isTerm ==> MIXEDIN -// -// 37: IMPLCLASS PRESUPER -// x.isClass ==> IMPLCLASS -// x.isTerm ==> PRESUPER - -import scala.collection.{ mutable, immutable } -import symtab.Flags.ExplicitFlags - -class TransFlagManager[T <: Global](val global: T) { - import global._ - import definitions._ - - private var trackerStack: List[FlagTracker] = Nil - private def trackerString = trackerStack.mkString(" ") - - class FlagTracker(val name: String) { - private val mask = symtab.Flags.TRANS_FLAG - private val seen = new mutable.HashSet[Symbol] - - private def doWeOwnFlag = trackerStack.headOption exists (_ eq this) - private def isOK = trackerStack.isEmpty || (trackerStack.head eq this) - - def apply(sym: Symbol) = { - if (!isOK) - log("Testing " + sym.name + " for " + name + " flag, but not at top of stack: " + trackerString) - - sym hasFlag mask - } - def set(sym: Symbol) = { - if (!isOK) - log("Tried to set " + name + " but not at top of stack: " + trackerString) - - seen += sym - sym setFlag mask - } - def reset(sym: Symbol) = { - if (!isOK) - log("Tried to clear " + name + " but not at top of stack: " + trackerString) - - seen -= sym - sym resetFlag mask - } - def clear() { - if (!doWeOwnFlag && seen.nonEmpty) - log("Clearing " + seen.size + " " + name + " flags even though the stack is: " + trackerString) - - seen foreach (_ resetFlag mask) - seen.clear() - } - } - - def forceClear() = { - if (trackerStack.nonEmpty) { - log("Warning: force clearing the stack at " + phase + ": " + trackerString) - trackerStack foreach (_.clear()) - trackerStack = Nil - } - } - - def claimTransFlag(label: String): FlagTracker = { - if (trackerStack.isEmpty || trackerStack.head.name != label) - trackerStack ::= new FlagTracker(label) - - trackerStack.head - } - def releaseTransFlag(label: String): Boolean = { - trackerStack.isEmpty || { - if (trackerStack.head.name == label) { - trackerStack.head.clear() - trackerStack = trackerStack.tail - true - } - else { - log("Warning: trying to release " + label + " flag but the stack is: " + trackerStack.mkString(" ")) - false - } - } - } - def holdingTransFlag[U](label: String)(f: FlagTracker => U): U = { - try { - val t = claimTransFlag(label) - f(t) - } - finally { - releaseTransFlag(label) - } - } -} - - -/** Some functions for generating comments and methods involving flags, - * with the output determined by reflection so we can have a little more - * assurance that documentation and debugging output match up with reality. - * - * For the compiler, the output can be generated with: - * scala scala.tools.nsc.util.FlagsUtilCompiler - */ -class FlagsUtil(flagsObject: AnyRef) { - /** Override to tweak flag strings before output. */ - def addFlagMetadata(name: String) = name - - /** Runs the generative methods in this class. */ - def reflectiveAnalyzeFlags() = { - mkFlagsTable() - println("") - mkFlagToStringMethod() - } - /** A list of the flag names found at each bit position. - */ - def reflectiveFlagNames: List[List[String]] = { - val pairs = flagMethods map { m => - m.getName -> ((m invoke flagsObject) match { - case x: java.lang.Integer => x.intValue: Long - case x: java.lang.Long => x.longValue - }) - } - (0 to 63).toList map { idx => - pairs collect { case (name, value) if value == (1L << idx) => name } - } - } - /** Prints a comment table identifying all the flags (as seen - * via reflection) and at what bit each is located. - */ - def mkFlagsTable() = { - val markedFlagNames = reflectiveFlagNames map (_ map addFlagMetadata) - - val widths = 0 to 2 map { column => - markedFlagNames collect { case xs if xs.length > column => - xs(column).length - } max - } - val fmt = "// %2d: " + (widths map ("%" + _ + "s") mkString " ") - def padded(xs: List[String]) = xs match { - case Nil => List("", "", "") - case x :: Nil => List(x, "", "") - case x1 :: x2 :: Nil => List(x1, x2, "") - case _ => xs take 3 - } - println("// Generated by mkFlagsTable() at " + now + "\n//") - // prints the grid showing which flags are at each index - for ((names, idx) <- markedFlagNames.zipWithIndex) { - println(fmt.format(idx :: padded(names) : _*)) - } - } - /** Prints an implementation of flagToString based on the reflectively - * determined contents of the flags class. - */ - def mkFlagToStringMethod() = { - def key(xs: List[String], flag: Long) = xs match { - case Nil => "%19s".format("0x" + "%x".format(flag) + "L") - case x :: _ => - if (x.head.isLower) "`" + x + "`" - else x - } - def value(xs: List[String], flag: Long) = "\"" + (xs match { - case Nil => "" - case x :: Nil if (flag & ExplicitFlags) != 0 => x.toLowerCase - case xs => xs.map(_.toLowerCase).mkString("<", "/", ">") - }) + "\"" - val pairs: List[(String, String)] = reflectiveFlagNames.zipWithIndex map { - case (xs, idx) => (key(xs, 1L << idx), value(xs, 1L << idx)) - } - val keyWidth = pairs map (_._1.length) max - val bodyWidth = pairs map (_._2.length) max - val fmt = " case %" + keyWidth + "s => %-" + bodyWidth + "s // (1L << %d)" - - println("// Generated by mkFlagToStringMethod() at " + now) - println("@annotation.switch override def flagToString(flag: Long): String = flag match {") - for (((key, body), idx) <- pairs.zipWithIndex) { - print(fmt.format(key, body, idx)) - println("") - } - println(" case _ => \"\"") - println("}") - } - - def isFlagName(s: String) = s stripPrefix "late" stripPrefix "not" forall (x => x.isUpper || x == '_') - def flagMethods = flagsObject.getClass.getMethods.toList filter (x => isFlagName(x.getName)) sortBy (_.getName) - private def now = new java.util.Date toString -} - -object FlagsUtil { - import reflect.internal.ModifierFlags - - trait MarkModifiers extends FlagsUtil { - lazy val isModifiersFlag = classOf[ModifierFlags].getMethods map (_.getName) filter isFlagName toSet - override def addFlagMetadata(name: String) = { - if (isModifiersFlag(name)) name + "/M" - else name - } - } -} - -/** Convenience standalone programs. - */ -object FlagsUtilCompiler extends FlagsUtil(symtab.Flags) with FlagsUtil.MarkModifiers { - def main(args: Array[String]): Unit = reflectiveAnalyzeFlags() -} - -object FlagsUtilLibrary extends FlagsUtil(reflect.internal.Flags) with FlagsUtil.MarkModifiers { - def main(args: Array[String]): Unit = reflectiveAnalyzeFlags() -} - diff --git a/src/compiler/scala/tools/nsc/util/Indenter.scala b/src/compiler/scala/tools/nsc/util/Indenter.scala deleted file mode 100644 index f9ddc4a194..0000000000 --- a/src/compiler/scala/tools/nsc/util/Indenter.scala +++ /dev/null @@ -1,85 +0,0 @@ -package scala.tools.nsc -package util - -import java.io.PrintStream - -class Indenter(var stringFn: Any => String) { - def this() = this("" + _) - def out: PrintStream = System.out - - var indentSpaces = 2 - var isSorted = false - var openString = "" - var closeString = "" - - def braces: this.type = { - openString = " {" - closeString = "}" - this - } - def sorted: this.type = { isSorted = true ; this } - def stringify(fn: Any => String): this.type = { - stringFn = fn - this - } - - def atStartOfLine = startOfLine - private var indentLevel = 0 - private var startOfLine = true - def indent: this.type = { indentLevel += 1 ; this } - def undent: this.type = { indentLevel -= 1 ; this } - def currentIndent = " " * indentLevel * indentSpaces - def printIndent() = { - out.print(currentIndent) - startOfLine = true - } - - // Execute the given body indented one level further. - def >>[T](body: => T): T = { - indentLevel += 1 - try body - finally indentLevel -= 1 - } - - def openIndent(token: Any) { - print(token + "\n") - indent - printIndent() - } - def closeIndent(token: Any) { - print("\n") - undent - printIndent() - print(token) - } - def finishLine(token: Any) { - print(token) - printIndent() - } - def nextIndent(endOfLine: Any) = finishLine(endOfLine) - - def block(label: String)(body: => Unit) { - if (label != "" || openString != "") - pp(label + openString) - - this >> body - - if (closeString != "") - pp(closeString) - } - def print(x: Any) = { - out print stringFn(x) - out.flush() - startOfLine = false - } - def pps(xs: TraversableOnce[Any]) { - if (isSorted) xs.toSeq.sortBy("" + _) foreach pp - else xs foreach pp - } - def pp(x: Any) { - printIndent() - out println stringFn(x) - out.flush() - startOfLine = false - } -} diff --git a/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala b/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala deleted file mode 100644 index d25698ed38..0000000000 --- a/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala +++ /dev/null @@ -1,71 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import io.{ Fileish, Sources } -import Exceptional._ - -class FrameContext(frame: JavaStackFrame, codeSources: Sources) { - val sourceFile = locateSource(codeSources, frame) - import frame._ - - def windowWidth = 3 - def windowSize = windowWidth * 2 + 1 - - lazy val context = sourceFile collect { - case f if line > 0 => - val start = math.max(0, line - windowWidth) - f.lines().toList.slice(start, start + windowSize) - } getOrElse Nil - - protected def fallbackContext = "%s (%s:%s)".format(tag, fileName, line) - - private def linestr(index: Int) = { - val current = line - windowWidth + index - val marker = if (current == line) "*" else " " - marker + current - } - private def contextLines = context.zipWithIndex map { - case (l, i) => linestr(i) + ": " + l + "\n" - } - override def toString = - if (context.isEmpty) fallbackContext - else contextLines.mkString(tag + "\n", "", "") -} - -object FrameContext { - def apply(elem: StackTraceElement): FrameContext = apply(new JavaStackFrame(elem)) - def apply(frame: JavaStackFrame): FrameContext = new FrameContext(frame, Sources()) -} - -class JavaStackFrame(val elem: StackTraceElement) { - def className: String = elem.getClassName() - def methodName: String = elem.getMethodName() - def fileName: String = elem.getFileName() - def line: Int = elem.getLineNumber() - - private def segs = className takeWhile (ch => ch != '$' && ch != '(') split '.' toList ; - lazy val pkgName = segs.init mkString "." - lazy val shortName = segs.last - lazy val shortestName = if (fileName startsWith (shortName + ".")) "<--" else shortName - - private def standardString(which: String) = - "%s.%s(%s:%s)".format(which, methodName, fileName, line) - - def locationString = fileName + ":" + line - def javaString = standardString(className) - def shortNameString = standardString(shortName) - def tag = "[%s.%s]".format(shortName, methodName) - - override def toString = shortNameString -} - -object JavaStackFrame { - def apply(elem: StackTraceElement) = new JavaStackFrame(elem) - def frames(xs: Array[StackTraceElement]): Array[JavaStackFrame] = xs map (x => new JavaStackFrame(x)) - def frames(t: Throwable): Array[JavaStackFrame] = frames(Exceptional.unwrap(t).getStackTrace) -} diff --git a/src/compiler/scala/tools/nsc/util/ProxyReport.scala b/src/compiler/scala/tools/nsc/util/ProxyReport.scala deleted file mode 100644 index 4fc86c3a32..0000000000 --- a/src/compiler/scala/tools/nsc/util/ProxyReport.scala +++ /dev/null @@ -1,146 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import scala.collection.{ mutable, immutable, generic } - -/** A class for analyzing forwarding/proxy relationships. - */ -trait ProxyReport { - val global: Global - import global._ - import definitions._ - - private object classes { - def isIgnorable(sym: Symbol) = sym :: sym.allOverriddenSymbols exists { s => - ObjectClass isSubClass s.owner - } - def nonPrivateMethods(sym: Symbol) = { - val methods = sym.initialize.tpe.nonPrivateMembers filter { x => - x.isMethod && !x.isConstructor && !x.isPrivate && !isIgnorable(x) - } - methods foreach (m => m.initialize.info.paramss.flatten foreach (_.initialize)) - methods - } - lazy val GlobalClass = getRequiredClass(classOf[Global].getName) - lazy val GenericClass = getRequiredModule("scala.collection.generic").moduleClass - lazy val CollectionClass = getRequiredModule("scala.collection").moduleClass - - def getType(name: String) = getMember(GlobalClass, newTypeName(name)) - def getColl(name: String) = getMember(CollectionClass, newTypeName(name)) - def getGeneric(name: String) = getMember(GenericClass, newTypeName(name)) - - // the following operations + those in RewrappingTypeProxy are all operations - // in class Type that are overridden in some subclass - // Important to keep this up-to-date when new operations are added! - def TypeClass = getType("Type") - def SimpleTypeProxy = getType("SimpleTypeProxy") - def RewrappingTypeProxy = getType("RewrappingTypeProxy") - - def TraversableForwarder = getGeneric("TraversableForwarder") - def IterableForwarder = getGeneric("IterableForwarder") - def SeqForwarder = getGeneric("SeqForwarder") - def TraversableLike = getColl("TraversableLike") - def TraversableProxy = getColl("TraversableProxyLike") - def IterableLike = getColl("IterableLike") - def IterableProxy = getColl("IterableProxyLike") - def MapLike = getColl("MapLike") - def MapProxy = getColl("MapProxyLike") - def SeqLike = getColl("SeqLike") - def SeqProxy = getColl("SeqProxyLike") - def SetLike = getColl("SetLike") - def SetProxy = getColl("SetProxyLike") - } - import classes._ - - val wrappedHeader = """ -/** With respect to %s, %s wraps: - */ -trait Wrapped { - """.trim - val unwrappedHeader = """ -/** With respect to %s, %s does NOT wrap: - */ -trait Unwrapped { - """.trim - - def wrapReport(underlying: Symbol, proxy: Symbol) = { - val underlyingMs = nonPrivateMethods(underlying) - val proxyMs = nonPrivateMethods(proxy) filterNot (_.owner == underlying) - val (wrapped, unwrapped) = underlyingMs partition (m => - proxyMs exists (p => - (p.name == m.name) && { - val self = proxy.thisType - val memberTp = self.memberType(p) - val parentTp = self.memberType(m) - - refChecks.overridesTypeInPrefix(memberTp, parentTp, self) - // || { - // // if (p.paramss.flatten.length == m.paramss.flatten.length) - // // println("names equal, overridesType false:\n " + ((p, m, memberTp, parentTp, self)) + "\n") - // - // false - // } - } - ) - ) - - def show(xs: List[Symbol], template: String) = { - val lines = xs.map(_.initialize.defString).sorted.map(" " + _ + "\n") - lines.mkString(template.format(underlying, proxy) + "\n", "", "}") - } - - show(wrapped, wrappedHeader) + "\n\n" + show(unwrapped, unwrappedHeader) - } - - lazy val wrappers = List( - TypeClass -> SimpleTypeProxy, - TypeClass -> RewrappingTypeProxy, - TraversableClass -> TraversableForwarder, - IterableClass -> IterableForwarder, - SeqClass -> SeqForwarder, - TraversableLike -> TraversableProxy, - IterableLike -> IterableProxy, - MapLike -> MapProxy, - SetLike -> SetProxy, - SeqLike -> SeqProxy - ) - - def generate(dir: io.Directory) = { - /** A proxy for a type (identified by field `underlying`) that forwards most - * operations to it (for exceptions, see WrappingProxy, which forwards even more operations). - * every operation that is overridden for some kind of types should be forwarded. - */ - for ((clazz, proxy) <- wrappers) { - val text = wrapReport(clazz, proxy) - val file = dir / (proxy.fullName + ".scala") toFile; - - file writeAll text - println("Created " + file) - } - } -} - -object ProxyReportRunner { - class ProxyGlobal(s: Settings) extends Global(s) { - object proxyReport extends { - val global: ProxyGlobal.this.type = ProxyGlobal.this - } with util.ProxyReport - } - - def main(args: Array[String]): Unit = { - if (args.isEmpty) - return println("Give an output directory as argument.") - - val dir = io.Directory(args(0)).createDirectory() - val s = new Settings() - s.processArguments(args.toList.tail, true) - val g = new ProxyGlobal(s) - val run = new g.Run() - g.afterTyper(g.proxyReport.generate(dir)) - } -} diff --git a/src/compiler/scala/tools/nsc/util/RegexCache.scala b/src/compiler/scala/tools/nsc/util/RegexCache.scala deleted file mode 100644 index 896c1a536f..0000000000 --- a/src/compiler/scala/tools/nsc/util/RegexCache.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Lex Spoon - */ - -package scala.tools.nsc -package util -import java.util.regex.Pattern -import scala.collection.mutable - -object RegexCache { - /** Maps patterns to compiled regexes */ - private val regexMap = mutable.Map.empty[String, Pattern] - - /** Lists the regexes that have been recorded in order */ - private val regexList = new mutable.Queue[String] - - private val regexesToCache = 1000 - - /** Compile a regex and add it to the cache */ - private def compileAndAdd(regex: String): Pattern = { - val pattern = Pattern.compile(regex) - - regexMap += (regex -> pattern) - regexList += regex - - if (regexMap.size > regexesToCache) - regexMap -= regexList.dequeue() - - pattern - } - - - /** Compile a regex, caching */ - def apply(regex: String): Pattern = - regexMap.get(regex) match { - case Some(pattern) => pattern - case None => compileAndAdd(regex) - } -} diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index 120cceb238..4c7920d6b3 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -16,8 +16,6 @@ import ScalaClassLoader._ import scala.util.control.Exception.{ catching } import language.implicitConversions -// import Exceptional.unwrap - trait HasClassPath { def classPathURLs: Seq[URL] } @@ -26,14 +24,6 @@ trait HasClassPath { * of java reflection. */ trait ScalaClassLoader extends JClassLoader { - /** Override to see classloader activity traced */ - protected def trace: Boolean = false - protected lazy val classLoaderUniqueId = "Cl#" + System.identityHashCode(this) - protected def classLoaderLog(msg: => String) { - if (trace) - Console.err.println("[" + classLoaderUniqueId + "] " + msg) - } - /** Executing an action with this classloader as context classloader */ def asContext[T](action: => T): T = { val saved = contextLoader @@ -55,18 +45,6 @@ trait ScalaClassLoader extends JClassLoader { def create(path: String): AnyRef = tryToInitializeClass[AnyRef](path) map (_.newInstance()) orNull - override def findClass(name: String) = { - val result = super.findClass(name) - classLoaderLog("findClass(%s) = %s".format(name, result)) - result - } - - override def loadClass(name: String, resolve: Boolean) = { - val result = super.loadClass(name, resolve) - classLoaderLog("loadClass(%s, %s) = %s".format(name, resolve, result)) - result - } - def constructorsOf[T <: AnyRef : ClassTag]: List[Constructor[T]] = classTag[T].erasure.getConstructors.toList map (_.asInstanceOf[Constructor[T]]) @@ -100,7 +78,6 @@ trait ScalaClassLoader extends JClassLoader { case null => Nil case p => p.loaderChain }) - override def toString = classLoaderUniqueId } /** Methods for obtaining various classloaders. @@ -173,7 +150,7 @@ object ScalaClassLoader { classloaderURLs :+= url super.addURL(url) } - def toLongString = urls.mkString("URLClassLoader(id=" + classLoaderUniqueId + "\n ", "\n ", "\n)\n") + def toLongString = urls.mkString("URLClassLoader(\n ", "\n ", "\n)\n") } def fromURLs(urls: Seq[URL], parent: ClassLoader = null): URLClassLoader = diff --git a/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala b/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala deleted file mode 100644 index 03e0f54606..0000000000 --- a/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import io.Sources - -trait ScalaPrefs { - def codeSources: Sources - def exceptionFormatter: Exceptional.Formatter -} - -trait LowPriorityScalaPrefs { - implicit object DefaultScalaPrefs extends ScalaPrefs { - def codeSources = Sources.defaultSources - def exceptionFormatter = Exceptional.Formatter(this) - } -} - -object ScalaPrefs extends LowPriorityScalaPrefs { - def apply(implicit prefs: ScalaPrefs): ScalaPrefs = prefs -} diff --git a/src/compiler/scala/tools/nsc/util/ShowPickled.scala b/src/compiler/scala/tools/nsc/util/ShowPickled.scala index fc39a218f8..53ed96d0d3 100644 --- a/src/compiler/scala/tools/nsc/util/ShowPickled.scala +++ b/src/compiler/scala/tools/nsc/util/ShowPickled.scala @@ -11,8 +11,6 @@ import java.io.{File, FileInputStream, PrintStream} import java.lang.Long.toHexString import java.lang.Float.intBitsToFloat import java.lang.Double.longBitsToDouble - -import cmd.program.Simple import scala.reflect.internal.{Flags, Names} import scala.reflect.internal.pickling.{ PickleBuffer, PickleFormat } import interpreter.ByteCode.scalaSigBytesForPath @@ -119,21 +117,18 @@ object ShowPickled extends Names { result.toInt } - def printFile(buf: PickleBuffer, out: PrintStream): Unit = printFile(buf, out, false) - def printFile(buf: PickleBuffer, out: PrintStream, bare: Boolean) { + def printFile(buf: PickleBuffer, out: PrintStream) { out.println("Version " + buf.readNat() + "." + buf.readNat()) val index = buf.createIndex val entryList = makeEntryList(buf, index) buf.readIndex = 0 - /** A print wrapper which discards everything if bare is true. - */ - def p(s: String) = if (!bare) out print s + def p(s: String) = out print s def printNameRef() { val idx = buf.readNat() val name = entryList nameAt idx - val toPrint = if (bare) " " + name else " %s(%s)".format(idx, name) + val toPrint = " %s(%s)".format(idx, name) out print toPrint } @@ -156,7 +151,7 @@ object ShowPickled extends Names { val accessBoundary = ( for (idx <- privateWithin) yield { val s = entryList nameAt idx - if (bare) s else idx + "(" + s + ")" + idx + "(" + s + ")" } ) val flagString = { @@ -283,31 +278,18 @@ object ShowPickled extends Names { try Some(new PickleBuffer(data, 0, data.length)) catch { case _: Exception => None } - def show(what: String, pickle: PickleBuffer, bare: Boolean) = { + def show(what: String, pickle: PickleBuffer) = { Console.println(what) val saved = pickle.readIndex pickle.readIndex = 0 - printFile(pickle, Console.out, bare) + printFile(pickle, Console.out) pickle.readIndex = saved } - private lazy val ShowPickledSpec = - Simple( - Simple.scalaProgramInfo("showPickled", "Usage: showPickled [--bare] <classname>"), - List("--bare" -> "suppress numbers in output"), - Nil, - null - ) - - /** Option --bare suppresses numbers so the output can be diffed. - */ def main(args: Array[String]) { - val runner = ShowPickledSpec instance args - import runner._ - - residualArgs foreach { arg => + args foreach { arg => (fromFile(arg) orElse fromName(arg)) match { - case Some(pb) => show(arg + ":", pb, parsed isSet "--bare") + case Some(pb) => show(arg + ":", pb) case _ => Console.println("Cannot read " + arg) } } diff --git a/src/compiler/scala/tools/nsc/util/Tracer.scala b/src/compiler/scala/tools/nsc/util/Tracer.scala deleted file mode 100644 index acbf60da5b..0000000000 --- a/src/compiler/scala/tools/nsc/util/Tracer.scala +++ /dev/null @@ -1,73 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package util - -import java.io.PrintStream -import scala.runtime.ScalaRunTime - -class Tracer(enabled: () => Boolean) { - def out: PrintStream = System.out - def stringify(x: Any) = ScalaRunTime stringOf x - - // So can pass tuples, lists, whatever as arguments and don't - // get a lot of extra parens or noisy prefixes. - def stringifyArgs(x: Any) = { - x match { - case x: TraversableOnce[_] => x map stringify mkString ", " - case x: Product => x.productIterator map stringify mkString ", " - case _ => stringify(x) - } - } - - private val LBRACE = "{" - private val RBRACE = "}" - private var indentLevel = 0 - private def spaces = " " * (indentLevel * 2) - private def pblock(result: Any) = { - p(LBRACE + "\n") - indented(p(spaces + stringify(result) + "\n")) - p(spaces + RBRACE + "\n") - } - private def passign(name: String, args: String) = - p(spaces + name + "(" + args + ") = ") - - private def indented[T](body: => T): T = { - indentLevel += 1 - try body - finally indentLevel -= 1 - } - private def p(s: String) = { - out.print(s) - out.flush() - } - - def apply[T](name: String, args: => Any)(body: => T): T = { - val result = body - - if (enabled()) { - passign(name, stringifyArgs(args)) - val resultToPrint = result match { - case Some(x) => x - case _ => result - } - // concise output optimization - val isOneliner = resultToPrint match { - case _: Boolean | _: None.type => true - case s: String => s.length < 40 - case _ => false - } - if (isOneliner) p(stringify(resultToPrint) + "\n") - else pblock(resultToPrint) - } - - result - } -} - -object Tracer { - def apply(enabled: => Boolean): Tracer = new Tracer(() => enabled) -} diff --git a/src/compiler/scala/tools/nsc/util/WorkScheduler.scala b/src/compiler/scala/tools/nsc/util/WorkScheduler.scala index 2534e1192d..8c037cbda5 100644 --- a/src/compiler/scala/tools/nsc/util/WorkScheduler.scala +++ b/src/compiler/scala/tools/nsc/util/WorkScheduler.scala @@ -30,6 +30,10 @@ class WorkScheduler { todo.dequeueAll(a => f(a).isDefined).map(a => f(a).get) } + def dequeueAllInterrupts(f: InterruptReq => Unit): Unit = synchronized { + interruptReqs.dequeueAll { iq => f(iq); true } + } + /** Called from server: return optional exception posted by client * Reset to no exception. */ diff --git a/src/compiler/scala/tools/reflect/Invoked.scala b/src/compiler/scala/tools/reflect/Invoked.scala deleted file mode 100644 index 516c6b9bf6..0000000000 --- a/src/compiler/scala/tools/reflect/Invoked.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package reflect - -import java.lang.reflect.{ Method, Proxy } - -/** A class representing a single method call. It is primarily for use - * in tandem with Mock. If the invocation did not target an InvocationHandler, - * proxy will be null. - */ -class Invoked private (val proxy: AnyRef, val m: Method, val args: List[AnyRef]) { - def name = m.getName - def arity = m.getParameterTypes.size - def returnType = m.getReturnType - def returns[T: ClassTag] = returnType == classTag[T].erasure - - def invokeOn(target: AnyRef) = m.invoke(target, args: _*) - def isObjectMethod = Set("toString", "equals", "hashCode") contains name - - override def toString = "Invoked: %s called with %s".format( - m.getName, - if (args.isEmpty) "no args" else "args '%s'".format(args mkString ", ") - ) -} - -object Invoked { - def apply(m: Method, args: Seq[Any]): Invoked = apply(null, m, args) - def apply(proxy: AnyRef, m: Method, args: Seq[Any]): Invoked = { - val fixedArgs = if (args == null) Nil else args.toList map (_.asInstanceOf[AnyRef]) - new Invoked(proxy, m, fixedArgs) - } - def unapply(x: Any) = x match { - case x: Invoked => Some((x.proxy, x.m, x.args)) - case _ => None - } - object NameAndArgs { - def unapply(x: Any) = x match { - case x: Invoked => Some((x.name, x.args)) - case _ => None - } - } - object NameAndArity { - def unapply(x: Any) = x match { - case x: Invoked => Some((x.name, x.arity)) - case _ => None - } - } -} diff --git a/src/compiler/scala/tools/reflect/Mock.scala b/src/compiler/scala/tools/reflect/Mock.scala deleted file mode 100644 index 52c052b8a2..0000000000 --- a/src/compiler/scala/tools/reflect/Mock.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package reflect - -import java.lang.reflect.{ Method, Proxy, InvocationHandler } - -/** A wrapper around java dynamic proxies to make it easy to pose - * as an interface. See SignalManager for an example usage. - */ -trait Mock extends (Invoked => AnyRef) { - mock => - - def interfaces: List[Class[_]] - def classLoader: ClassLoader - def apply(invoked: Invoked): AnyRef - - def newProxyInstance(handler: InvocationHandler): AnyRef = - Proxy.newProxyInstance(classLoader, interfaces.toArray, handler) - def newProxyInstance(): AnyRef = - newProxyInstance(newInvocationHandler()) - - def newInvocationHandler() = new InvocationHandler { - def invoke(proxy: AnyRef, method: Method, args: Array[AnyRef]) = - try { mock(Invoked(proxy, method, args)) } - catch { case _: NoClassDefFoundError => sys.exit(1) } - } -} - -/** The methods in Mock create the actual proxy instance which can be used - * in place of the associated interface(s). - */ -object Mock { - /** The default implementation calls the partial function if defined, and - * routes Object methods to the proxy: otherwise it throws an exception. - */ - def fromInterfaces(clazz: Class[_], clazzes: Class[_]*)(pf: PartialFunction[Invoked, AnyRef]): AnyRef = { - val ints = clazz :: clazzes.toList - require(ints forall (_.isInterface), "All class objects must represent interfaces") - - val mock = new Mock { - val interfaces = ints - def classLoader = clazz.getClassLoader - def apply(invoked: Invoked) = - if (pf.isDefinedAt(invoked)) pf(invoked) - else if (invoked.isObjectMethod) invoked invokeOn this - else throw new NoSuchMethodException("" + invoked) - } - mock.newProxyInstance() - } - /** Tries to implement all the class's interfaces. - */ - def fromClass(clazz: Class[_])(pf: PartialFunction[Invoked, AnyRef]): AnyRef = allInterfaces(clazz) match { - case Nil => sys.error(clazz + " implements no interfaces.") - case x :: xs => fromInterfaces(x, xs: _*)(pf) - } -} diff --git a/src/compiler/scala/tools/reflect/Shield.scala b/src/compiler/scala/tools/reflect/Shield.scala deleted file mode 100644 index f9c7e54454..0000000000 --- a/src/compiler/scala/tools/reflect/Shield.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package reflect - -import java.lang.reflect.Constructor -import nsc.util.ScalaClassLoader - -/** A support class for simplifying the otherwise disbelief-inspiring - * process of working with classes completely reflectively. This is - * the case with e.g. sun.misc.Signal* due to environments which are - * antagonistic to their use. See SignalManager for an example. - * - * The name "Shield" is a reference to shielding the JVM from knowledge - * of what we're doing. - */ -trait Shield { - def className: String - def classLoader: ScalaClassLoader - - // Override this if you are more ambitious about logging or throwing. - def onError[T >: Null](msg: String): T = null - - /** This is handy because all reflective calls want back an AnyRef but - * we will often be generating Units. - */ - protected implicit def boxedUnit(x: Unit): AnyRef = scala.runtime.BoxedUnit.UNIT - - lazy val clazz: Class[_] = classLoader.tryToLoadClass(className) getOrElse onError("Failed to load " + className) - lazy val methods = clazz.getMethods.toList - - def constructor(paramTypes: Class[_]*) = clazz.getConstructor(paramTypes: _*).asInstanceOf[Constructor[AnyRef]] - def method(name: String, arity: Int) = uniqueMethod(name, arity) - def field(name: String) = clazz getField name - - def matchingMethods(name: String, arity: Int) = methods filter (m => nameAndArity(m) == ((name, arity))) - def uniqueMethod(name: String, arity: Int) = matchingMethods(name, arity) match { - case List(x) => x - case _ => onError("No unique match for " + name) - } -} diff --git a/src/compiler/scala/tools/reflect/SigParser.scala b/src/compiler/scala/tools/reflect/SigParser.scala deleted file mode 100644 index 5d85778570..0000000000 --- a/src/compiler/scala/tools/reflect/SigParser.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package reflect - -import java.lang.reflect.{ GenericSignatureFormatError, Method } - -/** The usual reflection song and dance to avoid referencing - * any sun.* classes. - */ -class SigParser { - val SunSignatureParser = "sun.reflect.generics.parser.SignatureParser" - private lazy val makeMethod: Method = - try Class.forName(SunSignatureParser) getMethod "make" - catch { case t => null } - - def make() = makeMethod.invoke(null).asInstanceOf[SignatureParserInterface] - - private def wrap(op: => Any) = - try { op ; true } - catch { case _: GenericSignatureFormatError => false } - - def isParserAvailable = makeMethod != null - def verifyClass(s: String) = isParserAvailable && wrap(make() parseClassSig s) - def verifyMethod(s: String) = isParserAvailable && wrap(make() parseMethodSig s) - def verifyType(s: String) = isParserAvailable && wrap(make() parseTypeSig s) - - type ClassSignature <: AnyRef - type MethodTypeSignature <: AnyRef - type TypeSignature <: AnyRef - - type SignatureParserInterface = { - def isParserAvailable: Boolean - def parseClassSig(s: String): ClassSignature - def parseMethodSig(s: String): MethodTypeSignature - def parseTypeSig(s: String): TypeSignature - } -} -object SigParser extends SigParser { } diff --git a/src/compiler/scala/tools/reflect/UniversalFn.scala b/src/compiler/scala/tools/reflect/UniversalFn.scala deleted file mode 100644 index b0c2a19021..0000000000 --- a/src/compiler/scala/tools/reflect/UniversalFn.scala +++ /dev/null @@ -1,59 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package reflect - -import java.lang.reflect.{ Method, InvocationTargetException } -import java.{ lang => jl } - -/** For certain reflection tasks it is convenient to treat all methods - * as having the same signature: (Seq[AnyRef]) => AnyRef - * - * That is the "universal signature" and UniversalFn exists to provide - * it without abandoning the information we had before we needed it. - * One place this is used: closures can pose as arbitrary interfaces, - * and this is how we route the arguments from the actual method - * invocation (which targets a proxy object) to the original closure. - */ -class UniversalFn private (val closure: AnyRef, val method: Method) extends (Seq[AnyRef] => AnyRef) { - universal => - - /** Given an interface type argument, creates a proxy object of the - * type of the interface which implements all its methods by routing - * them to this universal function. Will throw an exception in the - * face of any bad data. - */ - def as[T: ClassTag] : T = { - val clazz = classTag[T].erasure - require(clazz.isInterface, "Type argument must be an interface.") - - val interfaceMethods = clazz.getDeclaredMethods.toSet - val proxy = Mock.fromInterfaces(clazz) { - case Invoked(_, m, args) if interfaceMethods(m) => universal(args) - } - proxy.asInstanceOf[T] - } - - def apply(xs: Seq[AnyRef]): AnyRef = - try method.invoke(closure, xs: _*) - catch { case x: InvocationTargetException => throw x.getCause() } -} - -object UniversalFn { - /** We use a private constructor so we can enforce some rules: we don't want - * universal functions to stack up, and right now we will only allow objects - * which appear to be closures (there's no reason not to eventually lift - * this restriction, but it should be harder to shoot your foot first.) - */ - def apply(closure: AnyRef): UniversalFn = closure match { - case x: UniversalFn => x - case _ => - val m = uniqueApply(closure) getOrElse { - throw new IllegalArgumentException("Argument must have exactly one non-bridge apply method.") - } - new UniversalFn(closure, m) - } -} diff --git a/src/compiler/scala/tools/reflect/package.scala b/src/compiler/scala/tools/reflect/package.scala deleted file mode 100644 index 0b0c0905cb..0000000000 --- a/src/compiler/scala/tools/reflect/package.scala +++ /dev/null @@ -1,43 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools - -import java.lang.reflect.Method -import java.{ lang => jl } - -package object reflect { - def nameAndArity(m: Method) = (m.getName, m.getParameterTypes.size) - def allInterfaces(cl: Class[_]): List[Class[_]] = - if (cl == null) Nil - else cl.getInterfaces.toList ++ allInterfaces(cl.getSuperclass) distinct - - def methodsNamed(target: AnyRef, name: String): List[Method] = - target.getClass.getMethods filter (x => x.getName == name) toList - - /** If there is a single non-bridge apply method in the given instance, - * return it: otherwise None. - */ - def uniqueApply(target: AnyRef) = { - methodsNamed(target, "apply") filterNot (_.isBridge) match { - case List(x) => Some(x) - case _ => None - } - } - - def zeroOfClass(clazz: Class[_]) = zeroOf(ClassTag(clazz)) - def zeroOf[T](implicit t: ClassTag[T]): AnyRef = { - if (t == classTag[Boolean] || t == classTag[jl.Boolean]) false: jl.Boolean - else if (t == classTag[Unit] || t == classTag[jl.Void] || t == classTag[scala.runtime.BoxedUnit]) scala.runtime.BoxedUnit.UNIT - else if (t == classTag[Char] || t == classTag[jl.Character]) 0.toChar: jl.Character - else if (t == classTag[Byte] || t == classTag[jl.Byte]) 0.toByte: jl.Byte - else if (t == classTag[Short] || t == classTag[jl.Short]) 0.toShort: jl.Short - else if (t == classTag[Int] || t == classTag[jl.Integer]) 0: jl.Integer - else if (t == classTag[Long] || t == classTag[jl.Long]) 0l: jl.Long - else if (t == classTag[Float] || t == classTag[jl.Float]) 0f: jl.Float - else if (t == classTag[Double] || t == classTag[jl.Double]) 0d: jl.Double - else null - } -} diff --git a/src/compiler/scala/tools/util/AbstractTimer.scala b/src/compiler/scala/tools/util/AbstractTimer.scala deleted file mode 100644 index 210a1ee53c..0000000000 --- a/src/compiler/scala/tools/util/AbstractTimer.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - - -package scala.tools.util - -import compat.Platform.currentTime -import scala.collection.mutable - -/** - * This abstract class implements the collection of timings. How the - * collected timings are issued has to be implemented in subclasses. - * - * @author Philippe Altherr - * @version 1.0 - */ -abstract class AbstractTimer { - - //######################################################################## - // Private Fields - - /** A stack for maintaining start times */ - private val starts = new mutable.Stack[Long]() - - //######################################################################## - // Public Methods - - /** Issues a timing information (duration in milliseconds). */ - def issue(message: String, duration: Long): Unit - - /** Starts a new timer. */ - def start() { - starts push currentTime - } - - /** Ends the current timer. */ - def stop(message: String) { - val stop = currentTime - issue(message, stop - starts.pop) - } - - /** Drops the current timer. */ - def drop() { - starts.pop - } - - //######################################################################## -} diff --git a/src/compiler/scala/tools/util/ClassPathSettings.scala b/src/compiler/scala/tools/util/ClassPathSettings.scala deleted file mode 100644 index d202279ea1..0000000000 --- a/src/compiler/scala/tools/util/ClassPathSettings.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2006-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -trait ClassPathSettings { - def javabootclasspath: String // -javabootclasspath - def javaextdirs: String // -javaextdirs - def bootclasspath: String // -bootclasspath - def extdirs: String // -extdirs - def classpath: String // -classpath - def sourcepath: String // -sourcepath -} - -// val debugLogger = { -// val f = File("/tmp/path-resolve-log.txt") -// if (f.exists) f.truncate() -// else f.createFile() -// -// val res = f.bufferedWriter() -// res write ("Started debug log: %s\n".format(new java.util.Date)) -// res -// } -// def log(msg: Any) = { -// Console println msg -// debugLogger.write(msg.toString + "\n") -// debugLogger flush -// } - diff --git a/src/compiler/scala/tools/util/EditDistance.scala b/src/compiler/scala/tools/util/EditDistance.scala deleted file mode 100644 index 0af34020a8..0000000000 --- a/src/compiler/scala/tools/util/EditDistance.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -object EditDistance { - import java.lang.Character.{ toLowerCase => lower } - - def similarString(name: String, allowed: TraversableOnce[String]): String = { - val suggested = suggestions(name, allowed.toSeq, maxDistance = 1, maxSuggestions = 2) - if (suggested.isEmpty) "" - else suggested.mkString(" (similar: ", ", ", ")") - } - - def suggestions(a: String, bs: Seq[String], maxDistance: Int, maxSuggestions: Int): Seq[String] = ( - bs map (b => (b, distance(a, b))) - filter (_._2 <= maxDistance) - sortBy (_._2) - take (maxSuggestions) - map (_._1) - ) - - def distance(a: String, b: String): Int = levenshtein(a, b, transpositions = true) - - def levenshtein(s: String, t: String, transpositions: Boolean): Int = { - val n = s.length - val m = t.length - if (n == 0) return m - if (m == 0) return n - - val d = Array.ofDim[Int](n + 1, m + 1) - var i = 0 - val max = math.max(m, n) - while (i <= max) { - if (i <= n) - d(i)(0) = i - if (i <= m) - d(0)(i) = i - i += 1 - } - i = 1 - - while (i <= n) { - val s_i = s(i - 1) - var j = 1 - while (j <= m) { - val t_j = t(j - 1) - val cost = if (lower(s_i) == lower(t_j)) 0 else 1 - - val c1 = d(i - 1)(j) + 1 - val c2 = d(i)(j - 1) + 1 - val c3 = d(i - 1)(j - 1) + cost - - d(i)(j) = c1 min c2 min c3 - - if (transpositions) { - if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1)) - d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost) - } - j += 1 - } - i += 1 - } - - d(n)(m) - } -} diff --git a/src/compiler/scala/tools/util/Javap.scala b/src/compiler/scala/tools/util/Javap.scala index 6d5988d1dd..42d8a910f0 100644 --- a/src/compiler/scala/tools/util/Javap.scala +++ b/src/compiler/scala/tools/util/Javap.scala @@ -10,7 +10,7 @@ import java.lang.reflect.{ GenericSignatureFormatError, Method, Constructor } import java.lang.{ ClassLoader => JavaClassLoader } import scala.tools.nsc.util.ScalaClassLoader import java.io.{ InputStream, PrintWriter, ByteArrayInputStream, FileNotFoundException } -import scala.tools.nsc.io.{ File, NullPrintStream } +import scala.tools.nsc.io.File import Javap._ trait Javap { diff --git a/src/compiler/scala/tools/util/Profiling.scala b/src/compiler/scala/tools/util/Profiling.scala deleted file mode 100644 index 44393eef20..0000000000 --- a/src/compiler/scala/tools/util/Profiling.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -/** This is a (very) minimal stub for profiling, the purpose - * of which is making it possible to integrate profiling hooks in - * the compiler without creating a dependency on any particular - * profiler. You can specify a profiler class (which must be an - * instance of this class) like so: - * - * // or -Yprofile:phase to profile individual phases - * scalac -Yprofile-class your.profiler.Class -Yprofile:all <files> - * - */ -abstract class Profiling { - def isActive: Boolean - def startProfiling(): Unit - def stopProfiling(): Unit - def captureSnapshot(): Unit - - def allocationFreq: Option[Int] // record every Nth allocation - def startRecordingAllocations(): Unit - def stopRecordingAllocations(): Unit - - def profile[T](body: => T): T = profileCPU(body) - - def profileCPU[T](body: => T): T = { - startProfiling() - val result = body - stopProfiling() - captureSnapshot() - result - } - - def profileMem[T](body: => T): T = { - startRecordingAllocations() - val result = body - stopRecordingAllocations() - result - } - - /** Advance the current object generation. - * - * Each object on the heap is associated to a generation number. Generations - * start at 1, and are automatically advanced on each snapshot capture. - */ - def advanceGeneration(desc: String = ""): Unit -} diff --git a/src/compiler/scala/tools/util/SignalManager.scala b/src/compiler/scala/tools/util/SignalManager.scala deleted file mode 100644 index e93297386a..0000000000 --- a/src/compiler/scala/tools/util/SignalManager.scala +++ /dev/null @@ -1,275 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -import java.lang.reflect.{ Method, Constructor } -import scala.tools.reflect._ -import scala.collection.{ mutable, immutable } -import nsc.io.timer -import nsc.util.{ ScalaClassLoader, Exceptional } -import Exceptional.unwrap -import scala.util.Random - -/** Signal handling code. 100% clean of any references to sun.misc: - * it's all reflection and proxies and invocation handlers and lasers, - * so even the choosiest runtimes will be cool with it. - * - * Sun/Oracle says sun.misc.* is unsupported and therefore so is all - * of this. Simple examples: - * {{{ - val manager = scala.tools.util.SignalManager // or you could make your own - // Assignment clears any old handlers; += chains them. - manager("HUP") = println("HUP 1!") - manager("HUP") += println("HUP 2!") - // Use raise() to raise a signal: this will print both lines - manager("HUP").raise() - // See a report on every signal's current handler - manager.dump() - * }}} - */ -class SignalManager(classLoader: ScalaClassLoader) { - def this() = this(ScalaClassLoader.appLoader) - private val illegalArgHandler: PartialFunction[Throwable, Boolean] = { - case x if unwrap(x).isInstanceOf[IllegalArgumentException] => false - } - private def fail(msg: String) = new SignalError(msg) - - object rSignalHandler extends Shield { - val className = "sun.misc.SignalHandler" - val classLoader = SignalManager.this.classLoader - - lazy val SIG_DFL = field("SIG_DFL") get null - lazy val SIG_IGN = field("SIG_IGN") get null - - /** Create a new signal handler based on the function. - */ - def apply(action: Invoked => Unit) = Mock.fromInterfaces(clazz) { - case inv @ Invoked.NameAndArgs("handle", _ :: Nil) => action(inv) - } - def empty = rSignalHandler(_ => ()) - } - import rSignalHandler.{ SIG_DFL, SIG_IGN } - - object rSignal extends Shield { - val className = "sun.misc.Signal" - val classLoader = SignalManager.this.classLoader - - lazy val handleMethod = method("handle", 2) - lazy val raiseMethod = method("raise", 1) - lazy val numberMethod = method("getNumber", 0) - - /** Create a new Signal with the given name. - */ - def apply(name: String) = constructor(classOf[String]) newInstance name - def handle(signal: AnyRef, current: AnyRef) = { - if (signal == null || current == null) fail("Signals cannot be null") - else handleMethod.invoke(null, signal, current) - } - def raise(signal: AnyRef) = { - if (signal == null) fail("Signals cannot be null") - else raiseMethod.invoke(null, signal) - } - def number(signal: AnyRef): Int = numberMethod.invoke(signal).asInstanceOf[Int] - - class WSignal(val name: String) { - lazy val signal = rSignal apply name - def number = rSignal number signal - def raise() = rSignal raise signal - def handle(handler: AnyRef) = rSignal.handle(signal, handler) - - def isError = false - def setTo(body: => Unit) = register(name, false, body) - def +=(body: => Unit) = register(name, true, body) - - /** It's hard to believe there's no way to get a signal's current - * handler without replacing it, but if there is I couldn't find - * it, so we have this swapping code. - */ - def withCurrentHandler[T](f: AnyRef => T): T = { - val swap = handle(rSignalHandler.empty) - - try f(swap) - finally handle(swap) - } - def isDefault = try withCurrentHandler { - case SIG_DFL => true - case _ => false - } catch illegalArgHandler - def isIgnored = try withCurrentHandler { - case SIG_IGN => true - case _ => false - } catch illegalArgHandler - def isSetTo(ref: AnyRef) = - try withCurrentHandler { _ eq ref } - catch illegalArgHandler - - def handlerString() = withCurrentHandler { - case SIG_DFL => "Default" - case SIG_IGN => "Ignore" - case x => "" + x - } - - override def toString = "%10s %s".format("SIG" + name, - try handlerString() - catch { case x: Exception => "VM threw " + unwrap(x) } - ) - override def equals(other: Any) = other match { - case x: WSignal => name == x.name - case _ => false - } - override def hashCode = name.## - } - } - type WSignal = rSignal.WSignal - - /** Adds a handler for the named signal. If shouldChain is true, - * the installed handler will call the previous handler after the - * new one has executed. If false, the old handler is dropped. - */ - private def register(name: String, shouldChain: Boolean, body: => Unit) = { - val signal = rSignal(name) - val current = rSignalHandler(_ => body) - val prev = rSignal.handle(signal, current) - - if (shouldChain) { - val chainer = rSignalHandler { inv => - val signal = inv.args.head - - inv invokeOn current - prev match { - case SIG_IGN | SIG_DFL => () - case _ => inv invokeOn prev - } - } - rSignal.handle(signal, chainer) - chainer - } - else current - } - - /** Use apply and update to get and set handlers. - */ - def apply(name: String): WSignal = - try { new WSignal(name) } - catch { case x: IllegalArgumentException => new SignalError(x.getMessage) } - - def update(name: String, body: => Unit): Unit = apply(name) setTo body - - class SignalError(message: String) extends WSignal("") { - override def isError = true - override def toString = message - } - - def public(name: String, description: String)(body: => Unit): Unit = { - try { - val wsig = apply(name) - if (wsig.isError) - return - - wsig setTo body - registerInfoHandler() - addPublicHandler(wsig, description) - } - catch { - case x: Exception => () // ignore failure - } - } - /** Makes sure the info handler is registered if we see activity. */ - private def registerInfoHandler() = { - val INFO = apply("INFO") - if (publicHandlers.isEmpty && INFO.isDefault) { - INFO setTo Console.println(info()) - addPublicHandler(INFO, "Print signal handler registry on console.") - } - } - private def addPublicHandler(wsig: WSignal, description: String) = { - if (publicHandlers contains wsig) () - else publicHandlers = publicHandlers.updated(wsig, description) - } - private var publicHandlers: Map[WSignal, String] = Map() - def info(): String = { - registerInfoHandler() - val xs = publicHandlers.toList sortBy (_._1.name) map { - case (wsig, descr) => " %2d %5s %s".format(wsig.number, wsig.name, descr) - } - - xs.mkString("\nSignal handler registry:\n", "\n", "") - } -} - -object SignalManager extends SignalManager { - private implicit def mkWSignal(name: String): WSignal = this(name) - private lazy val signalNumberMap = all map (x => x.number -> x) toMap - - def all = List( - HUP, INT, QUIT, ILL, TRAP, ABRT, EMT, FPE, // 1-8 - KILL, BUS, SEGV, SYS, PIPE, ALRM, TERM, URG, // 9-15 - STOP, TSTP, CONT, CHLD, TTIN, TTOU, IO, XCPU, // 16-23 - XFSZ, VTALRM, PROF, WINCH, INFO, USR1, USR2 // 24-31 - ) - /** Signals which are either inaccessible or which seem like - * particularly bad choices when looking for an open one. - */ - def reserved = Set(QUIT, TRAP, ABRT, KILL, BUS, SEGV, ALRM, STOP, INT) - def unreserved = all filterNot reserved - def defaultSignals() = unreserved filter (_.isDefault) - def ignoredSignals() = unreserved filter (_.isIgnored) - def findOpenSignal() = Random.shuffle(defaultSignals()).head - - def dump() = all foreach (x => println("%2s %s".format(x.number, x))) - - def apply(sigNumber: Int): WSignal = signalNumberMap(sigNumber) - - def HUP: WSignal = "HUP" - def INT: WSignal = "INT" - def QUIT: WSignal = "QUIT" - def ILL: WSignal = "ILL" - def TRAP: WSignal = "TRAP" - def ABRT: WSignal = "ABRT" - def EMT: WSignal = "EMT" - def FPE: WSignal = "FPE" - def KILL: WSignal = "KILL" - def BUS: WSignal = "BUS" - def SEGV: WSignal = "SEGV" - def SYS: WSignal = "SYS" - def PIPE: WSignal = "PIPE" - def ALRM: WSignal = "ALRM" - def TERM: WSignal = "TERM" - def URG: WSignal = "URG" - def STOP: WSignal = "STOP" - def TSTP: WSignal = "TSTP" - def CONT: WSignal = "CONT" - def CHLD: WSignal = "CHLD" - def TTIN: WSignal = "TTIN" - def TTOU: WSignal = "TTOU" - def IO: WSignal = "IO" - def XCPU: WSignal = "XCPU" - def XFSZ: WSignal = "XFSZ" - def VTALRM: WSignal = "VTALRM" - def PROF: WSignal = "PROF" - def WINCH: WSignal = "WINCH" - def INFO: WSignal = "INFO" - def USR1: WSignal = "USR1" - def USR2: WSignal = "USR2" - - /** Given a number of seconds, a signal, and a function: sets up a handler which upon - * receiving the signal once, calls the function with argument true, and if the - * signal is received again within the allowed time, calls it with argument false. - * (Otherwise it calls it with true and starts the timer over again.) - */ - def requireInterval(seconds: Int, wrapper: WSignal)(fn: Boolean => Unit) = { - var received = false - wrapper setTo { - if (received) fn(false) - else { - received = true - fn(true) - timer(seconds)(received = false) - } - } - } -} diff --git a/src/compiler/scala/tools/util/Signallable.scala b/src/compiler/scala/tools/util/Signallable.scala deleted file mode 100644 index af98bfac83..0000000000 --- a/src/compiler/scala/tools/util/Signallable.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -import java.security.AccessControlException - -/** A class for things which are signallable. - */ -abstract class Signallable[T] private (val signal: String, val description: String) { - private var last: Option[T] = None - private def lastString = last match { - case Some(()) => "" - case Some(x) => "" + x - case _ => "" - } - - /** The most recent result from the signal handler. */ - def lastResult: Option[T] = last - - /** Method to be executed when the associated signal is received. */ - def onSignal(): T - - // todo: - // def unregister(): Boolean - - override def toString = " SIG(%s) => %s%s".format( - signal, description, if (lastString == "") "" else " (" + lastString + ")" - ) -} - -object Signallable { - /** Same as the other apply, but an open signal is found for you. - */ - def apply[T](description: String)(body: => T): Signallable[T] = wrap { - apply(SignalManager.findOpenSignal().name, description)(body) - } - - /** Given a signal name, a description, and a handler body, this - * registers a signal handler and returns the Signallable instance. - * The signal handler registry is thereafter available by calling - * SignalManager.info(), or sending SIGINFO to the manager will - * dump it to console. - */ - def apply[T](signal: String, description: String)(body: => T): Signallable[T] = wrap { - val result = create[T](signal, description, body) - SignalManager.public(signal, description)(result.onSignal()) - result - } - - private def wrap[T](body: => Signallable[T]): Signallable[T] = - try body catch { case _: AccessControlException => null } - - private def create[T](signal: String, description: String, body: => T): Signallable[T] = - new Signallable[T](signal, description) { - def onSignal = { - val result = body - last = Some(result) - result - } - } -} diff --git a/src/compiler/scala/tools/util/SocketConnection.scala b/src/compiler/scala/tools/util/SocketConnection.scala deleted file mode 100644 index 6b56be569c..0000000000 --- a/src/compiler/scala/tools/util/SocketConnection.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - - -package scala.tools.util - -import java.io.{PrintWriter, InputStreamReader, BufferedReader} -import java.io.IOException -import java.net.{Socket, InetAddress} -import java.net.UnknownHostException - -/** This class implements the connection to the server. - * - * @author Martin Odersky - * @version 1.0 - */ -class SocketConnection(hostname: String, port: Int) { - - def this(port: Int) = this(InetAddress.getLocalHost().getHostName(), port) - - private var socket: Socket = _ - var out: PrintWriter = _ - var in: BufferedReader = _ - var errorMessage: String = _ - - def open(): Boolean = { - try { - socket = new Socket(hostname, port) - out = new PrintWriter(socket.getOutputStream(), true) - in = new BufferedReader(new InputStreamReader(socket.getInputStream())) - true - } catch { - case e: UnknownHostException => - errorMessage = "Don't know about host: " + hostname + "." - false - case e: IOException => - errorMessage = "Couldn't get I/O for the connection to: " + hostname + "." - false - } - } - - def close() { - in.close() - out.close() - socket.close() - } -} diff --git a/src/compiler/scala/tools/util/Which.scala b/src/compiler/scala/tools/util/Which.scala deleted file mode 100644 index 1cafe156b5..0000000000 --- a/src/compiler/scala/tools/util/Which.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools -package util - -import scala.tools.nsc._ - -/** A tool for identifying which classfile is being used. - * under the given conditions. - */ -object Which { - def main(args: Array[String]): Unit = { - val settings = new Settings() - val names = settings.processArguments(args.toList, true)._2 - val global = new Global(settings) - val cp = global.classPath - - import cp._ - - for (name <- names) { - def fail() = println("Could not find: %s".format(name)) - (cp findClass name) match { - case Some(classRep) => classRep.binary match { - case Some(f) => println("%s is %s".format(name, f)) - case _ => fail - } - case _ => fail - } - } - } -} - - - - diff --git a/src/compiler/scala/tools/util/color/Ansi.scala b/src/compiler/scala/tools/util/color/Ansi.scala deleted file mode 100644 index 1ed43579bb..0000000000 --- a/src/compiler/scala/tools/util/color/Ansi.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.util -package color - -import collection.mutable - -object Ansi { - final val ESC = '\u001b' // <esc> - final val LBR = '\u005b' // [ - final val CSI = new String(Array(ESC, LBR)) // control sequence introducer - final val CSI_FINAL = "m" // control sequence final byte - - def colors = List(Black, Red, Green, Yellow, Blue, Magenta, Cyan, White) - def effects = List(Reset, Bright, Faint, Italic, Underline, Blink, Inverse, Hidden, Strikethrough) - - // No, that's not the finale of "CSI: Crime Scene Investigation." - - def colorizerFor(codes: Seq[Int]): String => String = - s => ansiCodeToString(codes) + s + ansiCodeToString(0) - - def ansiCodeToString(code: Int): String = CSI + code + CSI_FINAL - def ansiCodeToString(codes: Seq[Int]): String = codes.mkString(CSI, ";", CSI_FINAL) -} - -/** An ansi control sequence. The colorize function prepends - * the control sequence to the given String and appends a - * reset sequence. - */ -class Ansi(atoms0: List[AnsiAtom]) { - val atoms = atoms0 sortBy (x => (!x.isAttr, x.isInstanceOf[AnsiBackground])) - val colorize = Ansi colorizerFor codes - - def codes = atoms map (_.code) - def /(that: AnsiAtom) = new Ansi(atoms :+ that) - // This looks redundant with / , but isn't - it is a way - // to ensure that the argument will be a background color, - // even if a foreground color is passed as an argument - // (as it will be implicitly converted.) - def on(that: AnsiBackground) = this / that - - // Convenience functions. - def reset = this / Reset - def bright = this / Bright - def faint = this / Faint - def italic = this / Italic - def underline = this / Underline - def blink = this / Blink - def inverse = this / Inverse - def hidden = this / Hidden - def strikethrough = this / Strikethrough - - // adjectives first - override def toString = atoms mkString " " -} diff --git a/src/compiler/scala/tools/util/color/AnsiAtom.scala b/src/compiler/scala/tools/util/color/AnsiAtom.scala deleted file mode 100644 index 5d5490f6e9..0000000000 --- a/src/compiler/scala/tools/util/color/AnsiAtom.scala +++ /dev/null @@ -1,51 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.util -package color - -case object Reset extends AnsiAttr(0) -case object Bright extends AnsiAttr(1) -case object Faint extends AnsiAttr(2) -case object Italic extends AnsiAttr(3) -case object Underline extends AnsiAttr(4) -case object Blink extends AnsiAttr(5) -case object Inverse extends AnsiAttr(7) -case object Hidden extends AnsiAttr(8) -case object Strikethrough extends AnsiAttr(9) - -case object Black extends AnsiForeground(30) -case object Red extends AnsiForeground(31) -case object Green extends AnsiForeground(32) -case object Yellow extends AnsiForeground(33) -case object Blue extends AnsiForeground(34) -case object Magenta extends AnsiForeground(35) -case object Cyan extends AnsiForeground(36) -case object White extends AnsiForeground(37) -case object Default extends AnsiForeground(39) - -/** One piece of an ansi control sequence. Either a color - * (foreground or background) or an attribute (e.g. bright, underline.) - * Control sequences are created from AnsiAtoms with the / operator. - */ -trait AnsiAtom { - def code: Int - def isAttr: Boolean -} -sealed abstract class AnsiAttr(val code: Int) extends AnsiAtom { - final def isAttr = true -} -sealed abstract class AnsiColor(val code: Int) extends AnsiAtom { - final def isAttr = false - def flip: AnsiColor -} -sealed abstract class AnsiForeground(code: Int) extends AnsiColor(code) { - require(30 <= code && code <= 39, code) - val flip: AnsiBackground = new AnsiBackground(this) -} -sealed class AnsiBackground(val flip: AnsiForeground) extends AnsiColor(flip.code + 10) { - require(40 <= code && code <= 49, code) - override def toString = "(on " + flip + " background)" -} diff --git a/src/compiler/scala/tools/util/color/CString.scala b/src/compiler/scala/tools/util/color/CString.scala deleted file mode 100644 index fa57229f09..0000000000 --- a/src/compiler/scala/tools/util/color/CString.scala +++ /dev/null @@ -1,37 +0,0 @@ -package scala.tools.util -package color - -/** A colorized String. It's difficult to achieve precise - * formatting and selective string colorization simultaneously, - * because all length-based calculations will break down in - * the face of the ansi controls. It doesn't do much yet, but - * this is here to eventually make that transparent. - */ -final class CString(val uncolorized: String, val colorized: String) { - def visibleLength = uncolorized.length - def colorizedLength = colorized.length - def show() = Console println colorized - def bytes() = colorized map (_.toByte) - def >() = show() - - def append(x: CString): CString = new CString(uncolorized + x.uncolorized, colorized + x.colorized) - def +(other: CString): CString = this append other - - override def toString = colorized -} - -class CStringOps(str: String) { - /** String to String operation. - * println("foo" in Red) - * println("bar" in Magenta.bright) - */ - def in(ansi: Ansi): String = ansi colorize str - - /** Gave in to one bit of punctuation, because everyone adds - * strings with '+' and we need something which higher precedence - * for it to be at all satisfying. - * - * "foo" %> Red + "bar" %> Magenta.bright - */ - def %>(ansi: Ansi): CString = new CString(str, in(ansi)) -} diff --git a/src/compiler/scala/tools/util/color/ColorNames.scala b/src/compiler/scala/tools/util/color/ColorNames.scala deleted file mode 100644 index ff4b01a9df..0000000000 --- a/src/compiler/scala/tools/util/color/ColorNames.scala +++ /dev/null @@ -1,391 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.util.color - -/** Raw data adapted from perl's Term-ExtendedColor, which is published - * under perl's Artistic license: http://dev.perl.org/licenses/artistic.html - * - * These aren't actually in use yet. - */ -trait ColorNames { - type ColorType - def translateCode(ansiCode: String): ColorType - - private implicit def liftAnsiCode(code: String): ColorType = translateCode(code) - - // Possible alternative names or aliases, also from the perl: - // - // reset, clear, normal reset all attributes - // bold, bright bold or bright, depending on implementation - // faint decreased intensity (not widely supported) - // italic, cursive italic or cursive - // underline, underscore underline - // blink slow blink - // blink_ms rapid blink (only supported in MS DOS) - // reverse, inverse, negative reverse video - // conceal conceal, or hide (not widely supported) - - // Brightest to darkest color - val red1: ColorType = "5;196" - val red2: ColorType = "5;160" - val red3: ColorType = "5;124" - val red4: ColorType = "5;088" - val red5: ColorType = "5;052" - - val green1: ColorType = "5;156" - val green2: ColorType = "5;150" - val green3: ColorType = "5;120" - val green4: ColorType = "5;114" - val green5: ColorType = "5;084" - val green6: ColorType = "5;078" - val green7: ColorType = "5;155" - val green8: ColorType = "5;149" - val green9: ColorType = "5;119" - val green10: ColorType = "5;113" - val green11: ColorType = "5;083" - val green12: ColorType = "5;077" - val green13: ColorType = "5;047" - val green14: ColorType = "5;041" - val green15: ColorType = "5;118" - val green16: ColorType = "5;112" - val green17: ColorType = "5;082" - val green18: ColorType = "5;076" - val green19: ColorType = "5;046" - val green20: ColorType = "5;040" - val green21: ColorType = "5;034" - val green22: ColorType = "5;028" - val green23: ColorType = "5;022" - val green24: ColorType = "5;107" - val green25: ColorType = "5;071" - val green26: ColorType = "5;070" - val green27: ColorType = "5;064" - val green28: ColorType = "5;065" - - val blue1: ColorType = "5;075" - val blue2: ColorType = "5;074" - val blue3: ColorType = "5;073" - val blue4: ColorType = "5;039" - val blue5: ColorType = "5;038" - val blue6: ColorType = "5;037" - val blue7: ColorType = "5;033" - val blue8: ColorType = "5;032" - val blue9: ColorType = "5;031" - val blue10: ColorType = "5;027" - val blue11: ColorType = "5;026" - val blue12: ColorType = "5;025" - val blue13: ColorType = "5;021" - val blue14: ColorType = "5;020" - val blue15: ColorType = "5;019" - val blue16: ColorType = "5;018" - val blue17: ColorType = "5;017" - - val yellow1: ColorType = "5;228" - val yellow2: ColorType = "5;222" - val yellow3: ColorType = "5;192" - val yellow4: ColorType = "5;186" - val yellow5: ColorType = "5;227" - val yellow6: ColorType = "5;221" - val yellow7: ColorType = "5;191" - val yellow8: ColorType = "5;185" - val yellow9: ColorType = "5;226" - val yellow10: ColorType = "5;220" - val yellow11: ColorType = "5;190" - val yellow12: ColorType = "5;184" - val yellow13: ColorType = "5;214" - val yellow14: ColorType = "5;178" - val yellow15: ColorType = "5;208" - val yellow16: ColorType = "5;172" - val yellow17: ColorType = "5;202" - val yellow18: ColorType = "5;166" - - val magenta1: ColorType = "5;219" - val magenta2: ColorType = "5;183" - val magenta3: ColorType = "5;218" - val magenta4: ColorType = "5;182" - val magenta5: ColorType = "5;217" - val magenta6: ColorType = "5;181" - val magenta7: ColorType = "5;213" - val magenta8: ColorType = "5;177" - val magenta9: ColorType = "5;212" - val magenta10: ColorType = "5;176" - val magenta11: ColorType = "5;211" - val magenta12: ColorType = "5;175" - val magenta13: ColorType = "5;207" - val magenta14: ColorType = "5;171" - val magenta15: ColorType = "5;205" - val magenta16: ColorType = "5;169" - val magenta17: ColorType = "5;201" - val magenta18: ColorType = "5;165" - val magenta19: ColorType = "5;200" - val magenta20: ColorType = "5;164" - val magenta21: ColorType = "5;199" - val magenta22: ColorType = "5;163" - val magenta23: ColorType = "5;198" - val magenta24: ColorType = "5;162" - val magenta25: ColorType = "5;197" - val magenta26: ColorType = "5;161" - - val gray1: ColorType = "5;255" - val gray2: ColorType = "5;254" - val gray3: ColorType = "5;253" - val gray4: ColorType = "5;252" - val gray5: ColorType = "5;251" - val gray6: ColorType = "5;250" - val gray7: ColorType = "5;249" - val gray8: ColorType = "5;248" - val gray9: ColorType = "5;247" - val gray10: ColorType = "5;246" - val gray11: ColorType = "5;245" - val gray12: ColorType = "5;244" - val gray13: ColorType = "5;243" - val gray14: ColorType = "5;242" - val gray15: ColorType = "5;241" - val gray16: ColorType = "5;240" - val gray17: ColorType = "5;239" - val gray18: ColorType = "5;238" - val gray19: ColorType = "5;237" - val gray20: ColorType = "5;236" - val gray21: ColorType = "5;235" - val gray22: ColorType = "5;234" - val gray23: ColorType = "5;233" - val gray24: ColorType = "5;232" - - val purple1: ColorType = "5;147" - val purple2: ColorType = "5;146" - val purple3: ColorType = "5;145" - val purple4: ColorType = "5;141" - val purple5: ColorType = "5;140" - val purple6: ColorType = "5;139" - val purple7: ColorType = "5;135" - val purple8: ColorType = "5;134" - val purple9: ColorType = "5;133" - val purple10: ColorType = "5;129" - val purple11: ColorType = "5;128" - val purple12: ColorType = "5;127" - val purple13: ColorType = "5;126" - val purple14: ColorType = "5;125" - val purple15: ColorType = "5;111" - val purple16: ColorType = "5;110" - val purple17: ColorType = "5;109" - val purple18: ColorType = "5;105" - val purple19: ColorType = "5;104" - val purple20: ColorType = "5;103" - val purple21: ColorType = "5;099" - val purple22: ColorType = "5;098" - val purple23: ColorType = "5;097" - val purple24: ColorType = "5;096" - val purple25: ColorType = "5;093" - val purple26: ColorType = "5;092" - val purple27: ColorType = "5;091" - val purple28: ColorType = "5;090" - val purple29: ColorType = "5;055" - val purple30: ColorType = "5;054" - - val cyan1: ColorType = "5;159" - val cyan2: ColorType = "5;158" - val cyan3: ColorType = "5;157" - val cyan4: ColorType = "5;153" - val cyan5: ColorType = "5;152" - val cyan6: ColorType = "5;151" - val cyan7: ColorType = "5;123" - val cyan8: ColorType = "5;122" - val cyan9: ColorType = "5;121" - val cyan10: ColorType = "5;117" - val cyan11: ColorType = "5;116" - val cyan12: ColorType = "5;115" - val cyan13: ColorType = "5;087" - val cyan14: ColorType = "5;086" - val cyan15: ColorType = "5;085" - val cyan16: ColorType = "5;081" - val cyan17: ColorType = "5;080" - val cyan18: ColorType = "5;079" - val cyan19: ColorType = "5;051" - val cyan20: ColorType = "5;050" - val cyan21: ColorType = "5;049" - val cyan22: ColorType = "5;045" - val cyan23: ColorType = "5;044" - val cyan24: ColorType = "5;043" - - val orange1: ColorType = "5;208" - val orange2: ColorType = "5;172" - val orange3: ColorType = "5;202" - val orange4: ColorType = "5;166" - val orange5: ColorType = "5;130" - - // Approximations of X11 color mappings - // https://secure.wikimedia.org/wikipedia/en/wiki/X11%20colors - - val aquamarine1: ColorType = "5;086" - val aquamarine3: ColorType = "5;079" - val blueviolet: ColorType = "5;057" - val cadetblue1: ColorType = "5;072" - val cadetblue2: ColorType = "5;073" - val chartreuse1: ColorType = "5;118" - val chartreuse2: ColorType = "5;082" - val chartreuse3: ColorType = "5;070" - val chartreuse4: ColorType = "5;064" - val cornflowerblue: ColorType = "5;069" - val cornsilk1: ColorType = "5;230" - val darkblue: ColorType = "5;018" - val darkcyan: ColorType = "5;036" - val darkgoldenrod: ColorType = "5;136" - val darkgreen: ColorType = "5;022" - val darkkhaki: ColorType = "5;143" - val darkmagenta1: ColorType = "5;090" - val darkmagenta2: ColorType = "5;091" - val darkolivegreen1: ColorType = "5;191" - val darkolivegreen2: ColorType = "5;155" - val darkolivegreen3: ColorType = "5;107" - val darkolivegreen4: ColorType = "5;113" - val darkolivegreen5: ColorType = "5;149" - val darkorange3: ColorType = "5;130" - val darkorange4: ColorType = "5;166" - val darkorange1: ColorType = "5;208" - val darkred1: ColorType = "5;052" - val darkred2: ColorType = "5;088" - val darkseagreen1: ColorType = "5;158" - val darkseagreen2: ColorType = "5;157" - val darkseagreen3: ColorType = "5;150" - val darkseagreen4: ColorType = "5;071" - val darkslategray1: ColorType = "5;123" - val darkslategray2: ColorType = "5;087" - val darkslategray3: ColorType = "5;116" - val darkturquoise: ColorType = "5;044" - val darkviolet: ColorType = "5;128" - val deeppink1: ColorType = "5;198" - val deeppink2: ColorType = "5;197" - val deeppink3: ColorType = "5;162" - val deeppink4: ColorType = "5;125" - val deepskyblue1: ColorType = "5;039" - val deepskyblue2: ColorType = "5;038" - val deepskyblue3: ColorType = "5;031" - val deepskyblue4: ColorType = "5;023" - val dodgerblue1: ColorType = "5;033" - val dodgerblue2: ColorType = "5;027" - val dodgerblue3: ColorType = "5;026" - val gold1: ColorType = "5;220" - val gold3: ColorType = "5;142" - val greenyellow: ColorType = "5;154" - val grey0: ColorType = "5;016" - val grey100: ColorType = "5;231" - val grey11: ColorType = "5;234" - val grey15: ColorType = "5;235" - val grey19: ColorType = "5;236" - val grey23: ColorType = "5;237" - val grey27: ColorType = "5;238" - val grey30: ColorType = "5;239" - val grey3: ColorType = "5;232" - val grey35: ColorType = "5;240" - val grey37: ColorType = "5;059" - val grey39: ColorType = "5;241" - val grey42: ColorType = "5;242" - val grey46: ColorType = "5;243" - val grey50: ColorType = "5;244" - val grey53: ColorType = "5;102" - val grey54: ColorType = "5;245" - val grey58: ColorType = "5;246" - val grey62: ColorType = "5;247" - val grey63: ColorType = "5;139" - val grey66: ColorType = "5;248" - val grey69: ColorType = "5;145" - val grey70: ColorType = "5;249" - val grey74: ColorType = "5;250" - val grey7: ColorType = "5;233" - val grey78: ColorType = "5;251" - val grey82: ColorType = "5;252" - val grey84: ColorType = "5;188" - val grey85: ColorType = "5;253" - val grey89: ColorType = "5;254" - val grey93: ColorType = "5;255" - val honeydew2: ColorType = "5;194" - val hotpink2: ColorType = "5;169" - val hotpink3: ColorType = "5;132" - val hotpink: ColorType = "5;205" - val indianred1: ColorType = "5;203" - val indianred: ColorType = "5;167" - val khaki1: ColorType = "5;228" - val khaki3: ColorType = "5;185" - val lightcoral: ColorType = "5;210" - val lightcyan1: ColorType = "5;195" - val lightcyan3: ColorType = "5;152" - val lightgoldenrod1: ColorType = "5;227" - val lightgoldenrod2: ColorType = "5;186" - val lightgoldenrod3: ColorType = "5;179" - val lightgreen: ColorType = "5;119" - val lightpink1: ColorType = "5;217" - val lightpink3: ColorType = "5;174" - val lightpink4: ColorType = "5;095" - val lightsalmon1: ColorType = "5;216" - val lightsalmon3: ColorType = "5;137" - val lightseagreen: ColorType = "5;037" - val lightskyblue1: ColorType = "5;153" - val lightskyblue3: ColorType = "5;109" - val lightslateblue: ColorType = "5;105" - val lightslategrey: ColorType = "5;103" - val lightsteelblue1: ColorType = "5;189" - val lightsteelblue3: ColorType = "5;146" - val lightsteelblue: ColorType = "5;147" - val lightyellow3: ColorType = "5;187" - val mediumorchid1: ColorType = "5;171" - val mediumorchid3: ColorType = "5;133" - val mediumorchid: ColorType = "5;134" - val mediumpurple1: ColorType = "5;141" - val mediumpurple2: ColorType = "5;135" - val mediumpurple3: ColorType = "5;097" - val mediumpurple4: ColorType = "5;060" - val mediumpurple: ColorType = "5;104" - val mediumspringgreen: ColorType = "5;049" - val mediumturquoise: ColorType = "5;080" - val mediumvioletred: ColorType = "5;126" - val mistyrose1: ColorType = "5;224" - val mistyrose3: ColorType = "5;181" - val navajowhite1: ColorType = "5;223" - val navajowhite3: ColorType = "5;144" - val navyblue: ColorType = "5;017" - val orangered1: ColorType = "5;202" - val orchid1: ColorType = "5;213" - val orchid2: ColorType = "5;212" - val orchid: ColorType = "5;170" - val palegreen1: ColorType = "5;121" - val palegreen3: ColorType = "5;077" - val paleturquoise1: ColorType = "5;159" - val paleturquoise4: ColorType = "5;066" - val palevioletred1: ColorType = "5;211" - val pink1: ColorType = "5;218" - val pink3: ColorType = "5;175" - val plum1: ColorType = "5;219" - val plum2: ColorType = "5;183" - val plum3: ColorType = "5;176" - val plum4: ColorType = "5;096" - val purple: ColorType = "5;129" - val rosybrown: ColorType = "5;138" - val royalblue1: ColorType = "5;063" - val salmon1: ColorType = "5;209" - val sandybrown: ColorType = "5;215" - val seagreen1: ColorType = "5;084" - val seagreen2: ColorType = "5;083" - val seagreen3: ColorType = "5;078" - val skyblue1: ColorType = "5;117" - val skyblue2: ColorType = "5;111" - val skyblue3: ColorType = "5;074" - val slateblue1: ColorType = "5;099" - val slateblue3: ColorType = "5;061" - val springgreen1: ColorType = "5;048" - val springgreen2: ColorType = "5;042" - val springgreen3: ColorType = "5;035" - val springgreen4: ColorType = "5;029" - val steelblue1: ColorType = "5;075" - val steelblue3: ColorType = "5;068" - val steelblue: ColorType = "5;067" - val tan: ColorType = "5;180" - val thistle1: ColorType = "5;225" - val thistle3: ColorType = "5;182" - val turquoise2: ColorType = "5;045" - val turquoise4: ColorType = "5;030" - val violet: ColorType = "5;177" - val wheat1: ColorType = "5;229" - val wheat4: ColorType = "5;101" -} diff --git a/src/compiler/scala/tools/util/color/package.scala b/src/compiler/scala/tools/util/color/package.scala deleted file mode 100644 index 3b3e85751e..0000000000 --- a/src/compiler/scala/tools/util/color/package.scala +++ /dev/null @@ -1,22 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.util - -/** - * Wrappers around ansi colors. - * - * @author Paul Phillips - * @version 2.10 - */ -package object color { - implicit def implicitLiftAnsiAtom(c: AnsiAtom): Ansi = new Ansi(List(c)) - implicit def implicitColorToBackground(c: AnsiColor): AnsiBackground = c match { - case x: AnsiBackground => x - case x: AnsiForeground => x.flip - } - implicit def implicitCStringOps(str: String): CStringOps = new CStringOps(str) - implicit def implicitCString(str: String): CString = new CString(str, str) -} diff --git a/src/intellij/dbc.iml.SAMPLE b/src/intellij/dbc.iml.SAMPLE deleted file mode 100644 index d2647cfb26..0000000000 --- a/src/intellij/dbc.iml.SAMPLE +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module type="JAVA_MODULE" version="4"> - <component name="FacetManager"> - <facet type="scala" name="Scala"> - <configuration> - <option name="compilerLibraryLevel" value="Project" /> - <option name="compilerLibraryName" value="compiler-locker" /> - <option name="maximumHeapSize" value="1536" /> - <option name="vmOptions" value="-Xms1536m -Xss1m -XX:MaxPermSize=128M -d32 -server -XX:+UseParallelGC" /> - </configuration> - </facet> - </component> - <component name="NewModuleRootManager" inherit-compiler-output="true"> - <exclude-output /> - <orderEntry type="inheritedJdk" /> - <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="module" module-name="library" /> - </component> -</module> - diff --git a/src/intellij/scala-lang.ipr.SAMPLE b/src/intellij/scala-lang.ipr.SAMPLE index 94a9329dd6..43d6362708 100644 --- a/src/intellij/scala-lang.ipr.SAMPLE +++ b/src/intellij/scala-lang.ipr.SAMPLE @@ -203,6 +203,7 @@ <module fileurl="file://$PROJECT_DIR$/scala.iml" filepath="$PROJECT_DIR$/scala.iml" /> <module fileurl="file://$PROJECT_DIR$/scalap.iml" filepath="$PROJECT_DIR$/scalap.iml" /> <module fileurl="file://$PROJECT_DIR$/swing.iml" filepath="$PROJECT_DIR$/swing.iml" /> + <module fileurl="file://$PROJECT_DIR$/test.iml" filepath="$PROJECT_DIR$/test.iml" /> </modules> </component> <component name="ProjectResources"> diff --git a/src/intellij/test.iml.SAMPLE b/src/intellij/test.iml.SAMPLE new file mode 100644 index 0000000000..e6729ae362 --- /dev/null +++ b/src/intellij/test.iml.SAMPLE @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/../../test" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="actors" /> + <orderEntry type="module" module-name="compiler" /> + <orderEntry type="module" module-name="continuations" /> + <orderEntry type="module" module-name="library" /> + <orderEntry type="module" module-name="swing" /> + <orderEntry type="module" module-name="partest" /> + </component> +</module> + diff --git a/src/library/scala/Array.scala b/src/library/scala/Array.scala index 36e95b303d..e7cf399fa4 100644 --- a/src/library/scala/Array.scala +++ b/src/library/scala/Array.scala @@ -362,8 +362,8 @@ object Array extends FallbackArrayBuilding { /** Returns an array containing a sequence of increasing integers in a range. * - * @param from the start value of the array - * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) + * @param start the start value of the array + * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) * @return the array with values in range `start, start + 1, ..., end - 1` * up to, but excluding, `end`. */ @@ -438,10 +438,8 @@ object Array extends FallbackArrayBuilding { * Arrays make use of two common pieces of Scala syntactic sugar, shown on lines 2 and 3 of the above * example code. * Line 2 is translated into a call to `apply(Int)`, while line 3 is translated into a call to - * `update(Int, T)`. For more information on these transformations, see the - * [[http://www.scala-lang.org/docu/files/ScalaReference.pdf Scala Language Specification v2.8]], Sections - * 6.6 and 6.15 respectively. - * + * `update(Int, T)`. + * * Two implicit conversions exist in [[scala.Predef]] that are frequently applied to arrays: a conversion * to [[scala.collection.mutable.ArrayOps]] (shown on line 4 of the example above) and a conversion * to [[scala.collection.mutable.WrappedArray]] (a subtype of [[scala.collections.Seq]]). @@ -465,8 +463,9 @@ object Array extends FallbackArrayBuilding { * * @author Martin Odersky * @version 1.0 - * @see [[http://www.scala-lang.org/docu/files/collections-api/collections_38.html#anchor "The Scala 2.8 Collections' API"]] - * section on `Array` by Martin Odersky for more information. + * @see [[http://www.scala-lang.org/docu/files/ScalaReference.pdf Scala Language Specification]], for in-depth information on the transformations the Scala compiler makes on Arrays (Sections 6.6 and 6.15 respectively.) + * @see [[http://docs.scala-lang.org/sips/completed/scala-2-8-arrays.html "Scala 2.8 Arrays"]] the Scala Improvement Document detailing arrays since Scala 2.8. + * @see [[http://docs.scala-lang.org/overviews/collections/arrays.html "The Scala 2.8 Collections' API"]] section on `Array` by Martin Odersky for more information. * @define coll array * @define Coll `Array` * @define orderDependent diff --git a/src/library/scala/Console.scala b/src/library/scala/Console.scala index 5e62df6c71..50e6626700 100644 --- a/src/library/scala/Console.scala +++ b/src/library/scala/Console.scala @@ -186,7 +186,6 @@ object Console { * } * }}} * - * @param in the new input stream. * @param thunk the code to execute with * the new input stream active * diff --git a/src/library/scala/Either.scala b/src/library/scala/Either.scala index 20451600a9..b35d8a7c8a 100644 --- a/src/library/scala/Either.scala +++ b/src/library/scala/Either.scala @@ -297,7 +297,7 @@ object Either { * Left(12).left.foreach(x => println(x)) // prints "12" * Right(12).left.foreach(x => println(x)) // doesn't print * }}} - * @param e The side-effecting function to execute. + * @param f The side-effecting function to execute. */ def foreach[U](f: A => U) = e match { case Left(a) => f(a) @@ -358,7 +358,7 @@ object Either { * Left(12).left.flatMap(x => Left("scala")) // Left("scala") * Right(12).left.flatMap(x => Left("scala") // Right(12) * }}} - * @param The function to bind across `Left`. + * @param f The function to bind across `Left`. */ def flatMap[BB >: B, X](f: A => Either[X, BB]) = e match { case Left(a) => f(a) @@ -462,7 +462,7 @@ object Either { * Right(12).right.foreach(x => println(x)) // prints "12" * Left(12).right.foreach(x => println(x)) // doesn't print * }}} - * @param e The side-effecting function to execute. + * @param f The side-effecting function to execute. */ def foreach[U](f: B => U) = e match { case Left(_) => {} @@ -516,7 +516,7 @@ object Either { /** * Binds the given function across `Right`. * - * @param The function to bind across `Right`. + * @param f The function to bind across `Right`. */ def flatMap[AA >: A, Y](f: B => Either[AA, Y]) = e match { case Left(a) => Left(a) diff --git a/src/library/scala/Enumeration.scala b/src/library/scala/Enumeration.scala index ea0d20957d..2b658ee4f7 100644 --- a/src/library/scala/Enumeration.scala +++ b/src/library/scala/Enumeration.scala @@ -113,8 +113,8 @@ abstract class Enumeration (initial: Int) extends Serializable { * enumeration, but no higher than 0. */ private var bottomId = if(initial < 0) initial else 0 - /** The highest integer amongst those used to identify values in this - * enumeration. */ + /** The one higher than the highest integer amongst those used to identify + * values in this enumeration. */ final def maxId = topId /** The value of this enumeration with given id `x` diff --git a/src/library/scala/Function.scala b/src/library/scala/Function.scala index 9fa56a332f..270581a3aa 100644 --- a/src/library/scala/Function.scala +++ b/src/library/scala/Function.scala @@ -20,7 +20,6 @@ object Function { * function `f,,1,, andThen ... andThen f,,n,,`. * * @param fs The given sequence of functions - * @return ... */ def chain[a](fs: Seq[a => a]): a => a = { x => (x /: fs) ((x, f) => f(x)) } @@ -38,7 +37,7 @@ object Function { * @param f a function `T => Option[R]` * @return a partial function defined for those inputs where * f returns `Some(_)` and undefined where `f` returns `None`. - * @see [[scala.PartialFunction#lift]] + * @see [[scala.PartialFunction]], method `lift`. */ def unlift[T, R](f: T => Option[R]): PartialFunction[T, R] = PartialFunction.unlifted(f) @@ -72,9 +71,6 @@ object Function { * * @note These functions are slotted for deprecation, but it is on * hold pending superior type inference for tupling anonymous functions. - * - * @param f ... - * @return ... */ // @deprecated("Use `f.tupled` instead") def tupled[a1, a2, b](f: (a1, a2) => b): Tuple2[a1, a2] => b = { diff --git a/src/library/scala/Option.scala b/src/library/scala/Option.scala index 44c8fba45f..c461b413d6 100644 --- a/src/library/scala/Option.scala +++ b/src/library/scala/Option.scala @@ -146,7 +146,7 @@ sealed abstract class Option[+A] extends Product with Serializable { /** Returns the result of applying $f to this $option's * value if the $option is nonempty. Otherwise, evaluates - * expression $ifEmpty. + * expression `ifEmpty`. * * @note This is equivalent to `$option map f getOrElse ifEmpty`. * diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 500ee87c46..c08462ac1b 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -160,7 +160,7 @@ object Predef extends LowPriorityImplicits { * is at least `ASSERTION`. * * @see elidable - * @param p the expression to test + * @param assertion the expression to test */ @elidable(ASSERTION) def assert(assertion: Boolean) { @@ -173,8 +173,8 @@ object Predef extends LowPriorityImplicits { * is at least `ASSERTION`. * * @see elidable - * @param p the expression to test - * @param msg a String to include in the failure message + * @param assertion the expression to test + * @param message a String to include in the failure message */ @elidable(ASSERTION) @inline final def assert(assertion: Boolean, message: => Any) { @@ -189,7 +189,7 @@ object Predef extends LowPriorityImplicits { * will not be generated if `-Xelide-below` is at least `ASSERTION`. * * @see elidable - * @param p the expression to test + * @param assumption the expression to test */ @elidable(ASSERTION) def assume(assumption: Boolean) { @@ -204,8 +204,8 @@ object Predef extends LowPriorityImplicits { * will not be generated if `-Xelide-below` is at least `ASSERTION`. * * @see elidable - * @param p the expression to test - * @param msg a String to include in the failure message + * @param assumption the expression to test + * @param message a String to include in the failure message */ @elidable(ASSERTION) @inline final def assume(assumption: Boolean, message: => Any) { @@ -217,7 +217,7 @@ object Predef extends LowPriorityImplicits { * This method is similar to `assert`, but blames the caller of the method * for violating the condition. * - * @param p the expression to test + * @param requirement the expression to test */ def require(requirement: Boolean) { if (!requirement) @@ -228,8 +228,8 @@ object Predef extends LowPriorityImplicits { * This method is similar to `assert`, but blames the caller of the method * for violating the condition. * - * @param p the expression to test - * @param msg a String to include in the failure message + * @param requirement the expression to test + * @param message a String to include in the failure message */ @inline final def require(requirement: Boolean, message: => Any) { if (!requirement) @@ -451,14 +451,14 @@ object Predef extends LowPriorityImplicits { } /** A type for which there is always an implicit value. - * @see fallbackCanBuildFrom in Array.scala + * @see [[scala.Array$]], method `fallbackCanBuildFrom` */ class DummyImplicit object DummyImplicit { /** An implicit value yielding a `DummyImplicit`. - * @see fallbackCanBuildFrom in Array.scala + * @see [[scala.Array$]], method `fallbackCanBuildFrom` */ implicit def dummyImplicit: DummyImplicit = new DummyImplicit } diff --git a/src/library/scala/Product.scala b/src/library/scala/Product.scala index 1459ab9ea5..8c42c60d98 100644 --- a/src/library/scala/Product.scala +++ b/src/library/scala/Product.scala @@ -19,7 +19,7 @@ package scala */ trait Product extends Any with Equals { /** The n^th^ element of this product, 0-based. In other words, for a - * product `A(x,,1,,, ..., x,,k,,)`, returns `x,,(n+1),, where `0 < n < k`. + * product `A(x,,1,,, ..., x,,k,,)`, returns `x,,(n+1),,` where `0 < n < k`. * * @param n the index of the element to return * @throws `IndexOutOfBoundsException` diff --git a/src/library/scala/Responder.scala b/src/library/scala/Responder.scala index 189d75a4ae..04c69bc330 100644 --- a/src/library/scala/Responder.scala +++ b/src/library/scala/Responder.scala @@ -21,9 +21,6 @@ package scala object Responder { /** Creates a responder that answer continuations with the constant `a`. - * - * @param x ... - * @return ... */ def constant[A](x: A) = new Responder[A] { def respond(k: A => Unit) = k(x) @@ -31,9 +28,6 @@ object Responder { /** Executes `x` and returns `'''true'''`, useful as syntactic * convenience in for comprehensions. - * - * @param x ... - * @return ... */ def exec[A](x: => Unit): Boolean = { x; true } diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index be9e0c290a..f400f18dab 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -132,7 +132,7 @@ object StringContext { * escape: `\\`, `\"`, `\'` * octal: `\d` `\dd` `\ddd` where `d` is an octal digit between `0` and `7`. * - * @param A string that may contain escape sequences + * @param str A string that may contain escape sequences * @return The string with all escape sequences expanded. */ def treatEscapes(str: String): String = { diff --git a/src/library/scala/annotation/elidable.scala b/src/library/scala/annotation/elidable.scala index 880b645daa..18be2450f5 100644 --- a/src/library/scala/annotation/elidable.scala +++ b/src/library/scala/annotation/elidable.scala @@ -18,18 +18,22 @@ import java.util.logging.Level * be omitted from generated code if the priority given the annotation * is lower than that given on the command line. * + * {{{ * @elidable(123) // annotation priority * scalac -Xelide-below 456 // command line priority - * + * }}} + * * The method call will be replaced with an expression which depends on * the type of the elided expression. In decreasing order of precedence: * + * {{{ * Unit () * Boolean false * T <: AnyVal 0 * T >: Null null * T >: Nothing Predef.??? - * + * }}} + * * Complete example: {{{ import annotation._, elidable._ diff --git a/src/library/scala/collection/BitSetLike.scala b/src/library/scala/collection/BitSetLike.scala index c0aaa9f28e..00cd63eb70 100644 --- a/src/library/scala/collection/BitSetLike.scala +++ b/src/library/scala/collection/BitSetLike.scala @@ -137,7 +137,7 @@ trait BitSetLike[+This <: BitSetLike[This] with SortedSet[Int]] extends SortedSe /** Computes the intersection between this bitset and another bitset by performing * a bitwise "and". - * @param that the bitset to intersect with. + * @param other the bitset to intersect with. * @return a new bitset consisting of all elements that are both in this * bitset and in the given bitset `other`. */ @@ -152,7 +152,7 @@ trait BitSetLike[+This <: BitSetLike[This] with SortedSet[Int]] extends SortedSe /** Computes the difference of this bitset and another bitset by performing * a bitwise "and-not". * - * @param that the set of bits to exclude. + * @param other the set of bits to exclude. * @return a bitset containing those bits of this * bitset that are not also contained in the given bitset `other`. */ @@ -167,7 +167,7 @@ trait BitSetLike[+This <: BitSetLike[This] with SortedSet[Int]] extends SortedSe /** Computes the symmetric difference of this bitset and another bitset by performing * a bitwise "exclusive-or". * - * @param that the other bitset to take part in the symmetric difference. + * @param other the other bitset to take part in the symmetric difference. * @return a bitset containing those bits of this * bitset or the other bitset that are not contained in both bitsets. */ @@ -184,7 +184,7 @@ trait BitSetLike[+This <: BitSetLike[This] with SortedSet[Int]] extends SortedSe /** Tests whether this bitset is a subset of another bitset. * - * @param that the bitset to test. + * @param other the bitset to test. * @return `true` if this bitset is a subset of `other`, i.e. if * every bit of this set is also an element in `other`. */ diff --git a/src/library/scala/collection/GenIterableLike.scala b/src/library/scala/collection/GenIterableLike.scala index 290a3b26b5..0fd9bac379 100644 --- a/src/library/scala/collection/GenIterableLike.scala +++ b/src/library/scala/collection/GenIterableLike.scala @@ -41,7 +41,7 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { /** Checks if the other iterable collection contains the same elements in the same order as this $coll. * * @param that the collection to compare with. - * @tparam B the type of the elements of collection `that`. + * @tparam A1 the type of the elements of collection `that`. * @return `true`, if both collections contain the same elements in the same order, `false` otherwise. * * @usecase def sameElements(that: GenIterable[A]): Boolean @@ -87,13 +87,13 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * @tparam A1 the type of the first half of the returned pairs (this is always a supertype * of the collection's element type `A`). * @tparam That the class of the returned collection. Where possible, `That` is - * the same class as the current collection class `Repr`, but this - * depends on the element type `(A1, Int)` being admissible for that class, - * which means that an implicit instance of type `CanBuildFrom[Repr, (A1, Int), That]`. - * is found. - * @tparam bf an implicit value of class `CanBuildFrom` which determines the - * result class `That` from the current representation type `Repr` - * and the new element type `(A1, Int)`. + * the same class as the current collection class `Repr`, but this + * depends on the element type `(A1, Int)` being admissible for that class, + * which means that an implicit instance of type `CanBuildFrom[Repr, (A1, Int), That]`. + * is found. + * @param bf an implicit value of class `CanBuildFrom` which determines the + * result class `That` from the current representation type `Repr` + * and the new element type `(A1, Int)`. * @return A new collection of type `That` containing pairs consisting of all elements of this * $coll paired with their index. Indices start at `0`. * diff --git a/src/library/scala/collection/GenMapLike.scala b/src/library/scala/collection/GenMapLike.scala index d611eaea43..4dd2a4fe37 100644 --- a/src/library/scala/collection/GenMapLike.scala +++ b/src/library/scala/collection/GenMapLike.scala @@ -42,7 +42,6 @@ trait GenMapLike[A, +B, +Repr] extends GenIterableLike[(A, B), Repr] with Equals * otherwise the result of the `default` computation. * @usecase def getOrElse(key: A, default: => B): B * @inheritdoc - * @tparam B the result type of the default computation. */ def getOrElse[B1 >: B](key: A, default: => B1): B1 diff --git a/src/library/scala/collection/GenSeqLike.scala b/src/library/scala/collection/GenSeqLike.scala index fd7892c9f9..cfa0ca101e 100644 --- a/src/library/scala/collection/GenSeqLike.scala +++ b/src/library/scala/collection/GenSeqLike.scala @@ -413,8 +413,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * * @param that the sequence of elements to remove * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo * @return a new collection of type `That` which contains all elements of this $coll * except some of occurrences of elements that also appear in `that`. * If an element value `x` appears @@ -438,8 +436,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * * @param that the sequence of elements to intersect with. * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo * @return a new collection of type `That` which contains all elements of this $coll * which also appear in `that`. * If an element value `x` appears diff --git a/src/library/scala/collection/GenTraversableLike.scala b/src/library/scala/collection/GenTraversableLike.scala index df652f99d9..eaec7a2a76 100644 --- a/src/library/scala/collection/GenTraversableLike.scala +++ b/src/library/scala/collection/GenTraversableLike.scala @@ -289,7 +289,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with /** Selects all elements of this $coll which satisfy a predicate. * - * @param p the predicate used to test elements. + * @param pred the predicate used to test elements. * @return a new $coll consisting of all elements of this $coll that satisfy the given * predicate `p`. Their order may not be preserved. */ @@ -297,7 +297,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with /** Selects all elements of this $coll which do not satisfy a predicate. * - * @param p the predicate used to test elements. + * @param pred the predicate used to test elements. * @return a new $coll consisting of all elements of this $coll that do not satisfy the given * predicate `p`. Their order may not be preserved. */ @@ -305,11 +305,11 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with /** Partitions this $coll in two ${coll}s according to a predicate. * - * @param p the predicate on which to partition. - * @return a pair of ${coll}s: the first $coll consists of all elements that - * satisfy the predicate `p` and the second $coll consists of all elements - * that don't. The relative order of the elements in the resulting ${coll}s - * may not be preserved. + * @param pred the predicate on which to partition. + * @return a pair of ${coll}s: the first $coll consists of all elements that + * satisfy the predicate `p` and the second $coll consists of all elements + * that don't. The relative order of the elements in the resulting ${coll}s + * may not be preserved. */ def partition(pred: A => Boolean): (Repr, Repr) @@ -354,8 +354,8 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * }}} * $orderDependent * - * @param from the lowest index to include from this $coll. - * @param until the lowest index to EXCLUDE from this $coll. + * @param unc_from the lowest index to include from this $coll. + * @param unc_until the lowest index to EXCLUDE from this $coll. * @return a $coll containing the elements greater than or equal to * index `from` extending up to (but not including) index `until` * of this $coll. @@ -375,7 +375,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with /** Takes longest prefix of elements that satisfy a predicate. * $orderDependent - * @param p The predicate used to test elements. + * @param pred The predicate used to test elements. * @return the longest prefix of this $coll whose elements all satisfy * the predicate `p`. */ @@ -388,7 +388,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * predicate `p` does not cause any side-effects. * $orderDependent * - * @param p the test predicate + * @param pred the test predicate * @return a pair consisting of the longest prefix of this $coll whose * elements all satisfy `p`, and the rest of this $coll. */ @@ -396,7 +396,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with /** Drops longest prefix of elements that satisfy a predicate. * $orderDependent - * @param p The predicate used to test elements. + * @param pred The predicate used to test elements. * @return the longest suffix of this $coll whose first element * does not satisfy the predicate `p`. */ @@ -411,3 +411,12 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with def stringPrefix: String } + +object GenTraversableLike { + /** Manufacture a conversion from collection representation type `Repr` to + * its corresponding `GenTraversableLike` given an implicitly available + * instance of `FromRepr[Repr]`. + * @see [[scala.collection.generic.FromRepr]] + */ + implicit def fromRepr[Repr](implicit fr : FromRepr[Repr]) = fr.hasElem +} diff --git a/src/library/scala/collection/GenTraversableOnce.scala b/src/library/scala/collection/GenTraversableOnce.scala index f4e3848d98..eab6b84ea8 100644 --- a/src/library/scala/collection/GenTraversableOnce.scala +++ b/src/library/scala/collection/GenTraversableOnce.scala @@ -312,7 +312,7 @@ trait GenTraversableOnce[+A] extends Any { * * @param num an implicit parameter defining a set of numeric operations * which includes the `+` operator to be used in forming the sum. - * @tparam B the result type of the `+` operator. + * @tparam A1 the result type of the `+` operator. * @return the sum of all elements of this $coll with respect to the `+` operator in `num`. * * @usecase def sum: A @@ -330,7 +330,7 @@ trait GenTraversableOnce[+A] extends Any { * * @param num an implicit parameter defining a set of numeric operations * which includes the `*` operator to be used in forming the product. - * @tparam B the result type of the `*` operator. + * @tparam A1 the result type of the `*` operator. * @return the product of all elements of this $coll with respect to the `*` operator in `num`. * * @usecase def product: A @@ -345,8 +345,8 @@ trait GenTraversableOnce[+A] extends Any { /** Finds the smallest element. * - * @param cmp An ordering to be used for comparing elements. - * @tparam B The type over which the ordering is defined. + * @param ord An ordering to be used for comparing elements. + * @tparam A1 The type over which the ordering is defined. * @return the smallest element of this $coll with respect to the ordering `cmp`. * * @usecase def min: A @@ -358,8 +358,8 @@ trait GenTraversableOnce[+A] extends Any { /** Finds the largest element. * - * @param cmp An ordering to be used for comparing elements. - * @tparam B The type over which the ordering is defined. + * @param ord An ordering to be used for comparing elements. + * @tparam A1 The type over which the ordering is defined. * @return the largest element of this $coll with respect to the ordering `cmp`. * * @usecase def max: A @@ -382,9 +382,9 @@ trait GenTraversableOnce[+A] extends Any { * $mayNotTerminateInf * $orderDependent * - * @param p the predicate used to test elements. - * @return an option value containing the first element in the $coll - * that satisfies `p`, or `None` if none exists. + * @param pred the predicate used to test elements. + * @return an option value containing the first element in the $coll + * that satisfies `p`, or `None` if none exists. */ def find(pred: A => Boolean): Option[A] @@ -459,9 +459,9 @@ trait GenTraversableOnce[+A] extends Any { /** Converts this $coll to an array. * - * @tparam B the type of the elements of the array. An `ArrayTag` for - * this type must be available. - * @return an array containing all elements of this $coll. + * @tparam A1 the type of the elements of the array. An `ArrayTag` for + * this type must be available. + * @return an array containing all elements of this $coll. * * @usecase def toArray: Array[A] * @inheritdoc diff --git a/src/library/scala/collection/IterableLike.scala b/src/library/scala/collection/IterableLike.scala index f4397f2167..2e9599058f 100644 --- a/src/library/scala/collection/IterableLike.scala +++ b/src/library/scala/collection/IterableLike.scala @@ -148,7 +148,7 @@ self => } /** Partitions elements in fixed size ${coll}s. - * @see Iterator#grouped + * @see [[scala.collection.Iterator]], method `grouped` * * @param size the number of elements per group * @return An iterator producing ${coll}s of size `size`, except the @@ -163,7 +163,18 @@ self => /** Groups elements in fixed size blocks by passing a "sliding window" * over them (as opposed to partitioning them, as is done in grouped.) - * @see Iterator#sliding + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @return An iterator producing ${coll}s of size `size`, except the + * last and the only element will be truncated if there are + * fewer elements than size. + */ + def sliding(size: Int): Iterator[Repr] = sliding(size, 1) + + /** Groups elements in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in grouped.) + * @see [[scala.collection.Iterator]], method `sliding` * * @param size the number of elements per group * @param step the distance between the first elements of successive @@ -172,7 +183,6 @@ self => * last and the only element will be truncated if there are * fewer elements than size. */ - def sliding(size: Int): Iterator[Repr] = sliding(size, 1) def sliding(size: Int, step: Int): Iterator[Repr] = for (xs <- iterator.sliding(size, step)) yield { val b = newBuilder diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala index e9a7906527..b2bbc8d888 100644 --- a/src/library/scala/collection/Iterator.scala +++ b/src/library/scala/collection/Iterator.scala @@ -824,7 +824,7 @@ trait Iterator[+A] extends TraversableOnce[A] { /** Creates a buffered iterator from this iterator. * - * @see BufferedIterator + * @see [[scala.collection.BufferedIterator]] * @return a buffered iterator producing the same values as this iterator. * @note Reuse: $consumesAndProducesIterator */ @@ -1079,11 +1079,12 @@ trait Iterator[+A] extends TraversableOnce[A] { if (i < from) origElems.hasNext else patchElems.hasNext || origElems.hasNext def next(): B = { + // We have to do this *first* just in case from = 0. + if (i == from) origElems = origElems drop replaced val result: B = if (i < from || !patchElems.hasNext) origElems.next() else patchElems.next() i += 1 - if (i == from) origElems = origElems drop replaced result } } diff --git a/src/library/scala/collection/MapLike.scala b/src/library/scala/collection/MapLike.scala index b2b687e75a..b9b8f62574 100644 --- a/src/library/scala/collection/MapLike.scala +++ b/src/library/scala/collection/MapLike.scala @@ -280,14 +280,14 @@ self => * * @usecase def + (kvs: (A, B)*): Map[A, B] * @inheritdoc - * @param the key/value pairs + * @param kvs the key/value pairs */ def + [B1 >: B] (kv1: (A, B1), kv2: (A, B1), kvs: (A, B1) *): Map[A, B1] = this + kv1 + kv2 ++ kvs /** Adds all key/value pairs in a traversable collection to this map, returning a new map. * - * @param kvs the collection containing the added key/value pairs + * @param xs the collection containing the added key/value pairs * @tparam B1 the type of the added values * @return a new map with the given bindings added to this map * diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index 044bd624ae..c87726ef2c 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -413,8 +413,6 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * * @param that the sequence of elements to remove * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo * @return a new collection of type `That` which contains all elements of this $coll * except some of occurrences of elements that also appear in `that`. * If an element value `x` appears @@ -444,8 +442,6 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * * @param that the sequence of elements to intersect with. * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo * @return a new collection of type `That` which contains all elements of this $coll * which also appear in `that`. * If an element value `x` appears @@ -570,7 +566,7 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ /** Sorts this $Coll according to the Ordering which results from transforming * an implicitly given Ordering with a transformation function. - * @see scala.math.Ordering + * @see [[scala.math.Ordering]] * $willNotTerminateInf * @param f the transformation function mapping elements * to some other domain `B`. @@ -595,7 +591,7 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * The sort is stable. That is, elements that are equal (as determined by * `lt`) appear in the same order in the sorted sequence as in the original. * - * @see scala.math.Ordering + * @see [[scala.math.Ordering]] * * @param ord the ordering to be used to compare elements. * @return a $coll consisting of the elements of this $coll @@ -856,7 +852,7 @@ object SeqLike { /** Finds a particular index at which one sequence occurs in another sequence. * Like `indexOf`, but finds the latest occurrence rather than earliest. * - * @see SeqLike#indexOf + * @see [[scala.collection.SeqLike], method `indexOf` */ def lastIndexOf[B]( source: Seq[B], sourceOffset: Int, sourceCount: Int, diff --git a/src/library/scala/collection/TraversableOnce.scala b/src/library/scala/collection/TraversableOnce.scala index 639610a30c..d42c037dee 100644 --- a/src/library/scala/collection/TraversableOnce.scala +++ b/src/library/scala/collection/TraversableOnce.scala @@ -16,8 +16,6 @@ import language.{implicitConversions, higherKinds} * or one or more times. * $traversableonceinfo * - * @tparam A the element type of the collection - * * @author Martin Odersky * @author Paul Phillips * @version 2.8 diff --git a/src/library/scala/collection/TraversableViewLike.scala b/src/library/scala/collection/TraversableViewLike.scala index 5ca038fd2e..eb2091a5f3 100644 --- a/src/library/scala/collection/TraversableViewLike.scala +++ b/src/library/scala/collection/TraversableViewLike.scala @@ -162,6 +162,8 @@ trait TraversableViewLike[+A, // if (b.isInstanceOf[NoBuilder[_]]) newFlatMapped(f).asInstanceOf[That] // else super.flatMap[B, That](f)(bf) } + override def flatten[B](implicit asTraversable: A => /*<:<!!!*/ GenTraversableOnce[B]) = + newFlatMapped(asTraversable) private[this] implicit def asThis(xs: Transformed[A]): This = xs.asInstanceOf[This] /** Boilerplate method, to override in each subclass diff --git a/src/library/scala/collection/convert/DecorateAsScala.scala b/src/library/scala/collection/convert/DecorateAsScala.scala index b170d8d139..539584b148 100644 --- a/src/library/scala/collection/convert/DecorateAsScala.scala +++ b/src/library/scala/collection/convert/DecorateAsScala.scala @@ -186,9 +186,9 @@ trait DecorateAsScala { * any side-effects of using it via the Scala interface will be visible via * the Java interface and vice versa. * - * @param m The `Dictionary` to be converted. - * @return An object with an `asScala` method that returns a Scala mutable - * `Map[String, String]` view of the argument. + * @param p The `Dictionary` to be converted. + * @return An object with an `asScala` method that returns a Scala mutable + * `Map[String, String]` view of the argument. */ implicit def dictionaryAsScalaMapConverter[A, B](p: ju.Dictionary[A, B]): AsScala[mutable.Map[A, B]] = new AsScala(dictionaryAsScalaMap(p)) @@ -200,9 +200,9 @@ trait DecorateAsScala { * any side-effects of using it via the Scala interface will be visible via * the Java interface and vice versa. * - * @param m The `Properties` to be converted. - * @return An object with an `asScala` method that returns a Scala mutable - * `Map[String, String]` view of the argument. + * @param p The `Properties` to be converted. + * @return An object with an `asScala` method that returns a Scala mutable + * `Map[String, String]` view of the argument. */ implicit def propertiesAsScalaMapConverter(p: ju.Properties): AsScala[mutable.Map[String, String]] = new AsScala(propertiesAsScalaMap(p)) diff --git a/src/library/scala/collection/convert/WrapAsJava.scala b/src/library/scala/collection/convert/WrapAsJava.scala index ecf91deb3a..fcfe402a68 100644 --- a/src/library/scala/collection/convert/WrapAsJava.scala +++ b/src/library/scala/collection/convert/WrapAsJava.scala @@ -24,8 +24,8 @@ trait WrapAsJava { * explicit call of `asIterator(java.util.Iterator)` then the original * Java Iterator will be returned. * - * @param i The Iterator to be converted. - * @return A Java Iterator view of the argument. + * @param it The Iterator to be converted. + * @return A Java Iterator view of the argument. */ implicit def asJavaIterator[A](it: Iterator[A]): ju.Iterator[A] = it match { case JIteratorWrapper(wrapped) => wrapped.asInstanceOf[ju.Iterator[A]] @@ -42,8 +42,8 @@ trait WrapAsJava { * explicit call of `asIterator(java.util.Enumeration)` then the * original Java Enumeration will be returned. * - * @param i The Iterator to be converted. - * @return A Java Enumeration view of the argument. + * @param it The Iterator to be converted. + * @return A Java Enumeration view of the argument. */ implicit def asJavaEnumeration[A](it: Iterator[A]): ju.Enumeration[A] = it match { case JEnumerationWrapper(wrapped) => wrapped.asInstanceOf[ju.Enumeration[A]] @@ -76,8 +76,8 @@ trait WrapAsJava { * explicit call of `asSizedIterable(java.util.Collection)` then the original * Java Collection will be returned. * - * @param i The SizedIterable to be converted. - * @return A Java Collection view of the argument. + * @param it The SizedIterable to be converted. + * @return A Java Collection view of the argument. */ implicit def asJavaCollection[A](it: Iterable[A]): ju.Collection[A] = it match { case JCollectionWrapper(wrapped) => wrapped.asInstanceOf[ju.Collection[A]] @@ -112,8 +112,8 @@ trait WrapAsJava { * explicit call of `asSeq(java.util.List)` then the original * Java List will be returned. * - * @param b The Seq to be converted. - * @return A Java List view of the argument. + * @param seq The Seq to be converted. + * @return A Java List view of the argument. */ implicit def mutableSeqAsJavaList[A](seq: mutable.Seq[A]): ju.List[A] = seq match { case JListWrapper(wrapped) => wrapped @@ -130,8 +130,8 @@ trait WrapAsJava { * explicit call of `asSeq(java.util.List)` then the original * Java List will be returned. * - * @param b The Seq to be converted. - * @return A Java List view of the argument. + * @param seq The Seq to be converted. + * @return A Java List view of the argument. */ implicit def seqAsJavaList[A](seq: Seq[A]): ju.List[A] = seq match { case JListWrapper(wrapped) => wrapped.asInstanceOf[ju.List[A]] diff --git a/src/library/scala/collection/convert/WrapAsScala.scala b/src/library/scala/collection/convert/WrapAsScala.scala index 14c64695ff..49f4d7cd99 100644 --- a/src/library/scala/collection/convert/WrapAsScala.scala +++ b/src/library/scala/collection/convert/WrapAsScala.scala @@ -25,8 +25,8 @@ trait WrapAsScala { * explicit call of `asIterator(scala.collection.Iterator)` then the * original Scala `Iterator` will be returned. * - * @param i The `Iterator` to be converted. - * @return A Scala `Iterator` view of the argument. + * @param it The `Iterator` to be converted. + * @return A Scala `Iterator` view of the argument. */ implicit def asScalaIterator[A](it: ju.Iterator[A]): Iterator[A] = it match { case IteratorWrapper(wrapped) => wrapped @@ -187,8 +187,8 @@ trait WrapAsScala { * `Dictionary` and any side-effects of using it via the Scala interface * will be visible via the Java interface and vice versa. * - * @param m The Dictionary to be converted. - * @return A Scala mutable Map[String, String] view of the argument. + * @param p The Dictionary to be converted. + * @return A Scala mutable Map[String, String] view of the argument. */ implicit def dictionaryAsScalaMap[A, B](p: ju.Dictionary[A, B]): mutable.Map[A, B] = p match { case DictionaryWrapper(wrapped) => wrapped @@ -202,8 +202,8 @@ trait WrapAsScala { * `Properties` and any side-effects of using it via the Scala interface * will be visible via the Java interface and vice versa. * - * @param m The Properties to be converted. - * @return A Scala mutable Map[String, String] view of the argument. + * @param p The Properties to be converted. + * @return A Scala mutable Map[String, String] view of the argument. */ implicit def propertiesAsScalaMap(p: ju.Properties): mutable.Map[String, String] = p match { case _ => new JPropertiesWrapper(p) diff --git a/src/library/scala/collection/generic/CanBuildFrom.scala b/src/library/scala/collection/generic/CanBuildFrom.scala index 3a335f357a..f3eff03d89 100644 --- a/src/library/scala/collection/generic/CanBuildFrom.scala +++ b/src/library/scala/collection/generic/CanBuildFrom.scala @@ -20,7 +20,7 @@ import scala.annotation.implicitNotFound * @tparam Elem the element type of the collection to be created. * @tparam To the type of the collection to be created. * - * @see Builder + * @see [[scala.collection.mutable.Builder]] * @author Martin Odersky * @author Adriaan Moors * @since 2.8 diff --git a/src/library/scala/collection/generic/FromRepr.scala b/src/library/scala/collection/generic/FromRepr.scala new file mode 100644 index 0000000000..c08761332c --- /dev/null +++ b/src/library/scala/collection/generic/FromRepr.scala @@ -0,0 +1,56 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2012, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.collection +package generic + +/** Type class witnessing that a collection representation type `Repr` has + * elements of type `A` and has a conversion to `GenTraversableLike[A, Repr]`. + * + * This type enables simple enrichment of `GenTraversable`s with extension + * methods which can make full use of the mechanics of the Scala collections + * framework in their implementation. + * + * Example usage, + * {{{ + * import scala.collection.generic.{ CanBuildFrom, FromRepr, HasElem } + * + * class FilterMapImpl[A, Repr](val r : Repr)(implicit hasElem : HasElem[Repr, A]) { + * def filterMap[B, That](f : A => Option[B]) + * (implicit cbf : CanBuildFrom[Repr, B, That]) : That = r.flatMap(f(_).toSeq) + * } + * + * implicit def filterMap[Repr : FromRepr](r : Repr) = new FilterMapImpl(r) + * + * val l = List(1, 2, 3, 4, 5) + * List(1, 2, 3, 4, 5) filterMap (i => if(i % 2 == 0) Some(i) else None) + * // == List(2, 4) + * }}} + * + * @author Miles Sabin + * @since 2.10 + */ +trait FromRepr[Repr] { + type A + val hasElem: HasElem[Repr, A] +} + +object FromRepr { + import language.higherKinds + + implicit val stringFromRepr : FromRepr[String] { type A = Char } = new FromRepr[String] { + type A = Char + val hasElem = implicitly[HasElem[String, Char]] + } + + implicit def genTraversableLikeFromRepr[C[_], A0] + (implicit hasElem0: HasElem[C[A0], A0]) : FromRepr[C[A0]] { type A = A0 } = new FromRepr[C[A0]] { + type A = A0 + val hasElem = hasElem0 + } +} diff --git a/src/library/scala/collection/generic/GenTraversableFactory.scala b/src/library/scala/collection/generic/GenTraversableFactory.scala index 94def7ab5d..2aaf93de05 100644 --- a/src/library/scala/collection/generic/GenTraversableFactory.scala +++ b/src/library/scala/collection/generic/GenTraversableFactory.scala @@ -201,8 +201,8 @@ abstract class GenTraversableFactory[CC[X] <: GenTraversable[X] with GenericTrav /** Produces a $coll containing a sequence of increasing of integers. * - * @param from the first element of the $coll - * @param end the end value of the $coll (the first value NOT contained) + * @param start the first element of the $coll + * @param end the end value of the $coll (the first value NOT contained) * @return a $coll with values `start, start + 1, ..., end - 1` */ def range[T: Integral](start: T, end: T): CC[T] = range(start, end, implicitly[Integral[T]].one) diff --git a/src/library/scala/collection/generic/GenericCompanion.scala b/src/library/scala/collection/generic/GenericCompanion.scala index badceac713..1844542315 100644 --- a/src/library/scala/collection/generic/GenericCompanion.scala +++ b/src/library/scala/collection/generic/GenericCompanion.scala @@ -16,7 +16,7 @@ import language.higherKinds * represent an unconstrained higher-kinded type. Typically * such classes inherit from trait `GenericTraversableTemplate`. * @tparam CC The type constructor representing the collection class. - * @see GenericTraversableTemplate + * @see [[scala.collection.generic.GenericTraversableTemplate]] * @author Martin Odersky * @since 2.8 * @define coll collection diff --git a/src/library/scala/collection/generic/GenericTraversableTemplate.scala b/src/library/scala/collection/generic/GenericTraversableTemplate.scala index 69eab43150..7cb0e812d8 100644 --- a/src/library/scala/collection/generic/GenericTraversableTemplate.scala +++ b/src/library/scala/collection/generic/GenericTraversableTemplate.scala @@ -73,8 +73,8 @@ trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNew /** Converts this $coll of pairs into two collections of the first and second * half of each pair. * - * @param A1 the type of the first half of the element pairs - * @param A2 the type of the second half of the element pairs + * @tparam A1 the type of the first half of the element pairs + * @tparam A2 the type of the second half of the element pairs * @param asPair an implicit conversion which asserts that the element type * of this $coll is a pair. * @return a pair ${coll}s, containing the first, respectively second @@ -94,9 +94,9 @@ trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNew /** Converts this $coll of triples into three collections of the first, second, * and third element of each triple. * - * @param A1 the type of the first member of the element triples - * @param A2 the type of the second member of the element triples - * @param A3 the type of the third member of the element triples + * @tparam A1 the type of the first member of the element triples + * @tparam A2 the type of the second member of the element triples + * @tparam A3 the type of the third member of the element triples * @param asTriple an implicit conversion which asserts that the element type * of this $coll is a triple. * @return a triple ${coll}s, containing the first, second, respectively diff --git a/src/library/scala/collection/generic/Growable.scala b/src/library/scala/collection/generic/Growable.scala index d6a263af2f..730cb18733 100644 --- a/src/library/scala/collection/generic/Growable.scala +++ b/src/library/scala/collection/generic/Growable.scala @@ -42,7 +42,7 @@ trait Growable[-A] extends Clearable { /** ${Add}s all elements produced by a TraversableOnce to this $coll. * - * @param iter the TraversableOnce producing the elements to $add. + * @param xs the TraversableOnce producing the elements to $add. * @return the $coll itself. */ def ++=(xs: TraversableOnce[A]): this.type = { xs.seq foreach += ; this } diff --git a/src/library/scala/collection/generic/Shrinkable.scala b/src/library/scala/collection/generic/Shrinkable.scala index 0c9dafefb1..593cd0f58e 100644 --- a/src/library/scala/collection/generic/Shrinkable.scala +++ b/src/library/scala/collection/generic/Shrinkable.scala @@ -43,7 +43,7 @@ trait Shrinkable[-A] { /** Removes all elements produced by an iterator from this $coll. * - * @param iter the iterator producing the elements to remove. + * @param xs the iterator producing the elements to remove. * @return the $coll itself */ def --=(xs: TraversableOnce[A]): this.type = { xs.seq foreach -= ; this } diff --git a/src/library/scala/collection/generic/Sorted.scala b/src/library/scala/collection/generic/Sorted.scala index ed9e11fd30..42eca72806 100644 --- a/src/library/scala/collection/generic/Sorted.scala +++ b/src/library/scala/collection/generic/Sorted.scala @@ -62,8 +62,6 @@ trait Sorted[K, +This <: Sorted[K, This]] { * and an upper-bound. * * @param from The upper-bound (exclusive) of the ranged projection. - * @param until ... - * @return ... */ def range(from: K, until: K): This = rangeImpl(Some(from), Some(until)) diff --git a/src/library/scala/collection/generic/Subtractable.scala b/src/library/scala/collection/generic/Subtractable.scala index cda71c0291..aed4f4f7da 100644 --- a/src/library/scala/collection/generic/Subtractable.scala +++ b/src/library/scala/collection/generic/Subtractable.scala @@ -52,7 +52,7 @@ trait Subtractable[A, +Repr <: Subtractable[A, Repr]] { self => /** Creates a new $coll from this $coll by removing all elements of another * collection. * - * @param elems the collection containing the removed elements. + * @param xs the collection containing the removed elements. * @return a new $coll that contains all elements of the current $coll * except one less occurrence of each of the elements of `elems`. */ diff --git a/src/library/scala/collection/generic/package.scala b/src/library/scala/collection/generic/package.scala index 32006b4bf0..e0351ebae6 100644 --- a/src/library/scala/collection/generic/package.scala +++ b/src/library/scala/collection/generic/package.scala @@ -4,6 +4,12 @@ import generic.CanBuildFrom package object generic { type CanBuild[-Elem, +To] = CanBuildFrom[Nothing, Elem, To] + /** The type of conversions from a collection representation type + * `Repr` to its corresponding GenTraversableLike. + * @see [[scala.collection.generic.FromRepr]] + */ + type HasElem[Repr, A] = Repr => GenTraversableLike[A, Repr] + @deprecated("use ArrayTagTraversableFactory instead", "2.10.0") type ClassManifestTraversableFactory[CC[X] <: Traversable[X] with GenericClassManifestTraversableTemplate[X, CC]] = ArrayTagTraversableFactory[CC] diff --git a/src/library/scala/collection/immutable/List.scala b/src/library/scala/collection/immutable/List.scala index b4c22c3b6c..6fd8d143ee 100644 --- a/src/library/scala/collection/immutable/List.scala +++ b/src/library/scala/collection/immutable/List.scala @@ -511,7 +511,7 @@ object List extends SeqFactory[List] { /** Transforms an Iterable of Eithers into a pair of lists. * - * @param xs the iterable of Eithers to separate + * @param es the iterable of Eithers to separate * @return a pair of lists. */ @deprecated("use `(for (Left(x) <- es) yield x, for (Right(x) <- es) yield x)` instead", "2.8.0") @@ -582,7 +582,7 @@ object List extends SeqFactory[List] { /** Tests whether the given predicate `p` holds * for all corresponding elements of the argument lists. * - * @param p function to apply to each pair of elements. + * @param f function to apply to each pair of elements. * @return `(p(a<sub>0</sub>,b<sub>0</sub>) && * ... && p(a<sub>n</sub>,b<sub>n</sub>))]` * if the lists are `[a<sub>0</sub>, ..., a<sub>k</sub>]`; @@ -604,7 +604,7 @@ object List extends SeqFactory[List] { /** Tests whether the given predicate `p` holds * for some corresponding elements of the argument lists. * - * @param p function to apply to each pair of elements. + * @param f function to apply to each pair of elements. * @return `n != 0 && (p(a<sub>0</sub>,b<sub>0</sub>) || * ... || p(a<sub>n</sub>,b<sub>n</sub>))]` if the lists are * `[a<sub>0</sub>, ..., a<sub>k</sub>]`, diff --git a/src/library/scala/collection/immutable/ListMap.scala b/src/library/scala/collection/immutable/ListMap.scala index 9289b35632..091443f909 100644 --- a/src/library/scala/collection/immutable/ListMap.scala +++ b/src/library/scala/collection/immutable/ListMap.scala @@ -152,7 +152,7 @@ extends AbstractMap[A, B] * method throws an exception if there is no mapping from the given * key to a value. * - * @param key the key + * @param k the key * @return the value associated with the given key. */ override def apply(k: A): B1 = apply0(this, k) @@ -162,7 +162,7 @@ extends AbstractMap[A, B] /** Checks if this map maps `key` to a value and return the * value if it exists. * - * @param key the key of the mapping of interest + * @param k the key of the mapping of interest * @return the value of the mapping, if it exists */ override def get(k: A): Option[B1] = get0(this, k) @@ -174,9 +174,6 @@ extends AbstractMap[A, B] /** This method allows one to create a new map with an additional mapping * from `key` to `value`. If the map contains already a mapping for `key`, * it will be overridden by this function. - * - * @param k ... - * @param v ... */ override def updated [B2 >: B1](k: A, v: B2): ListMap[A, B2] = { val m = if (contains(k)) this - k else this @@ -186,9 +183,6 @@ extends AbstractMap[A, B] /** Creates a new mapping without the given `key`. * If the map does not contain a mapping for the given key, the * method returns the same map. - * - * @param k ... - * @return ... */ override def - (k: A): ListMap[A, B1] = { // This definition used to result in stack overflows diff --git a/src/library/scala/collection/immutable/ListSet.scala b/src/library/scala/collection/immutable/ListSet.scala index 0331e01f35..ce3abaacb7 100644 --- a/src/library/scala/collection/immutable/ListSet.scala +++ b/src/library/scala/collection/immutable/ListSet.scala @@ -157,7 +157,7 @@ class ListSet[A] extends AbstractSet[A] /** Checks if this set contains element `elem`. * - * @param elem the element to check for membership. + * @param e the element to check for membership. * @return `'''true'''`, iff `elem` is contained in this set. */ override def contains(e: A) = containsInternal(this, e) diff --git a/src/library/scala/collection/immutable/MapLike.scala b/src/library/scala/collection/immutable/MapLike.scala index 84c87b4eae..6ae2d78188 100644 --- a/src/library/scala/collection/immutable/MapLike.scala +++ b/src/library/scala/collection/immutable/MapLike.scala @@ -35,9 +35,9 @@ import parallel.immutable.ParMap * It is also good idea to override methods `foreach` and * `size` for efficiency. * - * @param A the type of the keys contained in this collection. - * @param B the type of the values associated with the keys. - * @param This The type of the actual map implementation. + * @tparam A the type of the keys contained in this collection. + * @tparam B the type of the values associated with the keys. + * @tparam This The type of the actual map implementation. * * @author Martin Odersky * @version 2.8 diff --git a/src/library/scala/collection/immutable/Range.scala b/src/library/scala/collection/immutable/Range.scala index 5698d53b06..033331b58b 100644 --- a/src/library/scala/collection/immutable/Range.scala +++ b/src/library/scala/collection/immutable/Range.scala @@ -327,16 +327,16 @@ object Range { */ def apply(start: Int, end: Int, step: Int): Range = new Range(start, end, step) - /** Make an range from `start` to `end` inclusive with step value 1. + /** Make a range from `start` until `end` (exclusive) with step value 1. */ def apply(start: Int, end: Int): Range = new Range(start, end, 1) - /** Make an inclusive range from start to end with given step value. + /** Make an inclusive range from `start` to `end` with given step value. * @note step != 0 */ @inline def inclusive(start: Int, end: Int, step: Int): Range.Inclusive = new Inclusive(start, end, step) - /** Make an inclusive range from start to end with step value 1. + /** Make an inclusive range from `start` to `end` with step value 1. */ @inline def inclusive(start: Int, end: Int): Range.Inclusive = new Inclusive(start, end, 1) diff --git a/src/library/scala/collection/immutable/Stack.scala b/src/library/scala/collection/immutable/Stack.scala index c63c1ce232..473ac6b0b0 100644 --- a/src/library/scala/collection/immutable/Stack.scala +++ b/src/library/scala/collection/immutable/Stack.scala @@ -84,7 +84,7 @@ class Stack[+A] protected (protected val elems: List[A]) * the stack. The last element returned by the traversable object * will be on top of the new stack. * - * @param elems the iterator object. + * @param xs the iterator object. * @return the stack with the new elements on top. */ def pushAll[B >: A](xs: TraversableOnce[B]): Stack[B] = diff --git a/src/library/scala/collection/immutable/Stream.scala b/src/library/scala/collection/immutable/Stream.scala index f3e7214c5f..9f5f98ddf4 100644 --- a/src/library/scala/collection/immutable/Stream.scala +++ b/src/library/scala/collection/immutable/Stream.scala @@ -716,8 +716,8 @@ self => /** A substream starting at index `from` and extending up to (but not including) * index `until`. This returns a `Stream` that is lazily evaluated. * - * @param start The index of the first element of the returned subsequence - * @param end The index of the element following the returned subsequence + * @param from The index of the first element of the returned subsequence + * @param until The index of the element following the returned subsequence * @return A new string containing the elements requested from `start` until * `end`. * diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index d1605bf637..e41b17a5e8 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -274,7 +274,7 @@ self => * `scala.BigDecimal`) are unwrapped to pass a type which `Formatter` * understands. * - * @param locale an instance of `java.util.Locale` + * @param l an instance of `java.util.Locale` * @param args the arguments used to instantiating the pattern. * @throws `java.lang.IllegalArgumentException` */ diff --git a/src/library/scala/collection/interfaces/IterableMethods.scala b/src/library/scala/collection/interfaces/IterableMethods.scala deleted file mode 100644 index 8efc3fe6f9..0000000000 --- a/src/library/scala/collection/interfaces/IterableMethods.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -import generic._ -import mutable.Buffer -import annotation.unchecked.uncheckedVariance - -/** - * @since 2.8 - */ -trait IterableMethods[+A, +This <: IterableLike[A, This] with Iterable[A]] extends TraversableMethods[A, This] { - self: Iterable[A] => - - // abstract - def iterator: Iterator[A] - - // concrete - def dropRight(n: Int): Iterable[A] - def grouped(size: Int): Iterator[Iterable[A]] - def sameElements[B >: A](that: GenIterable[B]): Boolean - def sliding(size: Int): Iterator[Iterable[A]] - def sliding(size: Int, step: Int): Iterator[Iterable[A]] - def takeRight(n: Int): Iterable[A] - def zipAll[B, A1 >: A, That](that: GenIterable[B], e1: A1, e2: B)(implicit bf: CanBuildFrom[This, (A1, B), That]): That - def zipWithIndex[A1 >: A, That](implicit bf: CanBuildFrom[This, (A1, Int), That]): That - def zip[A1 >: A, B, That](that: GenIterable[B])(implicit bf: CanBuildFrom[This, (A1, B), That]): That - - override def view: IterableView[A, This] - override def view(from: Int, until: Int): IterableView[A, This] -} diff --git a/src/library/scala/collection/interfaces/MapMethods.scala b/src/library/scala/collection/interfaces/MapMethods.scala deleted file mode 100644 index bc38ccdd2e..0000000000 --- a/src/library/scala/collection/interfaces/MapMethods.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -import generic._ - -/** - * @since 2.8 - */ -trait MapMethods[A, +B, +This <: MapLike[A, B, This] with Map[A, B]] - extends IterableMethods[(A, B), This] - with SubtractableMethods[A, This] { - self: Map[A, B] => - - // abstract - def empty: This - def get(key: A): Option[B] - def iterator: Iterator[(A, B)] - def + [B1 >: B] (kv: (A, B1)): Map[A, B1] - def - (key: A): This - - // concrete - def getOrElse[B1 >: B](key: A, default: => B1): B1 - def apply(key: A): B - def contains(key: A): Boolean - def isDefinedAt(key: A): Boolean - def keys: Iterable[A] - def keysIterator: Iterator[A] - def keySet: Set[A] - def values: Iterable[B] - def valuesIterator: Iterator[B] - def default(key: A): B - def filterKeys(p: A => Boolean): Map[A, B] - def mapValues[C](f: B => C): Map[A, C] - def updated [B1 >: B](key: A, value: B1): Map[A, B1] - def + [B1 >: B] (elem1: (A, B1), elem2: (A, B1), elems: (A, B1) *): Map[A, B1] - def ++[B1 >: B](xs: GenTraversableOnce[(A, B1)]): Map[A, B1] -} diff --git a/src/library/scala/collection/interfaces/SeqMethods.scala b/src/library/scala/collection/interfaces/SeqMethods.scala deleted file mode 100644 index 4327073d21..0000000000 --- a/src/library/scala/collection/interfaces/SeqMethods.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -import generic._ -import mutable.Buffer - -/** - * @since 2.8 - */ -trait SeqMethods[+A, +This <: SeqLike[A, This] with Seq[A]] extends IterableMethods[A, This] { - self: Seq[A] => - - // abstract - def apply(idx: Int): A - def length: Int - - // concrete - def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[This, B, That]): That - def :+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[This, B, That]): That - def combinations(n: Int): Iterator[This] - def contains(elem: Any): Boolean - def containsSlice[B](that: Seq[B]): Boolean - def corresponds[B](that: Seq[B])(p: (A,B) => Boolean): Boolean - def diff[B >: A, That](that: Seq[B]): This - def distinct: This - def endsWith[B](that: Seq[B]): Boolean - def indexOfSlice[B >: A](that: Seq[B]): Int - def indexOfSlice[B >: A](that: Seq[B], fromIndex: Int): Int - def indexOf[B >: A](elem: B): Int - def indexOf[B >: A](elem: B, from: Int): Int - def indexWhere(p: A => Boolean): Int - def indexWhere(p: A => Boolean, from: Int): Int - def indices: Range - def intersect[B >: A, That](that: Seq[B]): This - def isDefinedAt(x: Int): Boolean - def lastIndexOfSlice[B >: A](that: Seq[B]): Int - def lastIndexOfSlice[B >: A](that: Seq[B], fromIndex: Int): Int - def lastIndexOf[B >: A](elem: B): Int - def lastIndexOf[B >: A](elem: B, end: Int): Int - def lastIndexWhere(p: A => Boolean): Int - def lastIndexWhere(p: A => Boolean, end: Int): Int - def lengthCompare(len: Int): Int - def padTo[B >: A, That](len: Int, elem: B)(implicit bf: CanBuildFrom[This, B, That]): That - def patch[B >: A, That](from: Int, patch: GenSeq[B], replaced: Int)(implicit bf: CanBuildFrom[This, B, That]): That - def permutations: Iterator[This] - def prefixLength(p: A => Boolean): Int - def reverse: This - def reverseIterator: Iterator[A] - def reverseMap[B, That](f: A => B)(implicit bf: CanBuildFrom[This, B, That]): That - def segmentLength(p: A => Boolean, from: Int): Int - def sortBy[B](f: A => B)(implicit ord: Ordering[B]): This - def sortWith(lt: (A, A) => Boolean): This - def sorted[B >: A](implicit ord: Ordering[B]): This - def startsWith[B](that: Seq[B]): Boolean - def startsWith[B](that: Seq[B], offset: Int): Boolean - def union[B >: A, That](that: Seq[B])(implicit bf: CanBuildFrom[This, B, That]): That - def updated[B >: A, That](index: Int, elem: B)(implicit bf: CanBuildFrom[This, B, That]): That - - // refinements - def view: SeqView[A, This] - def view(from: Int, until: Int): SeqView[A, This] -} diff --git a/src/library/scala/collection/interfaces/SetMethods.scala b/src/library/scala/collection/interfaces/SetMethods.scala deleted file mode 100644 index 3b6214f45c..0000000000 --- a/src/library/scala/collection/interfaces/SetMethods.scala +++ /dev/null @@ -1,51 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -import generic._ -import mutable.Buffer -import annotation.unchecked.uncheckedVariance - -/** - * @since 2.8 - */ -trait SubtractableMethods[A, +This <: Subtractable[A, This]] { - def -(elem: A): This - def -(elem1: A, elem2: A, elems: A*): This - def --(xs: TraversableOnce[A]): This -} - -/** - * @since 2.8 - */ -trait SetMethods[A, +This <: SetLike[A, This] with Set[A]] - extends IterableMethods[A, This] - with SubtractableMethods[A, This] { - self: Set[A] => - - // abstract - def empty: This - def contains(elem: A): Boolean - def + (elem: A): This - def - (elem: A): This - - // concrete - def & (that: Set[A]): This - def &~ (that: Set[A]): This - def + (elem1: A, elem2: A, elems: A*): This - def apply(elem: A): Boolean - def diff(that: Set[A]): This - def intersect(that: Set[A]): This - def subsetOf(that: Set[A]): Boolean - def subsets(len: Int): Iterator[This] - def subsets: Iterator[This] - def union(that: Set[A]): This - def | (that: Set[A]): This -} diff --git a/src/library/scala/collection/interfaces/TraversableMethods.scala b/src/library/scala/collection/interfaces/TraversableMethods.scala deleted file mode 100644 index 8aba39093d..0000000000 --- a/src/library/scala/collection/interfaces/TraversableMethods.scala +++ /dev/null @@ -1,63 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -import generic._ - -/** - * @since 2.8 - */ -trait TraversableMethods[+A, +This <: TraversableLike[A, This]] extends TraversableOnceMethods[A] { - self: Traversable[A] => - - // maps/iteration - def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[This, B, That]): That - def map[B, That](f: A => B)(implicit bf: CanBuildFrom[This, B, That]): That - def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[This, B, That]): That - def scanLeft[B, That](z: B)(op: (B, A) => B)(implicit bf: CanBuildFrom[This, B, That]): That - def scanRight[B, That](z: B)(op: (A, B) => B)(implicit bf: CanBuildFrom[This, B, That]): That - - // new collections - def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[This, B, That]): That - def ++[B >: A, That](that: GenTraversableOnce[B])(implicit bf: CanBuildFrom[This, B, That]): That - - // element retrieval - def head: A - def headOption: Option[A] - def last: A - def lastOption: Option[A] - - // subcollections - def drop(n: Int): Traversable[A] - def dropWhile(p: A => Boolean): Traversable[A] - def filter(p: A => Boolean): Traversable[A] - def filterNot(p: A => Boolean): Traversable[A] - def init: Traversable[A] - def inits: Iterator[This] - def slice(from: Int, until: Int): Traversable[A] - def tail: Traversable[A] - def tails: Iterator[This] - def take(n: Int): Traversable[A] - def takeWhile(p: A => Boolean): Traversable[A] - def withFilter(p: A => Boolean): FilterMonadic[A, Traversable[A]] - - // subdivisions - def groupBy[K](f: A => K): Map[K, Traversable[A]] - def partition(p: A => Boolean): (Traversable[A], Traversable[A]) - def span(p: A => Boolean): (Traversable[A], Traversable[A]) - def splitAt(n: Int): (Traversable[A], Traversable[A]) - - // info - def stringPrefix: String - - // views - def view: TraversableView[A, This] - def view(from: Int, until: Int): TraversableView[A, This] -} diff --git a/src/library/scala/collection/interfaces/TraversableOnceMethods.scala b/src/library/scala/collection/interfaces/TraversableOnceMethods.scala deleted file mode 100644 index 543d59d118..0000000000 --- a/src/library/scala/collection/interfaces/TraversableOnceMethods.scala +++ /dev/null @@ -1,77 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.collection -package interfaces - -trait TraversableOnceMethods[+A] { - self: TraversableOnce[A] => - - def foreach[U](f: A => U): Unit - def size: Int - protected[this] def reversed: TraversableOnce[A] - - // tests - def hasDefiniteSize: Boolean - def isEmpty: Boolean - def isTraversableAgain: Boolean - def nonEmpty: Boolean - - // applying a predicate - def collectFirst[B](pf: PartialFunction[A, B]): Option[B] - def count(p: A => Boolean): Int - def exists(p: A => Boolean): Boolean - def find(p: A => Boolean): Option[A] - def forall(p: A => Boolean): Boolean - - // folds - def /:[B](z: B)(op: (B, A) => B): B - def :\[B](z: B)(op: (A, B) => B): B - def foldLeft[B](z: B)(op: (B, A) => B): B - def foldRight[B](z: B)(op: (A, B) => B): B - def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] - def reduceLeft[B >: A](op: (B, A) => B): B - def reduceRightOption[B >: A](op: (A, B) => B): Option[B] - def reduceRight[B >: A](op: (A, B) => B): B - - // copies - def copyToArray[B >: A](xs: Array[B]): Unit - def copyToArray[B >: A](xs: Array[B], start: Int): Unit - def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Unit - def copyToBuffer[B >: A](dest: mutable.Buffer[B]): Unit - - // conversions - def toArray[B >: A : ArrayTag]: Array[B] - def toBuffer[B >: A]: mutable.Buffer[B] - def toIndexedSeq: immutable.IndexedSeq[A] - def toIterable: Iterable[A] - def toIterator: Iterator[A] - def toList: List[A] - def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] - def toSeq: Seq[A] - def toSet[B >: A]: immutable.Set[B] - def toStream: Stream[A] - def toTraversable: Traversable[A] - - // type-constrained folds - def maxBy[B](f: A => B)(implicit cmp: Ordering[B]): A - def max[B >: A](implicit cmp: Ordering[B]): A - def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A - def min[B >: A](implicit cmp: Ordering[B]): A - def product[B >: A](implicit num: Numeric[B]): B - def sum[B >: A](implicit num: Numeric[B]): B - - // strings - def mkString(start: String, sep: String, end: String): String - def mkString(sep: String): String - def mkString: String - - def addString(buf: StringBuilder, start: String, sep: String, end: String): StringBuilder - def addString(buf: StringBuilder, sep: String): StringBuilder - def addString(buf: StringBuilder): StringBuilder -} diff --git a/src/library/scala/collection/mutable/ArrayOps.scala b/src/library/scala/collection/mutable/ArrayOps.scala index 57e81fdb9c..0807721f7d 100644 --- a/src/library/scala/collection/mutable/ArrayOps.scala +++ b/src/library/scala/collection/mutable/ArrayOps.scala @@ -61,7 +61,7 @@ abstract class ArrayOps[T] extends ArrayLike[T, Array[T]] with CustomParalleliza * into a single array. * * @tparam U Type of row elements. - * @param asArray A function that converts elements of this array to rows - arrays of type `U`. + * @param asTrav A function that converts elements of this array to rows - arrays of type `U`. * @return An array obtained by concatenating rows of this array. */ def flatten[U, To](implicit asTrav: T => collection.Traversable[U], m: ArrayTag[U]): Array[U] = { diff --git a/src/library/scala/collection/mutable/ArrayStack.scala b/src/library/scala/collection/mutable/ArrayStack.scala index 04a318d0c3..ed5f39f21b 100644 --- a/src/library/scala/collection/mutable/ArrayStack.scala +++ b/src/library/scala/collection/mutable/ArrayStack.scala @@ -152,7 +152,7 @@ extends AbstractSeq[T] /** Pushes all the provided elements in the traversable object onto the stack. * - * @param x The source of elements to push. + * @param xs The source of elements to push. * @return A reference to this stack. */ override def ++=(xs: TraversableOnce[T]): this.type = { xs.seq foreach += ; this } diff --git a/src/library/scala/collection/mutable/BufferProxy.scala b/src/library/scala/collection/mutable/BufferProxy.scala index aa1b20d240..db3b039461 100644 --- a/src/library/scala/collection/mutable/BufferProxy.scala +++ b/src/library/scala/collection/mutable/BufferProxy.scala @@ -77,7 +77,7 @@ trait BufferProxy[A] extends Buffer[A] with Proxy { /** Prepend an element to this list. * - * @param elem the element to prepend. + * @param elems the elements to prepend. */ override def prepend(elems: A*) { self.prependAll(elems) } diff --git a/src/library/scala/collection/mutable/IndexedSeqLike.scala b/src/library/scala/collection/mutable/IndexedSeqLike.scala index 4bd5ea1e89..2ff7ac8272 100644 --- a/src/library/scala/collection/mutable/IndexedSeqLike.scala +++ b/src/library/scala/collection/mutable/IndexedSeqLike.scala @@ -43,7 +43,7 @@ trait IndexedSeqLike[A, +Repr] extends scala.collection.IndexedSeqLike[A, Repr] /** Replaces element at given index with a new value. * - * @param n the index of the element to replace. + * @param idx the index of the element to replace. * @param elem the new value. * @throws IndexOutOfBoundsException if the index is not valid. */ diff --git a/src/library/scala/collection/mutable/ListBuffer.scala b/src/library/scala/collection/mutable/ListBuffer.scala index 5c580f9c09..cd743999bc 100644 --- a/src/library/scala/collection/mutable/ListBuffer.scala +++ b/src/library/scala/collection/mutable/ListBuffer.scala @@ -208,7 +208,7 @@ final class ListBuffer[A] * one. Instead, it will insert a new element at index `n`. * * @param n the index where a new element will be inserted. - * @param iter the iterable object providing all elements to insert. + * @param seq the iterable object providing all elements to insert. * @throws Predef.IndexOutOfBoundsException if `n` is out of bounds. */ def insertAll(n: Int, seq: Traversable[A]) { @@ -338,8 +338,8 @@ final class ListBuffer[A] /** Remove a single element from this buffer. May take time linear in the * buffer size. * - * @param x the element to remove. - * @return this $coll. + * @param elem the element to remove. + * @return this $coll. */ override def -= (elem: A): this.type = { if (exported) copy() diff --git a/src/library/scala/collection/mutable/MapLike.scala b/src/library/scala/collection/mutable/MapLike.scala index b8b1152099..3046207533 100644 --- a/src/library/scala/collection/mutable/MapLike.scala +++ b/src/library/scala/collection/mutable/MapLike.scala @@ -18,6 +18,28 @@ import parallel.mutable.ParMap * $mapNote * $mapTags * @since 2.8 + * + * @define mapNote + * '''Implementation note:''' + * This trait provides most of the operations of a mutable `Map` + * independently of its representation. It is typically inherited by + * concrete implementations of maps. + * + * To implement a concrete mutable map, you need to provide + * implementations of the following methods: + * {{{ + * def get(key: A): Option[B] + * def iterator: Iterator[(A, B)] + * def += (kv: (A, B)): This + * def -= (key: A): This + * }}} + * If you wish that methods like `take`, `drop`, `filter` also return the same kind of map + * you should also override: + * {{{ + * def empty: This + * }}} + * It is also good idea to override methods `foreach` and + * `size` for efficiency. */ trait MapLike[A, B, +This <: MapLike[A, B, This] with Map[A, B]] extends scala.collection.MapLike[A, B, This] diff --git a/src/library/scala/collection/mutable/ObservableBuffer.scala b/src/library/scala/collection/mutable/ObservableBuffer.scala index a619edf281..6b5079e402 100644 --- a/src/library/scala/collection/mutable/ObservableBuffer.scala +++ b/src/library/scala/collection/mutable/ObservableBuffer.scala @@ -70,4 +70,18 @@ trait ObservableBuffer[A] extends Buffer[A] with Publisher[Message[A] with Undoa def undo() { throw new UnsupportedOperationException("cannot undo") } }) } + + abstract override def insertAll(n: Int, elems: collection.Traversable[A]) { + super.insertAll(n, elems) + var curr = n - 1 + val msg = elems.foldLeft(new Script[A]() with Undoable { + def undo() { throw new UnsupportedOperationException("cannot undo") } + }) { + case (msg, elem) => + curr += 1 + msg += Include(Index(curr), elem) + } + publish(msg) + } + } diff --git a/src/library/scala/collection/mutable/QueueProxy.scala b/src/library/scala/collection/mutable/QueueProxy.scala index df86545a70..fb76fa609f 100644 --- a/src/library/scala/collection/mutable/QueueProxy.scala +++ b/src/library/scala/collection/mutable/QueueProxy.scala @@ -49,7 +49,7 @@ trait QueueProxy[A] extends Queue[A] with Proxy { /** Adds all elements provided by an iterator at the end of the queue. The * elements are prepended in the order they are given out by the iterator. * - * @param iter an iterator + * @param it an iterator */ override def ++=(it: TraversableOnce[A]): this.type = { self ++= it diff --git a/src/library/scala/collection/mutable/SeqLike.scala b/src/library/scala/collection/mutable/SeqLike.scala index 7194f15bc2..3a77558e94 100644 --- a/src/library/scala/collection/mutable/SeqLike.scala +++ b/src/library/scala/collection/mutable/SeqLike.scala @@ -28,8 +28,8 @@ trait SeqLike[A, +This <: SeqLike[A, This] with Seq[A]] /** Replaces element at given index with a new value. * - * @param n the index of the element to replace. - * @param lem the new value. + * @param idx the index of the element to replace. + * @param elem the new value. * @throws IndexOutOfBoundsException if the index is not valid. */ def update(idx: Int, elem: A) diff --git a/src/library/scala/collection/mutable/SynchronizedBuffer.scala b/src/library/scala/collection/mutable/SynchronizedBuffer.scala index a14605d60a..1c34046e88 100644 --- a/src/library/scala/collection/mutable/SynchronizedBuffer.scala +++ b/src/library/scala/collection/mutable/SynchronizedBuffer.scala @@ -61,7 +61,7 @@ trait SynchronizedBuffer[A] extends Buffer[A] { /** Appends a number of elements provided by a traversable object * via its `foreach` method. * - * @param iter the iterable object. + * @param xs the iterable object. */ override def ++=(xs: TraversableOnce[A]): this.type = synchronized[this.type] { super.++=(xs) @@ -102,7 +102,7 @@ trait SynchronizedBuffer[A] extends Buffer[A] { /** Prepend an element to this list. * - * @param elem the element to prepend. + * @param elems the elements to prepend. */ override def prepend(elems: A*): Unit = prependAll(elems) diff --git a/src/library/scala/collection/package.scala b/src/library/scala/collection/package.scala index 0dd4405cf7..237ca28018 100644 --- a/src/library/scala/collection/package.scala +++ b/src/library/scala/collection/package.scala @@ -14,7 +14,7 @@ package scala * == Guide == * * A detailed guide for the collections library is available - * at [[http://www.scala-lang.org/docu/files/collections-api]]. + * at [[http://docs.scala-lang.org/overviews/collections/introduction.html]]. * * == Using Collections == * @@ -67,7 +67,7 @@ package scala * Also note that the collections library was carefully designed to include several implementations of * each of the three basic collection types. These implementations have specific performance * characteristics which are described - * in [[http://www.scala-lang.org/docu/files/collections-api the guide]]. + * in [[http://docs.scala-lang.org/overviews/collections/performance-characteristics.html the guide]]. * * === Converting between Java Collections === * diff --git a/src/library/scala/collection/parallel/ParIterableLike.scala b/src/library/scala/collection/parallel/ParIterableLike.scala index 4cb229fc0f..321f259f5d 100644 --- a/src/library/scala/collection/parallel/ParIterableLike.scala +++ b/src/library/scala/collection/parallel/ParIterableLike.scala @@ -491,8 +491,8 @@ self: ParIterableLike[T, Repr, Sequential] => * * $abortsignalling * - * @param p a predicate used to test elements - * @return true if `p` holds for all elements, false otherwise + * @param pred a predicate used to test elements + * @return true if `p` holds for all elements, false otherwise */ def forall(pred: T => Boolean): Boolean = { tasksupport.executeAndWaitResult(new Forall(pred, splitter assign new DefaultSignalling with VolatileAbort)) @@ -502,8 +502,8 @@ self: ParIterableLike[T, Repr, Sequential] => * * $abortsignalling * - * @param p a predicate used to test elements - * @return true if `p` holds for some element, false otherwise + * @param pred a predicate used to test elements + * @return true if `p` holds for some element, false otherwise */ def exists(pred: T => Boolean): Boolean = { tasksupport.executeAndWaitResult(new Exists(pred, splitter assign new DefaultSignalling with VolatileAbort)) @@ -517,8 +517,8 @@ self: ParIterableLike[T, Repr, Sequential] => * * $abortsignalling * - * @param p predicate used to test the elements - * @return an option value with the element if such an element exists, or `None` otherwise + * @param pred predicate used to test the elements + * @return an option value with the element if such an element exists, or `None` otherwise */ def find(pred: T => Boolean): Option[T] = { tasksupport.executeAndWaitResult(new Find(pred, splitter assign new DefaultSignalling with VolatileAbort)) @@ -685,7 +685,7 @@ self: ParIterableLike[T, Repr, Sequential] => * @tparam That type of the resulting collection * @param z neutral element for the operator `op` * @param op the associative operator for the scan - * @param cbf combiner factory which provides a combiner + * @param bf $bfinfo * @return a collection containing the prefix scan of the elements in the original collection * * @usecase def scan(z: T)(op: (T, T) => T): $Coll[T] diff --git a/src/library/scala/collection/parallel/ParSeqLike.scala b/src/library/scala/collection/parallel/ParSeqLike.scala index b3c527da84..be5ab03ba7 100644 --- a/src/library/scala/collection/parallel/ParSeqLike.scala +++ b/src/library/scala/collection/parallel/ParSeqLike.scala @@ -162,7 +162,7 @@ self => * * $abortsignalling * - * @tparam U the element type of `that` parallel sequence + * @tparam S the element type of `that` parallel sequence * @param that the parallel sequence this sequence is being searched for * @param offset the starting offset for the search * @return `true` if there is a sequence `that` starting at `offset` in this sequence, `false` otherwise @@ -287,9 +287,7 @@ self => /** Computes the multiset intersection between this $coll and another sequence. * * @param that the sequence of elements to intersect with. - * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo + * @tparam U the element type of `that` parallel sequence * @return a new collection of type `That` which contains all elements of this $coll * which also appear in `that`. * If an element value `x` appears diff --git a/src/library/scala/collection/parallel/RemainsIterator.scala b/src/library/scala/collection/parallel/RemainsIterator.scala index c5910ff2c8..a67a4d8eb7 100644 --- a/src/library/scala/collection/parallel/RemainsIterator.scala +++ b/src/library/scala/collection/parallel/RemainsIterator.scala @@ -39,8 +39,7 @@ private[collection] trait RemainsIterator[+T] extends Iterator[T] { /** Augments iterators with additional methods, mostly transformers, * assuming they iterate an iterable collection. * - * @param T type of the elements iterated. - * @param IterRepr iterator type. + * @tparam T type of the elements iterated. */ private[collection] trait AugmentedIterableIterator[+T] extends RemainsIterator[T] { @@ -377,7 +376,7 @@ private[collection] trait AugmentedSeqIterator[+T] extends AugmentedIterableIter /** Parallel iterators allow splitting and provide a `remaining` method to * obtain the number of elements remaining in the iterator. * - * @param T type of the elements iterated. + * @tparam T type of the elements iterated. */ trait IterableSplitter[+T] extends AugmentedIterableIterator[T] @@ -537,7 +536,7 @@ self => /** Parallel sequence iterators allow splitting into arbitrary subsets. * - * @param T type of the elements iterated. + * @tparam T type of the elements iterated. */ trait SeqSplitter[+T] extends IterableSplitter[T] diff --git a/src/library/scala/collection/parallel/Tasks.scala b/src/library/scala/collection/parallel/Tasks.scala index 4a581f219e..7a0116b3b3 100644 --- a/src/library/scala/collection/parallel/Tasks.scala +++ b/src/library/scala/collection/parallel/Tasks.scala @@ -541,7 +541,7 @@ trait ExecutionContextTasks extends Tasks { // this part is a hack which allows switching val driver: Tasks = executionContext match { - case eci: scala.concurrent.impl.ExecutionContextImpl => eci.executorService match { + case eci: scala.concurrent.impl.ExecutionContextImpl => eci.executor match { case fjp: ForkJoinPool => new ForkJoinTaskSupport(fjp) case tpe: ThreadPoolExecutor => new ThreadPoolTaskSupport(tpe) case _ => ??? diff --git a/src/library/scala/collection/parallel/mutable/ParHashMap.scala b/src/library/scala/collection/parallel/mutable/ParHashMap.scala index 05b3f89fa1..8d39d6e0de 100644 --- a/src/library/scala/collection/parallel/mutable/ParHashMap.scala +++ b/src/library/scala/collection/parallel/mutable/ParHashMap.scala @@ -26,7 +26,8 @@ import collection.parallel.Task * `ParHashMap` is a parallel map which internally keeps elements within a hash table. * It uses chaining to resolve collisions. * - * @tparam T type of the elements in the parallel hash map + * @tparam K type of the keys in the parallel hash map + * @tparam V type of the values in the parallel hash map * * @define Coll `ParHashMap` * @define coll parallel hash map diff --git a/src/library/scala/concurrent/Channel.scala b/src/library/scala/concurrent/Channel.scala index f6d6341151..7aeccd8d05 100644 --- a/src/library/scala/concurrent/Channel.scala +++ b/src/library/scala/concurrent/Channel.scala @@ -25,7 +25,6 @@ class Channel[A] { private var nreaders = 0 /** - * @param x ... */ def write(x: A) = synchronized { lastWritten.elem = x diff --git a/src/library/scala/concurrent/ConcurrentPackageObject.scala b/src/library/scala/concurrent/ConcurrentPackageObject.scala index a3d985733e..330a2f0e25 100644 --- a/src/library/scala/concurrent/ConcurrentPackageObject.scala +++ b/src/library/scala/concurrent/ConcurrentPackageObject.scala @@ -8,10 +8,9 @@ package scala.concurrent -import java.util.concurrent.{ Executors, ExecutorService, ThreadFactory } +import java.util.concurrent.{ Executors, Executor, ThreadFactory } import scala.concurrent.forkjoin.{ ForkJoinPool, ForkJoinWorkerThread } import scala.concurrent.util.Duration -import ConcurrentPackageObject._ import language.implicitConversions @@ -20,7 +19,7 @@ import language.implicitConversions abstract class ConcurrentPackageObject { /** A global execution environment for executing lightweight tasks. */ - lazy val defaultExecutionContext = new impl.ExecutionContextImpl(null) + lazy val defaultExecutionContext: ExecutionContext with Executor = impl.ExecutionContextImpl.fromExecutor(null: Executor) val currentExecutionContext = new ThreadLocal[ExecutionContext] @@ -36,19 +35,6 @@ abstract class ConcurrentPackageObject { case _ => true } - private[concurrent] def resolveEither[T](source: Either[Throwable, T]): Either[Throwable, T] = source match { - case Left(t) => resolver(t) - case _ => source - } - - private[concurrent] def resolver[T](throwable: Throwable): Either[Throwable, T] = throwable match { - case t: scala.runtime.NonLocalReturnControl[_] => Right(t.value.asInstanceOf[T]) - case t: scala.util.control.ControlThrowable => Left(new ExecutionException("Boxed ControlThrowable", t)) - case t: InterruptedException => Left(new ExecutionException("Boxed InterruptedException", t)) - case e: Error => Left(new ExecutionException("Boxed Error", e)) - case t => Left(t) - } - /* concurrency constructs */ /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. @@ -102,18 +88,3 @@ abstract class ConcurrentPackageObject { @inline implicit final def int2durationops(x: Int): DurationOps = new DurationOps(x) } - -private[concurrent] object ConcurrentPackageObject { - // TODO, docs, return type - // Note that having this in the package object led to failures when - // compiling a subset of sources; it seems that the wildcard is not - // properly handled, and you get messages like "type _$1 defined twice". - // This is consistent with other package object breakdowns. - // private val resolverFunction: PartialFunction[Throwable, Either[Throwable, _]] = { - // case t: scala.runtime.NonLocalReturnControl[_] => Right(t.value) - // case t: scala.util.control.ControlThrowable => Left(new ExecutionException("Boxed ControlThrowable", t)) - // case t: InterruptedException => Left(new ExecutionException("Boxed InterruptedException", t)) - // case e: Error => Left(new ExecutionException("Boxed Error", e)) - // case t => Left(t) - // } -} diff --git a/src/library/scala/concurrent/ExecutionContext.scala b/src/library/scala/concurrent/ExecutionContext.scala index 4666674b5b..436a17a33b 100644 --- a/src/library/scala/concurrent/ExecutionContext.scala +++ b/src/library/scala/concurrent/ExecutionContext.scala @@ -47,15 +47,16 @@ object ExecutionContext { /** Creates an `ExecutionContext` from the given `ExecutorService`. */ - def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit = defaultReporter): ExecutionContext with Executor = new impl.ExecutionContextImpl(e, reporter) + def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit = defaultReporter): ExecutionContext with ExecutorService = + impl.ExecutionContextImpl.fromExecutorService(e, reporter) /** Creates an `ExecutionContext` from the given `Executor`. */ - def fromExecutor(e: Executor, reporter: Throwable => Unit = defaultReporter): ExecutionContext with Executor = new impl.ExecutionContextImpl(e, reporter) + def fromExecutor(e: Executor, reporter: Throwable => Unit = defaultReporter): ExecutionContext with Executor = + impl.ExecutionContextImpl.fromExecutor(e, reporter) def defaultReporter: Throwable => Unit = { - // `Error`s are currently wrapped by `resolver`. - // Also, re-throwing `Error`s here causes an exception handling test to fail. + // re-throwing `Error`s here causes an exception handling test to fail. //case e: Error => throw e case t => t.printStackTrace() } diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index 70b3c3dbbb..4df2bb63af 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -96,9 +96,9 @@ trait Future[+T] extends Awaitable[T] { * * $multipleCallbacks */ - def onSuccess[U](pf: PartialFunction[T, U]): this.type = onComplete { - case Left(t) => // do nothing - case Right(v) => if (pf isDefinedAt v) pf(v) else { /*do nothing*/ } + def onSuccess[U](pf: PartialFunction[T, U]): Unit = onComplete { + case Right(v) if pf isDefinedAt v => pf(v) + case _ => } /** When this future is completed with a failure (i.e. with a throwable), @@ -113,9 +113,9 @@ trait Future[+T] extends Awaitable[T] { * * $multipleCallbacks */ - def onFailure[U](callback: PartialFunction[Throwable, U]): this.type = onComplete { - case Left(t) => if (isFutureThrowable(t) && callback.isDefinedAt(t)) callback(t) else { /*do nothing*/ } - case Right(v) => // do nothing + def onFailure[U](callback: PartialFunction[Throwable, U]): Unit = onComplete { + case Left(t) if (isFutureThrowable(t) && callback.isDefinedAt(t)) => callback(t) + case _ => } /** When this future is completed, either through an exception, or a value, @@ -126,7 +126,7 @@ trait Future[+T] extends Awaitable[T] { * * $multipleCallbacks */ - def onComplete[U](func: Either[Throwable, T] => U): this.type + def onComplete[U](func: Either[Throwable, T] => U): Unit /* Miscellaneous */ @@ -169,7 +169,7 @@ trait Future[+T] extends Awaitable[T] { onComplete { case Left(t) => p success t - case Right(v) => p failure (new NoSuchElementException("Future.failed not completed with a throwable. Instead completed with: " + v)) + case Right(v) => p failure (new NoSuchElementException("Future.failed not completed with a throwable.")) } p.future @@ -184,24 +184,56 @@ trait Future[+T] extends Awaitable[T] { */ def foreach[U](f: T => U): Unit = onComplete { case Right(r) => f(r) - case Left(_) => // do nothing + case _ => // do nothing + } + + /** 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 + * exception thrown when 's' or 'f' is applied, that exception will be propagated + * to the resulting future. + * + * @param s function that transforms a successful result of the receiver into a + * successful result of the returned future + * @param f function that transforms a failure of the receiver into a failure of + * the returned future + * @return a future that will be completed with the transformed value + */ + def transform[S](s: T => S, f: Throwable => Throwable): Future[S] = { + val p = Promise[S]() + + onComplete { + case result => + try { + result match { + case Left(t) => p failure f(t) + case Right(r) => p success s(r) + } + } catch { + case NonFatal(t) => p failure t + } + } + + p.future } /** Creates a new future by applying a function to the successful result of * this future. If this future is completed with an exception then the new * future will also contain this exception. * - * $forComprehensionExample + * $forComprehensionExamples */ - def map[S](f: T => S): Future[S] = { + def map[S](f: T => S): Future[S] = { // transform(f, identity) val p = Promise[S]() onComplete { - case Left(t) => p failure t - case Right(v) => - try p success f(v) - catch { - case NonFatal(t) => p complete resolver(t) + case result => + try { + result match { + case Right(r) => p success f(r) + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, S]] + } + } catch { + case NonFatal(t) => p failure t } } @@ -213,21 +245,21 @@ trait Future[+T] extends Awaitable[T] { * If this future is completed with an exception then the new future will * also contain this exception. * - * $forComprehensionExample + * $forComprehensionExamples */ def flatMap[S](f: T => Future[S]): Future[S] = { val p = Promise[S]() onComplete { - case Left(t) => p failure t + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, S]] case Right(v) => try { f(v) onComplete { - case Left(t) => p failure t + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, S]] case Right(v) => p success v } } catch { - case NonFatal(t) => p complete resolver(t) + case NonFatal(t) => p failure t } } @@ -254,13 +286,13 @@ trait Future[+T] extends Awaitable[T] { val p = Promise[T]() onComplete { - case Left(t) => p failure t + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, T]] case Right(v) => try { if (pred(v)) p success v else p failure new NoSuchElementException("Future.filter predicate is not satisfied by: " + v) } catch { - case NonFatal(t) => p complete resolver(t) + case NonFatal(t) => p failure t } } @@ -303,13 +335,13 @@ trait Future[+T] extends Awaitable[T] { val p = Promise[S]() onComplete { - case Left(t) => p failure t + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, S]] case Right(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 complete resolver(t) + case NonFatal(t) => p failure t } } @@ -334,7 +366,9 @@ trait Future[+T] extends Awaitable[T] { onComplete { case Left(t) if pf isDefinedAt t => try { p success pf(t) } - catch { case NonFatal(t) => p complete resolver(t) } + catch { + case NonFatal(t) => p failure t + } case otherwise => p complete otherwise } @@ -362,7 +396,7 @@ trait Future[+T] extends Awaitable[T] { try { p completeWith pf(t) } catch { - case NonFatal(t) => p complete resolver(t) + case NonFatal(t) => p failure t } case otherwise => p complete otherwise } @@ -382,7 +416,7 @@ trait Future[+T] extends Awaitable[T] { val p = Promise[(T, U)]() this onComplete { - case Left(t) => p failure t + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, (T, U)]] case Right(r) => that onSuccess { case r2 => p success ((r, r2)) @@ -429,7 +463,7 @@ trait Future[+T] extends Awaitable[T] { val p = Promise[S]() onComplete { - case l: Left[Throwable, _] => p complete l.asInstanceOf[Either[Throwable, S]] + case l: Left[_, _] => p complete l.asInstanceOf[Left[Throwable, S]] case Right(t) => p complete (try { Right(boxedType(tag.erasure).cast(t).asInstanceOf[S]) @@ -468,9 +502,7 @@ trait Future[+T] extends Awaitable[T] { val p = Promise[T]() onComplete { - case r => - try if (pf isDefinedAt r) pf(r) - finally p complete r + case r => try if (pf isDefinedAt r) pf(r) finally p complete r } p.future @@ -491,11 +523,7 @@ trait Future[+T] extends Awaitable[T] { */ def either[U >: T](that: Future[U]): Future[U] = { val p = Promise[U]() - - val completePromise: PartialFunction[Either[Throwable, U], _] = { - case Left(t) => p tryFailure t - case Right(v) => p trySuccess v - } + val completePromise: PartialFunction[Either[Throwable, U], _] = { case result => p tryComplete result } this onComplete completePromise that onComplete completePromise diff --git a/src/library/scala/concurrent/Promise.scala b/src/library/scala/concurrent/Promise.scala index f7ec0714cf..2e9de4a0d0 100644 --- a/src/library/scala/concurrent/Promise.scala +++ b/src/library/scala/concurrent/Promise.scala @@ -66,7 +66,7 @@ trait Promise[T] { /** Completes the promise with a value. * - * @param value The value to complete the promise with. + * @param v The value to complete the promise with. * * $promiseCompletion */ @@ -106,7 +106,7 @@ object Promise { /** Creates a promise object which can be completed with a value. * * @tparam T the type of the value in the promise - * @param execctx the execution context on which the promise is created on + * @param executor the execution context on which the promise is created on * @return the newly created `Promise` object */ def apply[T]()(implicit executor: ExecutionContext): Promise[T] = new impl.Promise.DefaultPromise[T]() @@ -114,7 +114,7 @@ object Promise { /** Creates an already completed Promise with the specified exception. * * @tparam T the type of the value in the promise - * @param execctx the execution context on which the promise is created on + * @param executor the execution context on which the promise is created on * @return the newly created `Promise` object */ def failed[T](exception: Throwable)(implicit executor: ExecutionContext): Promise[T] = new impl.Promise.KeptPromise[T](Left(exception)) @@ -122,7 +122,7 @@ object Promise { /** Creates an already completed Promise with the specified result. * * @tparam T the type of the value in the promise - * @param execctx the execution context on which the promise is created on + * @param executor the execution context on which the promise is created on * @return the newly created `Promise` object */ def successful[T](result: T)(implicit executor: ExecutionContext): Promise[T] = new impl.Promise.KeptPromise[T](Right(result)) diff --git a/src/library/scala/concurrent/Scheduler.scala b/src/library/scala/concurrent/Scheduler.scala index e2eb4d69fe..6645abcc4e 100644 --- a/src/library/scala/concurrent/Scheduler.scala +++ b/src/library/scala/concurrent/Scheduler.scala @@ -35,7 +35,7 @@ trait Scheduler { /** Schedules a thunk for execution after a given delay. * * @param delay the duration after which the thunk should be executed - * @param thunk the thunk that is scheduled for execution + * @param task the thunk that is scheduled for execution * @return a `Cancellable` that may be used to cancel the execution * of the thunk */ diff --git a/src/library/scala/concurrent/SyncVar.scala b/src/library/scala/concurrent/SyncVar.scala index 43f2ec57c0..5a6d95c2ed 100644 --- a/src/library/scala/concurrent/SyncVar.scala +++ b/src/library/scala/concurrent/SyncVar.scala @@ -55,19 +55,32 @@ class SyncVar[A] { def take(): A = synchronized { try get - finally unset() + finally unsetVal() } - // TODO: this method should be private - def set(x: A): Unit = synchronized { - isDefined = true - value = Some(x) - notifyAll() + /** Waits for this SyncVar to become defined at least for + * `timeout` milliseconds (possibly more), and takes its + * value by first reading and then removing the value from + * the SyncVar. + * + * @param timeout the amount of milliseconds to wait, 0 means forever + * @return `None` if variable is undefined after `timeout`, `Some(value)` otherwise + */ + def take(timeout: Long): A = synchronized { + try get(timeout).get + finally unsetVal() } + // TODO: this method should be private + // [Heather] the reason why: it doesn't take into consideration + // whether or not the SyncVar is already defined. So, set has been + // deprecated in order to eventually be able to make "setting" private + @deprecated("Use `put` instead, as `set` is potentionally error-prone", "2.10.0") + def set(x: A): Unit = setVal(x) + def put(x: A): Unit = synchronized { while (isDefined) wait() - set(x) + setVal(x) } def isSet: Boolean = synchronized { @@ -75,10 +88,33 @@ class SyncVar[A] { } // TODO: this method should be private + // [Heather] the reason why: it doesn't take into consideration + // whether or not the SyncVar is already defined. So, unset has been + // deprecated in order to eventually be able to make "unsetting" private + @deprecated("Use `take` instead, as `unset` is potentionally error-prone", "2.10.0") def unset(): Unit = synchronized { isDefined = false value = None notifyAll() } + + // `setVal` exists so as to retroactively deprecate `set` without + // deprecation warnings where we use `set` internally. The + // implementation of `set` was moved to `setVal` to achieve this + private def setVal(x: A): Unit = synchronized { + isDefined = true + value = Some(x) + notifyAll() + } + + // `unsetVal` exists so as to retroactively deprecate `unset` without + // deprecation warnings where we use `unset` internally. The + // implementation of `unset` was moved to `unsetVal` to achieve this + private def unsetVal(): Unit = synchronized { + isDefined = false + value = None + notifyAll() + } + } diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 7dc3ed2988..1083a93439 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -10,18 +10,20 @@ package scala.concurrent.impl -import java.util.concurrent.{Callable, Executor, ExecutorService, Executors, ThreadFactory} +import java.util.concurrent.{ Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit } +import java.util.Collection import scala.concurrent.forkjoin._ -import scala.concurrent.{ExecutionContext, resolver, Awaitable} -import scala.concurrent.util.{ Duration } +import scala.concurrent.{ ExecutionContext, Awaitable } +import scala.concurrent.util.Duration -private[scala] class ExecutionContextImpl(es: AnyRef, reporter: Throwable => Unit = ExecutionContext.defaultReporter) -extends ExecutionContext with Executor { - import ExecutionContextImpl._ - - val executorService: AnyRef = if (es eq null) getExecutorService else es +private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: Throwable => Unit) extends ExecutionContext with Executor { + + val executor: Executor = es match { + case null => createExecutorService + case some => some + } // to ensure that the current execution context thread local is properly set def executorsThreadFactory = new ThreadFactory { @@ -42,51 +44,46 @@ extends ExecutionContext with Executor { } } - def getExecutorService: AnyRef = - if (scala.util.Properties.isJavaAtLeast("1.6")) { - val vendor = scala.util.Properties.javaVmVendor - if ((vendor contains "Oracle") || (vendor contains "Sun") || (vendor contains "Apple")) - new ForkJoinPool( - Runtime.getRuntime.availableProcessors(), + def createExecutorService: ExecutorService = try { new ForkJoinPool( + Runtime.getRuntime.availableProcessors(), //FIXME from config forkJoinPoolThreadFactory, - null, - false) - else - Executors.newCachedThreadPool(executorsThreadFactory) - } else Executors.newCachedThreadPool(executorsThreadFactory) + null, //FIXME we should have an UncaughtExceptionHandler, see what Akka does + true) //FIXME I really think this should be async... + } catch { + case NonFatal(t) => + System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to Executors.newCachedThreadPool") + t.printStackTrace(System.err) + Executors.newCachedThreadPool(executorsThreadFactory) + } - def execute(runnable: Runnable): Unit = executorService match { + def execute(runnable: Runnable): Unit = executor match { case fj: ForkJoinPool => Thread.currentThread match { case fjw: ForkJoinWorkerThread if fjw.getPool eq fj => - val fjtask = runnable match { + (runnable match { case fjt: ForkJoinTask[_] => fjt case _ => ForkJoinTask.adapt(runnable) - } - fjtask.fork - case _ => - fj.execute(runnable) + }).fork + case _ => fj.execute(runnable) } - case executor: Executor => - executor execute runnable + case generic => generic execute runnable } def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { Future.releaseStack(this) - executorService match { + executor match { case fj: ForkJoinPool => var result: T = null.asInstanceOf[T] - val managedBlocker = new ForkJoinPool.ManagedBlocker { + ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { @volatile var isdone = false - def block() = { - result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) + def block(): Boolean = { + result = awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) // FIXME what happens if there's an exception thrown here? isdone = true true } def isReleasable = isdone - } - ForkJoinPool.managedBlock(managedBlocker) + }) result case _ => awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) @@ -94,12 +91,29 @@ extends ExecutionContext with Executor { } def reportFailure(t: Throwable) = reporter(t) - } private[concurrent] object ExecutionContextImpl { - + + def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextImpl = new ExecutionContextImpl(e, reporter) + def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextImpl with ExecutorService = + new ExecutionContextImpl(es, reporter) with ExecutorService { + final def asExecutorService: ExecutorService = executor.asInstanceOf[ExecutorService] + override def execute(command: Runnable) = executor.execute(command) + override def shutdown() { asExecutorService.shutdown() } + override def shutdownNow() = asExecutorService.shutdownNow() + override def isShutdown = asExecutorService.isShutdown + override def isTerminated = asExecutorService.isTerminated + override def awaitTermination(l: Long, timeUnit: TimeUnit) = asExecutorService.awaitTermination(l, timeUnit) + override def submit[T](callable: Callable[T]) = asExecutorService.submit(callable) + override def submit[T](runnable: Runnable, t: T) = asExecutorService.submit(runnable, t) + override def submit(runnable: Runnable) = asExecutorService.submit(runnable) + override def invokeAll[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAll(callables) + override def invokeAll[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAll(callables, l, timeUnit) + override def invokeAny[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAny(callables) + override def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAny(callables, l, timeUnit) + } } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 20d4122e8f..a54e81bd05 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -57,7 +57,7 @@ private[concurrent] object Future { case NonFatal(e) => // Commenting out reporting for now, since it produces too much output in the tests //executor.reportFailure(e) - scala.concurrent.resolver(e) + Left(e) } } }) @@ -65,6 +65,8 @@ private[concurrent] object Future { promise.future } + private[impl] val throwableId: Throwable => Throwable = identity _ + // an optimization for batching futures // TODO we should replace this with a public queue, // so that it can be stolen from diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index da70b3dea5..78053f5117 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -11,7 +11,7 @@ package scala.concurrent.impl import java.util.concurrent.TimeUnit.{ NANOSECONDS, MILLISECONDS } -import scala.concurrent.{Awaitable, ExecutionContext, resolveEither, resolver, blocking, CanAwait, TimeoutException} +import scala.concurrent.{ Awaitable, ExecutionContext, blocking, CanAwait, TimeoutException, ExecutionException } //import scala.util.continuations._ import scala.concurrent.util.Duration import scala.util @@ -26,6 +26,20 @@ private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with Fu object Promise { + + private def resolveEither[T](source: Either[Throwable, T]): Either[Throwable, T] = source match { + case Left(t) => resolver(t) + case _ => source + } + + private def resolver[T](throwable: Throwable): Either[Throwable, T] = throwable match { + case t: scala.runtime.NonLocalReturnControl[_] => Right(t.value.asInstanceOf[T]) + case t: scala.util.control.ControlThrowable => Left(new ExecutionException("Boxed ControlThrowable", t)) + case t: InterruptedException => Left(new ExecutionException("Boxed InterruptedException", t)) + case e: Error => Left(new ExecutionException("Boxed Error", e)) + case t => Left(t) + } + /** Default promise implementation. */ class DefaultPromise[T](implicit val executor: ExecutionContext) extends AbstractPromise with Promise[T] { self => @@ -98,7 +112,7 @@ object Promise { } } - def onComplete[U](func: Either[Throwable, T] => U): this.type = { + def onComplete[U](func: Either[Throwable, T] => U): Unit = { @tailrec //Tries to add the callback, if already completed, it dispatches the callback to be executed def dispatchOrAddCallback(): Unit = getState match { @@ -106,7 +120,6 @@ object Promise { case listeners: List[_] => if (updateState(listeners, func :: listeners)) () else dispatchOrAddCallback() } dispatchOrAddCallback() - this } private final def notifyCompleted(func: Either[Throwable, T] => Any, result: Either[Throwable, T]) { @@ -130,10 +143,9 @@ object Promise { def tryComplete(value: Either[Throwable, T]): Boolean = false - def onComplete[U](func: Either[Throwable, T] => U): this.type = { - val completedAs = value.get + def onComplete[U](func: Either[Throwable, T] => U): Unit = { + val completedAs = value.get // Avoid closing over "this" Future.dispatchFuture(executor, () => func(completedAs)) - this } def ready(atMost: Duration)(implicit permit: CanAwait): this.type = this diff --git a/src/library/scala/io/Source.scala b/src/library/scala/io/Source.scala index 5e09e13680..319e021f04 100644 --- a/src/library/scala/io/Source.scala +++ b/src/library/scala/io/Source.scala @@ -100,8 +100,6 @@ object Source { /** Create a `Source` from array of bytes, decoding * the bytes according to codec. * - * @param bytes ... - * @param enc ... * @return the created `Source` instance. */ def fromBytes(bytes: Array[Byte])(implicit codec: Codec): Source = diff --git a/src/library/scala/math/BigInt.scala b/src/library/scala/math/BigInt.scala index ff52ca9bec..af2ab04576 100644 --- a/src/library/scala/math/BigInt.scala +++ b/src/library/scala/math/BigInt.scala @@ -71,11 +71,7 @@ object BigInt { new BigInt(new BigInteger(bitlength, certainty, rnd.self)) /** Constructs a randomly generated BigInt, uniformly distributed over the - * range 0 to (2 ^ numBits - 1), inclusive. - * - * @param numbits ... - * @param rnd ... - * @return ... + * range `0` to `(2 ^ numBits - 1)`, inclusive. */ def apply(numbits: Int, rnd: scala.util.Random): BigInt = new BigInt(new BigInteger(numbits, rnd.self)) @@ -87,10 +83,6 @@ object BigInt { /** Translates the string representation of a `BigInt` in the * specified `radix` into a BigInt. - * - * @param x ... - * @param radix ... - * @return ... */ def apply(x: String, radix: Int): BigInt = new BigInt(new BigInteger(x, radix)) diff --git a/src/library/scala/math/package.scala b/src/library/scala/math/package.scala index 0417461f85..71a7f8e5ed 100644 --- a/src/library/scala/math/package.scala +++ b/src/library/scala/math/package.scala @@ -115,21 +115,10 @@ package object math { def min(x: Float, y: Float): Float = java.lang.Math.min(x, y) def min(x: Double, y: Double): Double = java.lang.Math.min(x, y) - def signum(x: Double): Double = - if (x == 0d) 0d - else if (x < 0) -1.0 - else if (x > 0) 1.0 - else x // NaN - - def signum(x: Float): Float = - if (x == 0f) 0f - else if (x < 0) -1.0f - else if (x > 0) 1.0f - else x // NaN - - def signum(x: Long): Long = java.lang.Long.signum(x) - def signum(x: Int): Int = java.lang.Integer.signum(x) + def signum(x: Long): Long = java.lang.Long.signum(x) + def signum(x: Float): Float = java.lang.Math.signum(x) + def signum(x: Double): Double = java.lang.Math.signum(x) // ----------------------------------------------------------------------- // root functions diff --git a/src/library/scala/parallel/Future.scala b/src/library/scala/parallel/Future.scala index f61d376093..8b71794756 100644 --- a/src/library/scala/parallel/Future.scala +++ b/src/library/scala/parallel/Future.scala @@ -12,6 +12,8 @@ package scala.parallel /** A future is a function without parameters that will block the caller if * the parallel computation associated with the function is not completed. + * + * @tparam R the type of the result * * @since 2.9 */ @@ -23,7 +25,6 @@ trait Future[@specialized +R] extends (() => R) { * '''Note:''' creating a circular dependency between futures by calling * this method will result in a deadlock. * - * @tparam R the type of the result * @return the result * @throws the exception that was thrown during a parallel computation */ diff --git a/src/library/scala/reflect/DummyMirror.scala b/src/library/scala/reflect/DummyMirror.scala index 422598caef..aa731f62db 100644 --- a/src/library/scala/reflect/DummyMirror.scala +++ b/src/library/scala/reflect/DummyMirror.scala @@ -306,7 +306,6 @@ class DummyMirror(cl: ClassLoader) extends api.Mirror { val THIS: TermName = DummyName val BITMAP_NORMAL: TermName = DummyName val BITMAP_TRANSIENT: TermName = DummyName - val BITMAP_PRIVATE: TermName = DummyName val BITMAP_CHECKINIT: TermName = DummyName val BITMAP_CHECKINIT_TRANSIENT: TermName = DummyName val INTERPRETER_IMPORT_WRAPPER: String = "" diff --git a/src/library/scala/reflect/api/StandardNames.scala b/src/library/scala/reflect/api/StandardNames.scala index 60c64f947a..a17ea216f7 100644 --- a/src/library/scala/reflect/api/StandardNames.scala +++ b/src/library/scala/reflect/api/StandardNames.scala @@ -73,7 +73,6 @@ trait StandardNames { val BITMAP_NORMAL: TermName val BITMAP_TRANSIENT: TermName - val BITMAP_PRIVATE: TermName val BITMAP_CHECKINIT: TermName val BITMAP_CHECKINIT_TRANSIENT: TermName diff --git a/src/library/scala/reflect/api/ToolBoxes.scala b/src/library/scala/reflect/api/ToolBoxes.scala index aefd6b511c..15c9fcc403 100644 --- a/src/library/scala/reflect/api/ToolBoxes.scala +++ b/src/library/scala/reflect/api/ToolBoxes.scala @@ -59,14 +59,14 @@ trait ToolBoxes { self: Universe => * Note that this does not revert the tree to its pre-typer shape. * For more info, read up https://issues.scala-lang.org/browse/SI-5464. */ - def resetAllAttrs[T <: Tree](tree: T): T + def resetAllAttrs(tree: Tree): Tree /** Recursively resets locally defined symbols and types in a given tree. * * Note that this does not revert the tree to its pre-typer shape. * For more info, read up https://issues.scala-lang.org/browse/SI-5464. */ - def resetLocalAttrs[T <: Tree](tree: T): T + def resetLocalAttrs(tree: Tree): Tree /** Compiles and runs a tree using this ToolBox. * diff --git a/src/library/scala/reflect/api/Trees.scala b/src/library/scala/reflect/api/Trees.scala index ca4e60f3f6..3427136fde 100644 --- a/src/library/scala/reflect/api/Trees.scala +++ b/src/library/scala/reflect/api/Trees.scala @@ -727,7 +727,7 @@ trait Trees { self: Universe => def Ident(sym: Symbol): Ident /** Marks underlying reference to id as boxed. - * @pre: id must refer to a captured variable + * @pre id must refer to a captured variable * A reference such marked will refer to the boxed entity, no dereferencing * with `.elem` is done on it. * This tree node can be emitted by macros such as reify that call referenceCapturedVariable. diff --git a/src/library/scala/reflect/api/Types.scala b/src/library/scala/reflect/api/Types.scala index 5c7563c2c5..3d42242641 100755 --- a/src/library/scala/reflect/api/Types.scala +++ b/src/library/scala/reflect/api/Types.scala @@ -111,10 +111,12 @@ trait Types { self: Universe => * Proceed analogously for thistypes referring to outer classes. * * Example: + * {{{ * class D[T] { def m: T } * class C extends p.D[Int] * T.asSeenFrom(ThisType(C), D) (where D is owner of m) * = Int + * }}} */ def asSeenFrom(pre: Type, clazz: Symbol): Type @@ -481,10 +483,6 @@ trait Types { self: Universe => def refinedType(parents: List[Type], owner: Symbol, decls: Scope, pos: Position): Type /** The canonical creator for a refined type with an initially empty scope. - * - * @param parents ... - * @param owner ... - * @return ... */ def refinedType(parents: List[Type], owner: Symbol): Type diff --git a/src/library/scala/reflect/api/Universe.scala b/src/library/scala/reflect/api/Universe.scala index 9670780522..05b5963c73 100755 --- a/src/library/scala/reflect/api/Universe.scala +++ b/src/library/scala/reflect/api/Universe.scala @@ -28,18 +28,23 @@ abstract class Universe extends Symbols * * For instance, given the abstract syntax tree representation of the <[ x + 1 ]> expression: * + * {{{ * Apply(Select(Ident("x"), "+"), List(Literal(Constant(1)))) + * }}} * * The reifier transforms it to the following expression: - * + * + * {{{ * <[ * val $mr: scala.reflect.api.Universe = <reference to the Universe that calls the reify> * $mr.Expr[Int]($mr.Apply($mr.Select($mr.Ident($mr.newFreeVar("x", <Int>, x), "+"), List($mr.Literal($mr.Constant(1)))))) * ]> + * }}} * * Reification performs expression splicing (when processing Expr.eval and Expr.value) * and type splicing (for every type T that has a TypeTag[T] implicit in scope): * + * {{{ * val two = mirror.reify(2) // Literal(Constant(2)) * val four = mirror.reify(two.eval + two.eval) // Apply(Select(two.tree, newTermName("$plus")), List(two.tree)) * @@ -51,6 +56,7 @@ abstract class Universe extends Symbols * val factory = c.reify{ new Queryable[T] } * ... * } + * }}} * * The transformation looks mostly straightforward, but it has its tricky parts: * * Reifier retains symbols and types defined outside the reified tree, however diff --git a/src/library/scala/reflect/makro/Typers.scala b/src/library/scala/reflect/makro/Typers.scala index c62c5f254c..90024a4f7a 100644 --- a/src/library/scala/reflect/makro/Typers.scala +++ b/src/library/scala/reflect/makro/Typers.scala @@ -66,14 +66,14 @@ trait Typers { * Note that this does not revert the tree to its pre-typer shape. * For more info, read up https://issues.scala-lang.org/browse/SI-5464. */ - def resetAllAttrs[T <: Tree](tree: T): T + def resetAllAttrs(tree: Tree): Tree /** Recursively resets locally defined symbols and types in a given tree. * * Note that this does not revert the tree to its pre-typer shape. * For more info, read up https://issues.scala-lang.org/browse/SI-5464. */ - def resetLocalAttrs[T <: Tree](tree: T): T + def resetLocalAttrs(tree: Tree): Tree /** Represents an error during typechecking */ diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java index 258a176671..8fe9a017d0 100644 --- a/src/library/scala/runtime/BoxesRunTime.java +++ b/src/library/scala/runtime/BoxesRunTime.java @@ -228,7 +228,7 @@ public final class BoxesRunTime * as yet have not. * * Note: Among primitives, Float.NaN != Float.NaN, but the boxed - * verisons are equal. This still needs reconciliation. + * versions are equal. This still needs reconciliation. */ public static int hashFromLong(java.lang.Long n) { int iv = n.intValue(); @@ -242,6 +242,9 @@ public final class BoxesRunTime long lv = n.longValue(); if (lv == dv) return java.lang.Long.valueOf(lv).hashCode(); + + float fv = n.floatValue(); + if (fv == dv) return java.lang.Float.valueOf(fv).hashCode(); else return n.hashCode(); } public static int hashFromFloat(java.lang.Float n) { diff --git a/src/library/scala/runtime/RichDouble.scala b/src/library/scala/runtime/RichDouble.scala index f702b9e3da..396323d1e8 100644 --- a/src/library/scala/runtime/RichDouble.scala +++ b/src/library/scala/runtime/RichDouble.scala @@ -18,14 +18,12 @@ final class RichDouble(val self: Double) extends FractionalProxy[Double] { /** Converts an angle measured in degrees to an approximately equivalent * angle measured in radians. * - * @param x an angle, in degrees * @return the measurement of the angle x in radians. */ def toRadians: Double = math.toRadians(self) /** Converts an angle measured in radians to an approximately equivalent * angle measured in degrees. - * @param x angle, in radians * @return the measurement of the angle x in degrees. */ def toDegrees: Double = math.toDegrees(self) diff --git a/src/library/scala/runtime/RichFloat.scala b/src/library/scala/runtime/RichFloat.scala index 6b72a9dd55..4fc9e8864a 100644 --- a/src/library/scala/runtime/RichFloat.scala +++ b/src/library/scala/runtime/RichFloat.scala @@ -18,7 +18,6 @@ final class RichFloat(val self: Float) extends FractionalProxy[Float] { /** Converts an angle measured in degrees to an approximately equivalent * angle measured in radians. * - * @param x an angle, in degrees * @return the measurement of the angle `x` in radians. */ def toRadians: Float = math.toRadians(self).toFloat @@ -26,7 +25,6 @@ final class RichFloat(val self: Float) extends FractionalProxy[Float] { /** Converts an angle measured in radians to an approximately equivalent * angle measured in degrees. * - * @param x angle, in radians * @return the measurement of the angle `x` in degrees. */ def toDegrees: Float = math.toDegrees(self).toFloat diff --git a/src/library/scala/runtime/Statics.java b/src/library/scala/runtime/Statics.java new file mode 100644 index 0000000000..485511ecbb --- /dev/null +++ b/src/library/scala/runtime/Statics.java @@ -0,0 +1,89 @@ +package scala.runtime; + +/** Not for public consumption. Usage by the runtime only. + */ + +public final class Statics { + public static int mix(int hash, int data) { + int h = mixLast(hash, data); + h = Integer.rotateLeft(h, 13); + return h * 5 + 0xe6546b64; + } + + public static int mixLast(int hash, int data) { + int k = data; + + k *= 0xcc9e2d51; + k = Integer.rotateLeft(k, 15); + k *= 0x1b873593; + + return hash ^ k; + } + + public static int finalizeHash(int hash, int length) { + return avalanche(hash ^ length); + } + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + public static int avalanche(int h) { + h ^= h >>> 16; + h *= 0x85ebca6b; + h ^= h >>> 13; + h *= 0xc2b2ae35; + h ^= h >>> 16; + + return h; + } + + public static int longHash(long lv) { + if ((int)lv == lv) + return (int)lv; + else + return (int)(lv ^ (lv >>> 32)); + } + + public static int doubleHash(double dv) { + int iv = (int)dv; + if (iv == dv) + return iv; + + float fv = (float)dv; + if (fv == dv) + return java.lang.Float.floatToIntBits(fv); + + long lv = (long)dv; + if (lv == dv) + return (int)lv; + + lv = Double.doubleToLongBits(dv); + return (int)(lv ^ (lv >>> 32)); + } + + public static int floatHash(float fv) { + int iv = (int)fv; + if (iv == fv) + return iv; + + long lv = (long)fv; + if (lv == fv) + return (int)(lv^(lv>>>32)); + + return java.lang.Float.floatToIntBits(fv); + } + + public static int anyHash(Object x) { + if (x == null) + return 0; + + if (x instanceof java.lang.Long) + return longHash(((java.lang.Long)x).longValue()); + + if (x instanceof java.lang.Double) + return doubleHash(((java.lang.Double)x).doubleValue()); + + if (x instanceof java.lang.Float) + return floatHash(((java.lang.Float)x).floatValue()); + + return x.hashCode(); + } +} diff --git a/src/library/scala/sys/Prop.scala b/src/library/scala/sys/Prop.scala index 33b88f119f..687a32cf7d 100644 --- a/src/library/scala/sys/Prop.scala +++ b/src/library/scala/sys/Prop.scala @@ -38,7 +38,7 @@ trait Prop[+T] { /** Sets the property. * - * @param the new string value + * @param newValue the new string value * @return the old value, or null if it was unset. */ def set(newValue: String): String diff --git a/src/library/scala/sys/SystemProperties.scala b/src/library/scala/sys/SystemProperties.scala index 4853da3495..d5777922b4 100644 --- a/src/library/scala/sys/SystemProperties.scala +++ b/src/library/scala/sys/SystemProperties.scala @@ -78,6 +78,5 @@ object SystemProperties { lazy val preferIPv4Stack = bool("java.net.preferIPv4Stack", "system should prefer IPv4 sockets") lazy val preferIPv6Addresses = bool("java.net.preferIPv6Addresses", "system should prefer IPv6 addresses") lazy val noTraceSupression = bool("scala.control.noTraceSuppression", "scala should not suppress any stack trace creation") - lazy val traceSourcePath = str("scala.control.sourcepath", "sourcepath for looking up stack trace elements") } diff --git a/src/library/scala/sys/package.scala b/src/library/scala/sys/package.scala index 16faded419..119ab59c22 100644 --- a/src/library/scala/sys/package.scala +++ b/src/library/scala/sys/package.scala @@ -50,7 +50,7 @@ package object sys { /** A bidirectional, mutable Map representing the current system Properties. * * @return a SystemProperties. - * @see `scala.sys.SystemProperties` + * @see [[scala.sys.SystemProperties]] */ def props: SystemProperties = new SystemProperties @@ -69,8 +69,8 @@ package object sys { * * Note that shutdown hooks are NOT guaranteed to be run. * - * @param the body of code to run at shutdown - * @return the Thread which will run the shutdown hook. + * @param body the body of code to run at shutdown + * @return the Thread which will run the shutdown hook. */ def addShutdownHook(body: => Unit): ShutdownHookThread = ShutdownHookThread(body) diff --git a/src/library/scala/testing/Benchmark.scala b/src/library/scala/testing/Benchmark.scala index c8e31766ea..9acae34d4e 100644 --- a/src/library/scala/testing/Benchmark.scala +++ b/src/library/scala/testing/Benchmark.scala @@ -49,9 +49,6 @@ trait Benchmark { /** Run the benchmark the specified number of times and return a list with * the execution times in milliseconds in reverse order of the execution. - * - * @param noTimes ... - * @return ... */ def runBenchmark(noTimes: Int): List[Long] = for (i <- List.range(1, noTimes + 1)) yield { diff --git a/src/library/scala/text/Document.scala b/src/library/scala/text/Document.scala index c0a54799a3..6a0fca40ca 100644 --- a/src/library/scala/text/Document.scala +++ b/src/library/scala/text/Document.scala @@ -33,9 +33,6 @@ abstract class Document { /** * Format this document on `writer` and try to set line * breaks so that the result fits in `width` columns. - * - * @param width ... - * @param writer ... */ def format(width: Int, writer: Writer) { type FmtState = (Int, Boolean, Document) diff --git a/src/library/scala/util/Marshal.scala b/src/library/scala/util/Marshal.scala index e2ebe7851c..209cd6c062 100644 --- a/src/library/scala/util/Marshal.scala +++ b/src/library/scala/util/Marshal.scala @@ -16,6 +16,7 @@ package scala.util * @author Stephane Micheloud * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") object Marshal { import java.io._ import scala.reflect.ClassTag diff --git a/src/library/scala/util/MurmurHash3.scala b/src/library/scala/util/MurmurHash3.scala index 33d9d2f0e5..fbb67116dd 100644 --- a/src/library/scala/util/MurmurHash3.scala +++ b/src/library/scala/util/MurmurHash3.scala @@ -19,7 +19,7 @@ import java.lang.Integer.{ rotateLeft => rotl } * to remedy some weaknesses and improve performance. This represents the * latest and supposedly final version of the algortihm (revision 136). * - * @see http://code.google.com/p/smhasher + * @see [[http://code.google.com/p/smhasher]] */ class MurmurHash3 { /** Mix in a block of data into an intermediate hash value. */ diff --git a/src/library/scala/util/Random.scala b/src/library/scala/util/Random.scala index 17c356801b..65a1b8c685 100644 --- a/src/library/scala/util/Random.scala +++ b/src/library/scala/util/Random.scala @@ -101,8 +101,7 @@ class Random(val self: java.util.Random) { /** Returns a new collection of the same type in a randomly chosen order. * - * @param coll the [[scala.collection.TraversableOnce]] to shuffle - * @return the shuffled [[scala.collection.TraversableOnce]] + * @return the shuffled collection */ def shuffle[T, CC[X] <: TraversableOnce[X]](xs: CC[T])(implicit bf: CanBuildFrom[CC[T], T, CC[T]]): CC[T] = { val buf = new ArrayBuffer[T] ++= xs diff --git a/src/library/scala/util/Try.scala b/src/library/scala/util/Try.scala index efa2fcabb8..8faba236f0 100644 --- a/src/library/scala/util/Try.scala +++ b/src/library/scala/util/Try.scala @@ -92,8 +92,8 @@ sealed abstract class Try[+T] { def andThen[U](f: T => Try[U]): Try[U] = flatMap(f) /** - * Transforms a nested `Try`, i.e., a `Try` of type `Try[Try[T]]`, - * into an un-nested `Try`, i.e., a `Try` of type `Try[T]`. + * Transforms a nested `Try`, ie, a `Try` of type `Try[Try[T]]`, + * into an un-nested `Try`, ie, a `Try` of type `Try[T]`. */ def flatten[U](implicit ev: T <:< Try[U]): Try[U] diff --git a/src/library/scala/util/automata/BaseBerrySethi.scala b/src/library/scala/util/automata/BaseBerrySethi.scala index c78b2d0790..d8d260c478 100644 --- a/src/library/scala/util/automata/BaseBerrySethi.scala +++ b/src/library/scala/util/automata/BaseBerrySethi.scala @@ -17,6 +17,7 @@ import scala.collection.{ mutable, immutable } * [[scala.util.automata.NondetWordAutom]] over `A` using the celebrated * position automata construction (also called ''Berry-Sethi'' or ''Glushkov''). */ +@deprecated("This class will be removed", "2.10.0") abstract class BaseBerrySethi { val lang: Base import lang.{ Alt, Eps, Meta, RegExp, Sequ, Star } @@ -54,9 +55,6 @@ abstract class BaseBerrySethi { /** Starts from the right-to-left * precondition: pos is final * pats are successor patterns of a Sequence node - * - * @param r ... - * @return ... */ protected def compFollow(rs: Seq[RegExp]): Set[Int] = { follow(0) = @@ -72,10 +70,6 @@ abstract class BaseBerrySethi { } /** Returns the first set of an expression, setting the follow set along the way. - * - * @param fol1 ... - * @param r ... - * @return ... */ protected def compFollow1(fol1: Set[Int], r: RegExp): Set[Int] = r match { case x: Alt => Set((x.rs reverseMap (compFollow1(fol1, _))).flatten: _*) @@ -92,8 +86,6 @@ abstract class BaseBerrySethi { } /** Returns the "Sethi-length" of a pattern, creating the set of position along the way. - * - * @param r ... */ protected def traverse(r: RegExp): Unit = r match { // (is tree automaton stuff, more than Berry-Sethi) diff --git a/src/library/scala/util/automata/DetWordAutom.scala b/src/library/scala/util/automata/DetWordAutom.scala index 16c5d2944b..c6d72f1a06 100644 --- a/src/library/scala/util/automata/DetWordAutom.scala +++ b/src/library/scala/util/automata/DetWordAutom.scala @@ -20,6 +20,7 @@ import scala.collection.{ mutable, immutable } * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") abstract class DetWordAutom[T <: AnyRef] { val nstates: Int val finals: Array[Int] diff --git a/src/library/scala/util/automata/Inclusion.scala b/src/library/scala/util/automata/Inclusion.scala index 63133998b4..4eaf1dfc02 100644 --- a/src/library/scala/util/automata/Inclusion.scala +++ b/src/library/scala/util/automata/Inclusion.scala @@ -17,14 +17,12 @@ package scala.util.automata * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") trait Inclusion[A <: AnyRef] { val labels: Seq[A] /** Returns true if `dfa1` is included in `dfa2`. - * - * @param dfa1 ... - * @param dfa2 ... */ def inclusion(dfa1: DetWordAutom[A], dfa2: DetWordAutom[A]) = { diff --git a/src/library/scala/util/automata/NondetWordAutom.scala b/src/library/scala/util/automata/NondetWordAutom.scala index b09e82ca11..3b6f0b251a 100644 --- a/src/library/scala/util/automata/NondetWordAutom.scala +++ b/src/library/scala/util/automata/NondetWordAutom.scala @@ -17,6 +17,7 @@ import scala.collection.{ immutable, mutable } * All states are reachable. Accepting states are those for which * the partial function `finals` is defined. */ +@deprecated("This class will be removed", "2.10.0") abstract class NondetWordAutom[T <: AnyRef] { val nstates: Int val labels: Seq[T] diff --git a/src/library/scala/util/automata/SubsetConstruction.scala b/src/library/scala/util/automata/SubsetConstruction.scala index 81805fce2f..1cdcd734cd 100644 --- a/src/library/scala/util/automata/SubsetConstruction.scala +++ b/src/library/scala/util/automata/SubsetConstruction.scala @@ -10,6 +10,7 @@ package scala.util.automata import scala.collection.{ mutable, immutable } +@deprecated("This class will be removed", "2.10.0") class SubsetConstruction[T <: AnyRef](val nfa: NondetWordAutom[T]) { import nfa.labels diff --git a/src/library/scala/util/automata/WordBerrySethi.scala b/src/library/scala/util/automata/WordBerrySethi.scala index a7ad92e648..1d4d1f971f 100644 --- a/src/library/scala/util/automata/WordBerrySethi.scala +++ b/src/library/scala/util/automata/WordBerrySethi.scala @@ -17,6 +17,7 @@ import scala.util.regexp.WordExp * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") abstract class WordBerrySethi extends BaseBerrySethi { override val lang: WordExp @@ -52,7 +53,6 @@ abstract class WordBerrySethi extends BaseBerrySethi { /** Returns the first set of an expression, setting the follow set along * the way. * - * @param fol1 ... * @param r the regular expression * @return the computed set */ diff --git a/src/library/scala/util/grammar/HedgeRHS.scala b/src/library/scala/util/grammar/HedgeRHS.scala index 8fb3d4c5dc..da109f41c5 100644 --- a/src/library/scala/util/grammar/HedgeRHS.scala +++ b/src/library/scala/util/grammar/HedgeRHS.scala @@ -10,13 +10,17 @@ package scala.util.grammar +@deprecated("This class will be removed", "2.10.0") abstract class HedgeRHS /** Right hand side of a hedge production, deriving a single tree. */ +@deprecated("This class will be removed", "2.10.0") case class ConsRHS(tnt: Int, hnt: Int) extends HedgeRHS /** Right hand side of a hedge production, deriving any hedge. */ +@deprecated("This class will be removed", "2.10.0") case object AnyHedgeRHS extends HedgeRHS /** Right hand side of a hedge production, deriving the empty hedge. */ +@deprecated("This class will be removed", "2.10.0") case object EmptyHedgeRHS extends HedgeRHS diff --git a/src/library/scala/util/grammar/TreeRHS.scala b/src/library/scala/util/grammar/TreeRHS.scala index ebe16b25bd..d6e7c01588 100644 --- a/src/library/scala/util/grammar/TreeRHS.scala +++ b/src/library/scala/util/grammar/TreeRHS.scala @@ -11,9 +11,12 @@ package scala.util.grammar /** Right hand side of a tree production. */ +@deprecated("This class will be removed", "2.10.0") abstract class TreeRHS /** Right hand side of a tree production, labelled with a letter from an alphabet. */ +@deprecated("This class will be removed", "2.10.0") case class LabelledRHS[A](label: A, hnt: Int) extends TreeRHS +@deprecated("This class will be removed", "2.10.0") case object AnyTreeRHS extends TreeRHS diff --git a/src/library/scala/util/parsing/ast/AbstractSyntax.scala b/src/library/scala/util/parsing/ast/AbstractSyntax.scala index 220643a0d7..67e8a87221 100644 --- a/src/library/scala/util/parsing/ast/AbstractSyntax.scala +++ b/src/library/scala/util/parsing/ast/AbstractSyntax.scala @@ -14,6 +14,7 @@ import scala.util.parsing.input.Positional * * @author Adriaan Moors */ +@deprecated("This class will be removed", "2.10.0") trait AbstractSyntax { /** The base class for elements of the abstract syntax tree. */ diff --git a/src/library/scala/util/parsing/ast/Binders.scala b/src/library/scala/util/parsing/ast/Binders.scala index 09ad5ce2ab..b93c24fde4 100644 --- a/src/library/scala/util/parsing/ast/Binders.scala +++ b/src/library/scala/util/parsing/ast/Binders.scala @@ -24,6 +24,7 @@ import language.implicitConversions * * @author Adriaan Moors */ +@deprecated("This class will be removed", "2.10.0") trait Mappable { trait Mapper { def apply[T <% Mappable[T]](x: T): T } /* TODO: having type `Forall T. T => T` is too strict: sometimes we want to allow `Forall T >: precision. T => T` for some type `precision`, so that, @@ -325,11 +326,11 @@ trait Binders extends AbstractSyntax with Mappable { // TODO: move this to some utility object higher in the scala hierarchy? /** Returns a given result, but executes the supplied closure before returning. * (The effect of this closure does not influence the returned value.) - * - * @param result the result to be returned - * @param block code to be executed, purely for its side-effects */ trait ReturnAndDo[T]{ + /** + * @param block code to be executed, purely for its side-effects + */ def andDo(block: => Unit): T } diff --git a/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala b/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala index bc71391bdb..06567ea348 100644 --- a/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala +++ b/src/library/scala/util/parsing/combinator/JavaTokenParsers.scala @@ -9,6 +9,8 @@ package scala.util.parsing.combinator +import annotation.migration + /** `JavaTokenParsers` differs from [[scala.util.parsing.combinator.RegexParsers]] * by adding the following definitions: * @@ -39,12 +41,13 @@ trait JavaTokenParsers extends RegexParsers { /** Double quotes (`"`) enclosing a sequence of: * * - Any character except double quotes, control characters or backslash (`\`) - * - A backslash followed by a slash, another backslash, or one of the letters - * `b`, `f`, `n`, `r` or `t`. + * - A backslash followed by another backslash, a single or double quote, or one + * of the letters `b`, `f`, `n`, `r` or `t` * - `\` followed by `u` followed by four hexadecimal digits */ + @migration("`stringLiteral` allows escaping single and double quotes, but not forward slashes any longer.", "2.10.0") def stringLiteral: Parser[String] = - ("\""+"""([^"\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*"""+"\"").r + ("\""+"""([^"\p{Cntrl}\\]|\\[\\'"bfnrt]|\\u[a-fA-F0-9]{4})*"""+"\"").r /** A number following the rules of `decimalNumber`, with the following * optional additions: * diff --git a/src/library/scala/util/parsing/combinator/lexical/StdLexical.scala b/src/library/scala/util/parsing/combinator/lexical/StdLexical.scala index 3ac5c07dc4..5d7386b5c1 100644 --- a/src/library/scala/util/parsing/combinator/lexical/StdLexical.scala +++ b/src/library/scala/util/parsing/combinator/lexical/StdLexical.scala @@ -25,8 +25,8 @@ import scala.collection.mutable * `delimiters` set. * * Usually this component is used to break character-based input into - * bigger tokens, which are then passed to a token-parser {@see - * [[scala.util.parsing.combinator.syntactical.TokenParsers]]}. + * bigger tokens, which are then passed to a token-parser (see + * [[scala.util.parsing.combinator.syntactical.TokenParsers]].) * * @author Martin Odersky * @author Iulian Dragos diff --git a/src/library/scala/util/parsing/combinator/testing/RegexTest.scala b/src/library/scala/util/parsing/combinator/testing/RegexTest.scala index 255730e5db..ff3554a6af 100644 --- a/src/library/scala/util/parsing/combinator/testing/RegexTest.scala +++ b/src/library/scala/util/parsing/combinator/testing/RegexTest.scala @@ -5,10 +5,14 @@ import scala.util.parsing.combinator._ import scala.util.parsing.input._ import language.postfixOps +@deprecated("This class will be removed", "2.10.0") case class Ident(s: String) +@deprecated("This class will be removed", "2.10.0") case class Number(n: Int) +@deprecated("This class will be removed", "2.10.0") case class Str(s: String) +@deprecated("This class will be removed", "2.10.0") object RegexTest extends RegexParsers { val ident: Parser[Any] = """[a-zA-Z_]\w*""".r ^^ (s => Ident(s)) val number: Parser[Any] = """\d\d*""".r ^^ (s => Number(s.toInt)) diff --git a/src/library/scala/util/parsing/combinator/testing/Tester.scala b/src/library/scala/util/parsing/combinator/testing/Tester.scala index 4607dc8843..1b98d63289 100644 --- a/src/library/scala/util/parsing/combinator/testing/Tester.scala +++ b/src/library/scala/util/parsing/combinator/testing/Tester.scala @@ -28,6 +28,7 @@ import scala.util.parsing.combinator.syntactical.TokenParsers * @author Martin Odersky * @author Adriaan Moors */ +@deprecated("This class will be removed", "2.10.0") abstract class Tester { val syntactic: TokenParsers { val lexical: Lexical } diff --git a/src/library/scala/util/parsing/input/CharArrayReader.scala b/src/library/scala/util/parsing/input/CharArrayReader.scala index e798c9883a..63d76c9382 100644 --- a/src/library/scala/util/parsing/input/CharArrayReader.scala +++ b/src/library/scala/util/parsing/input/CharArrayReader.scala @@ -21,10 +21,8 @@ object CharArrayReader { /** A character array reader reads a stream of characters (keeping track of their positions) * from an array. * - * @param source an array of characters + * @param chars an array of characters * @param index starting offset into the array; the first element returned will be `source(index)` - * @param line the line number of the first element (counting from index `0` of `source`) - * @param column the column number of the first element (counting from index `0` of `source`) * * @author Martin Odersky * @author Adriaan Moors diff --git a/src/library/scala/util/parsing/input/OffsetPosition.scala b/src/library/scala/util/parsing/input/OffsetPosition.scala index c2483c44e3..57a2c9c4c2 100644 --- a/src/library/scala/util/parsing/input/OffsetPosition.scala +++ b/src/library/scala/util/parsing/input/OffsetPosition.scala @@ -45,10 +45,9 @@ case class OffsetPosition(source: java.lang.CharSequence, offset: Int) extends P /** The column number referred to by the position; column numbers start at 1. */ def column: Int = offset - index(line - 1) + 1 - /** The contents of the line numbered `lnum` (must not contain a new-line character). + /** The contents of the line numbered at the current offset. * - * @param lnum a 1-based integer index into the `document` - * @return the line at `lnum` (not including a newline) + * @return the line at `offset` (not including a newline) */ def lineContents: String = source.subSequence(index(line - 1), index(line)).toString @@ -59,7 +58,7 @@ case class OffsetPosition(source: java.lang.CharSequence, offset: Int) extends P /** Compare this position to another, by first comparing their line numbers, * and then -- if necessary -- using the columns to break a tie. * - * @param `that` a `Position` to compare to this `Position` + * @param that a `Position` to compare to this `Position` * @return true if this position's line number or (in case of equal line numbers) * column is smaller than the corresponding components of `that` */ diff --git a/src/library/scala/util/parsing/input/PagedSeqReader.scala b/src/library/scala/util/parsing/input/PagedSeqReader.scala index 134cf0a79a..284afef57b 100644 --- a/src/library/scala/util/parsing/input/PagedSeqReader.scala +++ b/src/library/scala/util/parsing/input/PagedSeqReader.scala @@ -23,7 +23,7 @@ object PagedSeqReader { /** A character array reader reads a stream of characters (keeping track of their positions) * from an array. * - * @param source the source sequence + * @param seq the source sequence * @param offset starting offset. * * @author Martin Odersky diff --git a/src/library/scala/util/parsing/input/Position.scala b/src/library/scala/util/parsing/input/Position.scala index be817013a0..9cb0031746 100644 --- a/src/library/scala/util/parsing/input/Position.scala +++ b/src/library/scala/util/parsing/input/Position.scala @@ -27,10 +27,7 @@ trait Position { /** The column number referred to by the position; column numbers start at 1. */ def column: Int - /** The contents of the line numbered `lnum` (must not contain a new-line character). - * - * @param lnum a 1-based integer index into the `document` - * @return the line at `lnum` (not including a newline) + /** The contents of the line at this position. (must not contain a new-line character). */ protected def lineContents: String diff --git a/src/library/scala/util/parsing/input/StreamReader.scala b/src/library/scala/util/parsing/input/StreamReader.scala index 8244177359..3858dc3210 100644 --- a/src/library/scala/util/parsing/input/StreamReader.scala +++ b/src/library/scala/util/parsing/input/StreamReader.scala @@ -13,14 +13,16 @@ import scala.collection.immutable.PagedSeq /** An object to create a `StreamReader` from a `java.io.Reader`. * - * @param in the `java.io.Reader` that provides the underlying - * stream of characters for this Reader. - * * @author Miles Sabin */ object StreamReader { final val EofCh = '\032' + /** Create a `StreamReader` from a `java.io.Reader`. + * + * @param in the `java.io.Reader` that provides the underlying + * stream of characters for this Reader. + */ def apply(in: java.io.Reader): StreamReader = { new StreamReader(PagedSeq.fromReader(in), 0, 1) } diff --git a/src/library/scala/util/regexp/Base.scala b/src/library/scala/util/regexp/Base.scala index 8e23d46eb9..81962ea8bd 100644 --- a/src/library/scala/util/regexp/Base.scala +++ b/src/library/scala/util/regexp/Base.scala @@ -15,8 +15,9 @@ package scala.util.regexp * @author Burak Emir * @version 1.0 */ -abstract class Base -{ + +@deprecated("This class will be removed", "2.10.0") +abstract class Base { type _regexpT <: RegExp abstract class RegExp { diff --git a/src/library/scala/util/regexp/PointedHedgeExp.scala b/src/library/scala/util/regexp/PointedHedgeExp.scala index 23aa46448c..056031a339 100644 --- a/src/library/scala/util/regexp/PointedHedgeExp.scala +++ b/src/library/scala/util/regexp/PointedHedgeExp.scala @@ -15,6 +15,7 @@ package scala.util.regexp * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") abstract class PointedHedgeExp extends Base { type _regexpT <: RegExp diff --git a/src/library/scala/util/regexp/SyntaxError.scala b/src/library/scala/util/regexp/SyntaxError.scala index 0f5c2af187..c19dfe126e 100644 --- a/src/library/scala/util/regexp/SyntaxError.scala +++ b/src/library/scala/util/regexp/SyntaxError.scala @@ -16,4 +16,5 @@ package scala.util.regexp * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") class SyntaxError(e: String) extends RuntimeException(e) diff --git a/src/library/scala/util/regexp/WordExp.scala b/src/library/scala/util/regexp/WordExp.scala index cf146934d1..05674f118c 100644 --- a/src/library/scala/util/regexp/WordExp.scala +++ b/src/library/scala/util/regexp/WordExp.scala @@ -38,6 +38,7 @@ package scala.util.regexp * @author Burak Emir * @version 1.0 */ +@deprecated("This class will be removed", "2.10.0") abstract class WordExp extends Base { abstract class Label diff --git a/src/library/scala/xml/Atom.scala b/src/library/scala/xml/Atom.scala index 72572fb5e4..7bed714f68 100644 --- a/src/library/scala/xml/Atom.scala +++ b/src/library/scala/xml/Atom.scala @@ -37,9 +37,6 @@ class Atom[+A](val data: A) extends SpecialNode with Serializable { /** Returns text, with some characters escaped according to the XML * specification. - * - * @param sb ... - * @return ... */ def buildString(sb: StringBuilder): StringBuilder = Utility.escape(data.toString, sb) diff --git a/src/library/scala/xml/Comment.scala b/src/library/scala/xml/Comment.scala index 014cead47c..9ce053190a 100644 --- a/src/library/scala/xml/Comment.scala +++ b/src/library/scala/xml/Comment.scala @@ -11,7 +11,7 @@ package scala.xml /** The class `Comment` implements an XML node for comments. * * @author Burak Emir - * @param text the text contained in this node, may not contain "--" + * @param commentText the text contained in this node, may not contain "--" */ case class Comment(commentText: String) extends SpecialNode { diff --git a/src/library/scala/xml/EntityRef.scala b/src/library/scala/xml/EntityRef.scala index 60feeb845d..66438135c8 100644 --- a/src/library/scala/xml/EntityRef.scala +++ b/src/library/scala/xml/EntityRef.scala @@ -12,7 +12,7 @@ package scala.xml * * @author Burak Emir * @version 1.0 - * @param text the text contained in this node. + * @param entityName the name of the entity reference, for example `amp`. */ case class EntityRef(entityName: String) extends SpecialNode { final override def doCollectNamespaces = false diff --git a/src/library/scala/xml/MetaData.scala b/src/library/scala/xml/MetaData.scala index b44a817499..e98ec90aca 100644 --- a/src/library/scala/xml/MetaData.scala +++ b/src/library/scala/xml/MetaData.scala @@ -102,7 +102,6 @@ extends AbstractIterable[MetaData] * @param namespace_uri namespace uri of key * @param owner the element owning this attribute list * @param key the attribute key - * @return ... */ final def apply(namespace_uri: String, owner: Node, key: String): Seq[Node] = apply(namespace_uri, owner.scope, key) @@ -112,15 +111,12 @@ extends AbstractIterable[MetaData] * * @param namespace_uri namespace uri of key * @param scp a namespace scp (usually of the element owning this attribute list) - * @param key to be looked fore + * @param k to be looked for * @return value as Seq[Node] if key is found, null otherwise */ - def apply(namespace_uri:String, scp:NamespaceBinding, k:String): Seq[Node] + def apply(namespace_uri: String, scp: NamespaceBinding, k: String): Seq[Node] /** returns a copy of this MetaData item with next field set to argument. - * - * @param next ... - * @return ... */ def copy(next: MetaData): MetaData @@ -208,31 +204,13 @@ extends AbstractIterable[MetaData] } /** - * @param scope ... - * @return `'''true'''` iff ... */ def wellformed(scope: NamespaceBinding): Boolean - /** - * @param key ... - * @return ... - */ def remove(key: String): MetaData - /** - * @param namespace ... - * @param scope ... - * @param key ... - * @return ... - */ def remove(namespace: String, scope: NamespaceBinding, key: String): MetaData - /** - * @param namespace ... - * @param owner ... - * @param key ... - * @return ... - */ final def remove(namespace: String, owner: Node, key: String): MetaData = remove(namespace, owner.scope, key) } diff --git a/src/library/scala/xml/Node.scala b/src/library/scala/xml/Node.scala index 02e34e1bdc..9cf1869efc 100755 --- a/src/library/scala/xml/Node.scala +++ b/src/library/scala/xml/Node.scala @@ -155,24 +155,18 @@ abstract class Node extends NodeSeq { /** * String representation of this node * - * @param stripComment if true, strips comment nodes from result - * @return ... + * @param stripComments if true, strips comment nodes from result */ def buildString(stripComments: Boolean): String = Utility.serialize(this, stripComments = stripComments).toString /** * Same as `toString('''false''')`. - * - * @see <code><a href="#toString">toString(Boolean)</a></code> */ override def toString(): String = buildString(false) /** * Appends qualified name of this node to `StringBuilder`. - * - * @param sb ... - * @return ... */ def nameToString(sb: StringBuilder): StringBuilder = { if (null != prefix) { diff --git a/src/library/scala/xml/NodeSeq.scala b/src/library/scala/xml/NodeSeq.scala index f0be338fcf..40ddc7d85c 100644 --- a/src/library/scala/xml/NodeSeq.scala +++ b/src/library/scala/xml/NodeSeq.scala @@ -88,9 +88,6 @@ abstract class NodeSeq extends AbstractSeq[Node] with immutable.Seq[Node] with S * There is no support for searching a prefixed attribute by its literal prefix. * * The document order is preserved. - * - * @param that ... - * @return ... */ def \(that: String): NodeSeq = { def fail = throw new IllegalArgumentException(that) @@ -138,9 +135,6 @@ abstract class NodeSeq extends AbstractSeq[Node] with immutable.Seq[Node] with S * There is no support for searching a prefixed attribute by its literal prefix. * * The document order is preserved. - * - * @param that ... - * @return ... */ def \\ (that: String): NodeSeq = { def filt(cond: (Node) => Boolean) = this flatMap (_.descendant_or_self) filter cond diff --git a/src/library/scala/xml/PrefixedAttribute.scala b/src/library/scala/xml/PrefixedAttribute.scala index b80d6a1c73..5cab113d85 100644 --- a/src/library/scala/xml/PrefixedAttribute.scala +++ b/src/library/scala/xml/PrefixedAttribute.scala @@ -11,10 +11,10 @@ package scala.xml /** prefixed attributes always have a non-null namespace. * - * @param pre ... - * @param key ... + * @param pre + * @param key * @param value the attribute value - * @param next ... + * @param next1 */ class PrefixedAttribute( val pre: String, diff --git a/src/library/scala/xml/PrettyPrinter.scala b/src/library/scala/xml/PrettyPrinter.scala index 64dbd00f2f..da82aca33a 100755 --- a/src/library/scala/xml/PrettyPrinter.scala +++ b/src/library/scala/xml/PrettyPrinter.scala @@ -42,10 +42,6 @@ class PrettyPrinter(width: Int, step: Int) { } /** Try to cut at whitespace. - * - * @param s ... - * @param ind ... - * @return ... */ protected def cut(s: String, ind: Int): List[Item] = { val tmp = width - cur @@ -74,10 +70,6 @@ class PrettyPrinter(width: Int, step: Int) { } /** Try to make indented box, if possible, else para. - * - * @param ind ... - * @param s ... - * @return ... */ protected def makeBox(ind: Int, s: String) = if (cur + s.length > width) { // fits in this line @@ -99,10 +91,6 @@ class PrettyPrinter(width: Int, step: Int) { cur = 0 } - /** - * @param n ... - * @return ... - */ protected def leafTag(n: Node) = { def mkLeaf(sb: StringBuilder) { sb append '<' @@ -149,7 +137,6 @@ class PrettyPrinter(width: Int, step: Int) { private def doPreserve(node: Node) = node.attribute(XML.namespace, XML.space).map(_.toString == XML.preserve) getOrElse false - /** @param tail: what we'd like to sqeeze in */ protected def traverse(node: Node, pscope: NamespaceBinding, ind: Int): Unit = node match { case Text(s) if s.trim() == "" => @@ -210,7 +197,6 @@ class PrettyPrinter(width: Int, step: Int) { * given namespace to prefix mapping to the given string buffer. * * @param n the node to be serialized - * @param pmap the namespace to prefix mapping * @param sb the stringbuffer to append to */ def format(n: Node, sb: StringBuilder) { // entry point @@ -250,9 +236,9 @@ class PrettyPrinter(width: Int, step: Int) { /** Returns a formatted string containing well-formed XML with * given namespace to prefix mapping. * - * @param n the node to be serialized - * @param pmap the namespace to prefix mapping - * @return ... + * @param n the node to be serialized + * @param pscope the namespace to prefix mapping + * @return the formatted string */ def format(n: Node, pscope: NamespaceBinding = null): String = sbToString(format(n, pscope, _)) diff --git a/src/library/scala/xml/ProcInstr.scala b/src/library/scala/xml/ProcInstr.scala index b3f4ba9186..152bcf989f 100644 --- a/src/library/scala/xml/ProcInstr.scala +++ b/src/library/scala/xml/ProcInstr.scala @@ -12,8 +12,8 @@ package scala.xml /** an XML node for processing instructions (PI) * * @author Burak Emir - * @param target target name of this PI - * @param text text contained in this node, may not contain "?>" + * @param target target name of this PI + * @param proctext text contained in this node, may not contain "?>" */ case class ProcInstr(target: String, proctext: String) extends SpecialNode { diff --git a/src/library/scala/xml/TextBuffer.scala b/src/library/scala/xml/TextBuffer.scala index bcd5fc731f..3d62afb2ef 100644 --- a/src/library/scala/xml/TextBuffer.scala +++ b/src/library/scala/xml/TextBuffer.scala @@ -25,9 +25,6 @@ class TextBuffer val sb = new StringBuilder() /** Appends this string to the text buffer, trimming whitespaces as needed. - * - * @param cs ... - * @return ... */ def append(cs: Seq[Char]): this.type = { cs foreach { c => diff --git a/src/library/scala/xml/Utility.scala b/src/library/scala/xml/Utility.scala index 062a62e240..17c91fa52c 100755 --- a/src/library/scala/xml/Utility.scala +++ b/src/library/scala/xml/Utility.scala @@ -77,9 +77,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Escapes the characters < > & and " from string. - * - * @param text ... - * @return ... */ final def escape(text: String): String = sbToString(escape(text, _)) @@ -102,10 +99,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Appends escaped string to `s`. - * - * @param text ... - * @param s ... - * @return ... */ final def escape(text: String, s: StringBuilder): StringBuilder = { // Implemented per XML spec: @@ -135,8 +128,6 @@ object Utility extends AnyRef with parsing.TokenTests { * Appends unescaped string to `s`, `amp` becomes `&`, * `lt` becomes `<` etc.. * - * @param ref ... - * @param s ... * @return `'''null'''` if `ref` was not a predefined entity. */ final def unescape(ref: String, s: StringBuilder): StringBuilder = @@ -145,18 +136,12 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Returns a set of all namespaces used in a sequence of nodes * and all their descendants, including the empty namespaces. - * - * @param nodes ... - * @return ... */ def collectNamespaces(nodes: Seq[Node]): mutable.Set[String] = nodes.foldLeft(new mutable.HashSet[String]) { (set, x) => collectNamespaces(x, set) ; set } /** * Adds all namespaces in node to set. - * - * @param n ... - * @param set ... */ def collectNamespaces(n: Node, set: mutable.Set[String]) { if (n.doCollectNamespaces) { @@ -273,9 +258,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Returns prefix of qualified name if any. - * - * @param name ... - * @return ... */ final def prefix(name: String): Option[String] = (name indexOf ':') match { case -1 => None @@ -284,11 +266,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Returns a hashcode for the given constituents of a node - * - * @param uri - * @param label - * @param attribHashCode - * @param children */ def hashCode(pre: String, label: String, attribHashCode: Int, scpeHash: Int, children: Seq[Node]) = scala.util.MurmurHash3.orderedHash(label +: attribHashCode +: scpeHash +: children, pre.##) @@ -298,10 +275,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Appends "s" if string `s` does not contain ", * 's' otherwise. - * - * @param s ... - * @param sb ... - * @return ... */ def appendQuoted(s: String, sb: StringBuilder) = { val ch = if (s contains '"') '\'' else '"' @@ -310,10 +283,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Appends "s" and escapes and " i s with \" - * - * @param s ... - * @param sb ... - * @return ... */ def appendEscapedQuoted(s: String, sb: StringBuilder): StringBuilder = { sb.append('"') @@ -324,11 +293,6 @@ object Utility extends AnyRef with parsing.TokenTests { sb.append('"') } - /** - * @param s ... - * @param index ... - * @return ... - */ def getName(s: String, index: Int): String = { if (index >= s.length) null else { @@ -341,9 +305,6 @@ object Utility extends AnyRef with parsing.TokenTests { /** * Returns `'''null'''` if the value is a correct attribute value, * error message if it isn't. - * - * @param value ... - * @return ... */ def checkAttributeValue(value: String): String = { var i = 0 @@ -365,12 +326,6 @@ object Utility extends AnyRef with parsing.TokenTests { null } - /** - * new - * - * @param value ... - * @return ... - */ def parseAttributeValue(value: String): Seq[Node] = { val sb = new StringBuilder var rfb: StringBuilder = null @@ -426,11 +381,6 @@ object Utility extends AnyRef with parsing.TokenTests { * | "&#x" '0'..'9'|'A'..'F'|'a'..'f' { hexdigit } ";" * }}} * See [66] - * - * @param ch ... - * @param nextch ... - * @param reportSyntaxError ... - * @return ... */ def parseCharRef(ch: () => Char, nextch: () => Unit, reportSyntaxError: String => Unit, reportTruncatedError: String => Unit): String = { val hex = (ch() == 'x') && { nextch(); true } diff --git a/src/library/scala/xml/dtd/DocType.scala b/src/library/scala/xml/dtd/DocType.scala index f0ef09b901..64aa7e2f74 100644 --- a/src/library/scala/xml/dtd/DocType.scala +++ b/src/library/scala/xml/dtd/DocType.scala @@ -14,7 +14,7 @@ package dtd * * @author Burak Emir * - * @param target name of this DOCTYPE + * @param name name of this DOCTYPE * @param extID None, or Some(external ID of this doctype) * @param intSubset sequence of internal subset declarations */ diff --git a/src/library/scala/xml/dtd/ExternalID.scala b/src/library/scala/xml/dtd/ExternalID.scala index fdde18a926..a0a5818d07 100644 --- a/src/library/scala/xml/dtd/ExternalID.scala +++ b/src/library/scala/xml/dtd/ExternalID.scala @@ -41,7 +41,7 @@ abstract class ExternalID extends parsing.TokenTests /** a system identifier * * @author Burak Emir - * @param systemLiteral the system identifier literal + * @param systemId the system identifier literal */ case class SystemID(systemId: String) extends ExternalID { val publicId = null @@ -54,8 +54,8 @@ case class SystemID(systemId: String) extends ExternalID { /** a public identifier (see http://www.w3.org/QA/2002/04/valid-dtd-list.html). * * @author Burak Emir - * @param publicLiteral the public identifier literal - * @param systemLiteral (can be null for notation pubIDs) the system identifier literal + * @param publicId the public identifier literal + * @param systemId (can be null for notation pubIDs) the system identifier literal */ case class PublicID(publicId: String, systemId: String) extends ExternalID { if (!checkPubID(publicId)) diff --git a/src/library/scala/xml/include/sax/XIncludeFilter.scala b/src/library/scala/xml/include/sax/XIncludeFilter.scala index 4bd0336a26..52ddf6b476 100644 --- a/src/library/scala/xml/include/sax/XIncludeFilter.scala +++ b/src/library/scala/xml/include/sax/XIncludeFilter.scala @@ -249,7 +249,7 @@ class XIncludeFilter extends XMLFilterImpl { * calls to `characters()`. It's used to include files with `parse="text"`. * * @param url URL of the document that will be read - * @param encoding Encoding of the document; e.g. UTF-8, + * @param encoding1 Encoding of the document; e.g. UTF-8, * ISO-8859-1, etc. * @return void * @throws SAXException if the requested document cannot diff --git a/src/library/scala/xml/parsing/ExternalSources.scala b/src/library/scala/xml/parsing/ExternalSources.scala index ca6cea4c67..127d66bf6f 100644 --- a/src/library/scala/xml/parsing/ExternalSources.scala +++ b/src/library/scala/xml/parsing/ExternalSources.scala @@ -23,11 +23,6 @@ import scala.io.Source trait ExternalSources { self: ExternalSources with MarkupParser with MarkupHandler => - /** ... - * - * @param systemId ... - * @return ... - */ def externalSource(systemId: String): Source = { if (systemId startsWith "http:") return Source fromURL new URL(systemId) diff --git a/src/library/scala/xml/parsing/FactoryAdapter.scala b/src/library/scala/xml/parsing/FactoryAdapter.scala index 5aad0e7ce1..507a14a418 100644 --- a/src/library/scala/xml/parsing/FactoryAdapter.scala +++ b/src/library/scala/xml/parsing/FactoryAdapter.scala @@ -158,7 +158,7 @@ abstract class FactoryAdapter extends DefaultHandler with factory.XMLLoader[Node /** End element. * @param uri - * @param localName + * @param _localName * @param qname * @throws org.xml.sax.SAXException if .. */ diff --git a/src/library/scala/xml/parsing/MarkupHandler.scala b/src/library/scala/xml/parsing/MarkupHandler.scala index 83db2f177d..8d66fd0a7f 100755 --- a/src/library/scala/xml/parsing/MarkupHandler.scala +++ b/src/library/scala/xml/parsing/MarkupHandler.scala @@ -64,7 +64,6 @@ abstract class MarkupHandler extends Logged * @param pos the position in the source file * @param pre the prefix * @param label the local name - * @param attrs the attributes (metadata) */ def elemEnd(pos: Int, pre: String, label: String): Unit = () @@ -77,7 +76,6 @@ abstract class MarkupHandler extends Logged * @param attrs the attributes (metadata) * @param empty `true` if the element was previously empty; `false` otherwise. * @param args the children of this element - * @return ... */ def elem(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, empty: Boolean, args: NodeSeq): NodeSeq diff --git a/src/library/scala/xml/parsing/MarkupParserCommon.scala b/src/library/scala/xml/parsing/MarkupParserCommon.scala index c6da4bb546..096f8a8f38 100644 --- a/src/library/scala/xml/parsing/MarkupParserCommon.scala +++ b/src/library/scala/xml/parsing/MarkupParserCommon.scala @@ -54,8 +54,8 @@ private[scala] trait MarkupParserCommon extends TokenTests { xTakeUntil(mkProcInstr(_, n, _), () => tmppos, "?>") } - /** attribute value, terminated by either ' or ". value may not contain <. - * @param endch either ' or " + /** attribute value, terminated by either `'` or `"`. value may not contain `<`. + @param endCh either `'` or `"` */ def xAttributeValue(endCh: Char): String = { val buf = new StringBuilder diff --git a/src/library/scala/xml/pull/XMLEvent.scala b/src/library/scala/xml/pull/XMLEvent.scala index dff81e8ed6..58d7d9fac5 100644 --- a/src/library/scala/xml/pull/XMLEvent.scala +++ b/src/library/scala/xml/pull/XMLEvent.scala @@ -38,7 +38,7 @@ case class EvElemEnd(pre: String, label: String) extends XMLEvent case class EvText(text: String) extends XMLEvent /** An entity reference was encountered. - * @param the name of the entity, e.g. `gt` when encountering the entity `>` + * @param entity the name of the entity, e.g. `gt` when encountering the entity `>` */ case class EvEntityRef(entity: String) extends XMLEvent diff --git a/src/library/scala/xml/transform/BasicTransformer.scala b/src/library/scala/xml/transform/BasicTransformer.scala index 002f86abe7..0ae417a7f8 100644 --- a/src/library/scala/xml/transform/BasicTransformer.scala +++ b/src/library/scala/xml/transform/BasicTransformer.scala @@ -18,11 +18,6 @@ package transform */ abstract class BasicTransformer extends Function1[Node,Node] { - /** - * @param n ... - * @param ns ... - * @return ... - */ protected def unchanged(n: Node, ns: Seq[Node]) = ns.length == 1 && (ns.head == n) diff --git a/src/partest/scala/tools/partest/nest/SBTRunner.scala b/src/partest/scala/tools/partest/nest/SBTRunner.scala index 750e270c18..5d994eeb37 100644 --- a/src/partest/scala/tools/partest/nest/SBTRunner.scala +++ b/src/partest/scala/tools/partest/nest/SBTRunner.scala @@ -54,15 +54,17 @@ object SBTRunner extends DirectRunner { val config = parseArgs(args, CommandLineOptions()) fileManager.SCALAC_OPTS = config.scalacOptions fileManager.CLASSPATH = config.classpath getOrElse sys.error("No classpath set") + + def findClasspath(jar: String, name: String): Option[String] = { + val optJar = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches (".*"+jar+".*\\.jar"))).headOption + val optClassDir = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches (".*"+name+File.separator+"classes"))).headOption + optJar orElse optClassDir + } // Find scala library jar file... - val lib: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-library.*\\.jar")).headOption - fileManager.LATEST_LIB = lib getOrElse sys.error("No scala-library found! Classpath = " + fileManager.CLASSPATH) - val comp: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-compiler.*\\.jar")).headOption - fileManager.LATEST_COMP = comp getOrElse sys.error("No scala-compiler found! Classpath = " + fileManager.CLASSPATH) - val partest: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-partest.*\\.jar")).headOption - fileManager.LATEST_PARTEST = partest getOrElse sys.error("No scala-partest found! Classpath = " + fileManager.CLASSPATH) - val actors: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-actors.*\\.jar")).headOption - fileManager.LATEST_ACTORS = actors getOrElse sys.error("No scala-actors found! Classpath = " + fileManager.CLASSPATH) + fileManager.LATEST_LIB = findClasspath("scala-library", "scala-library") getOrElse sys.error("No scala-library found! Classpath = " + fileManager.CLASSPATH) + fileManager.LATEST_COMP = findClasspath("scala-compiler", "scala-compiler") getOrElse sys.error("No scala-compiler found! Classpath = " + fileManager.CLASSPATH) + fileManager.LATEST_PARTEST = findClasspath("scala-partest", "partest") getOrElse sys.error("No scala-partest found! Classpath = " + fileManager.CLASSPATH) + fileManager.LATEST_ACTORS = findClasspath("scala-actors", "actors") getOrElse sys.error("No scala-actors found! Classpath = " + fileManager.CLASSPATH) // TODO - Do something useful here!!! fileManager.JAVAC_CMD = "javac" diff --git a/src/yourkit/scala/tools/util/YourkitProfiling.scala b/src/yourkit/scala/tools/util/YourkitProfiling.scala deleted file mode 100644 index 677a85112b..0000000000 --- a/src/yourkit/scala/tools/util/YourkitProfiling.scala +++ /dev/null @@ -1,63 +0,0 @@ -package scala.tools -package util - -import com.yourkit.api._ -import com.yourkit.runtime._ -import nsc.io._ - -class YourkitProfiling extends Profiling { - @volatile private var active = false - @volatile private var freq: Option[Int] = None - lazy val controller = new Controller - - def defaultFreq = 100 - def allocationFreq = freq - def setAllocationFreq(x: Int) = freq = if (x <= 0) None else Some(x) - - def startRecordingAllocations() = { - controller.startAllocationRecording(true, freq getOrElse defaultFreq, false, 0) - } - def stopRecordingAllocations() = { - controller.stopAllocationRecording() - } - - def startProfiling(): Unit = { - if (isActive) - return - - active = true - daemonize { - try { - controller.startCPUProfiling(ProfilingModes.CPU_SAMPLING, Controller.DEFAULT_FILTERS) - if (freq.isDefined) - startRecordingAllocations() - } - catch { - case _: PresentableException => () // if it's already running, no big deal - } - } - } - - def captureSnapshot() = { - daemonize(controller.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP)) - } - - def stopProfiling() = { - try { - if (freq.isDefined) - stopRecordingAllocations() - - controller.stopCPUProfiling() - } - catch { - case _: PresentableException => () // if it's already running, no big deal - } - finally active = false - } - - def advanceGeneration(desc: String) { - controller.advanceGeneration(desc) - } - - def isActive = active -} |