From ffa0918f4bf6e94bc9ca6256cc262e294cbf306a Mon Sep 17 00:00:00 2001 From: Chicken Bones Date: Thu, 27 Jun 2013 06:06:04 +1000 Subject: [PATCH] Completed ASM Mixin compiler and factory. --- .../multipart/MultipartGenerator.scala | 4 +- codechicken/multipart/TileMultipart.scala | 6 +- .../multipart/asm/ASMMixinCompiler.scala | 551 ++++++++++++++++++ .../multipart/asm/ASMMixinFactory.scala | 311 ++++++++++ codechicken/multipart/asm/ByteCodecs.scala | 213 +++++++ .../multipart/asm/ScalaSignature.scala | 342 +++++++++++ codechicken/multipart/asm/StackAnalyser.scala | 299 ++++++++++ 7 files changed, 1723 insertions(+), 3 deletions(-) create mode 100644 codechicken/multipart/asm/ASMMixinCompiler.scala create mode 100644 codechicken/multipart/asm/ASMMixinFactory.scala create mode 100644 codechicken/multipart/asm/ByteCodecs.scala create mode 100644 codechicken/multipart/asm/ScalaSignature.scala create mode 100644 codechicken/multipart/asm/StackAnalyser.scala diff --git a/codechicken/multipart/MultipartGenerator.scala b/codechicken/multipart/MultipartGenerator.scala index 6647d86..bad53a1 100644 --- a/codechicken/multipart/MultipartGenerator.scala +++ b/codechicken/multipart/MultipartGenerator.scala @@ -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 diff --git a/codechicken/multipart/TileMultipart.scala b/codechicken/multipart/TileMultipart.scala index a3bc783..58d1252 100644 --- a/codechicken/multipart/TileMultipart.scala +++ b/codechicken/multipart/TileMultipart.scala @@ -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 diff --git a/codechicken/multipart/asm/ASMMixinCompiler.scala b/codechicken/multipart/asm/ASMMixinCompiler.scala new file mode 100644 index 0000000..b972492 --- /dev/null +++ b/codechicken/multipart/asm/ASMMixinCompiler.scala @@ -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, "", "()V", null, null) + minit.visitVarInsn(ALOAD, 0) + minit.visitMethodInsn(INVOKESPECIAL, cnode.superName, "", "()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("")) + throw new IllegalArgumentException("Static initialisers are not permitted "+cnode.name+" as a multipart trait") + + if(mnode.name.equals("")) + { + 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("") && + 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)) + } + + +} \ No newline at end of file diff --git a/codechicken/multipart/asm/ASMMixinFactory.scala b/codechicken/multipart/asm/ASMMixinFactory.scala new file mode 100644 index 0000000..4358160 --- /dev/null +++ b/codechicken/multipart/asm/ASMMixinFactory.scala @@ -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, "", "()V", null, null) + mv.visitCode() + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKESPECIAL, superName, "", "()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, "", "()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, "", "()V", null, null) + mv.visitCode() + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "", "()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 +} \ No newline at end of file diff --git a/codechicken/multipart/asm/ByteCodecs.scala b/codechicken/multipart/asm/ByteCodecs.scala new file mode 100644 index 0000000..d52c4a4 --- /dev/null +++ b/codechicken/multipart/asm/ByteCodecs.scala @@ -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) + } +} \ No newline at end of file diff --git a/codechicken/multipart/asm/ScalaSignature.scala b/codechicken/multipart/asm/ScalaSignature.scala new file mode 100644 index 0000000..79fe44d --- /dev/null +++ b/codechicken/multipart/asm/ScalaSignature.scala @@ -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 = "" + 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 = "" + } + + 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) + } +} \ No newline at end of file diff --git a/codechicken/multipart/asm/StackAnalyser.scala b/codechicken/multipart/asm/StackAnalyser.scala new file mode 100644 index 0000000..957301e --- /dev/null +++ b/codechicken/multipart/asm/StackAnalyser.scala @@ -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 _ => + } + } +} \ No newline at end of file