Completed ASM Mixin compiler and factory.

This commit is contained in:
Chicken Bones committed 2013-06-27 06:06:04 +10:00
1 parent 0bfc73c5f7
commit ffa0918f4b
7 files changed
+1723 -3

No files matched your search

@@ -13,7 +13,7 @@ import scala.collection.mutable.ListBuffer
import scala.collection.mutable.ListBuffer
import codechicken.multipart.asm.IMultipartFactory
import codechicken.multipart.asm.ScalaCompilerFactory
import codechicken.multipart.asm.ASMExtensionFactory
import codechicken.multipart.asm.ASMMixinFactory
object MultipartGenerator
{
@@ -23,7 +23,7 @@ object MultipartGenerator
private var partTraitMap_c:Map[Class[_], Seq[String]] = Map()
private var partTraitMap_s:Map[Class[_], Seq[String]] = Map()
var factory:IMultipartFactory = ASMExtensionFactory
var factory:IMultipartFactory = ASMMixinFactory
def partTraitMap(client:Boolean) = if(client) partTraitMap_c else partTraitMap_s
+5 -1
View File
@@ -51,7 +51,11 @@ class TileMultipart extends TileEntity
loadFrom(that)
}
def copyFrom(that:TileMultipart)//Potentially auto gen this
/**
* This method should be used for copying all the data from the fields in that container tile.
* This method will be automatically generated on java tile traits with fields if it is not overridden.
*/
def copyFrom(that:TileMultipart)
{
partList = that.partList
doesTick = that.doesTick
@@ -0,0 +1,551 @@
package codechicken.multipart.asm
import scala.collection.mutable.{Map => MMap, ListBuffer => MList, Set => MSet}
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._
import cpw.mods.fml.relauncher.RelaunchClassLoader
import org.objectweb.asm.tree._
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.ClassReader
import org.objectweb.asm.util.TraceClassVisitor
import org.objectweb.asm.util.Textifier
import org.objectweb.asm.Type
import org.objectweb.asm.MethodVisitor
import Type._
import codechicken.core.asm.ASMHelper._
import codechicken.core.asm.ObfuscationMappings._
import java.io.File
import java.io.PrintWriter
import ScalaSignature._
import java.lang.reflect.Method
import java.lang.reflect.Modifier
object DebugPrinter
{
val dir = new File("asm/multipart")
if(!dir.exists)
dir.mkdirs()
for(file <- dir.listFiles)
file.delete
def defined(name:String, bytes:Array[Byte])
{
val fileout = new File(dir, name.replace('/', '#')+".txt")
val pout = new PrintWriter(fileout)
new ClassReader(bytes).accept(new TraceClassVisitor(null, new Textifier(), pout), 0)
pout.close()
}
}
object ASMMixinCompiler
{
val cl = getClass.getClassLoader.asInstanceOf[RelaunchClassLoader]
val m_defineClass = classOf[ClassLoader].getDeclaredMethod("defineClass", classOf[Array[Byte]], Integer.TYPE, Integer.TYPE)
m_defineClass.setAccessible(true)
private val traitByteMap = MMap[String, Array[Byte]]()
private val mixinMap = MMap[String, MixinInfo]()
def define(name:String, bytes:Array[Byte]) =
{
internalDefine(name, bytes)
m_defineClass.invoke(cl, bytes, 0:Integer, bytes.length:Integer).asInstanceOf[Class[_]]
}
def internalDefine(name:String, bytes:Array[Byte])
{
traitByteMap.put(name.replace('.', '/'), bytes)
BaseNodeInfo.clear(name)
DebugPrinter.defined(name, bytes)
}
def classNode(name:String) = traitByteMap.getOrElseUpdate(name.replace('.', '/'), cl.getClassBytes(name.replace('/', '.'))) match {
case null => null
case v => createClassNode(v, ClassReader.EXPAND_FRAMES)
}
def isScala(cnode:ClassNode) = ScalaSigReader.ann(cnode).isDefined
def isTrait(cnode:ClassNode) = ScalaSigReader.read(ScalaSigReader.ann(cnode).get).evalT[ClassSymbol](0).isTrait
def getMixinInfo(name:String) = mixinMap.get(name)
case class FieldMixin(name:String, desc:String, access:Int)
{
def accessName(owner:String) = if((access & ACC_PRIVATE) != 0)
owner.replace('/', '$')+"$$"+name
else
name
}
case class MixinInfo(name:String, parent:String, fields:Seq[FieldMixin],
methods:Seq[MethodNode], supers:Map[String, String])
{
def tname = name+"$class"
}
object BaseNodeInfo
{
private val baseNodeMap = MMap[String, BaseNodeInfo]()
def clear(name:String) = baseNodeMap.remove(name)
def getNodeInfo(name:String) = baseNodeMap.getOrElseUpdate(name, new BaseNodeInfo(name))
private def getNodeInfo(clazz:ClassInfoSource) = clazz match {
case null => null
case v => baseNodeMap.getOrElseUpdate(v.name, new BaseNodeInfo(v))
}
case class MethodNodeInfo(owner:String, m:MethodInfoSource)
{
def name = m.name
def desc = m.desc
}
trait MethodInfoSource
{
def name:String
def desc:String
def isPrivate:Boolean
def isAbstract:Boolean
}
trait ClassInfoSource
{
def name:String
def superClass:Option[BaseNodeInfo]
def interfaces:Seq[BaseNodeInfo]
def methods:Seq[MethodInfoSource]
}
implicit def JClassInfoSource(clazz:Class[_]) = if(clazz == null) null else new JClassInfoSource(clazz)
class JClassInfoSource(clazz:Class[_]) extends ClassInfoSource
{
case class JMethodInfoSource(method:Method) extends MethodInfoSource
{
def name = method.getName
def desc = getType(method).getDescriptor()
def isPrivate = Modifier.isPrivate(method.getModifiers)
def isAbstract = Modifier.isAbstract(method.getModifiers)
}
def name = clazz.getName.replace('.', '/')
def superClass = Option(getNodeInfo(clazz.getSuperclass))
def interfaces = clazz.getInterfaces.map(getNodeInfo(_))
def methods = clazz.getMethods.map(JMethodInfoSource(_))
}
implicit def ClassNodeInfoSource(cnode:ClassNode) = if(cnode == null) null else new ClassNodeInfoSource(cnode)
class ClassNodeInfoSource(cnode:ClassNode) extends ClassInfoSource
{
case class MethodNodeInfoSource(mnode:MethodNode) extends MethodInfoSource
{
def name = mnode.name
def desc = mnode.desc
def isPrivate = (mnode.access & ACC_PRIVATE) != 0
def isAbstract = (mnode.access & ACC_ABSTRACT) != 0
}
def name = cnode.name
def superClass = Option(getNodeInfo(cnode.superName))
def interfaces = cnode.interfaces match {
case null => Seq()
case v => v.map(getNodeInfo(_))
}
def methods = cnode.methods.map(MethodNodeInfoSource(_))
}
class ScalaNodeInfoSource(cnode:ClassNode) extends ClassNodeInfoSource(cnode)
{
val sig = ScalaSigReader.read(ScalaSigReader.ann(cnode).get)
val csym = sig.evalT(0):ClassSymbol
override def superClass = Option(getNodeInfo(csym.jParent(sig)))
override def interfaces = csym.jInterfaces(sig).map(getNodeInfo(_))
}
def classInfo(name:String):ClassInfoSource = name match {
case null => null
case s => classNode(s) match {
case null => cl.findClass(s.replace('/', '.'))
case v if isScala(v) => new ScalaNodeInfoSource(v)
case v => v
}
}
}
import BaseNodeInfo._
class BaseNodeInfo(val clazz:ClassInfoSource)
{
def this(name:String) = this(classInfo(name))
val publicmethods = exportedMethods(clazz)//map of nameDesc to owner+method
def exportedMethods(o:ClassInfoSource):Map[String, MethodNodeInfo] =
{
val eMethods = o.methods.filter(!_.isPrivate)//defined accessable methods
.map(m => (m.name+m.desc, MethodNodeInfo(o.name, m))).toMap
eMethods ++ (o.superClass++o.interfaces).flatMap(_.publicmethods)
}
}
def finishBridgeCall(mv:MethodVisitor, mvdesc:String, opcode:Int, owner:String, name:String, desc:String)
{
val args = getArgumentTypes(mvdesc)
for(i <- 0 until args.length)
mv.visitVarInsn(args(i).getOpcode(ILOAD), i+1)
mv.visitMethodInsn(opcode, owner, name, desc)
mv.visitInsn(getReturnType(mvdesc).getOpcode(IRETURN))
mv.visitMaxs(args.length+1, args.length+1)
}
def writeBridge(mv:MethodVisitor, mvdesc:String, opcode:Int, owner:String, name:String, desc:String)
{
mv.visitVarInsn(ALOAD, 0)
finishBridgeCall(mv, mvdesc, opcode, owner, name, desc)
}
def writeStaticBridge(mv:MethodNode, mname:String, t:MixinInfo) =
writeBridge(mv, mv.desc, INVOKESTATIC, t.tname, mname, staticDesc(t.name, mv.desc))
def mixinClasses(name:String, superClass:String, traits:Seq[String]) =
{
val traitInfos = traits.map(t => mixinMap(t.replace('.', '/')))
val cnode = new ClassNode()
cnode.visit(V1_6, ACC_PUBLIC, name, null, superClass.replace('.', '/'), traitInfos.map(_.name).toArray[String])
val minit = cnode.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
minit.visitVarInsn(ALOAD, 0)
minit.visitMethodInsn(INVOKESPECIAL, cnode.superName, "<init>", "()V")
val prevInfos = MList[MixinInfo]()
traitInfos.foreach{ t =>
minit.visitVarInsn(ALOAD, 0)
minit.visitMethodInsn(INVOKESTATIC, t.tname, "$init$", "(L"+t.name+";)V")
t.fields.foreach{ f =>
val fv = cnode.visitField(ACC_PRIVATE, f.accessName(t.name), f.desc, null, null).asInstanceOf[FieldNode]
var mv = cnode.visitMethod(ACC_PUBLIC, fv.name, "()"+f.desc, null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, name, fv.name, fv.desc)
mv.visitInsn(getType(fv.desc).getOpcode(IRETURN))
mv.visitMaxs(1, 1)
mv = cnode.visitMethod(ACC_PUBLIC, fv.name+"_$eq", "("+f.desc+")V", null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(getType(fv.desc).getOpcode(ILOAD), 1)
mv.visitFieldInsn(PUTFIELD, name, fv.name, fv.desc)
mv.visitInsn(RETURN)
mv.visitMaxs(2, 2)
}
t.supers.foreach{ s =>
val (name, desc) = seperateDesc(s._1)
val mv = cnode.visitMethod(ACC_PUBLIC, t.name.replace('/', '$')+"$$super$"+name, desc, null, null).asInstanceOf[MethodNode]
prevInfos.reverse.find(t => t.supers.contains(s._1)) match {//each super goes to the one before
case Some(st) => writeStaticBridge(mv, name, st)
case None => writeBridge(mv, desc, INVOKESPECIAL, s._2, name, desc)
}
}
prevInfos+=t
}
val methodSigs = MSet[String]()
traitInfos.reverse.foreach{ t => //last trait gets first pick on methods
t.methods.foreach{ m =>
if(!methodSigs(m.name+m.desc))
{
val mv = cnode.visitMethod(ACC_PUBLIC, m.name, m.desc, null, Array(m.exceptions:_*)).asInstanceOf[MethodNode]
copy(m, mv)
mv.instructions = new InsnList()
mv.tryCatchBlocks.clear()
writeStaticBridge(mv, m.name, t)
methodSigs+=m.name+m.desc
}
}
}
minit.visitInsn(RETURN)
minit.visitMaxs(1, 1)
define(cnode.name, createBytes(cnode, 0))
}
def seperateDesc(nameDesc:String) = {
val n = nameDesc.indexOf('(')
(nameDesc.substring(0, n), nameDesc.substring(n))
}
def staticDesc(owner:String, desc:String) =
{
val descT = getMethodType(desc)
getMethodDescriptor(descT.getReturnType, (getType("L"+owner+";")+:descT.getArgumentTypes):_*)
}
def getSuper(minsn:MethodInsnNode, stack:StackAnalyser):Option[String] =
{
import StackAnalyser._
if(minsn.getOpcode != INVOKESPECIAL) return None
val oname = stack.owner.getInternalName
if(minsn.owner.equals(oname)) return None//private this
stack.peek(Type.getType(minsn.desc).getArgumentTypes.length) match {
case This(o) =>
case _ => return None//have to be invoked on this
}
getSuper(minsn.name+minsn.desc, oname)
}
def getSuper(nameDesc:String, cname:String) =
getNodeInfo(cname).clazz.superClass match {
case None => None//no super class
case Some(o) => o.publicmethods.get(nameDesc) match {
case None => None//no method with sig in super
case Some(m) => if(m.m.isAbstract)
None //abstract can't be called for supers
else
Some(m.owner)//congrats, that's a super
}
}
def registerJavaTrait(cnode:ClassNode)
{
if((cnode.access & ACC_INTERFACE) != 0)
throw new IllegalArgumentException("Cannot register java interface "+cnode.name+" as a multipart trait. Try register passThroughInterface")
if((cnode.access & ACC_ABSTRACT) != 0)
throw new IllegalArgumentException("Cannot register abstract class "+cnode.name+" as a multipart trait")
if(!cnode.innerClasses.isEmpty)
throw new IllegalArgumentException("Inner classes are not permitted for "+cnode.name+" as a multipart trait")
val inode = new ClassNode()//impl node
inode.visit(V1_6, ACC_ABSTRACT|ACC_PUBLIC, cnode.name+"$class", null, "java/lang/Object", null)
inode.sourceFile = cnode.sourceFile
val fields = cnode.fields.map(f => (f.name, FieldMixin(f.name, f.desc, f.access))).toMap
val superInfo = getNodeInfo(cnode.superName)
val supers = MMap[String, String]()//nameDesc to super owner
val methods = MList[MethodNode]()
val tnode = new ClassNode()//trait node (interface)
tnode.visit(V1_6, ACC_INTERFACE|ACC_ABSTRACT|ACC_PUBLIC, cnode.name, null, "java/lang/Object", Array(cnode.interfaces:_*))
def fname(name:String) = fields(name).accessName(cnode.name)
fields.values.foreach{ fnode =>
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, fname(fnode.name), "()"+fnode.desc, null, null)
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, fname(fnode.name)+"_$eq", "("+fnode.desc+")V", null, null)
}
def superInsn(minsn:MethodInsnNode) =
{
val bridgeName = cnode.name.replace('/', '$')+"$$super$"+minsn.name
if(!supers.contains(minsn.name+minsn.desc))
{
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, bridgeName, minsn.desc, null, null)
supers.put(minsn.name+minsn.desc, minsn.owner)
}
new MethodInsnNode(INVOKEINTERFACE, cnode.name, bridgeName, minsn.desc)
}
def staticClone(mnode:MethodNode, name:String, access:Int) =
{
val mv = inode.visitMethod(access|ACC_STATIC, name,
staticDesc(cnode.name, mnode.desc),
null, Array(mnode.exceptions:_*)).asInstanceOf[MethodNode]
copy(mnode, mv)
mv
}
def staticTransform(mnode:MethodNode, base:MethodNode)
{
val stack = new StackAnalyser(getType(cnode.name), base)
val insnList = mnode.instructions
var insn = insnList.getFirst
def replace(newinsn:AbstractInsnNode)
{
insnList.insert(insn, newinsn)
insnList.remove(insn)
insn = newinsn
}
//transform
while(insn != null)
{
insn match {
case finsn:FieldInsnNode => insn.getOpcode match
{
case GETFIELD => replace(new MethodInsnNode(INVOKEINTERFACE, cnode.name,
fname(finsn.name), "()"+finsn.desc))
case PUTFIELD => replace(new MethodInsnNode(INVOKEINTERFACE, cnode.name,
fname(finsn.name)+"_$eq", "("+finsn.desc+")V"))
case _ =>
}
case minsn:MethodInsnNode => insn.getOpcode match
{
case INVOKESPECIAL =>
if(getSuper(minsn, stack).isDefined)
replace(superInsn(minsn))
case INVOKEVIRTUAL =>
if(minsn.owner.equals(cnode.name))
minsn.setOpcode(INVOKEINTERFACE)
case _ =>
}
case _ =>
}
stack.visitInsn(insn)
insn = insn.getNext
}
}
def convertMethod(mnode:MethodNode)
{
if(mnode.name.equals("<clinit>"))
throw new IllegalArgumentException("Static initialisers are not permitted "+cnode.name+" as a multipart trait")
if(mnode.name.equals("<init>"))
{
if(!mnode.desc.equals("()V"))
throw new IllegalArgumentException("Constructor arguments are not permitted "+cnode.name+" as a multipart trait")
var mv = staticClone(mnode, "$init$", ACC_PUBLIC)
def removeSuperConstructor()
{
def throwI = throw new IllegalArgumentException("Invalid constructor insn sequence "+cnode.name)
val insnList = mv.instructions
var insn = insnList.getFirst
var state = 0
while(insn != null && state < 2)
{
var next = insn.getNext
insn match {
case linsn:LabelNode =>
case linsn:LineNumberNode =>
case vinsn:VarInsnNode =>
if(state == 0)
{
if(vinsn.getOpcode == ALOAD && vinsn.`var` == 0)
{
state = 1
insnList.remove(insn)
}
else
throwI
}
else
throwI
case minsn:MethodInsnNode =>
if(state == 1)
{
if(minsn.getOpcode == INVOKESPECIAL && minsn.name.equals("<init>") &&
minsn.owner.equals(cnode.superName))
{
insnList.remove(insn)
state = 2
}
else
throwI
}
else
throwI
case _ => throwI
}
insn = next
}
if(state != 2)
throwI
}
removeSuperConstructor()
staticTransform(mv, mnode)
return
}
if((mnode.access & ACC_PRIVATE) == 0)
{
val mv = tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, mnode.name, mnode.desc, null, Array(mnode.exceptions:_*))
methods+=mv.asInstanceOf[MethodNode]
}
//convert that method!
val access = if((mnode.access & ACC_PRIVATE) == 0) ACC_PUBLIC else ACC_PRIVATE
var mv = staticClone(mnode, mnode.name, access)
staticTransform(mv, mnode)
}
cnode.methods.foreach(convertMethod)
define(inode.name, createBytes(inode, 0))
define(tnode.name, createBytes(tnode, 0))
mixinMap.put(tnode.name, MixinInfo(tnode.name, cnode.superName,
fields.values.toSeq,
methods.toSeq, supers.toMap))
}
def registerScalaTrait(cnode:ClassNode)
{
for(i <- cnode.interfaces)
if(classNode(i+"$class") != null)
throw new IllegalArgumentException("Multipart trait "+cnode.name+" cannot extend other traits")
val fieldAccessors = MMap[String, MethodSymbol]()
val fields = MList[MethodSymbol]()
val methods = MList[MethodNode]()
val supers = MMap[String, String]()
val sig = ScalaSigReader.read(ScalaSigReader.ann(cnode).get)
val csym:ClassSymbol = sig.evalT(0)
for(i <- 0 until sig.table.length)
{
import ScalaSignature._
val e = sig.table(i)
if(e.id == 8)//method
{
val sym:MethodSymbol = sig.evalT(i)
if(sym.isParam || !sym.owner.equals(csym)){}
else if(sym.isAccessor)
{
fieldAccessors.put(sym.name, sym)
}
else if(sym.isMethod)
{
val desc = sym.jDesc(sig)
if(sym.name.contains("super$"))
{
val name = sym.name.substring(6)
supers.put(name+desc, getSuper(name+desc, cnode.name).get)
}
else if(!sym.name.equals("$init$") && !sym.isPrivate)
{
methods+=findMethod(new DescriptorMapping(cnode.name, sym.name, desc), cnode)
}
}
else
{
fields+=sym
}
}
}
mixinMap.put(cnode.name, MixinInfo(cnode.name, csym.jParent(sig),
fields.map(sym => FieldMixin(sym.name.trim, getReturnType(sym.jDesc(sig)).getDescriptor,
if(fieldAccessors(sym.name.trim).isPrivate) ACC_PRIVATE else ACC_PUBLIC)),
methods, supers.toMap))
}
}
@@ -0,0 +1,311 @@
package codechicken.multipart.asm
import scala.collection.Seq
import codechicken.multipart.TileMultipart
import codechicken.multipart.TileMultipartClient
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.ClassWriter
import codechicken.core.asm.CC_ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.Label
import cpw.mods.fml.relauncher.ReflectionHelper
import org.objectweb.asm.commons.RemappingClassAdapter
import org.objectweb.asm.commons.SimpleRemapper
import scala.collection.mutable.ArrayBuffer
import org.objectweb.asm.tree._
import org.objectweb.asm.Type
import Type._
import scala.collection.immutable.Range
import codechicken.core.asm.ASMHelper._
import codechicken.core.asm.ObfuscationMappings._
import codechicken.multipart.MultipartGenerator
import ASMMixinCompiler._
object ASMMixinFactory extends IMultipartFactory
{
type TMClass = Class[_ <: TileMultipart]
private var ugenid = 0
private var generatorMap = Map[SuperSet, Constructor]()
def uniqueName(prefix:String):String = {
val ret = prefix+"$$"+ugenid
ugenid += 1
return ret
}
def simpleName(name:String) = name.substring(name.replace('/', '.').lastIndexOf('.')+1)
abstract class Constructor
{
def generate():TileMultipart
}
object SuperSet
{
val TileMultipartType = classOf[TileMultipart].getName
val TileMultipartClientType = classOf[TileMultipartClient].getName
def apply(types:Seq[String], client:Boolean) = new SuperSet(types, client)
}
class SuperSet(types:Seq[String], client:Boolean)
{
import SuperSet._
val set = baseType+:types.sorted
def baseType = if(client) TileMultipartClientType else TileMultipartType
override def equals(obj:Any) = obj match
{
case x:SuperSet => set == x.set
case _ => false
}
override def hashCode() = set.hashCode
def generate():TileMultipart = get.generate
def get() = generatorMap.getOrElse(this, gen_sync)
def gen_sync():Constructor = ASMMixinFactory.synchronized
{
return generatorMap.getOrElse(this, {
var gen = generator
generatorMap = generatorMap+(this->gen)
gen
})
}
def generator():Constructor =
{
val cmpClass = if(!types.isEmpty)
mixinClasses(uniqueName("TileMultipart_cmp"), set.head, set.drop(1))
else
cl.loadClass(baseType)
MultipartGenerator.registerTileClass(cmpClass.asInstanceOf[TMClass], types.toSet)
return constructor(cmpClass)
}
}
def constructor(clazz:Class[_]):Constructor =
{
val name = uniqueName("TileMultipart_gen")
val cw = new CC_ClassWriter(ASM4)
val superName = classOf[Constructor].getName.replace('.', '/')
val className = clazz.getName.replace('.', '/')
var mv:MethodVisitor = null
cw.visit(V1_6, ACC_PUBLIC|ACC_SUPER, name, null, superName, null)
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, superName, "<init>", "()V")
mv.visitInsn(RETURN)
mv.visitMaxs(1, 1)
mv.visitEnd()
mv = cw.visitMethod(ACC_PUBLIC, "generate", "()Lcodechicken/multipart/TileMultipart;", null, null)
mv.visitCode()
mv.visitTypeInsn(NEW, className)
mv.visitInsn(DUP)
mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V")
mv.visitInsn(ARETURN)
mv.visitMaxs(2, 1)
mv.visitEnd()
cw.visitEnd()
return define(name, cw.toByteArray).newInstance.asInstanceOf[Constructor]
}
private def autoCompleteJavaTrait(cnode:ClassNode)
{
if(!cnode.fields.isEmpty && findMethod(new DescriptorMapping(cnode.name, "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V"), cnode) == null)
{
val mv = cnode.visitMethod(ACC_PUBLIC, "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V", null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V");
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, cnode.name)
val end = new Label()
mv.visitJumpInsn(IFEQ, end)
cnode.fields.foreach{ f =>
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitFieldInsn(GETFIELD, cnode.name, f.name, f.desc)
mv.visitFieldInsn(PUTFIELD, cnode.name, f.name, f.desc)
}
mv.visitLabel(end)
mv.visitInsn(RETURN)
mv.visitMaxs(2, 2)
}
}
def registerTrait(s_interface:String, s_trait:String, client:Boolean)
{
val cnode = classNode(s_trait)
if(cnode == null)
throw new ClassNotFoundException(s_trait)
def superClass = getMixinInfo(cnode.name) match {
case Some(m) => m.parent
case None => BaseNodeInfo.getNodeInfo(cnode.name).clazz.superClass.get.clazz.name
}
superClass match {
case "codechicken/multipart/TileMultipartClient" => if(!client)
throw new IllegalArgumentException("Multipart trait "+s_trait+" cannot implement TileMultipartClient on the server")
case "codechicken/multipart/TileMultipart" =>
case _ => throw new IllegalArgumentException("Multipart trait "+s_trait+" must implement TileMultipart or TileMultipartClient")
}
if(getMixinInfo(cnode.name).isDefined)
return
if(isScala(cnode) && isTrait(cnode))
{
registerScalaTrait(cnode)
}
else
{
autoCompleteJavaTrait(cnode)
registerJavaTrait(cnode)
}
}
def generatePassThroughTrait(s_interface:String):String =
{
def passThroughTraitName(iName:String) =
"T" + (if(iName.startsWith("I")) iName.substring(1) else iName)
val tname = uniqueName(passThroughTraitName(simpleName(s_interface)))
val vname = "impl"
val iname = s_interface.replace('.', '/')
val idesc = "L"+iname+";"
val inode = classNode(s_interface)
if((inode.access&ACC_INTERFACE) == 0) throw new IllegalArgumentException(s_interface+" is not an interface.")
val cw = new CC_ClassWriter(ASM4)
var mv:MethodVisitor = null
var fv:FieldVisitor = null
cw.visit(V1_6, ACC_PUBLIC|ACC_SUPER, tname, null, "codechicken/multipart/TileMultipart", Array(iname))
{
fv = cw.visitField(ACC_PRIVATE, vname, idesc, null, null)
fv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "<init>", "()V")
mv.visitInsn(RETURN)
mv.visitMaxs(1, 1)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "bindPart", "(Lcodechicken/multipart/TMultiPart;)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "bindPart", "(Lcodechicken/multipart/TMultiPart;)V")
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, iname)
val l2 = new Label()
mv.visitJumpInsn(IFEQ, l2)
val l3 = new Label()
mv.visitLabel(l3)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(CHECKCAST, iname)
mv.visitFieldInsn(PUTFIELD, tname, vname, idesc)
mv.visitLabel(l2)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitInsn(RETURN)
mv.visitMaxs(2, 2)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "partRemoved", "(Lcodechicken/multipart/TMultiPart;I)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ILOAD, 2)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "partRemoved", "(Lcodechicken/multipart/TMultiPart;I)V")
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
val l2 = new Label()
mv.visitJumpInsn(IF_ACMPNE, l2)
val l3 = new Label()
mv.visitLabel(l3)
mv.visitVarInsn(ALOAD, 0)
mv.visitInsn(ACONST_NULL)
mv.visitFieldInsn(PUTFIELD, tname, vname, idesc)
mv.visitLabel(l2)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitInsn(RETURN)
mv.visitMaxs(3, 3)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "canAddPart", "(Lcodechicken/multipart/TMultiPart;)Z", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
val l1 = new Label()
mv.visitJumpInsn(IFNULL, l1)
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, iname)
mv.visitJumpInsn(IFEQ, l1)
val l2 = new Label()
mv.visitLabel(l2)
mv.visitInsn(ICONST_0)
mv.visitInsn(IRETURN)
mv.visitLabel(l1)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "canAddPart", "(Lcodechicken/multipart/TMultiPart;)Z")
mv.visitInsn(IRETURN)
mv.visitMaxs(2, 2)
mv.visitEnd()
}
def methods(cnode:ClassNode):Seq[MethodNode] =
{
val m = cnode.methods
if(cnode.interfaces != null)
m++=cnode.interfaces.flatMap(i => methods(classNode(i)))
m
}
def generatePassThroughMethod(m:MethodNode)
{
mv = cw.visitMethod(ACC_PUBLIC, m.name, m.desc, m.signature, Array(m.exceptions:_*))
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
finishBridgeCall(mv, m.desc, INVOKEINTERFACE, iname, m.name, m.desc)
}
methods(inode).foreach(generatePassThroughMethod(_))
cw.visitEnd()
internalDefine(tname, cw.toByteArray)
registerTrait(s_interface, tname, false)
return tname
}
def generateTile(types:Seq[String], client:Boolean) = SuperSet(types, client).generate
}
+213
View File
@@ -0,0 +1,213 @@
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2007-2013, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
package codechicken.multipart.asm
object ByteCodecs {
def avoidZero(src: Array[Byte]): Array[Byte] = {
var i = 0
val srclen = src.length
var count = 0
while (i < srclen) {
if (src(i) == 0x7f) count += 1
i += 1
}
val dst = new Array[Byte](srclen + count)
i = 0
var j = 0
while (i < srclen) {
val in = src(i)
if (in == 0x7f) {
dst(j) = (0xc0).toByte
dst(j + 1) = (0x80).toByte
j += 2
} else {
dst(j) = (in + 1).toByte
j += 1
}
i += 1
}
dst
}
def regenerateZero(src: Array[Byte]): Int = {
var i = 0
val srclen = src.length
var j = 0
while (i < srclen) {
val in: Int = src(i) & 0xff
if (in == 0xc0 && (src(i + 1) & 0xff) == 0x80) {
src(j) = 0x7f
i += 2
} else if (in == 0) {
src(j) = 0x7f
i += 1
} else {
src(j) = (in - 1).toByte
i += 1
}
j += 1
}
j
}
def encode8to7(src: Array[Byte]): Array[Byte] = {
val srclen = src.length
val dstlen = (srclen * 8 + 6) / 7
val dst = new Array[Byte](dstlen)
var i = 0
var j = 0
while (i + 6 < srclen) {
var in: Int = src(i) & 0xff
dst(j) = (in & 0x7f).toByte
var out: Int = in >>> 7
in = src(i + 1) & 0xff
dst(j + 1) = (out | (in << 1) & 0x7f).toByte
out = in >>> 6
in = src(i + 2) & 0xff
dst(j + 2) = (out | (in << 2) & 0x7f).toByte
out = in >>> 5
in = src(i + 3) & 0xff
dst(j + 3) = (out | (in << 3) & 0x7f).toByte
out = in >>> 4
in = src(i + 4) & 0xff
dst(j + 4) = (out | (in << 4) & 0x7f).toByte
out = in >>> 3
in = src(i + 5) & 0xff
dst(j + 5) = (out | (in << 5) & 0x7f).toByte
out = in >>> 2
in = src(i + 6) & 0xff
dst(j + 6) = (out | (in << 6) & 0x7f).toByte
out = in >>> 1
dst(j + 7) = out.toByte
i += 7
j += 8
}
if (i < srclen) {
var in: Int = src(i) & 0xff
dst(j) = (in & 0x7f).toByte; j += 1
var out: Int = in >>> 7
if (i + 1 < srclen) {
in = src(i + 1) & 0xff
dst(j) = (out | (in << 1) & 0x7f).toByte; j += 1
out = in >>> 6
if (i + 2 < srclen) {
in = src(i + 2) & 0xff
dst(j) = (out | (in << 2) & 0x7f).toByte; j += 1
out = in >>> 5
if (i + 3 < srclen) {
in = src(i + 3) & 0xff
dst(j) = (out | (in << 3) & 0x7f).toByte; j += 1
out = in >>> 4
if (i + 4 < srclen) {
in = src(i + 4) & 0xff
dst(j) = (out | (in << 4) & 0x7f).toByte; j += 1
out = in >>> 3
if (i + 5 < srclen) {
in = src(i + 5) & 0xff
dst(j) = (out | (in << 5) & 0x7f).toByte; j += 1
out = in >>> 2
}
}
}
}
}
if (j < dstlen) dst(j) = out.toByte
}
dst
}
def decode7to8(src: Array[Byte], srclen: Int): Int = {
var i = 0
var j = 0
val dstlen = (srclen * 7 + 7) / 8
while (i + 7 < srclen) {
var out: Int = src(i).toInt
var in: Byte = src(i + 1)
src(j) = (out | (in & 0x01) << 7).toByte
out = in >>> 1
in = src(i + 2)
src(j + 1) = (out | (in & 0x03) << 6).toByte
out = in >>> 2
in = src(i + 3)
src(j + 2) = (out | (in & 0x07) << 5).toByte
out = in >>> 3
in = src(i + 4)
src(j + 3) = (out | (in & 0x0f) << 4).toByte
out = in >>> 4
in = src(i + 5)
src(j + 4) = (out | (in & 0x1f) << 3).toByte
out = in >>> 5
in = src(i + 6)
src(j + 5) = (out | (in & 0x3f) << 2).toByte
out = in >>> 6
in = src(i + 7)
src(j + 6) = (out | in << 1).toByte
i += 8
j += 7
}
if (i < srclen) {
var out: Int = src(i).toInt
if (i + 1 < srclen) {
var in: Byte = src(i + 1)
src(j) = (out | (in & 0x01) << 7).toByte; j += 1
out = in >>> 1
if (i + 2 < srclen) {
in = src(i + 2)
src(j) = (out | (in & 0x03) << 6).toByte; j += 1
out = in >>> 2
if (i + 3 < srclen) {
in = src(i + 3)
src(j) = (out | (in & 0x07) << 5).toByte; j += 1
out = in >>> 3
if (i + 4 < srclen) {
in = src(i + 4)
src(j) = (out | (in & 0x0f) << 4).toByte; j += 1
out = in >>> 4
if (i + 5 < srclen) {
in = src(i + 5)
src(j) = (out | (in & 0x1f) << 3).toByte; j += 1
out = in >>> 5
if (i + 6 < srclen) {
in = src(i + 6)
src(j) = (out | (in & 0x3f) << 2).toByte; j += 1
out = in >>> 6
}
}
}
}
}
}
if (j < dstlen) src(j) = out.toByte
}
dstlen
}
def encode(xs: Array[Byte]): Array[Byte] = avoidZero(encode8to7(xs))
/**
* Destructively decodes array xs and returns the length of the decoded array.
*
* Sometimes returns (length+1) of the decoded array. Example:
*
* scala> val enc = scala.reflect.generic.ByteCodecs.encode(Array(1,2,3))
* enc: Array[Byte] = Array(2, 5, 13, 1)
*
* scala> scala.reflect.generic.ByteCodecs.decode(enc)
* res43: Int = 4
*
* scala> enc
* res44: Array[Byte] = Array(1, 2, 3, 0)
*
* However, this does not always happen.
*/
def decode(xs: Array[Byte]): Int = {
val len = regenerateZero(xs)
decode7to8(xs, len)
}
}
@@ -0,0 +1,342 @@
package codechicken.multipart.asm
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import java.util.{List => JList}
import scala.collection.mutable.{ListBuffer => MList}
import scala.collection.JavaConverters._
import scala.collection.JavaConversions._
import ScalaSignature._
object ScalaSignature
{
object Bytes
{
implicit def reader(bc:Bytes) = new ByteCodeReader(bc)
def apply(bytes:Array[Byte], pos:Int, len:Int) = new Bytes(bytes, pos, len)
def apply(bytes:Array[Byte]):Bytes = apply(bytes, 0, bytes.length)
}
class Bytes(val bytes:Array[Byte], val pos:Int, val len:Int)
{
def section() = bytes take pos drop len
}
trait Flags
{
def hasFlag(flag:Int):Boolean
def isProtected = hasFlag(0x00000001)
def isPrivate = hasFlag(0x00000004)
def isAbstract = hasFlag(0x00000008)
def isInterface = hasFlag(0x00000080)
def isMethod = hasFlag(0x00000200)
def isParam = hasFlag(0x00002000)
def isStatic = hasFlag(0x00800000)
def isTrait = hasFlag(0x02000000)
def isAccessor = hasFlag(0x08000000)
}
trait SymbolRef extends Flags
{
def full:String
def flags:Int
def hasFlag(flag:Int) = (flags&flag) != 0
}
case class ClassSymbol(name:String, owner:SymbolRef, flags:Int, info:Int) extends SymbolRef
{
override def toString = "ClassSymbol("+name+","+owner+","+flags.toHexString+","+info+")"
def full = owner.full+"."+name
def info(sig:ScalaSignature):ClassType = sig.evalT(info)
def jParent(sig:ScalaSignature) = info(sig).parent.jName
def jInterfaces(sig:ScalaSignature) = info(sig).interfaces.map(_.jName)
}
case class MethodSymbol(name:String, owner:SymbolRef, flags:Int, info:Int) extends SymbolRef
{
override def toString = "MethodSymbol("+name+","+owner+","+flags.toHexString+","+info+")"
def full = owner.full+"."+name
def info(sig:ScalaSignature):SMethodType = sig.evalT(info)
def jDesc(sig:ScalaSignature):String = info(sig).jDesc(sig)
}
case class ExternalSymbol(name:String) extends SymbolRef
{
override def toString = name
def full = name
def flags = 0
}
case object NoSymbol extends SymbolRef
{
def full = "<no symbol>"
def flags = 0
}
trait SMethodType
{
def jDesc(sig:ScalaSignature):String = "("+params.map(m => m.info(sig).returnType.jDesc).mkString+")"+returnType.jDesc
def returnType:TypeRef
def params:List[MethodSymbol]
}
case class ClassType(owner:SymbolRef, parents:List[TypeRef])
{
def parent = parents.head
def interfaces = parents.drop(1)
}
case class MethodType(returnType:TypeRef, params:List[MethodSymbol]) extends SMethodType
case class ParameterlessType(returnType:TypeRef) extends SMethodType
{
def params = List()
}
trait TypeRef
{
def name:String
def jName = name.replace('.', '/') match {
case "scala/AnyRef" => "java/lang/Object"
case s => s
}
def jDesc:String = name match
{
case "scala.Array" => null
case "scala.Long" => "J"
case "scala.Int" => "I"
case "scala.Short" => "S"
case "scala.Byte" => "B"
case "scala.Double" => "D"
case "scala.Float" => "F"
case "scala.Boolean" => "Z"
case "scala.Unit" => "V"
case _ => "L"+jName+";"
}
}
case class TypeRefType(owner:TypeRef, sym:SymbolRef, typArgs:List[TypeRef]) extends TypeRef with SMethodType
{
def params = List()
def returnType = this
def name = sym.full
override def jDesc = name match
{
case "scala.Array" => "["+typArgs(0).jDesc
case _ => super.jDesc
}
}
case class ThisType(sym:SymbolRef) extends TypeRef
{
def name = sym.full
}
case class SingleType(owner:TypeRef, sym:SymbolRef) extends TypeRef
{
def name = sym.full
}
case object NoType extends TypeRef
{
def name = "<no type>"
}
class SigEntry(val start:Int, val bytes:Bytes)
{
def id() = bytes.bytes(start)
def delete()
{
bytes.bytes(start) = 3
}
}
}
case class ScalaSignature(major:Int, minor:Int, table:Array[SigEntry], bytes:Bytes)
{
def evalS(i:Int):String = {
val e = table(i)
val bc = e.bytes
val bcr = bc:ByteCodeReader
return e.id match
{
case 1|2 => bcr.readString(bc.len)
case 3 => NoSymbol.full
case 9|10 => {
var s = evalS(bcr.readNat)
if(bc.pos+bc.len > bcr.pos)
s = evalS(bcr.readNat)+"."+s
s
}
}
}
def evalT[T](i:Int):T = eval(i).asInstanceOf[T]
def evalList[T](bcr:ByteCodeReader) =
{
var l = MList[T]()
while(bcr.more)
l+=evalT(bcr.readNat)
l.toList
}
def eval(i:Int):Any = {//we only parse the ones we actually care about
val e = table(i)
val bc = e.bytes
val bcr = bc:ByteCodeReader
return e.id match
{
case 1|2 => evalS(i)
case 6 => ClassSymbol(evalS(bcr.readNat), evalT(bcr.readNat), bcr.readNat, bcr.readNat)
case 8 => MethodSymbol(evalS(bcr.readNat), evalT(bcr.readNat), bcr.readNat, bcr.readNat)
case 9|10 => ExternalSymbol(evalS(i))
case 11|12 => NoType //12 is actually NoPrefixType (no lower bound)
case 13 => ThisType(evalT(bcr.readNat))
case 14 => SingleType(evalT(bcr.readNat), evalT(bcr.readNat))
case 16 => TypeRefType(evalT(bcr.readNat), evalT(bcr.readNat), evalList(bcr))
case 19 => ClassType(evalT(bcr.readNat), evalList(bcr))
case 20 => MethodType(evalT(bcr.readNat), evalList(bcr))
case 21|48 => ParameterlessType(evalT(bcr.readNat))//48 is actually a bounded super type, but it should work fine for our purposes
case _ => NoSymbol
}
}
}
class ByteCodeReader(val bc:Bytes)
{
var pos = bc.pos
def more() = pos < bc.pos+bc.len
def readString(len:Int) = advance(len)(new String(bc.bytes drop pos take len))
def readByte() = advance(1)(bc.bytes(pos))
def readNat():Int =
{
var r = 0
var b = 0
do
{
b = readByte
r = r<<7|b&0x7F
}
while((b&0x80) != 0)
return r
}
def advance[A](len:Int)(r:A):A =
{
if(pos+len > bc.pos+bc.len)
throw new IllegalArgumentException("Ran off the end of bytecode")
pos+=len
return r
}
def readEntry() =
{
val p = pos
val tpe:Int = readByte
val len = readNat
advance(len)(new SigEntry(p, new Bytes(bc.bytes, pos, len)))
}
def readSig() =
{
val major = readByte
val minor = readByte
val table = new Array[SigEntry](readNat)
for(i <- 0 until table.size)
table(i) = readEntry
ScalaSignature(major, minor, table, bc)
}
}
object ScalaSigReader
{
def decode(s:String):Array[Byte] =
{
val bytes = s.getBytes
return bytes take ByteCodecs.decode(bytes)
}
def encode(b:Array[Byte]):String =
{
val bytes = ByteCodecs.encode8to7(b)
var i = 0
while(i < bytes.length)
{
bytes(i) = ((bytes(i)+1)&0x7F).toByte
i+=1
}
return new String(bytes.take(bytes.length-1), "UTF-8")
}
def read(ann:AnnotationNode):ScalaSignature = Bytes(decode(ann.values.get(1).asInstanceOf[String])).readSig
def write(sig:ScalaSignature, ann:AnnotationNode) = ann.values.set(1, encode(sig.bytes.bytes))
def ann(cnode:ClassNode):Option[AnnotationNode] = cnode.visibleAnnotations match {
case null => None
case a => a.find(ann => ann.desc.equals("Lscala/reflect/ScalaSignature;"))
}
}
class ScalaSigSideTransformer
{
def transform(ann:AnnotationNode, cnode:ClassNode, removedFields:JList[FieldNode], removedMethods:JList[MethodNode])
{
if(removedFields.isEmpty && removedMethods.isEmpty)
return
val remFields = removedFields.asScala.map(f => (f.name, f.desc.replace('$', '/')))
val remMethods = removedMethods.asScala.map(f => (f.name, f.desc.replace('$', '/')))
val sig = ScalaSigReader.read(ann)
for(i <- 0 until sig.table.length)
{
val e = sig.table(i)
if(e.id == 8)//check and remove
{
val sym:MethodSymbol = sig.evalT(i)
if(sym.isAccessor)
{
val fName = if(sym.name.endsWith("_$eq")) sym.name.substring(0, sym.name.length-4) else sym.name
if(remFields.find(t => t._1 == sym.name.trim).nonEmpty)
{
e.delete()
val it = cnode.methods.iterator
while(it.hasNext)
{
val m = it.next
if(m.name == sym.name && m.desc == sym.jDesc(sig))
it.remove
}
}
}
else if(sym.isMethod)
{
if(remMethods.find(t => t._1 == sym.name && t._2 == sym.jDesc(sig)).nonEmpty)
e.delete()
}
else//field
{
if(remFields.find(t => t._1 == sym.name.trim).nonEmpty)
e.delete()
}
}
}
ScalaSigReader.write(sig, ann)
}
}
@@ -0,0 +1,299 @@
package codechicken.multipart.asm
import scala.collection.mutable.{Map => MMap, ListBuffer => MList}
import org.objectweb.asm.tree._
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.Type
import org.objectweb.asm.Type._
import scala.collection.JavaConversions._
object StackAnalyser
{
trait StackEntry
{
def getType:Type
}
case class This(owner:Type) extends StackEntry
{
def getType = owner
}
case class Param(i:Int, t:Type) extends StackEntry
{
def getType = t
}
case class Const(c:Any) extends StackEntry
{
def getType = c match {
case o:Byte => BYTE_TYPE
case o:Short => SHORT_TYPE
case o:Int => INT_TYPE
case o:Long => LONG_TYPE
case o:Float => FLOAT_TYPE
case o:Double => DOUBLE_TYPE
case o:Char => CHAR_TYPE
case o:Boolean => BOOLEAN_TYPE
case o:String => getObjectType("java/lang/String")
case null => getObjectType("java/lang/Object")
case _ => throw new IllegalArgumentException("Unknown const "+c)
}
}
case class UnaryOp(op:Int, e:StackEntry) extends StackEntry
{
def getType = e.getType
}
case class BinaryOp(op:Int, e2:StackEntry, e1:StackEntry) extends StackEntry
{
def getType = e1.getType
}
case class PrimitiveCast(e:StackEntry, t:Type) extends StackEntry
{
def getType = t
}
case class ReturnAddress() extends StackEntry
{
def getType = INT_TYPE
}
case class GetField(obj:StackEntry, field:FieldInsnNode) extends StackEntry
{
def getType = Type.getType(field.desc)
}
case class Invoke(op:Int, params:Array[StackEntry], obj:StackEntry, method:MethodInsnNode) extends StackEntry
{
def getType = Type.getMethodType(method.desc).getReturnType
}
case class New(t:Type) extends StackEntry
{
def getType = t
}
case class NewArray(len:StackEntry, t:Type) extends StackEntry
{
def getType = t
}
case class ArrayLength(array:StackEntry) extends StackEntry
{
def getType = INT_TYPE
}
case class ArrayLoad(index:StackEntry, e:StackEntry) extends StackEntry
{
def getType = e.getType.getElementType
}
case class Cast(obj:StackEntry, t:Type) extends StackEntry
{
def getType = t
}
case class NewMultiArray(sizes:Array[StackEntry], t:Type) extends StackEntry
{
def getType = t
}
case class CaughtException(t:Type) extends StackEntry
{
def getType = t
}
}
class StackAnalyser(val owner:Type, val m:MethodNode)
{
import StackAnalyser._
val stack = MList[StackEntry]()
val locals = MList[StackEntry]()
private val catchHandlers = MMap[LabelNode, TryCatchBlockNode]()
{
if((m.access & ACC_STATIC) == 0)
pushL(This(owner))
val ptypes = getArgumentTypes(m.desc)
for(i <- 0 until ptypes.length)
pushL(Param(i, ptypes(i)))
m.tryCatchBlocks.foreach(b => catchHandlers.put(b.handler, b))
}
def pushL(entry:StackEntry) = setL(locals.size, entry)
def setL(i:Int, entry:StackEntry) =
{
while(i+entry.getType.getSize > locals.size) locals += null
locals(i) = (entry)
if(entry.getType.getSize == 2)
locals(i+1) = entry
}
def push(entry:StackEntry) = insert(0, entry)
def pop(i:Int = 0) =
{
val e = stack.remove(stack.size-i-1)
if(e.getType.getSize == 2)
{
if(peek(i) != e) throw new IllegalStateException("Wide stack entry elems don't match ("+e+","+peek(i))
stack.remove(i)
}
e
}
def peek(i:Int = 0) = stack(stack.size-i-1)
def insert(i:Int, entry:StackEntry)
{
if(entry.getType.getSize == 0)
return
stack.insert(stack.size-i, entry)
if(entry.getType.getSize == 2)
stack.insert(stack.size-i, entry)
}
def popArgs(desc:String) =
{
val t = getType(desc)
val args = new Array[StackEntry](t.getArgumentTypes.length)
for(i <- 0 until args.length)
args(args.length-i-1) = pop()
args
}
def visitInsn(ainsn:AbstractInsnNode)
{
ainsn match {
case insn:InsnNode => ainsn.getOpcode match
{
case ACONST_NULL => push(Const(null))
case ICONST_M1 => push(Const(-1))
case ICONST_0 => push(Const(0))
case ICONST_1 => push(Const(1))
case ICONST_2 => push(Const(2))
case ICONST_3 => push(Const(3))
case ICONST_4 => push(Const(4))
case ICONST_5 => push(Const(5))
case LCONST_0 => push(Const(0L))
case LCONST_1 => push(Const(1L))
case FCONST_0 => push(Const(0F))
case FCONST_1 => push(Const(1F))
case FCONST_2 => push(Const(2F))
case DCONST_0 => push(Const(0D))
case DCONST_1 => push(Const(1D))
case i if(i >= IALOAD && i <= SALOAD) =>
push(ArrayLoad(pop(), pop()))
case i if(i >= IASTORE && i <= SASTORE) =>
pop(); pop(); pop()
case POP => pop()
case POP2 => pop(); pop()
case DUP => push(peek())
case DUP_X1 => insert(2, peek())
case DUP_X2 => insert(3, peek())
case DUP2 => push(peek(1)); push(peek(1))
case DUP2_X1 => insert(3, peek(1)); insert(3, peek())
case DUP2_X2 => insert(4, peek(1)); insert(4, peek())
case SWAP => push(pop(1))
case i if(i >= IADD && i <= DREM) =>
push(BinaryOp(i, pop(), pop()))
case i if(i >= INEG && i <= DNEG) =>
push(UnaryOp(i, pop()))
case i if(i >= ISHL && i <= LXOR) =>
push(BinaryOp(i, pop(), pop()))
case L2I|F2I|D2I => push(PrimitiveCast(pop(), DOUBLE_TYPE))
case I2L|F2L|D2L => push(PrimitiveCast(pop(), LONG_TYPE))
case I2F|L2F|D2F => push(PrimitiveCast(pop(), FLOAT_TYPE))
case I2D|L2D|F2D => push(PrimitiveCast(pop(), DOUBLE_TYPE))
case I2B => push(PrimitiveCast(pop(), BYTE_TYPE))
case I2C => push(PrimitiveCast(pop(), CHAR_TYPE))
case I2S => push(PrimitiveCast(pop(), SHORT_TYPE))
case i if(i >= LCMP && i <= DCMPG) =>
push(BinaryOp(i, pop(), pop()))
case i if(i >= IRETURN && i <= ARETURN) => pop()
case ARRAYLENGTH => push(ArrayLength(pop()))
case ATHROW => pop()
case MONITORENTER|MONITOREXIT => pop()
case _ =>
}
case iinsn:IntInsnNode => ainsn.getOpcode match
{
case BIPUSH => push(Const(iinsn.operand.toByte))
case SIPUSH => push(Const(iinsn.operand.toShort))
}
case ldcinsn:LdcInsnNode => ainsn.getOpcode match
{
case LDC => push(Const(ldcinsn.cst))
}
case vinsn:VarInsnNode => ainsn.getOpcode match
{
case i if(i >= ILOAD && i <= ALOAD) =>
push(locals(vinsn.`var`))
case i if(i >= ISTORE && i <= ASTORE) =>
setL(vinsn.`var`, pop())
}
case incinsn:IincInsnNode => ainsn.getOpcode match
{
case IINC => setL(incinsn.`var`, BinaryOp(IINC, Const(incinsn.incr), locals(incinsn.`var`)))
}
case jinsn:JumpInsnNode => ainsn.getOpcode match
{
case i if(i >= IFEQ && i <= IFLE) => pop()
case i if(i >= IF_ICMPEQ && i <= IF_ACMPNE) => pop(); pop()
case JSR => push(ReturnAddress())
case IFNULL|IFNONNULL => pop()
case GOTO =>
}
case sinsn:TableSwitchInsnNode => pop()
case linsn:LookupSwitchInsnNode => pop()
case finsn:FieldInsnNode => ainsn.getOpcode match
{
case GETSTATIC => push(GetField(null, finsn))
case PUTSTATIC => pop()
case GETFIELD => push(GetField(pop(), finsn))
case PUTFIELD => pop(); pop()
}
case minsn:MethodInsnNode => ainsn.getOpcode match
{
case INVOKEVIRTUAL|INVOKESPECIAL|INVOKEINTERFACE =>
push(Invoke(ainsn.getOpcode, popArgs(minsn.desc), pop(), minsn))
case INVOKESTATIC =>
push(Invoke(ainsn.getOpcode, popArgs(minsn.desc), null, minsn))
}
case tinsn:TypeInsnNode => ainsn.getOpcode match
{
case NEW => push(New(getType(tinsn.desc)))
case NEWARRAY => push(NewArray(pop(), getType(tinsn.desc)))
case ANEWARRAY => push(NewArray(pop(), getType("["+tinsn.desc)))
case CHECKCAST => push(Cast(pop(), getType(tinsn.desc)))
case INSTANCEOF => push(UnaryOp(INSTANCEOF, pop()))
}
case mainsn:MultiANewArrayInsnNode =>
{
val sizes = new Array[StackEntry](mainsn.dims)
for(i <- 0 until sizes.length)
sizes(i) = pop()
push(NewMultiArray(sizes, getType(mainsn.desc)))
}
/*case fnode:FrameNode => fnode.`type` match
{
case F_NEW|F_FULL => println("reset stacks/locals")
case F_APPEND => println("add local")
case F_CHOP => println("rem locals")
case F_SAME => println("reset")
case F_SAME1 => println("reset locals and all but bottom stack")
}*/
case lnode:LabelNode =>
{
catchHandlers.get(lnode) match
{
case Some(tcblock) => push(CaughtException(Type.getType(tcblock.`type`)))
case None =>
}
}
case _ =>
}
}
}