├── .gitignore ├── build.sbt ├── project └── build.properties ├── readme.md └── src ├── main ├── java │ └── metascala │ │ └── patches │ │ └── java │ │ └── lang │ │ └── reflect │ │ └── Array.java └── scala │ └── metascala │ ├── Heap.scala │ ├── Main.scala │ ├── VM.scala │ ├── Virtualizer.scala │ ├── imm │ ├── Model.scala │ ├── Type.scala │ └── package.scala │ ├── natives │ ├── Bindings.scala │ ├── Default.scala │ └── package.scala │ ├── opcodes │ ├── Conversion.scala │ ├── ConvertInsn.scala │ ├── Insn.scala │ └── package.scala │ ├── package.scala │ └── rt │ ├── Cls.scala │ ├── Method.scala │ ├── Obj.scala │ ├── Thread.scala │ └── package.scala └── test ├── java └── metascala │ ├── features │ ├── arrays │ │ └── MultiDimArrays.java │ ├── classes │ │ └── Inheritance.java │ ├── controlflow │ │ ├── IfElse.java │ │ ├── Loops.java │ │ └── Switches.java │ ├── exceptions │ │ └── Exceptions.java │ └── methods │ │ └── Statics.java │ └── full │ └── Sudoku.java └── scala └── metascala ├── Util.scala ├── features ├── ArrayTest.scala ├── CastingTest.scala ├── ClassesTest.scala ├── ControlFlowTest.scala ├── ExceptionTest.scala ├── IOTest.scala ├── MathTest.scala └── MethodTest.scala ├── full ├── ClassTest.scala ├── GCTests.scala ├── JavaLibTest.scala ├── MetacircularTest.scala ├── ReflectTests.scala └── ScalaLib.scala └── imm ├── Misc.scala └── Type.scala /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.idea_modules/ 3 | /project/project/ 4 | target/ 5 | lib_managed/ 6 | src_managed/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | organization := "com.example" 2 | version := "0.1" 3 | scalaVersion := "2.11.11" 4 | 5 | libraryDependencies ++= Seq( 6 | "org.ow2.asm" % "asm-debug-all" % "5.1", 7 | "org.scalatest" %% "scalatest" % "3.0.1" % "test", 8 | "org.mozilla" % "rhino" % "1.7R4" 9 | ) 10 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Metascala 2 | 3 | Metascala is a tiny [metacircular](http://en.wikipedia.org/wiki/Metacircular) [Java Virtual Machine (JVM)](http://en.wikipedia.org/wiki/Jvm) written in the [Scala](http://tinyurl.com/6etjds) programming language. Metascala is barely 3000 lines of Scala, and is complete enough that it is able to interpret itself metacircularly. Being written in Scala and compiled to [Java bytecode](http://en.wikipedia.org/wiki/Java_bytecode), the Metascala JVM requires a host JVM in order to run. 4 | 5 | The goal of Metascala is to create a platform to experiment with the JVM: a 3000 line JVM written in Scala is probably much more approachable than the 1,000,000 lines of C/C++ which make up [HotSpot](http://openjdk.java.net/groups/hotspot/), the standard implementation, and more amenable to implementing fun features like [continuations](http://en.wikipedia.org/wiki/Continuation), [isolates](http://www.javalobby.org/java/forums/t105978.html) or [value classes](https://blogs.oracle.com/jrose/entry/value_types_in_the_vm). The 3000 lines of code gives you: 6 | 7 | - The bytecode interpreter, together with all the run-time data structures 8 | - A [stack-machine](http://en.wikipedia.org/wiki/Stack_machine) to [SSA](http://en.wikipedia.org/wiki/Static_single_assignment_form) register-machine bytecode translator 9 | - A custom [heap](https://en.wikipedia.org/wiki/Memory_management), complete with a stop-the-world, copying [garbage collector](http://tinyurl.com/d77yltz) 10 | - Implementations of parts of the [JVM's native interface](http://en.wikipedia.org/wiki/Java_Native_Interface) 11 | 12 | Although it is far from a complete implementation, Metascala already provides the ability to run untrusted bytecode securely (albeit slowly), since every operation which could potentially cause harm (including memory allocations and CPU usage) is virtualized and can be controlled. [Ongoing work](#ongoing-work) includes tightening of the security guarantees, improving compatibility and increasing performance. 13 | 14 | # Getting Started 15 | 16 | Metascala requires [Scala 2.10](http://www.scala-lang.org/downloads) and is built using [SBT 12](http://www.scala-sbt.org/). After checking out the repository, if you have SBT installed, all you need to do is run 17 | 18 | ``` 19 | sbt 20 | > test-only metascala.features.* 21 | ``` 22 | 23 | Which will download the dependencies (currently just [asm](http://asm.ow2.org/)), compile the code, and run the unit tests in the [test/scala/features](test/scala/features) folder. Compiling Metascala could take a minute, but running the unit tests should take less than 10 seconds. These tests exercise individual pieces of functionality available on the JVM: math, methods, classes, exceptions, etc., and verify that the result of executing a method via Metascala is identical to the result of executing it directly via [reflection](http://docs.oracle.com/javase/tutorial/reflect/). 24 | 25 | Apart from the basic feature tests, metascala also includes basic tests for the [garbage collector](src/test/scala/metascala/full/GCTests.scala), [Java](src/test/scala/metascala/full/JavaLibTest.scala) and [Scala](src/test/scala/metascala/full/ScalaLib.scala) library functionality. Lastly, Metascala contains tests for [Meta-interpretation](src/test/scala/metascala/full/MetacircularTest.scala), which tests the ability for Metascala to interpret its own source code, which in turn interprets some simple programs (e.g. a square-root calculator). Meta-interpretation is extremely slow, and these tests take **several minutes** to run. 26 | 27 | ## Implementation 28 | 29 | Metascala is a simple Scala application, and compiles to Java bytecode like any other Scala program. It is literally a program that loads in a class file, parses it into a data structure and then has a `while(true)` loop that interprets the bytecodes one by one, updating the internal state of the VM following the [JVM Spec](http://docs.oracle.com/javase/specs/jvms/se7/html/) and spitting out an answer at the end. 30 | 31 | In fact, each Metascala JVM is a single Java object, containing in itself all state relevant to its own computation. Instantiating one and invoking methods using it is simple: 32 | 33 | ```scala 34 | val x = new metascala.VM() 35 | x.invoke("metascala.features.arrays.ArrayStuff", "bubbleSort", Seq(Array(6, 5, 2, 7, 3, 4, 9, 1, 8))) 36 | // Array(1, 2, 3, 4, 5, 6, 7, 8, 9) 37 | ``` 38 | 39 | Arguments passed into the `invoke()` method are converted from their real representation into virtual versions (the `vrt.*` classes) to be handled within the Metascala VM. The return value is similarly converted from a virtual value back to a real value before being given to the caller of `invoke()`. 40 | 41 | The main packages of interest in Metascala are: 42 | 43 | ### [metascala/imm](src/main/scala/metascala/imm) 44 | An immutable model of the data structures that make up a Java .class file. These are an almost direct conversion of the data structures provided by the [ASM Tree API](http://www.geekyarticles.com/2011/10/manipulating-java-class-files-with-asm_13.html), converted to idiomatic, immutable Scala case classes. These classes should be purely immutable, and should have no dependency on the rest of Metascala. 45 | 46 | ### [metascala/opcodes](src/main/scala/metascala/opcodes) 47 | This contains the logic related to [parsing and compiling](src/main/scala/metascala/opcodes/Conversion.scala) the Java bytecode instructions from the default hybrid stack/register format into an [SSA bytecode](src/main/scala/metascala/opcodes/Insn.scala), simplifying it in the process. Currently Metascala does not perform any real optimizations on the bytecode apart from linking up class/method names with the relevant classes and methods, but the [SSA](http://en.wikipedia.org/wiki/Static_single_assignment_form) format should make it easier to perform these operations in the future. 48 | 49 | ### [metascala/rt](src/main/scala/metascala/rt) 50 | Runtime data-structures that make up the JVM: [threads](src/main/scala/metascala/rt/Thread.scala), [classes](src/main/scala/metascala/rt/Cls.scala), [methods](src/main/scala/metascala/rt/Method.scala), [objects and arrays](src/main/scala/metascala/rt/Obj.scala). These classes also contain the mutable state associated with these constructs (e.g. static class fields) or Metascala-specific optimizations (e.g. pre-computed [vtables](http://en.wikipedia.org/wiki/Virtual_method_table)). 51 | 52 | ### [metascala/natives](src/main/scala/metascala/natives) 53 | Trapped methods, or [Bindings](src/main/scala/metascala/Bindings.scala), which when called within the Metascala VM result in some interaction with the Host VM. There is a [default](src/main/scala/metascala/Default.scala) implementation of bindings, but it can easily be swapped out for a custom version of Bindings e.g. to redirect filesystem access, or mock out `currentTimeMillis()` with a custom time. All interaction between the code inside the VM and the external world takes place through these Bindings. 54 | 55 | --------------------------------------------------------- 56 | 57 | Many concepts have classes in several of these packages representing them. For example, the abstract idea of a Java "Class" is modelled by: 58 | 59 | - `imm.Cls`: the immutable, VM-independent representation of a class parsed from a class file 60 | - `imm.Type.Cls`: a Type representing a Class signature. This contains the qualified name of the class (e.g. `"java.lang.String"`), which may or may not exist, and is also immutable 61 | - `rt.Cls`: the runtime representation of a class, with its mutable state (static fields) and VM-specific optimizations (e.g. vtables) 62 | 63 | These types are always referred to by their qualified names in the source code (i.e. `imm.Cls` rather than simply `Cls`) in order to avoid confusion between them or name collisions. 64 | 65 | ## Compatibility 66 | 67 | Metascala implements a subset of the [Java Virtual Machine Specification](http://docs.oracle.com/javase/specs/jvms/se7/html/). The implementation has been mostly focused on the features that Metascala needs to run. However, Metascala does not require (and hence does not implement) several things such as: 68 | 69 | - **Multiple Threads** 70 | - **Custom ClassLoaders** 71 | - **Enforcement of Access-Control modifiers** 72 | 73 | Apart from the language specification, there is a large amount of functionality in the JVM which is from *native* methods. These are required for the JVM to interact with the outside world in any way, and again Metascala only implements those which were necessary to interpret itself, leaving out a lot of other basic things such as 74 | 75 | - **Filesystem Access** 76 | - **Network Access** 77 | - **System.out.println** (`scala.Predef.println` works though!) 78 | 79 | Nonetheless, Metascala is compatible enough to interpret itself: a moderately sized Scala program which makes heavy use of the standard library, some basic reflection, and some external Java libraries (Basically only [ASM](http://asm.ow2.org/) right now). 80 | 81 | MetaScala has been tested on Windows 7 using the Sun JVM (Java 7), and Ubuntu 12.04 using OpenJDK 7. 82 | 83 | ## Performance 84 | 85 | The performance of Metascala is absolutely abysmal: it performs basic optimizations (e.g. pre-calculating virtual-dispatch tables and maintaining invoke-interface caches), but not much more. This is partly because it is written in Scala in a mostly immutable, functional style, and that results in overhead (lots of extra allocations and method calls) over an imperative, mutable style. The other part is probably a fundamental limitation of being an interpreter, and any major increase in performance would require pretty fundamental changes to the system. 86 | 87 | This can easily be improved a great deal in the future: micro-bottlenecks (e.g. for-comprehensions) can be optimized, and the SSA bytecode is amenable to analysis and optimization. Nonetheless, my focus so far has been on completeness and compliance; performance optimizations will have to wait. 88 | 89 | ## Security 90 | 91 | Part of Metascala's goal is to allow the developer to safely run arbitrary untrusted bytecode. Metascala's approach to security is completely separate from the JVM's existing security model. In short: everything is virtualized, and everything is controlled. Because it reads and interprets each and every bytecode one by one, code executed with the Metascala VM cannot: 92 | 93 | - **Loop forever**: Metascala can simply stop the interpretation after a certain amount of bytecodes have been utilized. 94 | - **Allocate unbounded memory**: code run with a Metascala VM runs on its own heap (basically one big byte array), with its own garbage collector. No matter how many allocations it makes, it cannot allocate more memory than the Metascala VM's heap has available. 95 | - **Perform unsafe actions**: by default, all methods or instructions interpreted by the Metascala VM only affect the Metascala VM's internal state, and nothing else. Any attempts to influence the outside world (e.g. by printing debug statements, or loading data from files) has to happen through an explicitly created Bindings object. This single-point-of-entry makes it easy to confidently and completely lock down the untrusted code. 96 | 97 | There are still some weaknesses in Metascala's security model: time spent garbage collecting isn't accounted for, neither is memory not directly allocated but required by the VM's auxiliary data structures (e.g. classes). These provide an attacker means to consume more resources than they should be allowed to, and solving this is part of the ongoing work. 98 | 99 | ## Ongoing Work 100 | 101 | Immediate work includes: 102 | 103 | - Fleshing out the completeness of the Java implementation: multiple Threads, ClassLoaders, Filesystem access, enough to run some standard Java benchmarks and applications like [Rhino Javascript](https://developer.mozilla.org/en/docs/Rhino) or the [Scala Compiler](https://github.com/scala/scala). 104 | - Moving more of the VM's runtime functionality data-structures inside of the Metascala VM, rather than outside. For example, making the garbage collector run inside the VM, or having the class-related data structures inside the VM's heap, would allow better control of the VMs resource usage since they would count toward any bytecode/memory limits imposed on the VM. 105 | - Optimizing performance using macros. e.g. replacing for-loops with [macro-based for-loops](https://github.com/non/spire#syntax), or using macros to pre-compile the [Bindings](src/main/scala/metascala/natives/Bindings.scala) table so the work does not need to be done at run-time. Reducing the start-up cost and run-time overhead would help make Metascala a lightweight container for untrusted code. 106 | 107 | Feel free to contact me (below) or open an issue/send a pull request if you're interested and want to help out. Contributions are welcome! 108 | 109 | ## Fun Facts 110 | 111 | - At only 3000 lines of source code, Metascala is probably one of the smallest JVMs ever. 112 | - At 60 seconds to compile, it's probably also one of the slowest to compile, compiling at only 50 lines per second. 113 | - Metascala isn't a metacircular Java/Scala interpreter, because it is currently unable to interpret the Java/Scala compilers. 114 | - The number of native method bindings to the JVM is huge, and unlike the virtual machine specification, completely undocumented, although it is necessary to run basically anything. The only way to find out what natives are missing is to run stuff and see it crash when it encounters a missing native method. 115 | - The 90kb of source code gets compiled into 1800kb of binaries, an increase of 20x. Compiled Scala results in a lot of class files. 116 | 117 | ## Credits 118 | 119 | Copyright (c) 2013, Li Haoyi (haoyi.sg at gmail.com) 120 | 121 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 122 | 123 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 124 | 125 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 126 | -------------------------------------------------------------------------------- /src/main/scala/metascala/Heap.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | class Heap(memorySize: Int, 4 | getRoots: () => Seq[Ref], 5 | getLinks: (Int, Int) => Seq[Int]){ 6 | 7 | val memory = new Array[Int](memorySize * 2) 8 | var start = 0 9 | var freePointer = 1 10 | 11 | def allocate(n: Int)(implicit registrar: Registrar) = { 12 | if (freePointer + n > memorySize + start) { 13 | println("COLLECT LOL") 14 | collect(start) 15 | if (freePointer + n > memorySize + start) { 16 | throw new Exception("Out of Memory!") 17 | } 18 | } 19 | val newFree = freePointer 20 | freePointer += n 21 | val ref = new ManualRef(newFree) 22 | registrar(ref) 23 | ref 24 | } 25 | 26 | def apply(n: Int): Int = memory(n) 27 | def update(n: Int, v: Int) = memory.update(n, v) 28 | 29 | def dump(start: Int = start, freePointer: Int = freePointer) = { 30 | s"Heap $start to $freePointer\n" + 31 | memory.slice(start, freePointer) 32 | .grouped(10) 33 | .map(_.map(_.toString.padTo(4, ' '))) 34 | .map(_.mkString) 35 | .mkString("\n") 36 | } 37 | 38 | 39 | def blit(freePointer: Int, src: Int) = { 40 | // println(s"blit free:$freePointer, src:$src") 41 | if (src == 0 || memory(src) == 0) { 42 | (0, freePointer) 43 | } else if (memory(src) == 0){ 44 | throw new Exception("Can't point to nothing! " + src + " -> " + memory(src)) 45 | }else if (memory(src + 1) >= 0){ 46 | val headerSize = if (isObj(memory(src))) rt.Obj.headerSize else rt.Arr.headerSize 47 | val length = memory(src+1) + headerSize 48 | 49 | // println(s"blit obj length: ${obj.heapSize}") 50 | System.arraycopy(memory, src, memory, freePointer, length) 51 | memory(src + 1) = -freePointer 52 | (freePointer, freePointer + length) 53 | 54 | 55 | }else{ 56 | // println(s"non-blitting $src -> ${-memory(src + 1)}") 57 | (-memory(src + 1), freePointer) 58 | } 59 | } 60 | def collect(from: Int){ 61 | val to = (from + memorySize) % (2 * memorySize) 62 | for(i <- to until to+memorySize){ 63 | memory(i) = 0 64 | } 65 | println("===============Collecting==================") 66 | //vm.threads(0).threadStack.map(x => x.runningClass.name + "/" + x.method.sig + "\t" + x.method.code.blocks(x.pc._1).insns(x.pc._2)).foreach(println) 67 | println("starting " + (freePointer - from)) 68 | // println(dump()) 69 | val roots = getRoots() 70 | // println(roots.map(_())) 71 | // println(s"allRoots ${roots.map(_())}") 72 | 73 | var scanPointer = 0 74 | if(from == 0){ 75 | freePointer = memorySize + 1 76 | scanPointer = memorySize + 1 77 | }else{ // to == memorySize 78 | start = 0 79 | freePointer = 1 80 | scanPointer = 1 81 | } 82 | 83 | for(root <- roots){ 84 | val oldRoot = root() 85 | val (newRoot, nfp) = blit(freePointer, oldRoot) 86 | freePointer = nfp 87 | // println() 88 | // println("Moving Root\t" + oldRoot + "\t" + newRoot) 89 | 90 | root() = newRoot 91 | // println(memory.drop(newRoot / 10 * 10).take(10).toList) 92 | // println(dump()) 93 | 94 | } 95 | // println("Done With Roots") 96 | while(scanPointer != freePointer){ 97 | // println() 98 | // println("Scanning " + scanPointer + "\t" + memory(scanPointer)) 99 | 100 | // println(memory.drop(scanPointer / 10 * 10).take(10).toList) 101 | // println(dump()) 102 | assert(scanPointer <= freePointer, s"scanPointer $scanPointer > freePointer $freePointer") 103 | 104 | val links = getLinks(memory(scanPointer), memory(scanPointer+1)) 105 | val length = memory(scanPointer + 1) + rt.Obj.headerSize 106 | 107 | for(i <- links){ 108 | val (newRoot, nfp) = blit(freePointer, memory(scanPointer + i)) 109 | memory(scanPointer + i) = newRoot 110 | freePointer = nfp 111 | } 112 | 113 | scanPointer += length 114 | } 115 | 116 | 117 | if (from == 0) start = memorySize 118 | else start = 0 119 | 120 | println("ending " + (freePointer - start)) 121 | 122 | println("==================Collectiong Compelete====================") 123 | // println(dump()) 124 | // println(roots.map(_())) 125 | // vm.getRoots 126 | //System.exit(0) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/metascala/Main.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | /** 4 | * Created by haoyi on 12/1/13. 5 | */ 6 | object Main { 7 | def main(args: Array[String]): Unit = { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/metascala/VM.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | import collection.mutable 4 | import annotation.tailrec 5 | import metascala.imm.Type 6 | import metascala.rt.{Obj, FrameDump, Thread} 7 | import metascala.natives.Bindings 8 | import metascala.imm.Type.Prim 9 | import org.objectweb.asm.ClassReader 10 | import org.objectweb.asm.tree.ClassNode 11 | 12 | 13 | 14 | 15 | /** 16 | * A Metascala VM. Call invoke() on it with a class, method name and arguments 17 | * to have it interpret some Java bytecode for you. It optionally takes in a set of 18 | * native bindings, as well as a logging function which it will use to log all of 19 | * its bytecode operations 20 | */ 21 | class VM(val natives: Bindings = Bindings.default, 22 | val insnLimit: Long = Long.MaxValue, 23 | val log: ((=>String) => Unit) = s => (), 24 | val memorySize: Int = 1 * 1024 * 1024) { 25 | 26 | private[this] implicit val vm = this 27 | 28 | var ready = false 29 | val internedStrings = mutable.Map[String, Int]() 30 | 31 | 32 | val heap = new Heap( 33 | memorySize, 34 | () => getRoots(), 35 | getLinks 36 | ) 37 | 38 | def alloc[T](func: Registrar => T) = { 39 | val tempRegistry = mutable.Set[Ref]() 40 | val res = func( 41 | new Registrar({ref => 42 | 43 | tempRegistry.add(ref) 44 | registry.add(ref) 45 | }, this) 46 | ) 47 | tempRegistry.map(registry.remove) 48 | res 49 | } 50 | 51 | val registry = mutable.Set[Ref]() 52 | 53 | 54 | 55 | 56 | val arrayTypeCache = mutable.Buffer[imm.Type](null) 57 | 58 | lazy val currentThread = { 59 | val thread = alloc(implicit r => 60 | "java/lang/Thread".allocObj( 61 | "group" -> "java/lang/ThreadGroup".allocObj(), 62 | "priority" -> 5 63 | ) 64 | )() 65 | interned.append(thread) 66 | thread 67 | } 68 | 69 | val interned = mutable.Buffer[Ref]() 70 | 71 | val typeObjCache = new mutable.HashMap[imm.Type, Ref] { 72 | override def apply(x: imm.Type) = this.getOrElseUpdate(x, 73 | vm.alloc( implicit r => 74 | "java/lang/Class".allocObj( 75 | "name" -> x.javaName.toVirtObj 76 | ) 77 | ) 78 | ) 79 | } 80 | 81 | ready = true 82 | def getLinks(tpe: Int, length: Int) = { 83 | if (isObj(tpe)){ 84 | for{ 85 | (x, i) <- ClsTable.clsIndex(-tpe).fieldList.zipWithIndex 86 | if x.desc.isRef 87 | } yield i + rt.Obj.headerSize 88 | } else{ 89 | if (arrayTypeCache(tpe).isRef){ 90 | for (i <- 0 until length) yield i + rt.Arr.headerSize 91 | }else Nil 92 | } 93 | } 94 | 95 | /** 96 | * Identify the list of all root object references within the virtual machine. 97 | */ 98 | def getRoots(): Seq[Ref] = { 99 | assert(ready) 100 | val stackRoots = for{ 101 | thread <- threads 102 | frame <- thread.threadStack 103 | (blockId, index) = frame.pc 104 | block = frame.method.code.blocks(blockId) 105 | (x, i) <- block.locals.zipWithIndex 106 | if x.isRef 107 | _ = if (frame.locals(i) == -1) println(frame.locals.toList, i) 108 | } yield new ArrRef(() => frame.locals(i), frame.locals(i) = _) 109 | 110 | // println(s"stackRoots ${stackRoots.map(_())}") 111 | 112 | val classRoots = for{ 113 | cls <- ClsTable.clsIndex.drop(1) 114 | } yield cls.statics.address 115 | 116 | val classRoots2 = for{ 117 | cls <- ClsTable.clsIndex.drop(1) 118 | i <- 0 until cls.staticList.length 119 | if cls.staticList(i).desc.isRef 120 | } yield new ArrRef( 121 | () => heap(cls.statics.address() + i + rt.Arr.headerSize), 122 | heap(cls.statics.address() + i + rt.Arr.headerSize) = _ 123 | ) 124 | 125 | val clsObjRoots = typeObjCache.values 126 | 127 | classRoots ++ classRoots2 ++ stackRoots ++ clsObjRoots ++ registry ++ interned 128 | 129 | } 130 | /** 131 | * Globally shared sun.misc.Unsafe object. 132 | */ 133 | lazy val theUnsafe = vm.alloc(rt.Obj.allocate("sun/misc/Unsafe")(_)) 134 | 135 | /** 136 | * Cache of all the classes loaded so far within the Metascala VM. 137 | */ 138 | implicit object ClsTable extends Cache[imm.Type.Cls, rt.Cls]{ 139 | val clsIndex = mutable.ArrayBuffer[rt.Cls](null) 140 | 141 | def calc(t: imm.Type.Cls): rt.Cls = { 142 | val input = natives.fileLoader( 143 | t.name + ".class" 144 | ).getOrElse( 145 | throw new Exception("Can't find " + t) 146 | ) 147 | val cr = new ClassReader(input) 148 | val classNode = new ClassNode() 149 | cr.accept(classNode, ClassReader.EXPAND_FRAMES) 150 | 151 | Option(classNode.superName).map(Type.Cls.read).map(vm.ClsTable) 152 | rt.Cls(classNode, clsIndex.length) 153 | } 154 | 155 | var startTime = System.currentTimeMillis() 156 | 157 | override def post(cls: rt.Cls) = { 158 | clsIndex.append(cls) 159 | } 160 | } 161 | 162 | def check(s: imm.Type, t: imm.Type): Boolean = { 163 | 164 | (s, t) match{ 165 | 166 | case (s: imm.Type.Cls, t: imm.Type.Cls) => s.cls.typeAncestry.contains(t) 167 | case (s: imm.Type.Arr, imm.Type.Cls("java/lang/Object")) => true 168 | case (s: imm.Type.Arr, imm.Type.Cls("java/lang/Cloneable")) => true 169 | case (s: imm.Type.Arr, imm.Type.Cls("java/io/Serializable")) => true 170 | case (imm.Type.Arr(imm.Type.Prim(a)), imm.Type.Arr(imm.Type.Prim(b))) => a == b 171 | case (imm.Type.Arr(sc: imm.Type), imm.Type.Arr(tc: imm.Type)) => check(sc, tc) 172 | case _ => false 173 | } 174 | } 175 | 176 | lazy val threads = List(new Thread()) 177 | 178 | def invoke(bootClass: String, mainMethod: String, args: Seq[Any] = Nil): Any = { 179 | println(s"Invoking VM with $bootClass.$mainMethod") 180 | 181 | val res = threads(0).invoke( 182 | imm.Type.Cls(bootClass), 183 | imm.Sig( 184 | mainMethod, 185 | imm.Type.Cls(bootClass) 186 | .cls 187 | .methods 188 | .find(x => x.sig.name == mainMethod) 189 | .map(_.sig.desc) 190 | .getOrElse(throw new IllegalArgumentException("Can't find method: " + mainMethod)) 191 | ), 192 | args 193 | ) 194 | res 195 | } 196 | 197 | def exec[T](thunk: => T): T = { 198 | val wrapped = () => thunk 199 | invoke(wrapped.getClass.getName.toSlash, "apply", Seq(wrapped)).cast[T] 200 | } 201 | println("Initialized VM") 202 | 203 | def resolveDirectRef(owner: Type.Cls, sig: imm.Sig)(implicit vm: VM): Option[rt.Method] = { 204 | 205 | val native = 206 | vm.natives 207 | .trapped 208 | .find(x => x.sig == sig && x.clsName == owner.name) 209 | 210 | val method = 211 | owner.cls 212 | .methods 213 | .find(_.sig == sig) 214 | 215 | 216 | native.orElse(method) 217 | } 218 | } 219 | 220 | class WrappedVmException(wrapped: Throwable) extends Exception(wrapped) 221 | case class UncaughtVmException(wrapped: Throwable) extends WrappedVmException(wrapped) 222 | case class InternalVmException(wrapped: Throwable) extends WrappedVmException(wrapped) 223 | 224 | /** 225 | * A generic cache, which provides pre-processing of keys and post processing of values. 226 | */ 227 | trait Cache[In, Out] extends (In => Out){ 228 | val cache = mutable.Map.empty[Any, Out] 229 | def pre(x: In): Any = x 230 | def calc(x: In): Out 231 | def post(y: Out): Unit = () 232 | def apply(x: In) = { 233 | val newX = pre(x) 234 | cache.get(newX) match{ 235 | case Some(y) => y 236 | case None => 237 | val newY = calc(x) 238 | cache(newX) = newY 239 | post(newY) 240 | newY 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /src/main/scala/metascala/Virtualizer.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | import scala.collection.mutable 3 | import imm.Type.Prim._ 4 | 5 | object Virtualizer { 6 | 7 | lazy val unsafe = { 8 | val field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe") 9 | field.setAccessible(true) 10 | val f = field.get(null) 11 | val g = f.asInstanceOf[sun.misc.Unsafe] 12 | g 13 | } 14 | 15 | def popVirtual(tpe: imm.Type, src: () => Val, refs: mutable.Map[Int, Any] = mutable.Map.empty)(implicit vm: VM): Any = { 16 | val x = tpe match { 17 | case V => () 18 | case p: imm.Type.Prim[_] => p.read(src) 19 | case _ => //reference type 20 | val address = src() 21 | if(address == 0) null 22 | else if (refs.contains(address)) refs(address) 23 | else tpe match{ 24 | case imm.Type.Cls("java/lang/Object") | imm.Type.Arr(_) if address.isArr => 25 | 26 | val tpe = vm.arrayTypeCache(vm.heap(address)) 27 | 28 | val clsObj = forName(tpe.name.toDot) 29 | val newArr = java.lang.reflect.Array.newInstance(clsObj, address.arr.arrayLength) 30 | 31 | for(i <- 0 until address.arr.arrayLength){ 32 | 33 | val cooked = tpe match{ 34 | case p: imm.Type.Prim[_] => p.read(reader(vm.heap.memory, address + rt.Arr.headerSize + i * tpe.size)) 35 | case x => popVirtual(tpe, reader(vm.heap.memory, address + rt.Arr.headerSize + i * tpe.size)) 36 | } 37 | java.lang.reflect.Array.set(newArr, i, cooked) 38 | } 39 | 40 | newArr 41 | case t @ imm.Type.Cls(name)=> 42 | val obj = unsafe.allocateInstance(Class.forName(address.obj.cls.name.toDot)) 43 | refs += (address -> obj) 44 | var index = 0 45 | for(field <- address.obj.cls.fieldList.distinct){ 46 | // workaround for http://bugs.sun.com/view_bug.do?bug_id=4763881 47 | if (field.name == "backtrace") index += 1 // just skip it 48 | else{ 49 | val f = getAllFields(obj.getClass).find(_.getName == field.name).get 50 | f.setAccessible(true) 51 | val popped = popVirtual(field.desc, reader(vm.heap.memory, address + rt.Obj.headerSize + index), refs) 52 | f.set(obj, popped ) 53 | index += field.desc.size 54 | } 55 | } 56 | obj 57 | 58 | } 59 | } 60 | 61 | x 62 | } 63 | 64 | def pushVirtual(thing: Any)(implicit registrar: Registrar): Seq[Int] = { 65 | val tmp = new mutable.Stack[Int]() 66 | pushVirtual(thing, tmp.push(_)) 67 | tmp.reverse 68 | } 69 | 70 | def pushVirtual(thing: Any, out: Val => Unit)(implicit registrar: Registrar): Unit = { 71 | 72 | implicit val vm = registrar.vm 73 | thing match { 74 | case null => out(0) 75 | case b: Boolean => Z.write(b, out) 76 | case b: Byte => B.write(b, out) 77 | case b: Char => C.write(b, out) 78 | case b: Short => S.write(b, out) 79 | case b: Int => I.write(b, out) 80 | case b: Float => F.write(b, out) 81 | case b: Long => J.write(b, out) 82 | case b: Double => D.write(b, out) 83 | case b: Array[_] => 84 | 85 | val tpe = imm.Type.Arr.read(b.getClass.getName.replace('.', '/')).innerType 86 | val arr = 87 | rt.Arr.allocate( 88 | tpe, 89 | b.flatMap(pushVirtual).map{x => 90 | val ref : Ref = new ManualRef(x) 91 | if (!b.getClass.getComponentType.isPrimitive) { 92 | registrar(ref) 93 | } 94 | ref 95 | } 96 | ) 97 | 98 | out(arr.address()) 99 | case b: Any => 100 | var index = 0 101 | val contents = mutable.Buffer.empty[Ref] 102 | val decFields = b.getClass.getDeclaredFields 103 | registrar.vm.log("push Object " + b.getClass.getName) 104 | decFields.foreach(x => 105 | registrar.vm.log(x.getName) 106 | ) 107 | vm.log("---------------------") 108 | for(field <- vm.ClsTable(imm.Type.Cls(b.getClass.getName.toSlash)).fieldList.distinct){ 109 | registrar.vm.log(field.name) 110 | } 111 | vm.log("=====================") 112 | for(field <- vm.ClsTable(imm.Type.Cls(b.getClass.getName.toSlash)).fieldList.distinct) yield { 113 | vm.log("Loop: " + field.name) 114 | val f = decFields.find(_.getName == field.name).get 115 | f.setAccessible(true) 116 | pushVirtual(f.get(b), x => { 117 | contents.append(x) 118 | if (!f.getType.isPrimitive) { 119 | registrar(contents.last) 120 | } 121 | }) 122 | index += field.desc.size 123 | } 124 | 125 | val obj = rt.Obj.allocate(b.getClass.getName.toSlash) 126 | contents.map(_()).map(writer(vm.heap.memory, obj.address() + rt.Obj.headerSize)) 127 | out(obj.address()) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/scala/metascala/imm/Model.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package imm 3 | 4 | import org.objectweb.asm.tree._ 5 | 6 | import org.objectweb.asm 7 | import asm.Label 8 | import collection.mutable 9 | import NullSafe._ 10 | 11 | object Field { 12 | def read(fn: FieldNode) = { 13 | 14 | Field( 15 | fn.access, 16 | fn.name, 17 | imm.Type.read(fn.desc), 18 | fn.signature.safeOpt, 19 | fn.value 20 | ) 21 | } 22 | 23 | } 24 | 25 | case class Field(access: Int, 26 | name: String, 27 | desc: metascala.imm.Type, 28 | signature: Option[String], 29 | value: Any){ 30 | def static = (access & Access.Static) != 0 31 | } 32 | 33 | 34 | 35 | case class Sig(name: String, desc: Desc){ 36 | override lazy val hashCode = name.hashCode + desc.hashCode 37 | def unparse = name + desc.unparse 38 | 39 | override def toString = unparse 40 | def shortName = { 41 | val some :+ last = name.split("/").toSeq 42 | (some.map(_(0)) :+ last).mkString("/") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/metascala/imm/Type.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package imm 3 | import collection.mutable 4 | import reflect.ClassTag 5 | 6 | 7 | object Type{ 8 | def read(s: String): Type = s match{ 9 | case x if Prim.all.contains(x(0)) => Prim.all(x(0)) 10 | case s if s.startsWith("L") && s.endsWith(";") => Cls.read(s.drop(1).dropRight(1)) 11 | case s if s.startsWith("[") => Arr.read(s) 12 | case s => Cls.read(s) 13 | } 14 | 15 | def readJava(s: String): Type = s match { 16 | case x if Prim.allJava.contains(x) => Prim.allJava(x) 17 | case s if s.startsWith("[") => Arr.readJava(s) 18 | case s => Cls.readJava(s) 19 | } 20 | 21 | /** 22 | * Reference types, which can either be Class or Array types 23 | */ 24 | trait Ref extends Type{ 25 | def methodType: Type.Cls 26 | def parent(implicit vm: VM): Option[Type] 27 | } 28 | object Arr{ 29 | def read(s: String) = Arr(Type.read(s.drop(1))) 30 | def readJava(s: String) = Arr(s.drop(1) match { 31 | case x if Prim.all.contains(x(0)) => Prim.all(x(0)) 32 | case x if x.startsWith("L") => Cls.read(x.drop(1).dropRight(1).toSlash) 33 | case x => Type.readJava(x) 34 | }) 35 | } 36 | 37 | /** 38 | * Array Types 39 | * @param innerType The type of the components of the array 40 | */ 41 | case class Arr(innerType: Type) extends Ref{ 42 | def size = 1 43 | def name = "[" + innerType.name 44 | def parent(implicit vm: VM) = Some(imm.Type.Cls("java/lang/Object")) 45 | def realCls = innerType.realCls 46 | def methodType = Type.Cls("java/lang/Object") 47 | def prettyRead(x: () => Val) = "[" + innerType.shortName + "#" + x() 48 | def javaName = innerType match{ 49 | case tpe: Cls => "[L" + tpe.javaName + ";" 50 | case tpe: Prim[_] => "[" + tpe.internalName 51 | case tpe => "[" + tpe.javaName 52 | } 53 | def internalName = "[" + innerType.internalName 54 | } 55 | object Cls{ 56 | def read(s: String) = Cls(s) 57 | def readJava(s: String) = Cls(s.toSlash) 58 | } 59 | 60 | /** 61 | * Class Types 62 | * @param name the fuly qualified name of the class 63 | */ 64 | case class Cls(name: String) extends Ref { 65 | assert(!name.contains('.'), "Cls name cannot contain . " + name) 66 | assert(!name.contains('['), "Cls name cannot contain [ " + name) 67 | def size = 1 68 | def cls(implicit vm: VM) = vm.ClsTable(this) 69 | def parent(implicit vm: VM) = this.cls.superType 70 | def realCls = classOf[Object] 71 | def methodType: Type.Cls = this 72 | 73 | override val hashCode = name.hashCode 74 | def prettyRead(x: () => Val) = shorten(name) + "#" + x() 75 | def internalName = "L" + name + ";" 76 | def javaName = name.replace('/', '.') 77 | } 78 | 79 | abstract class Prim[T: ClassTag](val size: Int, val javaName: String) extends imm.Type{ 80 | def read(x: () => Val): T 81 | def write(x: T, out: Val => Unit): Unit 82 | def boxedClass: Class[_] 83 | val primClass: Class[_] = implicitly[ClassTag[T]].runtimeClass 84 | def realCls = Class.forName(boxedClass.getName.replace('/', '.')) 85 | def prim = this 86 | def productPrefix: String 87 | def name = productPrefix 88 | def internalName = name 89 | def prettyRead(x: () => Val) = toString + "#" + read(x) 90 | } 91 | object Prim extends { 92 | def read(s: String) = all(s(0)) 93 | val all: Map[Char, Prim[_]] = Map( 94 | 'V' -> (V: Prim[_]), 95 | 'Z' -> (Z: Prim[_]), 96 | 'B' -> (B: Prim[_]), 97 | 'C' -> (C: Prim[_]), 98 | 'S' -> (S: Prim[_]), 99 | 'I' -> (I: Prim[_]), 100 | 'F' -> (F: Prim[_]), 101 | 'J' -> (J: Prim[_]), 102 | 'D' -> (D: Prim[_]) 103 | ) 104 | 105 | val allJava: Map[String, Prim[_]] = Map( 106 | "void" -> (V: Prim[_]), 107 | "boolean" -> (Z: Prim[_]), 108 | "byte" -> (B: Prim[_]), 109 | "char" -> (C: Prim[_]), 110 | "short" -> (S: Prim[_]), 111 | "int" -> (I: Prim[_]), 112 | "float" -> (F: Prim[_]), 113 | "long" -> (J: Prim[_]), 114 | "double" -> (D: Prim[_]) 115 | ) 116 | 117 | def unapply(p: Prim[_]) = Some(p.javaName) 118 | 119 | case object V extends Prim[Unit](0, "void"){ 120 | def apply(x: Val) = ??? 121 | def read(x: () => Val) = () 122 | def write(x: Unit, out: Val => Unit) = () 123 | def boxedClass = classOf[java.lang.Void] 124 | 125 | } 126 | type Z = Boolean 127 | case object Z extends Prim[Boolean](1, "boolean"){ 128 | def apply(x: Val) = x != 0 129 | def read(x: () => Val) = this(x()) 130 | def write(x: Boolean, out: Val => Unit) = out(if (x) 1 else 0) 131 | def boxedClass = classOf[java.lang.Boolean] 132 | } 133 | type B = Byte 134 | case object B extends Prim[Byte](1, "byte"){ 135 | def apply(x: Val) = x.toByte 136 | def read(x: () => Val) = this(x()) 137 | def write(x: Byte, out: Val => Unit) = out(x) 138 | def boxedClass = classOf[java.lang.Byte] 139 | } 140 | type C = Char 141 | case object C extends Prim[Char](1, "char"){ 142 | def apply(x: Val) = x.toChar 143 | def read(x: () => Val) = this(x()) 144 | def write(x: Char, out: Val => Unit) = out(x) 145 | def boxedClass = classOf[java.lang.Character] 146 | } 147 | type S = Short 148 | case object S extends Prim[Short](1, "short"){ 149 | def apply(x: Val) = x.toShort 150 | def read(x: () => Val) = this(x()) 151 | def write(x: Short, out: Val => Unit) = out(x) 152 | def boxedClass = classOf[java.lang.Short] 153 | } 154 | type I = Int 155 | case object I extends Prim[Int](1, "int"){ 156 | def apply(x: Val) = x 157 | def read(x: () => Val) = this(x()) 158 | def write(x: Int, out: Val => Unit) = out(x) 159 | def boxedClass = classOf[java.lang.Integer] 160 | } 161 | type F = Float 162 | case object F extends Prim[Float](1, "float"){ 163 | def apply(x: Val) = java.lang.Float.intBitsToFloat(x) 164 | def read(x: () => Val) = this(x()) 165 | def write(x: Float, out: Val => Unit) = out(java.lang.Float.floatToRawIntBits(x)) 166 | def boxedClass = classOf[java.lang.Float] 167 | } 168 | type J = Long 169 | case object J extends Prim[Long](2, "long"){ 170 | def apply(v1: Val, v2: Val) = v1.toLong << 32 | v2 & 0xFFFFFFFFL 171 | def read(x: () => Val) = { 172 | this(x(), x()) 173 | } 174 | def write(x: Long, out: Val => Unit) = { 175 | out((x >> 32).toInt) 176 | out(x.toInt) 177 | } 178 | def boxedClass = classOf[java.lang.Long] 179 | } 180 | type D = Double 181 | case object D extends Prim[Double](2, "double"){ 182 | def apply(v1: Val, v2: Val) = java.lang.Double.longBitsToDouble(J(v1, v2)) 183 | def read(x: () => Val) = java.lang.Double.longBitsToDouble(J.read(x)) 184 | def write(x: Double, out: Val => Unit) = J.write(java.lang.Double.doubleToRawLongBits(x), out) 185 | def boxedClass = classOf[java.lang.Double] 186 | } 187 | } 188 | 189 | 190 | } 191 | /** 192 | * Represents all variable types within the Metascala VM 193 | */ 194 | trait Type{ 195 | /** 196 | * The JVMs internal representation 197 | * - V Z B C S I F J D 198 | * - Ljava/lang/Object; [Ljava/lang/String; 199 | */ 200 | def internalName: String 201 | 202 | /** 203 | * Nice name to use for most things 204 | * - V Z B C S I F J D 205 | * - java/lang/Object [java/lang/String 206 | */ 207 | def name: String 208 | 209 | override def toString = name 210 | 211 | /** 212 | * A human-friendly name for this type that can be used everywhere without 213 | * cluttering the screen. 214 | * - V Z B C S I F J D 215 | * - j/l/Object [j/l/String 216 | */ 217 | def shortName = shorten(name) 218 | 219 | /** 220 | * The thing that's returned by Java's getName method 221 | * - void boolean byte char short int float long double 222 | * - java.lang.Object [java.lang.String; 223 | */ 224 | def javaName: String 225 | /** 226 | * Retrieves the Class object in the host JVM which represents the 227 | * given Type inside the Metascala VM 228 | */ 229 | def realCls: Class[_] 230 | 231 | /** 232 | * Reads an object of this type from the given input stream into a readable 233 | * representation 234 | */ 235 | def prettyRead(x: () => Val): String 236 | 237 | /** 238 | * 0, 1 or 2 for void, most things and double/long 239 | */ 240 | def size: Int 241 | 242 | 243 | 244 | def isRef: Boolean = this.isInstanceOf[imm.Type.Ref] 245 | 246 | } 247 | 248 | object Desc{ 249 | def read(s: String) = { 250 | val scala.Array(argString, ret) = s.drop(1).split(')') 251 | val args = mutable.Buffer.empty[String] 252 | var index = 0 253 | while(index < argString.length){ 254 | val firstChar = argString.indexWhere(x => "BCDFIJSZL".contains(x), index) 255 | val split = argString(firstChar) match{ 256 | case 'L' => argString.indexWhere(x => ";".contains(x), index) 257 | case _ => argString.indexWhere(x => "BCDFIJSZ".contains(x), index) 258 | } 259 | 260 | args.append(argString.substring(index, split+1)) 261 | index = split +1 262 | } 263 | Desc(args.map(Type.read), Type.read(ret)) 264 | } 265 | def unparse(t: Type): String = { 266 | t match{ 267 | case t: Type.Cls => t.name 268 | case t: Type.Arr => "[" + unparse(t.innerType) 269 | case x => x.name 270 | } 271 | } 272 | } 273 | 274 | /** 275 | * Represents the signature of a method. 276 | */ 277 | case class Desc(args: Seq[Type], ret: Type){ 278 | def unparse = "(" + args.map(Desc.unparse).foldLeft("")(_+_) + ")" + Desc.unparse(ret) 279 | def argSize = { 280 | val baseArgSize = args.length 281 | val longArgSize = args.count(x => x == Type.Prim.J || x == Type.Prim.D) 282 | 283 | baseArgSize + longArgSize 284 | } 285 | override def toString = unparse 286 | def shortName = "(" + args.map(Desc.unparse).map(shorten).foldLeft("")(_+_) + ")" + shorten(Desc.unparse(ret)) 287 | } 288 | -------------------------------------------------------------------------------- /src/main/scala/metascala/imm/package.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | import collection.convert.wrapAsScala._ 3 | import reflect.ClassTag 4 | 5 | /** 6 | * This metascala contains the code involved in reading .class files 7 | * (using ASM) and generating an immutable representation of all the data 8 | * structures encoded in the class file. 9 | * 10 | * Almost every class in this metascala will have a read() method, which takes 11 | * in some data structure (provided by ASM) and constructs an instance of the 12 | * class. read() is generally called recursively to construct the members of 13 | * each instance, until the entire instance is constructed. 14 | */ 15 | package object imm { 16 | 17 | /** 18 | * Convenience methods to provide a safe way of converting null Lists, Arrays 19 | * or Objects into empty Lists, Arrays or None 20 | */ 21 | object NullSafe{ 22 | implicit class nullSafeList[T](val list: java.util.List[T]) extends AnyVal{ 23 | def safeSeq: Seq[T] = { 24 | Option(list).toVector.flatten 25 | } 26 | } 27 | implicit class nullSafeArray[T](val list: Array[T]) extends AnyVal{ 28 | def safeSeq: Seq[T] = { 29 | Option(list).toVector.flatten 30 | } 31 | } 32 | implicit class nullSafeValue[T](val a: T) extends AnyVal{ 33 | def safeOpt: Option[T] = Option(a) 34 | } 35 | } 36 | 37 | object Access{ 38 | 39 | val Public = 0x0001 // 1 40 | val Private = 0x0002 // 2 41 | val Protected = 0x0004 // 4 42 | val Static = 0x0008 // 8 43 | val Final = 0x0010 // 16 44 | val Super = 0x0020 // 32 45 | val Volatile = 0x0040 // 64 46 | val Transient = 0x0080 // 128 47 | val Native = 0x0100 // 256 48 | val Interface = 0x0200 // 512 49 | val Abstract = 0x0400 // 1024 50 | val Strict = 0x0800 // 2048 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/metascala/natives/Bindings.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package natives 3 | 4 | object Bindings{ 5 | val default = new Default { 6 | 7 | } 8 | } 9 | 10 | /** 11 | * A set of bindings between method signatures and callable binding functions 12 | */ 13 | trait Bindings{ 14 | val trapped: Seq[rt.Method.Native] 15 | val fileLoader: String => Option[Array[Byte]] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/main/scala/metascala/natives/Default.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package natives 3 | 4 | 5 | import java.io.{ByteArrayInputStream, DataInputStream} 6 | import collection.mutable 7 | import metascala.rt 8 | import imm.Type.Prim._ 9 | import metascala.imm.Type.Prim 10 | import metascala.rt.Arr 11 | 12 | trait Default extends Bindings{ 13 | 14 | val fileLoader = (name: String) => { 15 | val slashName = s"/$name" 16 | 17 | val loaded = getClass.getResourceAsStream(slashName) 18 | if (loaded == null) None 19 | else { 20 | val stream = new DataInputStream(loaded) 21 | val bytes = new Array[Byte](stream.available()) 22 | stream.readFully(bytes) 23 | Some(bytes) 24 | } 25 | } 26 | 27 | 28 | val trapped = { 29 | Seq( 30 | "java"/( 31 | "lang"/( 32 | "Class"/( 33 | "desiredAssertionStatus0(Ljava/lang/Class;)Z".value(I)(0), 34 | "desiredAssertionStatus()Z".value(I)(0), 35 | "forName0(Ljava/lang/String;ZLjava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;".func(I, I, I, I, I){ 36 | (vt, name, boolean, classLoader, caller) => 37 | import vt.vm 38 | val nameString = name.toRealObj[String] 39 | val tpe = imm.Type.readJava(nameString) 40 | try{ 41 | if (!nameString.contains("["))vm.ClsTable(tpe.cast[imm.Type.Cls]) 42 | val x = vt.vm.typeObjCache(tpe)() 43 | x 44 | } catch{case e: Exception => 45 | throw new java.lang.ClassNotFoundException(nameString) 46 | } 47 | 48 | }, 49 | "getClassLoader0()Ljava/lang/ClassLoader;".value(I)(0), 50 | "getComponentType()Ljava/lang/Class;".func(I, I){ (vt, o) => 51 | import vt.vm 52 | val obj = o.obj 53 | 54 | val oldName = obj("name").toRealObj[String] 55 | vt.vm.typeObjCache(imm.Type.Arr.readJava(oldName).innerType)() 56 | }, 57 | 58 | "getDeclaredFields0(Z)[Ljava/lang/reflect/Field;".func(I, I, I){ (vt, o, public) => 59 | import vt.vm 60 | 61 | 62 | val obj = o.obj 63 | 64 | val name = obj("name").toRealObj[String] 65 | val cls = vm.ClsTable(imm.Type.Cls.readJava(name)) 66 | val realFields = cls.fieldList ++ cls.staticList 67 | 68 | vm.alloc(implicit r => 69 | "java/lang/reflect/Field".allocArr( 70 | realFields.zipWithIndex.map{ case (f, i) => 71 | "java/lang/reflect/Field".allocObj( 72 | "clazz" -> obj.address, 73 | "slot" -> (if (f.static) cls.staticList else cls.fieldList).indexOf(f), 74 | "name" -> vt.vm.internedStrings.getOrElseUpdate(f.name, f.name.toVirtObj), 75 | "modifiers" -> f.access, 76 | "type" -> vm.typeObjCache(f.desc) 77 | ) 78 | } 79 | ) 80 | )() 81 | // if (f.static) cls.staticList else cls.fieldList).indexOf(f) 82 | // f.static(cls.staticList, cls.fieldList).indexOf(f) 83 | }, 84 | "getDeclaredConstructors0(Z)[Ljava/lang/reflect/Constructor;".func(I, I, I){ (vt, o, bool) => 85 | import vt.vm 86 | val clsObj = o.obj 87 | val clsName = clsObj("name").toRealObj[String].toSlash 88 | val cls = vm.ClsTable(clsName) 89 | val realMethods = cls.methods.filter(_.sig.name == "") 90 | val vrtArr = vm.alloc(implicit r => 91 | "java/lang/reflect/Constructor".allocArr( 92 | realMethods.zipWithIndex.map{ case (f, i) => 93 | "java/lang/reflect/Constructor".allocObj( 94 | "clazz" -> clsObj.address, 95 | "slot" -> i, 96 | "parameterTypes" -> "java/lang/Class".allocArr( 97 | f.sig.desc.args.map(t => 98 | vt.vm.typeObjCache(imm.Type.readJava(t.realCls.getName)) 99 | ) 100 | ) 101 | ) 102 | } 103 | ) 104 | ) 105 | vrtArr() 106 | }, 107 | "getDeclaredMethods0(Z)[Ljava/lang/reflect/Method;".func(I, Z, I){ (vt, clsAddr, pub) => 108 | import vt.vm 109 | val cls = vt.vm.ClsTable(clsAddr.obj.apply("name").toRealObj[String].toSlash) 110 | vm.alloc(implicit r => 111 | "java/lang/reflect/Method".allocArr( 112 | cls.methods.map{ m => 113 | "java/lang/reflect/Method".allocObj( 114 | ) 115 | 116 | } 117 | ) 118 | )() 119 | }, 120 | "getEnclosingMethod0()[Ljava/lang/Object;".func(I, I){(vt, cls) => 0}, 121 | "getDeclaringClass()Ljava/lang/Class;".func(I, I){ (vt, cls) => 0}, 122 | "getInterfaces()[Ljava/lang/Class;".func(I, I){ (vt, clsAddr) => 123 | import vt.vm 124 | val cls = vt.vm.ClsTable(clsAddr.obj.apply("name").toRealObj[String].toSlash) 125 | vm.alloc(implicit r => 126 | "java/lang/Class".allocArr( 127 | cls.typeAncestry 128 | .filter(x => !cls.clsAncestry.contains(x)) 129 | .toSeq 130 | .map(x => vt.vm.typeObjCache(x.cls.tpe)) 131 | ) 132 | )() 133 | }, 134 | "getModifiers()I".func(I, I){ (vt, o) => 135 | import vt.vm 136 | val topClsName = o.obj.apply("name").toRealObj[String].toSlash 137 | 138 | vm.ClsTable(topClsName).accessFlags 139 | }, 140 | "getPrimitiveClass(Ljava/lang/String;)Ljava/lang/Class;".func(I, I){ (vt, o) => 141 | import vt.vm 142 | vt.vm.typeObjCache(imm.Type.readJava(o.toRealObj[String]))() 143 | }, 144 | "getSuperclass()Ljava/lang/Class;".func(I, I){ (vt, o) => 145 | import vt.vm 146 | val topClsName = o.obj.apply("name").toRealObj[String].toSlash 147 | 148 | vm.ClsTable(topClsName) 149 | .superType 150 | .map{_.name} 151 | .map(name => vt.vm.typeObjCache(imm.Type.readJava(name))()) 152 | .getOrElse(0) 153 | 154 | 155 | }, 156 | 157 | "isArray()Z".func(I, I){ (vt, o) => 158 | import vt.vm 159 | if(o.obj.apply("name").toRealObj[String].contains('[')) 1 else 0 160 | 161 | }, 162 | "isAssignableFrom(Ljava/lang/Class;)Z".func(I, I, I){ (vt, a, b) => 163 | import vt.vm 164 | val clsA = a.obj 165 | val clsB = b.obj 166 | val nameA = clsA("name").toRealObj[String] 167 | val nameB = clsB("name").toRealObj[String] 168 | 169 | def check(s: imm.Type, t: imm.Type)(implicit vm: VM): Boolean = { 170 | 171 | (s, t) match{ 172 | 173 | case (s: imm.Type.Cls, t: imm.Type.Cls) => s.cls.typeAncestry.contains(t) 174 | case (s: imm.Type.Arr, imm.Type.Cls("java/lang/Object")) => true 175 | case (s: imm.Type.Arr, imm.Type.Cls("java/lang/Cloneable")) => true 176 | case (s: imm.Type.Arr, imm.Type.Cls("java/io/Serializable")) => true 177 | case (imm.Type.Arr(imm.Type.Prim(a)), imm.Type.Arr(imm.Type.Prim(b))) => a == b 178 | case (imm.Type.Arr(sc: imm.Type), imm.Type.Arr(tc: imm.Type)) => check(sc, tc) 179 | case _ => false 180 | } 181 | } 182 | if (check(imm.Type.read(nameA.replace('.', '/')), imm.Type.read(nameB.replace('.', '/')))) 1 else 0 183 | }, 184 | "isInterface()Z".func(I, Z){ (vt, o) => 185 | import vt.vm 186 | val clsObj = o.obj 187 | vm.ClsTable( 188 | clsObj("name").toRealObj[String].toSlash 189 | ).isInterface 190 | }, 191 | "isPrimitive()Z".func(I, I){ (vt, o) => 192 | import vt.vm 193 | val clsObj = o.obj 194 | val name = clsObj("name").toRealObj[String] 195 | val res = Prim.allJava.contains(name) 196 | if (res) 1 else 0 197 | }, 198 | "registerNatives()V".value(V)(()) 199 | ), 200 | "ClassLoader"/( 201 | "getCaller(I)Ljava/lang/Class;".func(I, I){ (vt, o) => 202 | import vt.vm 203 | val name = o match{ 204 | case 0 => "java/lang/ClassLoader" 205 | case 1 => vt.threadStack(0).runningClass.name 206 | case 2 => vt.threadStack(1).runningClass.name 207 | } 208 | vt.vm.typeObjCache(imm.Type.readJava(name))() 209 | }, 210 | "getSystemResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;".func(I, I){ (vt, o) => 211 | import vt.vm 212 | 213 | val name = o.toRealObj[String] 214 | val stream = getClass.getResourceAsStream("/" + name) 215 | // println("getSystemResourceAsStream " + name + " " + stream) 216 | // println(getClass.getClassLoader) 217 | // println("XXX " + name + " " + getClass.getResourceAsStream(name)) 218 | // println("YYY " + name + " " + getClass.getResourceAsStream("/" + name)) 219 | 220 | if (stream == null) 0 221 | else{ 222 | val realResult = new DataInputStream(stream) 223 | 224 | val bytes = new Array[Byte](realResult.available()) 225 | realResult.readFully(bytes) 226 | val byteStream = new ByteArrayInputStream(bytes) 227 | vm.alloc(byteStream.toVirtObj(_)) 228 | } 229 | }, 230 | "registerNatives()V".value(V)(()) 231 | ), 232 | "Double"/( 233 | "doubleToRawLongBits(D)J".func(J, J){(vt, l) => l}, 234 | "longBitsToDouble(J)D".func(J, J){(vt, l) => l} 235 | ), 236 | "Float"/( 237 | "intBitsToFloat(I)F".func(I, I){(vt, l) => l}, 238 | "floatToRawIntBits(F)I".func(I, I){(vt, l) => l} 239 | ), 240 | "Object"/( 241 | "clone()Ljava/lang/Object;".func(I, I){(vt, l) => l}, 242 | "getClass()Ljava/lang/Class;".func(I, I){ (vt, value) => 243 | 244 | import vt.vm 245 | 246 | 247 | val string = 248 | if(value.isObj) value.obj.cls.tpe.javaName 249 | else value.arr.tpe.javaName 250 | 251 | vt.vm.typeObjCache(imm.Type.readJava(string))() 252 | }, 253 | 254 | "hashCode()I".func(I, I){(vt, l) => l}, 255 | "registerNatives()V".value(V){()} 256 | ), 257 | 258 | "Runtime"/( 259 | "freeMemory()J".value(J)(4*1024*1024), 260 | "availableProcessors()I".value(I)(1) 261 | ), 262 | 263 | "System"/( 264 | "arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V".func(I, I, I, I, I, V){ (vt, src, srcIndex, dest, destIndex, length) => 265 | System.arraycopy(vt.vm.heap.memory, src + srcIndex + rt.Arr.headerSize, vt.vm.heap.memory, dest + destIndex + rt.Arr.headerSize, length) 266 | }, 267 | 268 | "identityHashCode(Ljava/lang/Object;)I".func(I, I){(vt, l) => l}, 269 | "nanoTime()J".value(J)(System.nanoTime()), 270 | "currentTimeMillis()J".value(J)(System.currentTimeMillis()), 271 | "getProperty(Ljava/lang/String;)Ljava/lang/String;".value(I)(0), 272 | "getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;".value(I)(0), 273 | "registerNatives()V".value(V)(()) 274 | ), 275 | "String"/( 276 | "intern()Ljava/lang/String;".func(I, I){ (vt, addr) => 277 | import vt.vm 278 | val str = addr.toRealObj[String] 279 | val result = vt.vm.internedStrings.getOrElseUpdate(str, addr) 280 | result 281 | } 282 | ), 283 | 284 | "Thread"/( 285 | "registerNatives()V".value(V)(()), 286 | "currentThread()Ljava/lang/Thread;".func(I){ vt => 287 | import vt.vm 288 | 289 | vm.currentThread 290 | }, 291 | "setPriority0(I)V".value(V)(()), 292 | "isAlive()Z".value(Z)(false), 293 | "start0()V".value(V)(()) 294 | ), 295 | "Throwable"/( 296 | "fillInStackTrace()Ljava/lang/Throwable;".func(I, I){ (vt, addr) => 297 | import vt.vm 298 | val throwable = addr.obj 299 | val trace = vt.trace 300 | throwable("stackTrace") = vm.alloc(vt.trace.toVirtObj(_)) 301 | 302 | throwable.address() 303 | } 304 | ), 305 | "reflect"/( 306 | "Array"/( 307 | "newArray(Ljava/lang/Class;I)Ljava/lang/Object;".func(I, I, I){ (vt, cls, length) => 308 | import vt.vm 309 | val clsObj = cls.obj 310 | val clsName = clsObj("name").toRealObj[String] 311 | vm.alloc(rt.Arr.allocate(imm.Type.readJava(clsName), length)(_)).address() 312 | }, 313 | "set(Ljava/lang/Object;ILjava/lang/Object;)V".func(I, I, I, V){ (vt, arr, index, obj) => 314 | vt.invoke( 315 | imm.Type.Cls("metascala/patches/java/lang/reflect/Array"), 316 | imm.Sig("set", imm.Desc.read("(Ljava/lang/Object;ILjava/lang/Object;)V")), 317 | Seq(arr, index, obj) 318 | ) 319 | } 320 | ), 321 | "NativeConstructorAccessorImpl"/( 322 | "newInstance0(Ljava/lang/reflect/Constructor;[Ljava/lang/Object;)Ljava/lang/Object;".func(I, I, I){ 323 | (vt, cons, args) => 324 | import vt.vm 325 | val name = cons.obj.apply("clazz").obj.apply("name").toRealObj[String].toSlash 326 | vm.alloc(implicit r => 327 | rt.Obj.allocate(name) 328 | ).address() 329 | } 330 | ) 331 | ), 332 | "StrictMath"/( 333 | "log(D)D".func(D, D)( (vt, value) => 334 | math.log(value) 335 | ), 336 | "pow(DD)D".func(D, D, D)( (vt, value, value2) => 337 | math.pow(value, value2) 338 | ) 339 | ) 340 | ), 341 | 342 | "security"/( 343 | "AccessController"/( 344 | "doPrivileged(Ljava/security/PrivilegedExceptionAction;)Ljava/lang/Object;".func(I, I){ (vt, a) => 345 | 346 | import vt.vm 347 | val pa = a.obj 348 | val mRef = vt.vm.resolveDirectRef(pa.cls.tpe, pa.cls.methods.find(_.sig.name == "run").get.sig).get 349 | var x = 0 350 | vt.invoke(mRef, Seq(pa.address())) 351 | 352 | vt.returnedVal(0) 353 | }, 354 | "doPrivileged(Ljava/security/PrivilegedAction;)Ljava/lang/Object;".func(I, I){ (vt, a) => 355 | 356 | import vt.vm 357 | val pa = a.obj 358 | val mRef = vt.vm.resolveDirectRef(pa.cls.tpe, pa.cls.methods.find(_.sig.name == "run").get.sig).get 359 | var x = 0 360 | val ret = vt.invoke(mRef, Seq(pa.address())) 361 | 362 | 363 | vt.returnedVal(0) 364 | }, 365 | "getStackAccessControlContext()Ljava/security/AccessControlContext;".value(I)(0) 366 | 367 | ) 368 | ), 369 | "util"/( 370 | "concurrent"/( 371 | "atomic"/( 372 | "AtomicLong"/( 373 | "VMSupportsCS8()Z".func(Z){ vt => true } 374 | ) 375 | ) 376 | ) 377 | ) 378 | ), 379 | "scala"/( 380 | "Predef$"/( 381 | "println(Ljava/lang/Object;)V".func(I, I, V){ (vt, predef, o) => 382 | import vt.vm 383 | 384 | if (o.isObj){ 385 | val thing = o.obj 386 | println("Virtual\t" + thing.address().toRealObj[Object]) 387 | }else if(o.isArr){ 388 | val s = Virtualizer.popVirtual(imm.Type.Arr(vt.vm.arrayTypeCache(vt.vm.heap(o + 1))), () => o) 389 | println("Virtual\t" + s) 390 | }else{ 391 | println("Virtual\t" + null) 392 | } 393 | } 394 | ) 395 | ), 396 | "sun"/( 397 | "misc"/( 398 | "Hashing"/( 399 | "randomHashSeed(Ljava/lang/Object;)I".value(I)(31337) // sufficiently random 400 | ), 401 | "Unsafe"/( 402 | "arrayBaseOffset(Ljava/lang/Class;)I".value(I)(0), 403 | "arrayIndexScale(Ljava/lang/Class;)I".value(I)(1), 404 | "allocateInstance(Ljava/lang/Class;)Ljava/lang/Object;".func(I, I, I){ (vt, unsafe, clsPtr) => 405 | import vt.vm 406 | val name = clsPtr.obj.apply("name").toRealObj[String].toSlash 407 | val x = vt.vm.alloc{ implicit r => 408 | name.allocObj() 409 | }() 410 | x 411 | }, 412 | "addressSize()I".value(I)(4), 413 | "compareAndSwapInt(Ljava/lang/Object;JII)Z".func(I, I, J, I, I, Z){ (vt, unsafe, o, slot, expected ,x) => 414 | import vt.vm 415 | val obj = o.obj 416 | if (obj.members(slot.toInt) == expected){ 417 | obj.members(slot.toInt) = x 418 | true 419 | }else{ 420 | false 421 | } 422 | 423 | }, 424 | "compareAndSwapObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z".func(I, I, J, I, I, Z){ (vt, unsafe, o, slot, expected ,x) => 425 | import vt.vm 426 | val obj = o.obj 427 | if (obj.members(slot.toInt) == expected){ 428 | obj.members(slot.toInt) = x 429 | true 430 | }else{ 431 | false 432 | } 433 | 434 | }, 435 | "compareAndSwapLong(Ljava/lang/Object;JJJ)Z".func(I, I, J, J, J, Z){ (vt, unsafe, o, slot, expected ,x) => 436 | import vt.vm 437 | val obj = o.obj 438 | val current = J.read(reader(obj.members, slot.toInt)) 439 | if (current == expected){ 440 | J.write(x, writer(obj.members, slot.toInt)) 441 | true 442 | }else{ 443 | false 444 | } 445 | }, 446 | "ensureClassInitialized(Ljava/lang/Class;)V".func(I, I, V){ (vt, unsafe, cls) => 447 | () 448 | }, 449 | "getObject(Ljava/lang/Object;J)Ljava/lang/Object;".func(I, I, J, I){ (vt, unsafe, o, offset) => 450 | import vt.vm 451 | o.obj.members(offset.toInt) 452 | }, 453 | "getBooleanVolatile(Ljava/lang/Object;J)Z".func(I, I, J, Z){ (vt, unsafe, o, offset) => 454 | import vt.vm 455 | o.obj.members(offset.toInt) != 0 456 | }, 457 | "putBooleanVolatile(Ljava/lang/Object;JZ)V".func(I, I, J, Z, V){ (vt, unsafe, o, offset, bool) => 458 | import vt.vm 459 | Z.write(bool, o.obj.members(offset.toInt) = _) 460 | }, 461 | "getByteVolatile(Ljava/lang/Object;J)B".func(I, I, J, B){ (vt, unsafe, o, offset) => 462 | import vt.vm 463 | o.obj.members(offset.toInt).toByte 464 | }, 465 | "putByteVolatile(Ljava/lang/Object;JB)V".func(I, I, J, B, V){ (vt, unsafe, o, offset, byte) => 466 | import vt.vm 467 | B.write(byte, o.obj.members(offset.toInt) = _) 468 | }, 469 | "getCharVolatile(Ljava/lang/Object;J)C".func(I, I, J, C){ (vt, unsafe, o, offset) => 470 | import vt.vm 471 | o.obj.members(offset.toInt).toChar 472 | }, 473 | "putCharVolatile(Ljava/lang/Object;JC)V".func(I, I, J, C, V){ (vt, unsafe, o, offset, char) => 474 | import vt.vm 475 | C.write(char, o.obj.members(offset.toInt) = _) 476 | }, 477 | "getInt(Ljava/lang/Object;J)I".func(I, I, J, I){ (vt, unsafe, o, offset) => 478 | import vt.vm 479 | o.obj.members(offset.toInt) 480 | }, 481 | "getIntVolatile(Ljava/lang/Object;J)I".func(I, I, J, I){ (vt, unsafe, o, offset) => 482 | import vt.vm 483 | o.obj.members(offset.toInt) 484 | }, 485 | "putInt(Ljava/lang/Object;JI)V".func(I, I, J, I, V){ (vt, unsafe, o, offset, int) => 486 | import vt.vm 487 | I.write(int, o.obj.members(offset.toInt) = _) 488 | }, 489 | "getFloat(Ljava/lang/Object;J)F".func(I, I, J, F){ (vt, unsafe, o, offset) => 490 | import vt.vm 491 | F.read(() => o.obj.members(offset.toInt)) 492 | }, 493 | "putFloat(Ljava/lang/Object;JF)V".func(I, I, J, F, V){ (vt, unsafe, o, offset, float) => 494 | import vt.vm 495 | F.write(float, o.obj.members(offset.toInt) = _) 496 | }, 497 | "getLongVolatile(Ljava/lang/Object;J)J".func(I, I, J, J){ (vt, unsafe, o, offset) => 498 | import vt.vm 499 | J.read(reader(o.obj.members, offset.toInt)) 500 | }, 501 | "putLongVolatile(Ljava/lang/Object;JJ)V".func(I, I, J, J, V){ (vt, unsafe, o, offset, long) => 502 | import vt.vm 503 | J.write(long, writer(o.obj.members, offset.toInt)) 504 | }, 505 | "getDouble(Ljava/lang/Object;J)D".func(I, I, J, D){ (vt, unsafe, o, offset) => 506 | import vt.vm 507 | D.read(reader(o.obj.members, offset.toInt)) 508 | }, 509 | "putDouble(Ljava/lang/Object;JD)V".func(I, I, J, D, V){ (vt, unsafe, o, offset, double) => 510 | import vt.vm 511 | D.write(double, writer(o.obj.members, offset.toInt)) 512 | }, 513 | "getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;".func(I, I, J, I){ (vt, unsafe, o, offset) => 514 | import vt.vm 515 | o.obj.members(offset.toInt) 516 | }, 517 | "putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V".func(I, I, J, I, V){ (vt, unsafe, o, offset, ref) => 518 | import vt.vm 519 | o.obj.members(offset.toInt) = ref 520 | }, 521 | "putObject(Ljava/lang/Object;JLjava/lang/Object;)V".func(I, I, J, I, V){ (vt, unsafe, o, offset, ref) => 522 | import vt.vm 523 | o.obj.members(offset.toInt) = ref 524 | }, 525 | "putOrderedObject(Ljava/lang/Object;JLjava/lang/Object;)V".func(I, I, J, I, V){ (vt, unsafe, o, offset, ref) => 526 | import vt.vm 527 | if (o.isObj) 528 | o.obj.members(offset.toInt) = ref 529 | else{ 530 | o.arr(offset.toInt) = ref 531 | } 532 | }, 533 | "objectFieldOffset(Ljava/lang/reflect/Field;)J".func(I, I, J){(vt, unsafe, f) => 534 | import vt.vm 535 | val field = f.obj 536 | val s = field.apply("slot") 537 | s 538 | }, 539 | "staticFieldOffset(Ljava/lang/reflect/Field;)J".func(I, I, J){ (vt, unsafe, f) => 540 | import vt.vm 541 | val field = f.obj 542 | field.apply("slot") 543 | }, 544 | "staticFieldBase(Ljava/lang/reflect/Field;)Ljava/lang/Object;".func(I, I, I){(vt, unsafe, f) => 545 | import vt.vm 546 | vm.ClsTable(f.obj.apply("clazz").obj.apply("name").toRealObj[String].toSlash).statics.address() 547 | }, 548 | "registerNatives()V".value(V)(()), 549 | "getUnsafe()Lsun/misc/Unsafe;".func(I){vt => vt.vm.theUnsafe.address()}, 550 | "()V".value(V)(()) 551 | ), 552 | "VM"/( 553 | "getSavedProperty(Ljava/lang/String;)Ljava/lang/String;".value(I)(0), 554 | "initialize()V".value(V)(()) 555 | 556 | ) 557 | 558 | ), 559 | "reflect"/( 560 | "Reflection"/( 561 | "filterFields(Ljava/lang/Class;[Ljava/lang/reflect/Field;)[Ljava/lang/reflect/Field;".func(I, I, I){ (vt, cls, fs) => 562 | fs 563 | }, 564 | "getCallerClass(I)Ljava/lang/Class;".func(I, I){ (vt, n) => 565 | import vt.vm 566 | if (n >= vt.threadStack.length) 0 567 | else { 568 | val name = vt.threadStack(n).runningClass.name 569 | vt.vm.typeObjCache(imm.Type.readJava(name))() 570 | } 571 | }, 572 | "getCallerClass()Ljava/lang/Class;".func(I){ (vt) => 573 | import vt.vm 574 | val n = 1 575 | if (n >= vt.threadStack.length) 0 576 | else { 577 | val name = vt.threadStack(n).runningClass.name 578 | vt.vm.typeObjCache(imm.Type.readJava(name))() 579 | } 580 | }, 581 | "getClassAccessFlags(Ljava/lang/Class;)I".func(I, I){ (vt, o) => 582 | import vt.vm 583 | val addr = o.obj.apply("name") 584 | val str = addr.toRealObj[String] 585 | vm.ClsTable(str.toSlash).accessFlags 586 | } 587 | ) 588 | ) 589 | ), 590 | "metascala"/( 591 | "Virtualizer$"/( 592 | "unsafe()Lsun/misc/Unsafe;".func(I){vt => 593 | vt.vm.theUnsafe.address() 594 | } 595 | ) 596 | ) 597 | ).toRoute() 598 | } 599 | } -------------------------------------------------------------------------------- /src/main/scala/metascala/natives/package.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | import imm.Type.Prim 3 | import collection.mutable 4 | /** 5 | * `natives` contains the bindings between method calls internal to the Metascala 6 | * VM and external functionality of the host JVM. It defines bindings in a 7 | * hierarchical fashion, as well as a default set of bindings in Default.Scala. 8 | * 9 | * A Metascala VM takes a set of Bindings on instantiation, which can be customized 10 | * e.g. to trapped the calls to native methods and redirect them to wherever you want. 11 | * 12 | */ 13 | package object natives { 14 | 15 | 16 | /** 17 | * Implements a nice DSL to build the list of trapped method calls 18 | */ 19 | implicit class pimpedRoute(val m: Seq[(String, Any)]) extends AnyVal{ 20 | def toRoute(parts: List[String] = Nil): Seq[rt.Method.Native] = { 21 | m.flatMap{ case (k, v) => 22 | v match{ 23 | case thing: Seq[(String, Any)] => 24 | thing.toRoute(k :: parts).map { case rt.Method.Native(clsName, sig, func) => 25 | rt.Method.Native(if (clsName == "") k else k + "/" + clsName, sig, func) 26 | } 27 | case func: ((`rt`.Thread, () => Val, Int => Unit) => Unit) => 28 | val (name, descString) = k.splitAt(k.indexOf('(')) 29 | val desc = imm.Desc.read(descString) 30 | Vector(rt.Method.Native("", imm.Sig(name, desc), func)) 31 | } 32 | } 33 | } 34 | } 35 | implicit class pimpedMap(val s: String) extends AnyVal{ 36 | def /(a: (String, Any)*) = s -> a 37 | def func[T](out: Prim[T])(f: rt.Thread => T) = s -> { 38 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 39 | out.write(f(t), ret) 40 | } 41 | def func[A, T](a: Prim[A], out: Prim[T])(f: (rt.Thread, A) => T) = s -> { 42 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 43 | out.write(f(t, a.read(args)), ret) 44 | } 45 | def func[A, B, T](a: Prim[A], b: Prim[B], out: Prim[T])(f: (rt.Thread, A, B) => T) = s -> { 46 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 47 | out.write(f(t, a.read(args), b.read(args)), ret) 48 | } 49 | def func[A, B, C, T](a: Prim[A], b: Prim[B], c: Prim[C], out: Prim[T])(f: (rt.Thread, A, B, C) => T) = s -> { 50 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 51 | out.write(f(t, a.read(args), b.read(args), c.read(args)), ret) 52 | } 53 | def func[A, B, C, D, T](a: Prim[A], b: Prim[B], c: Prim[C], d: Prim[D], out: Prim[T])(f: (rt.Thread, A, B, C, D) => T) = s -> { 54 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 55 | out.write(f(t, a.read(args), b.read(args), c.read(args), d.read(args)), ret) 56 | } 57 | def func[A, B, C, D, E, T](a: Prim[A], b: Prim[B], c: Prim[C], d: Prim[D], e: Prim[E], out: Prim[T])(f: (rt.Thread, A, B, C, D, E) => T) = s -> { 58 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 59 | out.write(f(t, a.read(args), b.read(args), c.read(args), d.read(args), e.read(args)), ret) 60 | } 61 | def func[A, B, C, D, E, F, T](a: Prim[A], b: Prim[B], c: Prim[C], d: Prim[D], e: Prim[E], f: Prim[F], out: Prim[T])(func: (rt.Thread, A, B, C, D, E, F) => T) = s -> { 62 | (t: rt.Thread, args: () => Val, ret: Int => Unit) => 63 | out.write(func(t, a.read(args), b.read(args), c.read(args), d.read(args), e.read(args), f.read(args)), ret) 64 | } 65 | def value[T](out: Prim[T])(x: => T) = func(out)(t => x) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/metascala/opcodes/Conversion.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package opcodes 3 | 4 | import org.objectweb.asm.Type 5 | import scala.collection.mutable 6 | import imm.Type.Prim._ 7 | import org.objectweb.asm.tree._ 8 | import scala.collection.JavaConverters._ 9 | import org.objectweb.asm.tree.analysis._ 10 | import org.objectweb.asm.Opcodes._ 11 | import org.objectweb.asm.util.Printer.OPCODES 12 | class Box(val value: BasicValue) extends Value{ 13 | override def getSize = value.getSize 14 | override def toString = Math.abs(hashCode).toString.take(2) + "" + value + " " 15 | 16 | } 17 | class AbstractFunnyInterpreter(b: Boolean) extends Interpreter[Box](ASM4){ 18 | val internal = new BasicInterpreter() 19 | type AIN = AbstractInsnNode 20 | 21 | def newValue(tpe: org.objectweb.asm.Type) = 22 | if (tpe == null) new Box(BasicValue.UNINITIALIZED_VALUE) 23 | else if (tpe.getSort == Type.VOID) null 24 | else new Box(internal.newValue(tpe)) 25 | 26 | 27 | def newOperation(insn: AIN) = new Box(internal.newOperation(insn)) 28 | def copyOperation(insn: AIN, value: Box) = value 29 | def unaryOperation(insn: AIN, value: Box) = new Box(internal.unaryOperation(insn, value.value)) 30 | def binaryOperation(insn: AIN, v1: Box, v2: Box) = new Box(internal.binaryOperation(insn, v1.value, v2.value)) 31 | def ternaryOperation(insn: AIN, v1: Box, v2: Box, v3: Box) = new Box(internal.ternaryOperation(insn, v1.value, v2.value, v3.value)) 32 | def naryOperation(insn: AIN, vs: java.util.List[_ <: Box]) = new Box(internal.naryOperation(insn, vs.asScala.map(_.value).asJava)) 33 | def returnOperation(insn: AIN, value: Box, expected: Box) = () 34 | def merge(v1: Box, v2: Box) = if (b) v1 else v2 35 | } 36 | object IncrementalInterpreter extends AbstractFunnyInterpreter(false) 37 | object FullInterpreter extends AbstractFunnyInterpreter(true) 38 | 39 | object Conversion { 40 | val unconditionalJumps = Seq(GOTO, RETURN, ARETURN, IRETURN, FRETURN, LRETURN, DRETURN, ATHROW) 41 | 42 | def ssa(clsName: String, method: MethodNode)(implicit vm: VM): Code = { 43 | 44 | val allInsns = method.instructions.toArray 45 | 46 | val lineMap = { 47 | var lastLine = 0 48 | val lines = new Array[Int](allInsns.length) 49 | for((insn, i) <- allInsns.zipWithIndex){ 50 | insn match{ 51 | case x: LineNumberNode => lastLine = x.line 52 | case _ => () 53 | } 54 | lines(i) = lastLine 55 | } 56 | lines 57 | } 58 | 59 | lazy val extraFrames = new Analyzer(FullInterpreter).analyze(clsName, method) 60 | 61 | val blockMap: Array[Int] = { 62 | val jumps = allInsns.collect{case x: JumpInsnNode => x} 63 | val returns = Seq(RETURN, IRETURN, FRETURN, LRETURN, DRETURN, ARETURN, ATHROW) 64 | val exits = allInsns.filter(c => returns.contains(c.getOpcode)) 65 | val lookupSwitches = allInsns.collect{case x: LookupSwitchInsnNode => x} 66 | val tableSwitches = allInsns.collect{case x: TableSwitchInsnNode => x} 67 | val bases = 68 | exits ++ 69 | jumps ++ 70 | lookupSwitches ++ 71 | tableSwitches 72 | 73 | val targets = 74 | jumps.map(_.label) ++ 75 | lookupSwitches.flatMap(_.labels.toArray) ++ 76 | lookupSwitches.map(_.dflt) ++ 77 | tableSwitches.flatMap(_.labels.toArray) ++ 78 | tableSwitches.map(_.dflt) ++ 79 | method.tryCatchBlocks.asScala.map(_.handler) 80 | 81 | val map = new Array[Int](allInsns.length) 82 | for(i <- 1 until allInsns.length){ 83 | val prev = map(i - 1) 84 | map(i) = prev max map(i) 85 | if (bases.contains(allInsns(i)) && i + 1 < map.length){ 86 | map(i + 1) = prev + 1 87 | }else if (targets.contains(allInsns(i))){ 88 | map(i) = prev + 1 89 | } 90 | } 91 | map 92 | } 93 | 94 | val insnMap: Array[Int] = { 95 | val map = new Array[Int](allInsns.length) 96 | var count = 0 97 | for(i <- 1 until allInsns.length){ 98 | count += 1 99 | if (blockMap(i) != blockMap(i-1)) count = 0 100 | map(i) = count 101 | } 102 | map 103 | } 104 | 105 | implicit class pimpedLabel(x: LabelNode){ 106 | def block = blockMap(allInsns.indexOf(x)) 107 | def insn = insnMap(allInsns.indexOf(x)) 108 | } 109 | 110 | val blockInsns: Array[Array[AbstractInsnNode]] = { 111 | blockMap.zip(allInsns) 112 | .groupBy(_._1) 113 | .toArray 114 | .sortBy(_._1) 115 | .map(_._2.map(_._2)) 116 | 117 | } 118 | 119 | val blockLines: Array[Array[Int]] = { 120 | blockMap.zip(lineMap) 121 | .groupBy(_._1) 122 | .toArray 123 | .sortBy(_._1) 124 | .map(_._2.map(_._2)) 125 | } 126 | 127 | implicit def deref(label: LabelNode) = blockInsns.indexWhere(_.head == label) 128 | // println("blockInsns") 129 | 130 | val allFrames: Seq[Seq[Frame[Box]]] = { 131 | val links: Seq[(Int, Int)] = { 132 | val jumps = 133 | blockInsns.map(_.last) 134 | .zipWithIndex 135 | .flatMap{ 136 | case (x: JumpInsnNode, i) => Seq(i -> x.label.block) 137 | case (x: TableSwitchInsnNode, i) => (x.dflt +: x.labels.asScala).map(i -> _.block) 138 | case (x: LookupSwitchInsnNode, i) => (x.dflt +: x.labels.asScala).map(i -> _.block) 139 | case _ => Nil 140 | } 141 | val flows = 142 | for{ 143 | (a, b) <- (0 to blockMap.last - 1).zip(1 to blockMap.last) 144 | if !unconditionalJumps.contains(blockInsns(a).last.getOpcode) 145 | } yield (a, b) 146 | jumps ++ flows 147 | } 148 | 149 | val existing = new Array[Seq[Frame[Box]]](blockInsns.length) 150 | 151 | def copy(f: Frame[Box]) = { 152 | val newF = new Frame[Box](f) 153 | newF.clearStack() 154 | for(i <- 0 until f.getLocals) newF.setLocal(i, new Box(f.getLocal(i).value)) 155 | for(i <- 0 until f.getStackSize) newF.push(new Box(f.getStack(i).value)) 156 | newF 157 | } 158 | def handle(startFrame: Frame[Box], blockId: Int): Unit = { 159 | if (existing(blockId) != null) () 160 | else{ 161 | val theseFrames = blockInsns(blockId).scanLeft(startFrame){ (frame, insn) => 162 | val f = new Frame(frame) 163 | if (insn.getOpcode != -1) { 164 | f.execute(insn, IncrementalInterpreter) 165 | } 166 | f 167 | } 168 | existing(blockId) = theseFrames 169 | for{ 170 | (src, dest) <- links 171 | if src == blockId 172 | } handle(copy(theseFrames.last), dest) 173 | } 174 | } 175 | 176 | handle(extraFrames(0), 0) 177 | 178 | for(b <- method.tryCatchBlocks.asScala) { 179 | val catchFrame = extraFrames(allInsns.indexOf(b.handler)) 180 | handle(catchFrame, b.handler) 181 | } 182 | existing 183 | } 184 | 185 | // println("allFrames") 186 | // allFrames.map(println) 187 | // force in-order registration of method arguments in first block 188 | 189 | val blockBuffers = for{ 190 | (blockFrames, blockId) <- allFrames.zipWithIndex 191 | if blockFrames != null 192 | } yield { 193 | 194 | val buffer = mutable.Buffer.empty[Insn] 195 | 196 | val srcMap = mutable.Buffer.empty[Int] 197 | val lineMap = mutable.Buffer.empty[Int] 198 | val localMap = mutable.Map.empty[Box, Int] 199 | var symCount = 0 200 | val types = mutable.Buffer.empty[imm.Type] 201 | 202 | 203 | 204 | implicit def getBox(b: Box) = { 205 | if (!localMap.contains(b)){ 206 | localMap(b) = symCount 207 | symCount += b.value.getSize 208 | import org.objectweb.asm.Type 209 | assert (b != null) 210 | assert (b.value != null) 211 | assert (b.value.getType != null, "fail " + b.value) 212 | 213 | types.append(b.value.getType.getSort match{ 214 | case Type.BOOLEAN => Z 215 | case Type.CHAR => C 216 | case Type.BYTE => B 217 | case Type.SHORT => S 218 | case Type.INT => I 219 | case Type.FLOAT => F 220 | case Type.LONG => J 221 | case Type.DOUBLE => D 222 | case Type.ARRAY => imm.Type.Cls("java/lang/Object") 223 | case Type.OBJECT => imm.Type.Cls("java/lang/Object") 224 | case _ => ??? 225 | }) 226 | } 227 | localMap(b) 228 | } 229 | 230 | blockFrames(0).boxes.flatten.map(getBox) 231 | 232 | for ((insn, i) <- blockInsns(blockId).zipWithIndex) try { 233 | 234 | ConvertInsn( 235 | insn, 236 | (in: Insn) => { 237 | buffer.append(in) 238 | srcMap.append(i) 239 | lineMap.append(blockLines(blockId)(i)) 240 | }, 241 | blockFrames(i), 242 | blockFrames(i+1), 243 | blockMap 244 | ) 245 | } catch {case e => 246 | println("ConvertInsn Failed " + clsName) 247 | throw e 248 | } 249 | 250 | (buffer, types, localMap, blockFrames.head, blockFrames.last, lineMap) 251 | } 252 | 253 | if (false && method.name == "ensureObj") { 254 | println(s"--------------------- Converting ${method.name} ---------------------") 255 | for(i <- 0 until blockMap.length){ 256 | if (i == 0 || blockMap(i) != blockMap(i-1)) println("-------------- BasicBlock " + blockMap(i) + " --------------") 257 | val insn = OPCODES.lift(allInsns(i).getOpcode).getOrElse(allInsns(i).getClass.getSimpleName).padTo(30, ' ') 258 | val frame = Option(allFrames(blockMap(i))).map(_(insnMap(i)).toString).getOrElse("-") 259 | 260 | println(lineMap(i) + "\t" + insn + " | " + blockMap(i) + " | " + insnMap(i) + " |\t" + frame) 261 | } 262 | println("XXX") 263 | } 264 | 265 | val basicBlocks = for(((buffer, types, startMap, startFrame, _, lines), i) <- blockBuffers.zipWithIndex) yield { 266 | val phis = for(((buffer2, types2, endMap, _, endFrame, _), j) <- blockBuffers.zipWithIndex) yield { 267 | if (endFrame != null && startFrame != null && ((buffer2.length > 0 && buffer2.last.targets.contains(i)) || (i == j + 1))){ 268 | // println() 269 | if (false && method.name == "getCanonicalName") { 270 | println("Making Phi " + j + "->" + i) 271 | println("endFrame " + endFrame + "\t" + endFrame.boxes.flatten.map(endMap)) 272 | println("startFrame " + startFrame + "\t" + startFrame.boxes.flatten.map(startMap)) 273 | } 274 | // println("endFrame.boxes " + endFrame.boxes) 275 | // println("startFrame.boxes " + startFrame.boxes) 276 | // println("endMap " + endMap) 277 | // println("startMap " + startMap) 278 | // assert( 279 | // endFrame.boxes.length == startFrame.boxes.length, 280 | // "Start frame doesn't line up with End frame" 281 | // ) 282 | val zipped = for{ 283 | (Some(e), Some(s)) <- endFrame.boxes zip startFrame.boxes 284 | a <- endMap.get(e).toSeq 285 | b <- startMap.get(s).toSeq 286 | 287 | i <- 0 until e.getSize 288 | } yield (a + i, b + i) 289 | // println("zipped " + zipped) 290 | zipped 291 | }else Nil 292 | 293 | } 294 | BasicBlock(buffer, phis, types, lines) 295 | } 296 | 297 | if (false) { 298 | 299 | for ((block, i) <- basicBlocks.zipWithIndex){ 300 | println("") 301 | println(i + "\t" + block.phi.toList) 302 | println ("" + blockBuffers(i)._3) 303 | for(i <- 0 until block.insns.length){ 304 | println(block.lines(i) + "\t" + block.insns(i)) 305 | } 306 | } 307 | println("") 308 | println("") 309 | } 310 | 311 | 312 | 313 | val tryCatchBlocks = for(b <- method.tryCatchBlocks.asScala) yield{ 314 | TryCatchBlock( 315 | (b.start.block, b.start.insn), 316 | (b.end.block, b.end.insn), 317 | b.handler.block, 318 | blockBuffers(b.handler.block)._3(allFrames(blockMap(allInsns.indexOf(b.handler))).head.top()), 319 | Option(b.`type`).map(imm.Type.Cls.read) 320 | ) 321 | } 322 | 323 | 324 | Code(basicBlocks, tryCatchBlocks) 325 | } 326 | 327 | } -------------------------------------------------------------------------------- /src/main/scala/metascala/opcodes/ConvertInsn.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package opcodes 3 | 4 | 5 | 6 | import org.objectweb.asm.Type 7 | import scala.collection.mutable 8 | import metascala.imm.Type.Prim 9 | import metascala.imm.Type.Prim._ 10 | import scala.annotation.tailrec 11 | import org.objectweb.asm.tree._ 12 | import scala.collection.JavaConversions._ 13 | import scala.collection.JavaConverters._ 14 | import org.objectweb.asm.tree.analysis._ 15 | import org.objectweb.asm.Opcodes._ 16 | 17 | import Insn._ 18 | object ConvertInsn { 19 | case class F1[A, B](a: A => B, override val toString: String) extends Function1[A, B]{ 20 | def apply(x: A) = a(x) 21 | } 22 | case class F2[A, B, C](a: (A, B) => C, override val toString: String) extends Function2[A, B, C]{ 23 | def apply(x: A, y: B) = a(x, y) 24 | } 25 | def apply(insn: AbstractInsnNode, 26 | append: Insn => Unit, 27 | frame: Frame[Box], 28 | nextFrame: Frame[Box], 29 | blockMap: Array[Int]) 30 | (implicit vm: VM, getBox: Box => Int, deref: LabelNode => Int) = { 31 | 32 | def push[T](v: T, tpe: Prim[T]) = { 33 | append(Push(nextFrame.top(), tpe, v)) 34 | } 35 | def aLoad[T](p: Prim[T], tpe: imm.Type) ={ 36 | append(GetArray(nextFrame.top(), frame.top(), frame.top(1), tpe)) 37 | } 38 | def aStore[T](p: Prim[T], tpe: imm.Type) ={ 39 | append(PutArray(frame.top(), frame.top(1), frame.top(2), tpe)) 40 | } 41 | def binOp1[A](a: Prim[A])(f: (A, A) => A) = binOp(a, a, a)(f) 42 | def binOp[A, B, C](a: Prim[A], b: Prim[B], c: Prim[C])(f: (A, B) => C) = { 43 | val (bb, aa) = (getBox(frame.top()), getBox(frame.top(1))) 44 | append(BinOp(aa, a, bb, b, nextFrame.top(), c, f)) 45 | } 46 | def unaryOp[A, B](a: Prim[A], b: Prim[B])(f: A => B) = { 47 | append(UnaryOp(frame.top(), a, nextFrame.top(), b, f)) 48 | } 49 | def unaryBranch(label: LabelNode, f: Int => Boolean) = { 50 | append(Insn.UnaryBranch(frame.top(), label, f)) 51 | } 52 | def binaryBranch(label: LabelNode, f: (Int, Int) => Boolean) = { 53 | append(Insn.BinaryBranch(frame.top(), frame.top(1), label, f)) 54 | } 55 | def returnVal(tpe: imm.Type) = { 56 | append(Insn.ReturnVal(if (tpe == V) 0 else frame.top())) 57 | } 58 | def invokeVirtual(insn: MethodInsnNode, indexed: Boolean) = { 59 | val desc = imm.Desc.read(insn.desc) 60 | 61 | val cls = imm.Type.read(insn.owner) match{ 62 | case c: imm.Type.Cls => c 63 | case _ => imm.Type.Cls("java/lang/Object") 64 | } 65 | 66 | val args = for(j <- (0 until desc.args.length + 1).reverse) yield { 67 | frame.top(j) 68 | } 69 | getBox(args.last) 70 | val sig = new imm.Sig(insn.name, desc) 71 | val target = if (desc.ret == V) 0 else nextFrame.top(): Int 72 | 73 | val mIndex = 74 | if (!indexed) -1 75 | else vm.ClsTable(cls).vTable.indexWhere(_.sig == sig) 76 | 77 | append(Insn.InvokeVirtual(target, args.map(getBox), cls.cast[imm.Type.Cls], sig, mIndex)) 78 | } 79 | def invokeStatic(insn: MethodInsnNode, self: Int) = { 80 | val desc = imm.Desc.read(insn.desc) 81 | val m = vm.resolveDirectRef(insn.owner, imm.Sig(insn.name, desc)).get 82 | 83 | val args = for(j <- (0 until desc.args.length + self).reverse) yield { 84 | frame.top(j) 85 | } 86 | 87 | val target = if (desc.ret == V) 0 else nextFrame.top(): Int 88 | 89 | append(Insn.InvokeStatic(target, args.map(getBox), insn.owner, m)) 90 | } 91 | 92 | implicit def intInsnNode(x: AbstractInsnNode) = x.cast[IntInsnNode] 93 | implicit def jumpInsnNode(x: AbstractInsnNode) = x.cast[JumpInsnNode] 94 | implicit def methodInsnNode(x: AbstractInsnNode) = { 95 | x match { 96 | case x: MethodInsnNode => x 97 | case x: FieldInsnNode => new MethodInsnNode(x.getOpcode, x.owner, x.name, x.desc) 98 | } 99 | } 100 | def refField(list: rt.Cls => Seq[imm.Field], func: (rt.Cls, Int, imm.Type) => Insn) = { 101 | 102 | def resolve(cls: rt.Cls): (Int, rt.Cls) = { 103 | list(cls).lastIndexWhere(_.name == insn.name) match{ 104 | case -1 => resolve(cls.clsAncestry(1).cls) 105 | case x => (x, cls) 106 | } 107 | } 108 | val (index, cls) = resolve(insn.owner.cls) 109 | assert( 110 | index >= 0, 111 | s"no field found in ${insn.owner}: ${insn.name}\n" + 112 | "Fields\n" + list(insn.owner.cls).map(_.name).mkString("\n") 113 | ) 114 | val prim = list(cls)(index).desc 115 | append(func(cls, index - prim.size + 1, prim)) 116 | } 117 | 118 | insn.getOpcode match{ 119 | case ICONST_M1 => push(-1, I) 120 | case ICONST_0 => push(0, I) 121 | case ICONST_1 => push(1, I) 122 | case ICONST_2 => push(2, I) 123 | case ICONST_3 => push(3, I) 124 | case ICONST_4 => push(4, I) 125 | case ICONST_5 => push(5, I) 126 | case LCONST_0 => push(0L, J) 127 | case LCONST_1 => push(1L, J) 128 | case FCONST_0 => push(0F, F) 129 | case FCONST_1 => push(1F, F) 130 | case FCONST_2 => push(2F, F) 131 | case DCONST_0 => push(0D, D) 132 | case DCONST_1 => push(1D, D) 133 | case BIPUSH => push(insn.operand, I) 134 | case SIPUSH => push(insn.operand, I) 135 | case LDC => 136 | insn.cast[LdcInsnNode].cst match{ 137 | case s: String => 138 | val top = vm.alloc(Virtualizer.pushVirtual(s)(_)).apply(0) 139 | val index = vm.interned.length 140 | vm.interned.append(new ManualRef(top)) 141 | append(Ldc(nextFrame.top(), index)) 142 | case t: org.objectweb.asm.Type => 143 | val clsObj = vm.typeObjCache(imm.Type.read(t.getInternalName)) 144 | val index = vm.interned.length 145 | vm.interned.append(new ManualRef(clsObj())) 146 | append(Ldc(nextFrame.top(), index)) 147 | case x: java.lang.Byte => append(Insn.Push(nextFrame.top(), B, x: Byte)) 148 | case x: java.lang.Character => append(Insn.Push(nextFrame.top(), C, x: Char)) 149 | case x: java.lang.Short => append(Insn.Push(nextFrame.top(), S, x: Short)) 150 | case x: java.lang.Integer => append(Insn.Push(nextFrame.top(), I, x: Int)) 151 | case x: java.lang.Float => append(Insn.Push(nextFrame.top(), F, x: Float)) 152 | case x: java.lang.Long => append(Insn.Push(nextFrame.top(), J, x: Long)) 153 | case x: java.lang.Double => append(Insn.Push(nextFrame.top(), D, x: Double)) 154 | } 155 | 156 | case IALOAD => aLoad(I, I) 157 | case LALOAD => aLoad(J, J) 158 | case FALOAD => aLoad(F, F) 159 | case DALOAD => aLoad(D, D) 160 | case AALOAD => aLoad(I, imm.Type.Cls("java/lang/Object")) 161 | case BALOAD => aLoad(B, B) 162 | case CALOAD => aLoad(C, C) 163 | case SALOAD => aLoad(S, S) 164 | case IASTORE => aStore(I, I) 165 | case LASTORE => aStore(J, J) 166 | case FASTORE => aStore(F, F) 167 | case DASTORE => aStore(D, D) 168 | case AASTORE => aStore(I, imm.Type.Cls("java/lang/Object")) 169 | case BASTORE => aStore(B, B) 170 | case CASTORE => aStore(C, C) 171 | case SASTORE => aStore(S, S) 172 | case IADD => binOp1(I)(F2(_ + _, "IAdd")) 173 | case LADD => binOp1(J)(F2(_ + _, "LAdd")) 174 | case FADD => binOp1(F)(F2(_ + _, "FAdd")) 175 | case DADD => binOp1(D)(F2(_ + _, "DAdd")) 176 | case ISUB => binOp1(I)(F2(_ - _, "ISub")) 177 | case LSUB => binOp1(J)(F2(_ - _, "LSub")) 178 | case FSUB => binOp1(F)(F2(_ - _, "FSub")) 179 | case DSUB => binOp1(D)(F2(_ - _, "DSub")) 180 | case IMUL => binOp1(I)(F2(_ * _, "IMul")) 181 | case LMUL => binOp1(J)(F2(_ * _, "LMul")) 182 | case FMUL => binOp1(F)(F2(_ * _, "FMul")) 183 | case DMUL => binOp1(D)(F2(_ * _, "DMul")) 184 | case IDIV => binOp1(I)(F2(_ / _, "IDiv")) 185 | case LDIV => binOp1(J)(F2(_ / _, "LDiv")) 186 | case FDIV => binOp1(F)(F2(_ / _, "FDiv")) 187 | case DDIV => binOp1(D)(F2(_ / _, "DDiv")) 188 | case IREM => binOp1(I)(F2(_ % _, "IRem")) 189 | case LREM => binOp1(J)(F2(_ % _, "LRem")) 190 | case FREM => binOp1(F)(F2(_ % _, "FRem")) 191 | case DREM => binOp1(D)(F2(_ % _, "DRem")) 192 | case INEG => unaryOp(I, I)(F1(-_, "INeg")) 193 | case LNEG => unaryOp(J, J)(F1(-_, "LNeg")) 194 | case FNEG => unaryOp(F, F)(F1(-_, "FNeg")) 195 | case DNEG => unaryOp(D, D)(F1(-_, "DNeg")) 196 | case ISHL => binOp(I, I, I)(F2(_ << _, "IShl")) 197 | case LSHL => binOp(J, I, J)(F2(_ << _, "LShl")) 198 | case ISHR => binOp(I, I, I)(F2(_ >> _, "IShr")) 199 | case LSHR => binOp(J, I, J)(F2(_ >> _, "LShr")) 200 | case IUSHR => binOp(I, I, I)(F2(_ >>> _, "IUShr")) 201 | case LUSHR => binOp(J, I, J)(F2(_ >>> _, "LUShr")) 202 | case IAND => binOp(I, I, I)(F2(_ & _, "IAnd")) 203 | case LAND => binOp(J, J, J)(F2(_ & _, "LAnd")) 204 | case IOR => binOp(I, I, I)(F2(_ | _, "IOr")) 205 | case LOR => binOp(J, J, J)(F2(_ | _, "LOr")) 206 | case IXOR => binOp(I, I, I)(F2(_ ^ _, "IXOr")) 207 | case LXOR => binOp(J, J, J)(F2(_ ^ _, "LXOr")) 208 | case IINC => 209 | val x = insn.cast[IincInsnNode] 210 | val bvalue = new Box(new BasicValue(org.objectweb.asm.Type.INT_TYPE)) 211 | append(Insn.Push(bvalue, I, x.incr)) 212 | append(Insn.BinOp[I, I, I](bvalue, I, frame.getLocal(x.`var`), I, nextFrame.getLocal(x.`var`), I, F2[Int, Int, Int](_+_, "IInc"))) 213 | case I2L => unaryOp(I, J)(F1(_.toLong, "I2L")) 214 | case I2F => unaryOp(I, F)(F1(_.toFloat, "I2F")) 215 | case I2D => unaryOp(I, D)(F1(_.toDouble,"I2D")) 216 | case L2I => unaryOp(J, I)(F1(_.toInt, "L2I")) 217 | case L2F => unaryOp(J, F)(F1(_.toFloat, "L2F")) 218 | case L2D => unaryOp(J, D)(F1(_.toDouble,"L2D")) 219 | case F2I => unaryOp(F, I)(F1(_.toInt, "F2I")) 220 | case F2L => unaryOp(F, J)(F1(_.toLong, "F2L")) 221 | case F2D => unaryOp(F, D)(F1(_.toDouble,"F2D")) 222 | case D2I => unaryOp(D, I)(F1(_.toInt, "D2I")) 223 | case D2L => unaryOp(D, F)(F1(_.toLong, "D2L")) 224 | case D2F => unaryOp(D, F)(F1(_.toFloat, "D2F")) 225 | case I2B => unaryOp(I, B)(F1(_.toByte, "I2B")) 226 | case I2C => unaryOp(I, C)(F1(_.toChar, "I2C")) 227 | case I2S => unaryOp(I, S)(F1(_.toShort, "I2S")) 228 | case LCMP => binOp(J, J, I)(F2(_ compare _, "LCmp")) 229 | case FCMPL => binOp(F, F, I)(F2(_ compare _, "FCmpl")) 230 | case FCMPG => binOp(F, F, I)(F2(_ compare _, "FCmpg")) 231 | case DCMPL => binOp(D, D, I)(F2(_ compare _, "DCmpl")) 232 | case DCMPG => binOp(D, D, I)(F2(_ compare _, "DCmpG")) 233 | case IFEQ => unaryBranch(insn.label, F1(_ == 0, "IfEq")) 234 | case IFNE => unaryBranch(insn.label, F1(_ != 0, "IfNe")) 235 | case IFLT => unaryBranch(insn.label, F1(_ < 0, "IfLt")) 236 | case IFGE => unaryBranch(insn.label, F1(_ >= 0, "IfGe")) 237 | case IFGT => unaryBranch(insn.label, F1(_ > 0, "IfGt")) 238 | case IFLE => unaryBranch(insn.label, F1(_ <= 0, "IfLe")) 239 | case IF_ICMPEQ => binaryBranch(insn.label, F2(_ == _, "IfICmpEq")) 240 | case IF_ICMPNE => binaryBranch(insn.label, F2(_ != _, "IfICmpNe")) 241 | case IF_ICMPLT => binaryBranch(insn.label, F2(_ < _, "IfICmpLt")) 242 | case IF_ICMPGE => binaryBranch(insn.label, F2(_ >= _, "IfICmpGe")) 243 | case IF_ICMPGT => binaryBranch(insn.label, F2(_ > _, "IfICmpGt")) 244 | case IF_ICMPLE => binaryBranch(insn.label, F2(_ <= _, "IfICmpLe")) 245 | case IF_ACMPEQ => binaryBranch(insn.label, F2(_ == _, "IfACmpEq")) 246 | case IF_ACMPNE => binaryBranch(insn.label, F2(_ != _, "IfACmpNe")) 247 | case GOTO => append(Insn.Goto(insn.label)) 248 | case TABLESWITCH => 249 | val x = insn.cast[TableSwitchInsnNode] 250 | append(Insn.TableSwitch(frame.top(), x.min, x.max, x.dflt, x.labels.map(deref))) 251 | case LOOKUPSWITCH => 252 | val x = insn.cast[LookupSwitchInsnNode] 253 | append(Insn.LookupSwitch(frame.top(), x.dflt, x.keys.asScala.map(_.intValue), x.labels.map(deref))) 254 | case IRETURN => returnVal(I) 255 | case LRETURN => returnVal(J) 256 | case FRETURN => returnVal(F) 257 | case DRETURN => returnVal(D) 258 | case ARETURN => returnVal(imm.Type.Cls("java/lang/Object")) 259 | case RETURN => returnVal(V) 260 | case GETSTATIC => refField(_.staticList, Insn.GetStatic(nextFrame.top(), _, _, _)) 261 | case PUTSTATIC => refField(_.staticList, Insn.PutStatic(frame.top(), _, _, _)) 262 | case GETFIELD => refField(_.fieldList, (a, b, c) => Insn.GetField(nextFrame.top(), frame.top(), b, c)) 263 | case PUTFIELD => refField(_.fieldList, (a, b, c) => Insn.PutField(frame.top(), frame.top(1), b, c)) 264 | case INVOKESTATIC => invokeStatic(insn, 0) 265 | case INVOKESPECIAL => invokeStatic(insn, 1) 266 | case INVOKEVIRTUAL => invokeVirtual(insn, indexed=true) 267 | case INVOKEINTERFACE => invokeVirtual(insn, indexed=false) 268 | case NEW => append(Insn.New(nextFrame.top(), vm.ClsTable(insn.cast[TypeInsnNode].desc))) 269 | case NEWARRAY => 270 | val typeRef: imm.Type = insn.operand match{ 271 | case 4 => Z: imm.Type 272 | case 5 => C: imm.Type 273 | case 6 => F: imm.Type 274 | case 7 => D: imm.Type 275 | case 8 => B: imm.Type 276 | case 9 => S: imm.Type 277 | case 10 => I: imm.Type 278 | case 11 => J: imm.Type 279 | } 280 | append(Insn.NewArray(frame.top(), nextFrame.top(), typeRef)) 281 | case ANEWARRAY => append(Insn.NewArray(frame.top(), nextFrame.top(), imm.Type.read(insn.cast[TypeInsnNode].desc))) 282 | case ARRAYLENGTH => append(Insn.ArrayLength(frame.top(), nextFrame.top())) 283 | case ATHROW => append(Insn.AThrow(frame.top())) 284 | case CHECKCAST => append(Insn.CheckCast(frame.top(), nextFrame.top(), imm.Type.read(insn.cast[TypeInsnNode].desc))) 285 | case INSTANCEOF => append(Insn.InstanceOf(frame.top(), nextFrame.top(), imm.Type.read(insn.cast[TypeInsnNode].desc))) 286 | case MULTIANEWARRAY => 287 | val x = insn.cast[MultiANewArrayInsnNode] 288 | val dims = (0 until x.dims).reverse.map(frame.top) 289 | append(Insn.MultiANewArray(imm.Type.read(x.desc), nextFrame.top(), dims.map(getBox))) 290 | case IFNULL => unaryBranch(insn.label, F1(_ == 0, "IfNull")) 291 | case IFNONNULL => unaryBranch(insn.label, F1(_ != 0, "IfNonNull")) 292 | case ACONST_NULL | POP | POP2 | DUP | DUP_X1 | DUP_X2 | DUP2 | 293 | DUP2_X1 | DUP2_X2 | SWAP | ISTORE | LSTORE | FSTORE | DSTORE | 294 | ASTORE | ILOAD | LLOAD | FLOAD | DLOAD | ALOAD | NOP | -1 => 295 | () // These are "move" operations and can be ignored 296 | case MONITORENTER | MONITOREXIT => () // No monitors! 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/main/scala/metascala/opcodes/Insn.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package opcodes 3 | 4 | 5 | import metascala.imm.Type.Prim 6 | import metascala.imm.Type 7 | import metascala.imm 8 | 9 | class Jump(override val targets: Seq[Int]) extends Insn 10 | class Invoke extends Insn 11 | 12 | case class Code(blocks: Seq[BasicBlock] = Nil, 13 | tryCatches: Seq[TryCatchBlock] = Nil){ 14 | lazy val localSize = blocks.map(_.locals.map(_.size).sum).max 15 | } 16 | case class BasicBlock(insns: Seq[Insn], 17 | phi: Seq[Seq[(Sym, Sym)]], 18 | locals: Seq[imm.Type], 19 | lines: Seq[Int]) 20 | 21 | case class TryCatchBlock(start: (Sym, Sym), 22 | end: (Sym, Sym), 23 | handler: Int, 24 | destReg: Int, 25 | blockType: Option[Type.Cls]) 26 | 27 | case class Step(insn: Insn, line: Int) 28 | 29 | sealed trait Insn{ 30 | def targets: Seq[Int] = Nil 31 | } 32 | object Insn{ 33 | case class BinOp[A, B, R](a: Sym, pa: Prim[A], b: Sym, pb: Prim[B], out: Sym, pout: Prim[R], func: (A, B) => R) extends Insn 34 | case class UnaryOp[A, R](a: Sym, pa: Prim[A], out: Sym, pout: Prim[R], func: A => R) extends Insn 35 | 36 | case class UnaryBranch(a: Sym, target: Int, func: Int => Boolean) extends Jump(Seq(target)) 37 | case class BinaryBranch(a: Sym, b: Sym, target: Int, func: (Int, Int) => Boolean) extends Jump(Seq(target)) 38 | case class ReturnVal(a: Sym) extends Insn 39 | case class AThrow(src: Sym) extends Insn 40 | 41 | case class TableSwitch(src: Sym, min: Int, max: Int, default: Int, targetList: Seq[Int]) extends Jump(targetList :+ default) 42 | case class LookupSwitch(src: Sym, default: Int, keys: Seq[Int], targetList: Seq[Int]) extends Jump(targetList :+ default) 43 | case class Goto(target: Int) extends Jump(Seq(target)) 44 | 45 | case class CheckCast(src: Sym, dest: Sym, desc: Type) extends Insn 46 | case class ArrayLength(src: Sym, dest: Sym) extends Insn 47 | case class InstanceOf(src: Sym, dest: Sym, desc: Type) extends Insn 48 | 49 | case class Push[T](dest: Sym, prim: Prim[T], value: T) extends Insn 50 | case class Ldc(dest: Sym, value: Int) extends Insn 51 | 52 | case class InvokeStatic(dest: Sym, srcs: Seq[Sym], owner: Type.Cls, method: rt.Method) extends Invoke 53 | case class InvokeVirtual(dest: Sym, srcs: Seq[Sym], owner: Type.Cls, sig: imm.Sig, methodIndex: Int) extends Invoke 54 | 55 | case class New(target: Sym, cls: rt.Cls) extends Insn 56 | case class NewArray(src: Sym, dest: Sym, typeRef: imm.Type) extends Insn 57 | case class MultiANewArray(desc: Type, target: Sym, dims: Seq[Sym]) extends Insn 58 | 59 | // offsets fixed/fixed 60 | case class PutStatic(src: Sym, cls: rt.Cls, index: Int, prim: Type) extends Insn 61 | case class GetStatic(dest: Sym, cls: rt.Cls, index: Int, prim: Type) extends Insn 62 | 63 | // offsets relative/fixed 64 | case class PutField(src: Sym, obj: Sym, index: Int, prim: Type) extends Insn 65 | case class GetField(dest: Sym, obj: Sym, index: Int, prim: Type) extends Insn 66 | 67 | // offsets relative/relative 68 | case class PutArray(src: Sym, indexSrc: Sym, array: Sym, prim: Type) extends Insn 69 | case class GetArray(dest: Sym, indexSrc: Sym, array: Sym, prim: Type) extends Insn 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/metascala/opcodes/package.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | import org.objectweb.asm.tree.analysis.{BasicValue, Frame} 4 | 5 | package object opcodes { 6 | implicit def unbox(b: Box) = b.value 7 | implicit class pimpedFrame(x: Frame[Box]){ 8 | def top(n: Int = 0) = x.getStack(x.getStackSize - 1 - n) 9 | def boxes = { 10 | val locals = for { 11 | localId <- 0 until x.getLocals 12 | local <- Option(x.getLocal(localId)) 13 | 14 | } yield if (local.getType != null) Some(local) else None 15 | val stackVals = for { 16 | stackId <- 0 until x.getStackSize 17 | stackVal = x.getStack(stackId) 18 | } yield Some(stackVal) 19 | locals ++ stackVals 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/metascala/package.scala: -------------------------------------------------------------------------------- 1 | import metascala.rt 2 | import metascala.rt.{Arr, Obj, Thread} 3 | import collection.mutable 4 | import scala.reflect.ClassTag 5 | 6 | package object metascala { 7 | import imm.Type.Prim 8 | 9 | /** 10 | * Represents a number referencing the local variable pool. 11 | */ 12 | type Sym = Int 13 | 14 | class Registrar(f: Ref => Unit, val vm: VM) extends Function1[Ref, Unit]{ 15 | def apply(i: Ref) = f(i) 16 | } 17 | private[metascala] implicit class castable(val x: Any) extends AnyVal{ 18 | def cast[T] = x.asInstanceOf[T] 19 | } 20 | implicit class splitAllable[T](val c: Seq[T]) extends AnyVal{ 21 | def splitAll(positions: List[Int], index: Int = 0): Seq[Seq[T]] = positions match{ 22 | case Nil => Seq(c) 23 | case firstPos :: restPos => 24 | val (first, rest) = c.splitAt(firstPos - index) 25 | first +: rest.splitAll(restPos, firstPos) 26 | } 27 | } 28 | implicit class pimpedAny(val x: Any) extends AnyVal{ 29 | def toVirtObj(implicit registrar: Registrar) = { 30 | Virtualizer.pushVirtual(x).apply(0) 31 | } 32 | } 33 | def isObj(i: Int) = i < 0 34 | def isArr(i: Int) = i > 0 35 | implicit class pimpedVal(val v: Val) extends AnyVal{ 36 | def isObj(implicit vm: VM) = metascala.isObj(vm.heap(v)) 37 | def isArr(implicit vm: VM) = metascala.isArr(vm.heap(v)) 38 | def obj(implicit vm: VM) = { 39 | 40 | //assert(isObj, v + " " + vm.heap.memory.slice(v / 10 * 10, v / 10 * 10 + 10).toList) 41 | new Obj(v) 42 | } 43 | def arr(implicit vm: VM) = { 44 | // assert(isArr, v) 45 | new Arr(v) 46 | } 47 | 48 | def toRealObj[T](implicit vm: VM, ct: ClassTag[T]) = { 49 | Virtualizer.popVirtual(imm.Type.Cls(ct.runtimeClass.getName.toSlash), () => v) 50 | .cast[T] 51 | } 52 | } 53 | 54 | trait Ref{ 55 | def apply(): Int 56 | def update(i: Int): Unit 57 | } 58 | class ArrRef(val get: () => Int, val set: Int => Unit) extends Ref{ 59 | def apply() = get() 60 | def update(i: Int) = set(i) 61 | } 62 | implicit class ManualRef(var x: Int) extends Ref{ 63 | def apply() = x 64 | def update(i: Int) = x = i 65 | 66 | } 67 | 68 | object Val{ 69 | val Null = 0 70 | implicit def objToVal(x: Obj) = x.address 71 | implicit def arrToVal(x: Arr) = x.address 72 | } 73 | 74 | type Val = Int 75 | 76 | def forNameBoxed(name: String) = { 77 | if(Prim.all.contains(name(0))) 78 | Prim.all(name(0)).boxedClass 79 | else 80 | Class.forName(name) 81 | 82 | } 83 | def forName(name: String) = { 84 | if(Prim.all.contains(name(0))) 85 | Prim.all(name(0)).primClass 86 | else 87 | Class.forName(name) 88 | } 89 | 90 | def getAllFields(cls: Class[_]): Seq[java.lang.reflect.Field] = { 91 | Option(cls.getSuperclass) 92 | .toSeq 93 | .flatMap(getAllFields) 94 | .++(cls.getDeclaredFields) 95 | } 96 | 97 | implicit def stringToClass(s: String)(implicit vm: VM) = vm.ClsTable(imm.Type.Cls(s)) 98 | implicit def stringToClsType(s: String) = imm.Type.Cls(s) 99 | implicit def stringToDesc(x: String) = imm.Desc.read(x) 100 | implicit class pimpedString(val s: String) extends AnyVal{ 101 | def toDot = s.replace('/', '.') 102 | def toSlash = s.replace('.', '/') 103 | def allocObj(initMembers: (String, Ref)*)(implicit registrar: Registrar) = { 104 | implicit val vm = registrar.vm 105 | rt.Obj.allocate(s, initMembers:_*).address 106 | } 107 | def allocArr(backing: Seq[Ref])(implicit registrar: Registrar) = { 108 | implicit val vm = registrar.vm 109 | rt.Arr.allocate(s, backing.toArray).address 110 | } 111 | } 112 | def reader(src: Seq[Val], index: Int) = { 113 | var i = index 114 | () => { 115 | i += 1 116 | src(i - 1) 117 | } 118 | } 119 | def writer(src: mutable.Seq[Val], index: Int) = { 120 | var i = index 121 | (x: Int) => { 122 | i += 1 123 | src(i - 1) = x 124 | } 125 | } 126 | def blit(src: Seq[Int], srcIndex: Int, dest: mutable.Seq[Int], destIndex: Int, length: Int) = { 127 | var i = 0 128 | while(i < length){ 129 | dest(destIndex + i) = src(srcIndex + i) 130 | i+= 1 131 | } 132 | } 133 | 134 | def shorten(name: String) = { 135 | val some :+ last = name.split("/").toSeq 136 | (some.map(_(0)) :+ last).mkString("/") 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/scala/metascala/rt/Cls.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package rt 3 | 4 | import collection.mutable 5 | 6 | 7 | import metascala.{VM, imm} 8 | import metascala.imm.{Sig, Access, Type} 9 | import metascala.opcodes.{Conversion, Insn} 10 | import metascala.imm.Type.Prim.I 11 | import org.objectweb.asm.tree.ClassNode 12 | 13 | /** 14 | * A handle to a readable and writable value. 15 | */ 16 | class Var(var x: Val){ 17 | final def apply() = x 18 | final def update(y: Val){ 19 | x = y 20 | } 21 | } 22 | 23 | object Cls{ 24 | def apply(cn: ClassNode, index: Int)(implicit vm: VM) = { 25 | 26 | import imm.NullSafe._ 27 | val fields = cn.fields.safeSeq.map(imm.Field.read) 28 | val superType = cn.superName.safeOpt.map(Type.Cls.read) 29 | new Cls( 30 | tpe = imm.Type.Cls.read(cn.name), 31 | superType = superType, 32 | sourceFile = cn.sourceFile.safeOpt, 33 | interfaces = cn.interfaces.safeSeq.map(Type.Cls.read), 34 | accessFlags = cn.access, 35 | methods = 36 | cn.methods 37 | .safeSeq 38 | .zipWithIndex 39 | .map{case (mn, i) => 40 | new rt.Method.Cls( 41 | index, 42 | i, 43 | Sig(mn.name, imm.Desc.read(mn.desc)), 44 | mn.access, 45 | () => Conversion.ssa(cn.name, mn) 46 | ) 47 | }, 48 | fieldList = 49 | superType.toSeq.flatMap(_.cls.fieldList) ++ 50 | fields.filter(!_.static).flatMap{x => 51 | Seq.fill(x.desc.size)(x) 52 | }, 53 | staticList = 54 | fields.filter(_.static).flatMap{x => 55 | Seq.fill(x.desc.size)(x) 56 | }, 57 | outerCls = cn.outerClass.safeOpt.map(Type.Cls.read), 58 | index 59 | ) 60 | } 61 | } 62 | /** 63 | * The runtime mutable and VM-specific data of a Java Class 64 | */ 65 | class Cls(val tpe: imm.Type.Cls, 66 | val superType: Option[imm.Type.Cls], 67 | val sourceFile: Option[String], 68 | val interfaces: Seq[imm.Type.Cls], 69 | val accessFlags: Int, 70 | val methods: Seq[rt.Method.Cls], 71 | val fieldList: Seq[imm.Field], 72 | val staticList: Seq[imm.Field], 73 | val outerCls: Option[imm.Type.Cls], 74 | val index: Int) 75 | (implicit vm: VM){ 76 | import vm._ 77 | 78 | var initialized = false 79 | 80 | 81 | def checkInitialized()(implicit vm: VM): Unit = { 82 | if (!initialized){ 83 | initialized = true 84 | vm.resolveDirectRef(tpe, Sig("", imm.Desc.read("()V"))) 85 | .foreach(threads(0).invoke(_, Nil)) 86 | 87 | superType.foreach{ cls => 88 | vm.ClsTable(cls).checkInitialized() 89 | } 90 | } 91 | } 92 | 93 | val isInterface = (accessFlags & Access.Interface) != 0 94 | val statics = vm.alloc(implicit i => 95 | rt.Arr.allocate(I, staticList.length) 96 | ) 97 | 98 | def method(name: String, desc: imm.Desc): Option[rt.Method] = { 99 | clsAncestry.flatMap(_.methods) 100 | .find(m => m.sig.name == name && m.sig.desc == desc) 101 | } 102 | 103 | lazy val size = fieldList.length 104 | 105 | def name = tpe.name 106 | 107 | /** 108 | * All classes that this class inherits from 109 | */ 110 | lazy val clsAncestry: List[imm.Type.Cls] = { 111 | superType match{ 112 | case None => List(tpe) 113 | case Some(superT) => tpe :: superT.cls.clsAncestry 114 | } 115 | } 116 | 117 | /** 118 | * All classes and interfaces that this class inherits from 119 | */ 120 | lazy val typeAncestry: Set[imm.Type.Cls] = { 121 | Set(tpe) ++ 122 | superType.toSeq.flatMap(_.cls.typeAncestry) ++ 123 | interfaces.flatMap(_.cls.typeAncestry) 124 | } 125 | 126 | 127 | /** 128 | * The virtual function dispatch table 129 | */ 130 | lazy val vTable: Seq[rt.Method] = { 131 | val oldMethods = 132 | mutable.ArrayBuffer( 133 | superType 134 | .toArray 135 | .flatMap(_.vTable): _* 136 | ) 137 | 138 | methods.filter(!_.static) 139 | .foreach{ m => 140 | 141 | val index = oldMethods.indexWhere{ mRef => mRef.sig == m.sig } 142 | 143 | val native = vm.natives.trapped.find{case rt.Method.Native(clsName, sig, func) => 144 | (name == clsName) && sig == m.sig 145 | } 146 | 147 | val update = 148 | if (index == -1) oldMethods.append(_: Method) 149 | else oldMethods.update(index, _: Method) 150 | 151 | native match { 152 | case None => update(m) 153 | case Some(native) => update(native) 154 | } 155 | } 156 | 157 | oldMethods 158 | } 159 | 160 | /** 161 | * A hash map of the virtual function table, used for quick lookup 162 | * by method signature 163 | */ 164 | lazy val vTableMap = vTable.map(m => m.sig -> m).toMap 165 | 166 | override def toString() = { 167 | s"Cls($index, ${tpe.name})" 168 | } 169 | 170 | def shortName = shorten(tpe.name) 171 | 172 | def heapSize = fieldList.length + rt.Obj.headerSize 173 | } 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/main/scala/metascala/rt/Method.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package rt 3 | 4 | import metascala.opcodes.{Code, Conversion} 5 | import org.objectweb.asm.tree.MethodNode 6 | 7 | 8 | /** 9 | * A reference to a method with a specific signature 10 | */ 11 | trait Method{ 12 | def sig: imm.Sig 13 | lazy val argSize = sig.desc.args.foldLeft(0)(_ + _.size) 14 | } 15 | object Method{ 16 | case class Native(clsName: String, 17 | sig: imm.Sig, 18 | func: (rt.Thread, () => Int, Int => Unit) => Unit) 19 | extends Method{ 20 | override def toString = s"Method.Native(${clsName}, ${sig.unparse}})" 21 | } 22 | 23 | 24 | /** 25 | * A reference to a method belonging to a class 26 | */ 27 | case class Cls(clsIndex: Int, 28 | methodIndex: Int, 29 | sig: imm.Sig, 30 | accessFlags: Int, 31 | codeThunk: () => Code) 32 | (implicit vm: VM) extends Method{ 33 | lazy val cls = vm.ClsTable.clsIndex(clsIndex) 34 | lazy val code = codeThunk() 35 | 36 | def static = (accessFlags & imm.Access.Static) != 0 37 | def native = (accessFlags & imm.Access.Native) != 0 38 | override def toString = s"Method.Cls(${cls.name}, ${sig.unparse}})" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/metascala/rt/Obj.scala: -------------------------------------------------------------------------------- 1 | package metascala.rt 2 | 3 | import collection.mutable 4 | import metascala._ 5 | import metascala.imm 6 | import scala.Some 7 | 8 | 9 | object Obj{ 10 | val headerSize = 2 11 | def allocate(cls: rt.Cls, initMembers: (String, Ref)*)(implicit registrar: Registrar): Obj = { 12 | implicit val vm = registrar.vm 13 | val address = vm.heap.allocate(headerSize + cls.fieldList.length) 14 | // println("Allocating " + cls.name + " at " + address) 15 | vm.heap(address()) = -cls.index 16 | vm.heap(address() + 1) = cls.fieldList.length 17 | val obj = new Obj(address) 18 | for ((s, v) <- initMembers){ 19 | obj(s) = v() 20 | } 21 | obj 22 | } 23 | 24 | def unapply(x: Val)(implicit vm: VM) = new Obj(x) 25 | } 26 | 27 | class Obj(val address: Ref) 28 | (implicit vm: VM) { 29 | /** 30 | * Layout 31 | * ------ 32 | * 0 Class Index 33 | * 1 Length 34 | * 2 Field0 35 | * 3 Field1 36 | * ... 37 | * N FieldN-2 38 | */ 39 | import vm._ 40 | 41 | def cls: rt.Cls = vm.ClsTable.clsIndex(-vm.heap(address()).toInt) 42 | 43 | object members extends mutable.Seq[Val]{ 44 | def apply(n: Int): Val = { 45 | vm.heap(address() + n + Obj.headerSize) 46 | } 47 | 48 | def update(n: Int, v: Val): Unit = { 49 | vm.heap(address() + n + Obj.headerSize) = v 50 | } 51 | 52 | def length = cls.fieldList.length 53 | 54 | def iterator: Iterator[Val] = new Iterator[Val]{ 55 | var index = 0 56 | def hasNext = index < members.length 57 | def next() = { 58 | val x = vm.heap(address() + index + Obj.headerSize) 59 | index += 1 60 | x 61 | } 62 | } 63 | } 64 | 65 | def tpe = cls.tpe 66 | 67 | def heapSize = cls.heapSize 68 | 69 | def apply(name: String): Val = { 70 | members(tpe.fieldList.lastIndexWhere(_.name == name)) 71 | } 72 | 73 | def update(name: String, value: Val) = { 74 | 75 | val index = tpe.fieldList.lastIndexWhere(_.name == name) 76 | members(index + 1 - tpe.fieldList(index).desc.size) = value 77 | } 78 | 79 | def view = address + " " + vm.heap.memory.slice(address(), address() + heapSize).toList 80 | 81 | override def toString = { 82 | s"vrt.Obj(${cls.name})" 83 | } 84 | } 85 | 86 | 87 | object Arr{ 88 | val headerSize = 2 89 | 90 | /** 91 | * Allocates and returns an array of the specified type, with `n` elements. 92 | * This is multiplied with the size of the type being allocated when 93 | * calculating the total amount of memory benig allocated 94 | */ 95 | def allocate(t: imm.Type, n: scala.Int)(implicit registrar: Registrar): Arr = { 96 | rt.Arr.allocate(t, Array.fill[Int](n * t.size)(0).map(x => new ManualRef(x): Ref)) 97 | } 98 | def allocate(innerType: imm.Type, backing: Array[Ref])(implicit registrar: Registrar): Arr = { 99 | implicit val vm = registrar.vm 100 | val address = vm.heap.allocate(Arr.headerSize + backing.length) 101 | // println("Allocating Array[" + innerType.name + "] at " + address) 102 | vm.heap(address()) = vm.arrayTypeCache.length 103 | 104 | vm.arrayTypeCache.append(innerType) 105 | vm.heap(address() + 1) = backing.length / innerType.size 106 | backing.map(_()).copyToArray(vm.heap.memory, address() + Arr.headerSize) 107 | 108 | new Arr(address) 109 | } 110 | def unapply(x: Int)(implicit vm: VM): Option[Arr] = Some(new Arr(x)) 111 | } 112 | 113 | /** 114 | * Can be treated as a mutable sequence of Ints; this representation is 115 | * completely unaware of differently sized contents. 116 | */ 117 | class Arr(val address: Ref)(implicit vm: VM) extends mutable.Seq[Int]{ 118 | /** 119 | * Layout 120 | * ------ 121 | * 0 Class Index 122 | * 1 Length 123 | * 2 Field0 124 | * 3 Field1 125 | * ... 126 | * N FieldN-2 127 | */ 128 | 129 | /** 130 | * The type the array contains 131 | */ 132 | def innerType = vm.arrayTypeCache(vm.heap(address())) 133 | 134 | /** 135 | * The type of the entire array 136 | */ 137 | def tpe = imm.Type.Arr(innerType) 138 | 139 | /** 140 | * Length of the array, in Ints or Longs, from the POV of interpreted code 141 | */ 142 | def arrayLength = vm.heap(address() + 1) 143 | 144 | /** 145 | * Total size of the array on the heap 146 | */ 147 | def heapSize = Arr.headerSize + length 148 | 149 | /** 150 | * Length of the read-writable part of the array, in Ints. 151 | */ 152 | def length = vm.heap(address() + 1) * innerType.size 153 | def apply(index: scala.Int) = vm.heap(address() + index + Arr.headerSize) 154 | def update(index: scala.Int, value: Val) = vm.heap(address() + index + Arr.headerSize) = value 155 | 156 | override def toString = ""+vm.heap.memory.slice(address(), address() + heapSize).toList 157 | 158 | def iterator: Iterator[Int] = vm.heap.memory.view(address() + Arr.headerSize, address() + heapSize).iterator 159 | } -------------------------------------------------------------------------------- /src/main/scala/metascala/rt/Thread.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package rt 3 | 4 | import scala.collection.mutable 5 | import annotation.tailrec 6 | 7 | 8 | import imm.Access 9 | import metascala.opcodes.{TryCatchBlock, Invoke, Jump, Insn} 10 | import Insn._ 11 | 12 | 13 | import Insn.Push 14 | import Insn.InvokeStatic 15 | import Insn.ReturnVal 16 | /** 17 | * A single thread within the Metascala VM. 18 | */ 19 | class Thread(val threadStack: mutable.ArrayStack[Frame] = mutable.ArrayStack())(implicit val vm: VM){ 20 | import vm._ 21 | 22 | private[this] var opCount = 0L 23 | def getOpCount = opCount 24 | def frame = threadStack.top 25 | 26 | val returnedVal = Array(0, 0) 27 | private[this] var insnCount = 0L 28 | def count = insnCount 29 | def indent = "\t" * threadStack.filter(_.method.sig.name != "Dummy").length 30 | 31 | 32 | def doPhi(frame: Frame, oldBlock: Int, newBlock: Int) = { 33 | val phi = frame.method.code.blocks(newBlock).phi(oldBlock) 34 | val temp = phi.map(x => frame.locals(x._1)) 35 | java.util.Arrays.fill(frame.locals, 0) 36 | for (i <- 0 until temp.length){ 37 | val (src, dest) = (temp(i), phi(i)._2) 38 | frame.locals(dest) = src 39 | } 40 | phi 41 | } 42 | 43 | def jumpPhis(target: Int) = { 44 | doPhi(frame, frame.pc._1, target) 45 | frame.pc = (target, 0) 46 | } 47 | final def step(): Unit = try { 48 | // println(frame.pc) 49 | val code = frame.method.code 50 | insnCount += 1 51 | var block = code.blocks(frame.pc._1) 52 | while(block.insns.length == 0){ 53 | jumpPhis(frame.pc._1 + 1) 54 | block = code.blocks(frame.pc._1) 55 | } 56 | 57 | val node = block.insns(frame.pc._2) 58 | 59 | val r = reader(frame.locals, 0) 60 | 61 | // if (!threadStack.exists(x => x.method.sig.name == "" && x.runningClass.name.contains("java/"))) { 62 | // lazy val localSnapshot = 63 | // block.locals 64 | // .flatMap(x => Seq(x.prettyRead(r)).padTo(x.size, "~")) 65 | // .toList 66 | // 67 | // println(indent + "::\t" + frame.runningClass.shortName + "/" + frame.method.sig.shortName + ":" + block.lines(frame.pc._2) + "\t" + localSnapshot) 68 | // println(indent + "::\t" + frame.pc + "\t" + node ) 69 | // println(indent + "::\t" + vm.heap.dump().replace("\n", "\n" + indent + "::\t")) 70 | // } 71 | 72 | 73 | val currentFrame = frame 74 | 75 | def advancePc() = { 76 | if (currentFrame.pc._2 + 1 < code.blocks(currentFrame.pc._1).insns.length){ 77 | currentFrame.pc =(currentFrame.pc._1, currentFrame.pc._2 + 1) 78 | Nil 79 | }else if(currentFrame.pc._1 + 1 < code.blocks.length){ 80 | val phi = doPhi(currentFrame, currentFrame.pc._1, currentFrame.pc._1+1) 81 | currentFrame.pc = (currentFrame.pc._1+1, 0) 82 | phi 83 | }else { 84 | Nil 85 | } 86 | } 87 | 88 | node match { 89 | case ReturnVal(sym) => 90 | returnVal(frame.method.sig.desc.ret.size, sym) 91 | 92 | case Push(target, prim, value) => 93 | prim.write(value, writer(frame.locals, target)) 94 | advancePc() 95 | 96 | case New(target, cls) => 97 | cls.checkInitialized() 98 | val obj = vm.alloc(rt.Obj.allocate(cls.name)(_)) 99 | frame.locals(target) = obj.address() 100 | advancePc() 101 | 102 | case InvokeStatic(target, sources, owner, m) => 103 | owner.checkInitialized() 104 | // Check for InvokeSpecial, which gets folded into InvokeStatic 105 | val thisVal = sources.length > m.sig.desc.args.length 106 | val thisCell = if (thisVal) Seq(1) else Nil 107 | 108 | val args = 109 | sources.zip(thisCell ++ m.sig.desc.args.map(_.size)) 110 | .flatMap{case (s, t) => frame.locals.slice(s, s + t)} 111 | 112 | if (thisVal && args(0) == 0){ 113 | throwExWithTrace("java/lang/NullPointerException", "null") 114 | }else{ 115 | val phis = advancePc() 116 | val ptarget = phis.find(_._1 == target).fold(target)(_._2) 117 | prepInvoke(m, args, writer(frame.locals, ptarget)) 118 | } 119 | 120 | case InvokeVirtual(target, sources, owner, sig, mIndex) => 121 | val args = sources.zip(1 +: sig.desc.args.map(_.size)) 122 | .flatMap{case (s, t) => 123 | frame.locals.slice(s, s + t) 124 | } 125 | val phis = advancePc() 126 | 127 | 128 | val isNull = args(0) == 0 129 | 130 | if(isNull) { 131 | throwExWithTrace("java/lang/NullPointerException", "null") 132 | } else { 133 | val mRef = mIndex match{ 134 | case -1 => 135 | args(0).obj.cls.vTableMap(sig) 136 | case _ => 137 | val cls = 138 | if (args(0).isObj) args(0).obj.cls 139 | else owner.cls 140 | cls.vTable(mIndex) 141 | } 142 | val ptarget = phis.find(_._1 == target).fold(target)(_._2) 143 | prepInvoke(mRef, args, writer(frame.locals, ptarget)) 144 | } 145 | 146 | case ArrayLength(src, dest) => 147 | frame.locals(dest) = frame.locals(src).arr.arrayLength 148 | advancePc() 149 | 150 | case NewArray(src, dest, typeRef) => 151 | val newArray = vm.alloc(rt.Arr.allocate(typeRef, frame.locals(src))(_)) 152 | frame.locals(dest) = newArray.address() 153 | advancePc() 154 | 155 | case PutArray(src, index, array, prim) => 156 | val arr = frame.locals(array).arr 157 | if (0 <= frame.locals(index) && frame.locals(index) < arr.arrayLength){ 158 | blit(frame.locals, src, arr, frame.locals(index) * prim.size, prim.size) 159 | advancePc() 160 | }else{ 161 | throwExWithTrace("java/lang/ArrayIndexOutOfBoundsException", frame.locals(index).toString) 162 | } 163 | 164 | case GetArray(dest, index, array, prim) => 165 | val arr = frame.locals(array).arr 166 | if (0 <= frame.locals(index) && frame.locals(index) < arr.arrayLength){ 167 | blit(arr, frame.locals(index) * prim.size, frame.locals, dest, prim.size) 168 | advancePc() 169 | }else{ 170 | throwExWithTrace("java/lang/ArrayIndexOutOfBoundsException", frame.locals(index).toString) 171 | } 172 | 173 | case Ldc(target, index) => 174 | frame.locals(target) = vm.interned(index())() 175 | advancePc() 176 | 177 | case UnaryOp(src, psrc, dest, pout, func) => 178 | pout.write(func(psrc.read(reader(frame.locals, src))), writer(frame.locals, dest)) 179 | advancePc() 180 | 181 | case BinOp(a, pa, b, pb, dest, pout, func) => 182 | 183 | val va = pa.read(reader(frame.locals, a)) 184 | val vb = pb.read(reader(frame.locals, b)) 185 | 186 | val out = func(va, vb) 187 | 188 | pout.write(out, writer(frame.locals, dest)) 189 | advancePc() 190 | case PutStatic(src, cls, index, prim) => 191 | cls.checkInitialized() 192 | blit(frame.locals, src, cls.statics, index, prim.size) 193 | advancePc() 194 | case GetStatic(src, cls, index, prim) => 195 | cls.checkInitialized() 196 | blit(cls.statics, index, frame.locals, src, prim.size) 197 | advancePc() 198 | 199 | case PutField(src, obj, index, prim) => 200 | if (frame.locals(obj) == 0){ 201 | throwExWithTrace("java/lang/NullPointerException", "null") 202 | }else{ 203 | blit(frame.locals, src, frame.locals(obj).obj.members, index, prim.size) 204 | advancePc() 205 | } 206 | 207 | case GetField(src, obj, index, prim) => 208 | if (frame.locals(obj) == 0){ 209 | throwExWithTrace("java/lang/NullPointerException", "null") 210 | }else{ 211 | blit(frame.locals(obj).obj.members, index, frame.locals, src, prim.size) 212 | advancePc() 213 | } 214 | 215 | case BinaryBranch(symA, symB, target, func) => 216 | val (a, b) = (frame.locals(symA), frame.locals(symB)) 217 | if(func(b, a)) jumpPhis(target) 218 | else advancePc() 219 | 220 | case UnaryBranch(sym, target, func) => 221 | if(func(frame.locals(sym))) jumpPhis(target) 222 | else advancePc() 223 | 224 | case Goto(target) => 225 | jumpPhis(target) 226 | 227 | case TableSwitch(src, min, max, default, targets) => 228 | var done = false 229 | for ((k, t) <- min to max zip targets) yield { 230 | if (frame.locals(src) == k && !done){ 231 | jumpPhis(t) 232 | done = true 233 | } 234 | } 235 | if (!done) jumpPhis(default) 236 | case LookupSwitch(src, default, keys, targets) => 237 | var done = false 238 | for ((k, t) <- keys zip targets) yield { 239 | if (frame.locals(src) == k && !done){ 240 | jumpPhis(t) 241 | done = true 242 | } 243 | } 244 | if (!done) jumpPhis(default) 245 | 246 | case CheckCast(src, dest, desc) => 247 | frame.locals(src) match{ 248 | case top 249 | if (top.isArr && !check(top.arr.tpe, desc)) 250 | || (top.isObj && !check(top.obj.tpe, desc)) => 251 | 252 | throwExWithTrace("java/lang/ClassCastException", "") 253 | case _ => 254 | frame.locals(dest) = frame.locals(src) 255 | advancePc() 256 | } 257 | case InstanceOf(src, dest, desc) => 258 | frame.locals(dest) = frame.locals(src) match{ 259 | case 0 => 0 260 | case top if (top.isArr && !check(top.arr.tpe, desc)) || (top.isObj && !check(top.obj.tpe, desc)) => 261 | 0 262 | case _ => 1 263 | } 264 | advancePc() 265 | 266 | case MultiANewArray(desc, symbol, dims) => 267 | def rec(dims: List[Int], tpe: imm.Type): Val = { 268 | 269 | (dims, tpe) match { 270 | case (size :: tail, imm.Type.Arr(innerType: imm.Type.Ref)) => 271 | val newArr = vm.alloc(rt.Arr.allocate(innerType, size)(_)) 272 | for(i <- 0 until size){ 273 | newArr(i) = rec(tail, innerType) 274 | } 275 | newArr.address() 276 | 277 | case (size :: Nil, imm.Type.Arr(innerType)) => 278 | vm.alloc(rt.Arr.allocate(innerType, size)(_)).address() 279 | } 280 | } 281 | val dimValues = dims.map(frame.locals).toList 282 | 283 | val array = rec(dimValues, desc) 284 | frame.locals(symbol) = array 285 | advancePc() 286 | case AThrow(src) => 287 | this.throwException(frame.locals(src).obj) 288 | } 289 | 290 | 291 | opCount += 1 292 | if (opCount > vm.insnLimit){ 293 | throw new Exception("Ran out of instructions! Limit: " + vm.insnLimit) 294 | } 295 | 296 | }catch{case e: Throwable if !e.isInstanceOf[WrappedVmException] => 297 | val newEx = new InternalVmException(e) 298 | newEx.setStackTrace(trace) 299 | throw newEx 300 | } 301 | 302 | def trace = { 303 | 304 | threadStack.map( f => 305 | new StackTraceElement( 306 | f.runningClass.name.toDot, 307 | f.method.sig.name, 308 | f.runningClass.sourceFile.getOrElse(""), 309 | try f.method.code.blocks(f.pc._1).lines(f.pc._2) catch{case _ => 0 } 310 | ) 311 | ).toArray 312 | } 313 | 314 | def returnVal(size: Int, index: Int) = { 315 | // println(s"Returning size: $size, index: $index") 316 | for(i <- 0 until size){ 317 | frame.returnTo(frame.locals(index + i)) 318 | } 319 | this.threadStack.pop 320 | } 321 | final def throwExWithTrace(clsName: String, detailMessage: String) = { 322 | 323 | throwException( 324 | vm.alloc( implicit r => 325 | rt.Obj.allocate(clsName, 326 | "stackTrace" -> trace.toVirtObj, 327 | "detailMessage" -> detailMessage.toVirtObj 328 | ) 329 | ) 330 | ) 331 | } 332 | 333 | @tailrec final def throwException(ex: Obj, print: Boolean = true): Unit = { 334 | import math.Ordering.Implicits._ 335 | println("Throwing!") 336 | threadStack.headOption match{ 337 | case Some(frame)=> 338 | val handler = 339 | frame.method.code.tryCatches.find{x => 340 | (x.start <= frame.pc) && 341 | (x.end >= frame.pc) && 342 | !x.blockType.isDefined || 343 | x.blockType.map(ex.cls.typeAncestry.contains).getOrElse(false) 344 | } 345 | 346 | handler match{ 347 | case None => 348 | threadStack.pop() 349 | throwException(ex, false) 350 | case Some(TryCatchBlock(start, end, handler, dest, blockType)) => 351 | frame.locals(dest) = ex.address() 352 | frame.pc = (handler, 0) 353 | 354 | } 355 | case None => 356 | throw new UncaughtVmException( 357 | ex.address().toRealObj[Throwable] 358 | ) 359 | } 360 | } 361 | 362 | final def prepInvoke(mRef: rt.Method, 363 | args: Seq[Int], 364 | returnTo: Int => Unit) = { 365 | // println(indent + "PrepInvoke " + mRef + " with " + args) 366 | 367 | mRef match{ 368 | case rt.Method.Native(clsName, imm.Sig(name, desc), op) => 369 | try op(this, reader(args, 0), returnTo) 370 | catch{case e: Exception => 371 | throwExWithTrace(e.getClass.getName.toSlash, e.getMessage) 372 | } 373 | case m @ rt.Method.Cls(clsIndex, methodIndex, sig, static, codethunk) => 374 | 375 | assert(!m.native, "method cannot be native: " + ClsTable.clsIndex(clsIndex).name + " " + sig.unparse) 376 | val startFrame = new Frame( 377 | runningClass = ClsTable.clsIndex(clsIndex), 378 | method = m, 379 | returnTo = returnTo, 380 | locals = args.toArray.padTo(m.code.localSize, 0) 381 | ) 382 | 383 | //log(indent + "locals " + startFrame.locals) 384 | threadStack.push(startFrame) 385 | } 386 | } 387 | final def prepInvoke(tpe: imm.Type, 388 | sig: imm.Sig, 389 | args: Seq[Any], 390 | returnTo: Int => Unit) 391 | : Unit = { 392 | 393 | val tmp = mutable.Buffer.empty[Val] 394 | vm.alloc{ implicit r => 395 | args.map( 396 | Virtualizer.pushVirtual(_, tmp.append(_: Int)) 397 | ) 398 | 399 | prepInvoke( 400 | vm.resolveDirectRef(tpe.cast[imm.Type.Cls], sig).get, 401 | tmp, 402 | returnTo 403 | ) 404 | } 405 | 406 | } 407 | def invoke(mRef: rt.Method, args: Seq[Int]): Any = { 408 | val startHeight = threadStack.length 409 | prepInvoke(mRef, args, writer(returnedVal, 0)) 410 | 411 | while(threadStack.length != startHeight) step() 412 | 413 | 414 | Virtualizer.popVirtual(mRef.sig.desc.ret, reader(returnedVal, 0)) 415 | } 416 | 417 | def invoke(cls: imm.Type.Cls, sig: imm.Sig, args: Seq[Any]): Any = { 418 | val startHeight = threadStack.length 419 | prepInvoke(cls, sig, args, writer(returnedVal, 0)) 420 | 421 | while(threadStack.length != startHeight) step() 422 | Virtualizer.popVirtual(sig.desc.ret, reader(returnedVal, 0)) 423 | } 424 | } 425 | 426 | case class FrameDump(clsName: String, 427 | methodName: String, 428 | fileName: String, 429 | lineNumber: Int) 430 | 431 | 432 | /** 433 | * The stack frame created by every method call 434 | */ 435 | class Frame(var pc: (Int, Int) = (0, 0), 436 | val runningClass: rt.Cls, 437 | val method: rt.Method.Cls, 438 | var lineNum: Int = 0, 439 | val returnTo: Int => Unit, 440 | val locals: Array[Val]) 441 | -------------------------------------------------------------------------------- /src/main/scala/metascala/rt/package.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | /** 4 | * Contains runtime data structures that allocate up the virtual machine: the 5 | * runtime state of classes, threads, stack frames, etc. 6 | */ 7 | package object rt {} 8 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/arrays/MultiDimArrays.java: -------------------------------------------------------------------------------- 1 | package metascala.features.arrays; 2 | 3 | public class MultiDimArrays { 4 | public static int[][] make2D(int a, int b){ 5 | return new int[a][b]; 6 | } 7 | public static int[][][] make3D(int a, int b, int c){ 8 | return new int[a][b][c]; 9 | } 10 | public static int getAndSet(){ 11 | int[][] arr = new int[10][10]; 12 | for(int i = 0; i < 10; i++){ 13 | for(int j = 0; j < 10; j++){ 14 | arr[i][j] = i + j; 15 | } 16 | } 17 | int total = 0; 18 | for(int i = 0; i < 10; i++){ 19 | for(int j = 0; j < 10; j++){ 20 | total += arr[i][j]; 21 | } 22 | } 23 | return total; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/classes/Inheritance.java: -------------------------------------------------------------------------------- 1 | package metascala.features.classes; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Vector; 6 | 7 | public class Inheritance { 8 | public static String implement(int n){ 9 | Baas b = new Sheep(); 10 | return b.baa(n); 11 | } 12 | public static String abstractClass(){ 13 | Car toyota = new Toyota(); 14 | return toyota.vroom(); 15 | } 16 | 17 | public static String shadowedInheritedGet(){ 18 | Car honda = new Honda(); 19 | return honda.vroom(); 20 | } 21 | 22 | public static String shadowedInheritedSet(){ 23 | Car honda = new Honda(); 24 | honda.rev(); 25 | honda.cc++; 26 | ((Honda)honda).cc++; 27 | return honda.vroom(); 28 | } 29 | 30 | public static String superMethod(){ 31 | return new Toyota().superVStart(); 32 | } 33 | public static int staticInheritance(){ 34 | int a = Parent.x; 35 | Child1.x = 100; 36 | return a + Child1.x + Child2.x; 37 | } 38 | } 39 | interface ParentInterface{ 40 | public static int x = 30; 41 | } 42 | class Parent{ 43 | public static int x = 10; 44 | } 45 | class Child1 extends Parent{ 46 | public static int get(){ 47 | return x; 48 | } 49 | } 50 | class Cowc{} 51 | class Child2 extends Cowc implements ParentInterface{ 52 | public static int get(){ 53 | return x; 54 | } 55 | } 56 | class Sheep implements Baas{ 57 | public String baa(int n){ 58 | String s = "b"; 59 | for(int i = 0; i < n; i++) s = s + "a"; 60 | return s; 61 | } 62 | } 63 | interface Baas{ 64 | public String baa(int n); 65 | } 66 | class Toyota extends Car{ 67 | public Toyota(){ 68 | this.cc = 10; 69 | } 70 | 71 | public String vStart(){ 72 | return "vr"; 73 | } 74 | public String superVStart(){ 75 | return super.vStart(); 76 | } 77 | } 78 | class Honda extends Car{ 79 | public int cc = 5; 80 | public String vStart(){ 81 | return "v" + cc + "r" + ((Car)this).cc + "r" + super.cc; 82 | } 83 | } 84 | 85 | class Car{ 86 | public int cc; 87 | public String vStart(){ 88 | return ""; 89 | } 90 | public void rev(){ 91 | this.cc = this.cc + 1; 92 | } 93 | public String vroom(){ 94 | String s = vStart(); 95 | for(int i = 0; i < cc; i++){ 96 | s = s + "o"; 97 | } 98 | return s + "m"; 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/controlflow/IfElse.java: -------------------------------------------------------------------------------- 1 | package metascala.features.controlflow; 2 | 3 | public class IfElse { 4 | static int a = 10; 5 | final static int b = 312; 6 | 7 | public static int basicIf(){ 8 | if (a < b) return a; 9 | else return -a; 10 | } 11 | public static int ifNonIntZero(){ 12 | if (((byte)a) > 0) return a; 13 | else return -a; 14 | } 15 | public static int ifNonIntBinary(){ 16 | if (((byte)a) > (short)b) return a; 17 | else return -a; 18 | } 19 | public static int ifElseIf(){ 20 | if (a > b) return a; 21 | else if (a == b) return -a; 22 | else return b; 23 | } 24 | public static int ifElseIfBig(){ 25 | if (a > b) return 1; 26 | else if (a > 12) return 2; 27 | else if (b < 10) return 3; 28 | else if (b == a) return 4; 29 | else if (b > a) return 5; 30 | else if (a == 10) return 6; 31 | else if (b == 312) return 7; 32 | else return 8; 33 | } 34 | public static int mathMin(){ 35 | return Math.min(16, 1); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/controlflow/Loops.java: -------------------------------------------------------------------------------- 1 | package metascala.features.controlflow; 2 | 3 | 4 | 5 | public class Loops { 6 | public static int nullFor(int a){ 7 | int c = 0; 8 | for(int i = 0; i > a; i++) c++; 9 | return c; 10 | } 11 | public static int basicFor(int a){ 12 | int c = 1; 13 | for(int i = 0; i < a; i++) c = c * 2; 14 | return c; 15 | } 16 | public static int nullWhile(int a){ 17 | int c = 1; 18 | while(c > a) c++; 19 | return c; 20 | } 21 | public static int basicWhile(int a){ 22 | int c = 1; 23 | while(c < a) c = c * 2; 24 | return c; 25 | } 26 | public static double sqrtFinder(double n){ 27 | double guess = n / 2 + 5; 28 | 29 | while(true){ 30 | double errorSquared = guess*guess - n; 31 | errorSquared = errorSquared * errorSquared; 32 | if (errorSquared / n < 0.1) return guess; 33 | else{ 34 | guess = ((guess * guess) - n) / (2 * guess); 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/controlflow/Switches.java: -------------------------------------------------------------------------------- 1 | package metascala.features.controlflow; 2 | 3 | public class Switches { 4 | public static int smallSwitch(int a){ 5 | switch(a){ 6 | case 0: return 1; 7 | case 1: return 0; 8 | default: return 2; 9 | } 10 | } 11 | public static double bigDenseSwitch(int a){ 12 | switch(a){ 13 | case 0: return 1123.213; 14 | case 1: return 3212.321; 15 | case 2: return 123123.123; 16 | case 3: return 123.312; 17 | case 4: return 123123.1231; 18 | case 5: return 1231.3212; 19 | case 6: return 123.132123; 20 | case 7: return 32123.123; 21 | case 8: return 123123.12312; 22 | case 9: return 123123.3123; 23 | case 10: return 123123.1312; 24 | case 11: return 123123.2123; 25 | case 12: return 123321.123; 26 | case 13: return 123123.12312; 27 | case 14: return 123123.1231; 28 | case 15: return 1321231.1231; 29 | case 16: return 23123123.1231; 30 | case 17: return 123123123.123123; 31 | case 18: return 123123.1231; 32 | case 19: return 23123.12321; 33 | case 20: return 12312312.321; 34 | case 21: return 1232312.312; 35 | case 22: return 123123123.132123; 36 | case 23: return 123123123.1231; 37 | case 24: return 132123.1231; 38 | case 25: return 12312321.123; 39 | case 26: return 1232123.312; 40 | case 27: return 123123.12312; 41 | case 28: return 13212312.123123; 42 | case 29: return 2123123.1231231; 43 | default: return 123123.123123; 44 | } 45 | } 46 | public static double bigSparseSwitch(int a){ 47 | switch(a){ 48 | case 1: return 3212.321; 49 | case 2: return 123123.123; 50 | case 4: return 123.312; 51 | case 8: return 123123.1231; 52 | case 16: return 1231.3212; 53 | case 32: return 123.132123; 54 | case 62: return 32123.123; 55 | case 128: return 123123.12312; 56 | case 256: return 123123.3123; 57 | case 512: return 123123.1312; 58 | case 1024: return 123123.2123; 59 | case 2048: return 123321.123; 60 | case 4096: return 123123.12312; 61 | case 8192: return 123123.1231; 62 | case 16384: return 1321231.1231; 63 | case 32768: return 23123123.1231; 64 | case 65536: return 123123123.123123; 65 | case 131072: return 123123.1231; 66 | case 262144: return 23123.12321; 67 | case 524288: return 12312312.321; 68 | case 1048576: return 1232312.312; 69 | case 2097152: return 123123123.132123; 70 | default: return 123123.123123; 71 | } 72 | } 73 | public static int charSwitch(char c){ 74 | switch(c){ 75 | case 'a': return 1; 76 | case 'b': return 2; 77 | case 'c': return 3; 78 | case 'd': return 4; 79 | case 'e': return 5; 80 | case 'f': return 6; 81 | case 'g': return 7; 82 | case 'h': return 8; 83 | case 'i': return 9; 84 | case 'j': return 0; 85 | default: return 10; 86 | } 87 | } 88 | public static int byteSwitch(byte b){ 89 | switch(b){ 90 | case 1: return 1; 91 | case 2: return 2; 92 | case 4: return 3; 93 | case 16: return 4; 94 | case 32: return 5; 95 | case 64: return 6; 96 | case 127: return 7; 97 | case -128: return 8; 98 | default: return 10; 99 | } 100 | } 101 | public static int stringSwitch(int n){ 102 | switch("" + n){ 103 | case "0": return 0; 104 | case "1": return 1; 105 | default: return 2; 106 | } 107 | } 108 | public static String stringSwitchTwo(String s){ 109 | switch(s){ 110 | case "omg": return "iam"; 111 | case "wtf": return "cow"; 112 | case "bbq": return "hearme"; 113 | default: return "moo"; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/exceptions/Exceptions.java: -------------------------------------------------------------------------------- 1 | package metascala.features.exceptions; 2 | 3 | import java.io.IOException; 4 | 5 | public class Exceptions { 6 | 7 | public static int throwCatch(int a){ 8 | 9 | int b = 1; 10 | if (a >= 1) b += 1; 11 | else b += 2; 12 | 13 | try{ 14 | int j = a + 1; 15 | if(a > 0) throw new Exception(); 16 | b += j; 17 | }catch(Exception e){ 18 | return b - 1; 19 | } 20 | return b; 21 | } 22 | 23 | public static int multiCatch(int in){ 24 | try{ 25 | try{ 26 | try{ 27 | try{ 28 | switch (in){ 29 | case 0: throw new IOException("IO"); 30 | case 1: throw new ArrayIndexOutOfBoundsException("Array!"); 31 | case 2: throw new NullPointerException("NPE"); 32 | case 3: throw new IllegalArgumentException("IaE"); 33 | default: throw new Exception("Excc"); 34 | } 35 | }catch(IOException e){ 36 | return 0; 37 | 38 | } 39 | }catch(ArrayIndexOutOfBoundsException e){ 40 | return 1; 41 | 42 | } 43 | }catch(NullPointerException e){ 44 | return 2; 45 | 46 | } 47 | }catch(Exception e){ 48 | return 3; 49 | 50 | } 51 | } 52 | 53 | public static String nullPointer(Object o){ 54 | try{ 55 | return o.toString(); 56 | }catch(NullPointerException npe){ 57 | return "null!"; 58 | } 59 | } 60 | 61 | public static String arrayIndexOutOfBounds(int i){ 62 | try{ 63 | int[] a = {1, 2, 4, 8}; 64 | return "result! " + a[i] ; 65 | }catch(ArrayIndexOutOfBoundsException npe){ 66 | return npe.getMessage(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/metascala/features/methods/Statics.java: -------------------------------------------------------------------------------- 1 | package metascala.features.methods; 2 | 3 | public class Statics { 4 | public static int helloWorld(int n){ 5 | return timesTwo(n); 6 | } 7 | 8 | public static int timesTwo(int n){ 9 | return n * 2; 10 | } 11 | 12 | public static int helloWorld2(int a, int b){ 13 | return timesTwo2(a, b); 14 | } 15 | 16 | public static int timesTwo2(int a, int b){ 17 | return (a - b) * 2; 18 | } 19 | 20 | public static int tailFactorial(int n){ 21 | if (n == 1){ 22 | return 1; 23 | }else{ 24 | return n * tailFactorial(n-1); 25 | } 26 | } 27 | public static int fibonacci(int n){ 28 | if (n == 1 || n == 0){ 29 | return 1; 30 | }else{ 31 | return fibonacci(n-1) + fibonacci(n-2); 32 | } 33 | } 34 | public static int call(int x) { 35 | return x+1; 36 | } 37 | public static int callAtPhiBoundary(int i){ 38 | 39 | int size = (i < 0) ? 1 : call(i); 40 | return size; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/metascala/full/Sudoku.java: -------------------------------------------------------------------------------- 1 | package metascala.full; 2 | 3 | import java.util.*; 4 | import java.math.BigInteger; 5 | import java.util.concurrent.atomic.*; 6 | import java.util.regex.*; 7 | import java.lang.reflect.Array; 8 | 9 | public class Sudoku { 10 | public static String run(){ 11 | int[][] grid = { 12 | {5, 3, 0, 0, 7, 0, 0, 0, 0}, 13 | {6, 0, 0, 1, 9, 5, 0, 0, 0}, 14 | {0, 9, 8, 0, 0, 0, 0, 6, 0}, 15 | {8, 0, 0, 0, 6, 0, 0, 0, 3}, 16 | {4, 0, 0, 8, 0, 3, 0, 0, 1}, 17 | {7, 0, 0, 0, 2, 0, 0, 0, 6}, 18 | {0, 6, 0, 0, 0, 0, 2, 8, 0}, 19 | {0, 0, 0, 4, 1, 9, 0, 0, 5}, 20 | {0, 0, 0, 0, 8, 0, 0, 7, 9}, 21 | }; 22 | Sudoku.solve(0, 0, grid); 23 | return Sudoku.writeMatrix(grid); 24 | } 25 | static boolean solve(int i, int j, int[][] cells) { 26 | if (i == 9) { 27 | i = 0; 28 | if (++j == 9) 29 | return true; 30 | } 31 | if (cells[i][j] != 0) // skip filled cells 32 | return solve(i+1,j,cells); 33 | 34 | for (int val = 1; val <= 9; ++val) { 35 | if (legal(i,j,val,cells)) { 36 | cells[i][j] = val; 37 | if (solve(i+1,j,cells)) 38 | return true; 39 | } 40 | } 41 | cells[i][j] = 0; // reset on backtrack 42 | return false; 43 | } 44 | 45 | static boolean legal(int i, int j, int val, int[][] cells) { 46 | for (int k = 0; k < 9; ++k) // row 47 | if (val == cells[k][j]) 48 | return false; 49 | 50 | for (int k = 0; k < 9; ++k) // col 51 | if (val == cells[i][k]) 52 | return false; 53 | 54 | int boxRowOffset = (i / 3)*3; 55 | int boxColOffset = (j / 3)*3; 56 | for (int k = 0; k < 3; ++k) // box 57 | for (int m = 0; m < 3; ++m) 58 | if (val == cells[boxRowOffset+k][boxColOffset+m]) 59 | return false; 60 | 61 | return true; // no violations, so it's legal 62 | } 63 | 64 | static String writeMatrix(int[][] solution) { 65 | 66 | StringBuilder s = new StringBuilder("\n"); 67 | for (int i = 0; i < 9; ++i) { 68 | if (i % 3 == 0) 69 | s.append(" -----------------------\n"); 70 | for (int j = 0; j < 9; ++j) { 71 | if (j % 3 == 0) s.append("| "); 72 | s.append( 73 | solution[i][j] == 0 74 | ? " " 75 | : Integer.toString(solution[i][j]) 76 | ); 77 | 78 | s.append(' '); 79 | } 80 | s.append("|\n"); 81 | } 82 | s.append(" ----------------------- "); 83 | return s.toString(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/scala/metascala/Util.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | 3 | import java.io.{IOException, DataInputStream} 4 | import java.util 5 | import org.scalatest.FreeSpec 6 | import org.scalatest.exceptions.TestFailedException 7 | import scala.util.Random 8 | import metascala.natives.Default 9 | 10 | 11 | object Gen{ 12 | val conforms = null 13 | 14 | def chk[T1](test: T1 => Unit)(implicit gen1: Iterable[T1]) = { 15 | gen1.foreach(test) 16 | 17 | } 18 | def chk[T1, T2](test: (T1, T2) => Unit)(implicit gen1: Iterable[T1], gen2: Iterable[T2]) = { 19 | for ((i, j) <- gen1 zip gen2){ 20 | test(i, j) 21 | } 22 | } 23 | def chk[T1, T2, T3](test: (T1, T2, T3) => Unit)(implicit gen1: Iterable[T1], gen2: Iterable[T2], gen3: Iterable[T3]) = { 24 | for (((i, j), k) <- gen1 zip gen2 zip gen3){ 25 | test(i, j, k) 26 | } 27 | } 28 | 29 | implicit class multiplyGen(i: Int){ 30 | def **[A](a: => A) = Iterable.fill(i)(a) 31 | } 32 | def intAll = Random.nextInt() 33 | def floatAll: Float = { 34 | val x = java.lang.Float.intBitsToFloat(intAll) 35 | if (java.lang.Float.isNaN(x)) floatAll 36 | else x 37 | } 38 | def longAll = Random.nextLong() 39 | def doubleAll: Double = { 40 | val x = java.lang.Double.longBitsToDouble(longAll) 41 | if (java.lang.Double.isNaN(x)) doubleAll 42 | else x 43 | } 44 | 45 | 46 | def int(bits: Int) = Random.nextInt(1 << bits) 47 | 48 | def long(bits: Int) = { 49 | if (bits >= 32){ 50 | (Random.nextInt(1 << (bits - 32)).toLong << 32) | Random.nextInt(32) 51 | } else { 52 | Random.nextInt(1 << bits).toLong 53 | } 54 | 55 | } 56 | implicit val GenFloat = (i: Int) => Random.nextFloat() 57 | implicit val GenDouble = (i: Int) => Random.nextDouble() 58 | implicit val GenBoolean = (i: Int) => Random.nextBoolean() 59 | 60 | } 61 | 62 | object Util { 63 | def loadClass(name: String) = { 64 | val slashName = s"/${name.replace(".", "/")}.class" 65 | 66 | val loaded = getClass.getResourceAsStream(slashName) 67 | if (loaded == null) None 68 | else { 69 | val stream = new DataInputStream(loaded) 70 | val bytes = new Array[Byte](stream.available()) 71 | stream.readFully(bytes) 72 | //imm.Util.printClass(bytes) 73 | Some(bytes) 74 | } 75 | } 76 | 77 | class SingleClassVM(className: String, log: (=>String) => Unit, memorySize: Int = 1024) extends VM(log = log, memorySize=memorySize){ 78 | def run(main: String, args: Any*): Any ={ 79 | val res = invoke(className.replace('.', '/'), main, args) 80 | res 81 | } 82 | } 83 | 84 | def assertEquals(svmRes: Any, refRes: Any) = (svmRes, refRes) match{ 85 | case (a: Double, b: Double) if java.lang.Double.isNaN(a) && java.lang.Double.isNaN(b) => 86 | case (a: Array[_], b: Array[_]) if a.length == b.length && a.sameElements(b) => 87 | case (a: Array[AnyRef], b: Array[AnyRef]) if a.length == b.length && java.util.Arrays.deepEquals(a, b) => 88 | case _ => assert(svmRes == refRes, (svmRes, refRes)) 89 | } 90 | implicit class DoStuff(val vm: VM) { 91 | 92 | def test[T](thunk: => T) = { 93 | assertEquals(vm.exec(thunk), thunk) 94 | } 95 | def testFunc[A, B, C, R](t: (A, B, C) => R)(a: A, b: B, c: C) = { 96 | func(t, Seq(a, b, c)) 97 | } 98 | 99 | def testFunc[A, B, R](t: (A, B) => R)(a: A, b: B) = { 100 | func(t, Seq(a, b)) 101 | } 102 | def testFunc[A, R](t: (A) => R)(a: A) = { 103 | func(t, Seq(a)) 104 | } 105 | def testFunc[R](t: () => R) = { 106 | func(t, Nil) 107 | } 108 | def func(t: Any, args: Seq[Any]) = { 109 | val path = t.getClass.getName.replace('.', '/') 110 | println(path) 111 | println(getClass.getResourceAsStream(path + ".class")) 112 | println("args " + args) 113 | 114 | 115 | val method = t.getClass 116 | .getMethods() 117 | .find(_.getName == "apply") 118 | .get 119 | 120 | val svmRes = vm.invoke(path, "apply", Seq(t) ++ args.map(_.asInstanceOf[AnyRef])) 121 | 122 | val refRes = method.invoke(t, args.map(_.asInstanceOf[AnyRef]):_*) 123 | 124 | val inString = args.toString 125 | println("svmRes " + svmRes) 126 | println("refRes " + refRes) 127 | 128 | try{ 129 | assertEquals(svmRes, refRes) 130 | }catch {case ex: TestFailedException => 131 | println("Test failed for input") 132 | println(inString) 133 | 134 | throw ex 135 | } 136 | } 137 | } 138 | class ReflectiveRunner(className: String){ 139 | def run(main: String, args: Any*) = { 140 | val method = java.lang.Class.forName(className) 141 | .getMethods() 142 | .find(_.getName == main) 143 | .get 144 | 145 | 146 | 147 | method.invoke(null, args.map(x => x.asInstanceOf[AnyRef]):_*) 148 | } 149 | } 150 | class Tester(className: String, log: (=>String) => Unit = x => (), memorySize: Int = 1 * 1024 * 1024){ 151 | 152 | implicit val svm = new Util.SingleClassVM(className, log, memorySize) 153 | val ref = new ReflectiveRunner(className) 154 | def run(main: String, args: Any*) = { 155 | 156 | val svmRes = svm.run(main, args:_*) 157 | val refRes = ref.run(main, args:_*) 158 | val inString = args.toString 159 | println("svmRes " + svmRes) 160 | println("refRes " + refRes) 161 | println("args " + args) 162 | try{ 163 | assertEquals(svmRes, refRes) 164 | }catch {case ex: TestFailedException => 165 | println("Test failed for input") 166 | println(inString) 167 | 168 | throw ex 169 | } 170 | } 171 | } 172 | 173 | } 174 | class BufferLog(size: Int, delay: Long = 0) extends ((=> String) => Unit){ 175 | val buffer = new Array[String](size) 176 | var index = 0 177 | var count = 0l 178 | def apply(s: =>String) = { 179 | 180 | count += 1 181 | if (count > delay){ 182 | buffer(index) = s 183 | index = (index + 1) % size 184 | } 185 | } 186 | def lines = buffer.drop(index) ++ buffer.take(index) 187 | } 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/ArrayTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | 7 | object ArrayTest{ 8 | def bubbleSort(arr: Array[Int]) = { 9 | for(i <- 1 until arr.length) { 10 | for(j <- 1 until arr.length){ 11 | if (arr(j-1) > arr(j)){ 12 | val temp = arr(j) 13 | arr(j) = arr(j-1) 14 | arr(j-1) = temp 15 | } 16 | } 17 | } 18 | arr 19 | } 20 | } 21 | class ArrayTest extends FreeSpec{ 22 | import Util._ 23 | val tester = new VM() 24 | "array stuff" - { 25 | "makeIntArray" in { 26 | for(i <- Seq(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)){ 27 | tester.test(new Array[Int](i)) 28 | } 29 | } 30 | "makeFloatArray" in tester.test{ 31 | val arr = new Array[Float](3) 32 | arr(0) = 0.25f; arr(1) = 0.5f; arr(2) = 0.75f 33 | arr 34 | } 35 | "makeStringArray" in tester.test{ 36 | val arr = new Array[String](3) 37 | arr(0) = "omg"; arr(1) = "wtf"; arr(2) = "bbq" 38 | arr 39 | } 40 | "longArrayOps" in { 41 | for(i <- Seq(0, 1, 2)){ 42 | tester.test{ 43 | val arr = new Array[Long](3) 44 | arr(0) = 12345; arr(1) = 67890; arr(2) = 12345 45 | arr(i) 46 | } 47 | } 48 | } 49 | "doubleArrayOps" in { 50 | val in = Array(2.1, 2.72) 51 | tester.test{ 52 | for(i <- 0 until in.length){ 53 | in(i) = in(i) + i 54 | } 55 | for(i <- 0 until in.length){ 56 | in(i) = in(i) + i 57 | } 58 | in 59 | } 60 | } 61 | 62 | "arrayLength" in tester.test{ 63 | val arr0 = new Array[String](0) 64 | val arr1 = new Array[Int](5) 65 | arr1(0) = 1; arr1(1) = 2; arr1(2) = 3; arr1(3) = 4; arr1(4) = 5 66 | val arr2 = new Array[Double](5) 67 | arr2(0) = 0.1; arr2(1) = 0.2; arr2(2) = 0.3; arr2(3) = 0.4 68 | arr0.length + arr1.length + arr2.length 69 | } 70 | "arraySet" in tester.test{ 71 | val arr = new Array[Int](10) 72 | for(i <- 0 until 10){ 73 | arr(i) = i 74 | } 75 | arr 76 | } 77 | "arrayGet" in tester.test{ 78 | var total = 0 79 | val arr = new Array[Int](10) 80 | for(i <- 0 until 10){ 81 | arr(i) = i 82 | } 83 | for(i <- 0 until 10){ 84 | total = total + 1000 * arr(i) 85 | } 86 | total 87 | } 88 | "getSet" in tester.test{ 89 | val buf = new Array[Char](10) 90 | val digits = new Array[Char](10) 91 | digits(0) = '0'; digits(1) = '1' 92 | digits(2) = '2'; digits(3) = '3' 93 | digits(4) = '4'; digits(5) = '5' 94 | digits(6) = '6'; digits(7) = '7' 95 | digits(8) = '8'; digits(9) = '9' 96 | var charPos = buf.length 97 | var r = 10 98 | while(charPos > 0){ 99 | r -= 1 100 | charPos -= 1 101 | buf(charPos) = digits(r) 102 | } 103 | buf(r + 5) 104 | } 105 | "bubbleSort" in { 106 | // val tester = new Tester("metascala.features.ArrayTest") 107 | 108 | val inputs = Seq( 109 | Array(0, 1, 2, 3, 4, 5, 6, 7), 110 | Array(7, 6, 5, 4, 3, 2, 1, 0), 111 | Array(0, 1, 2, 3, 4, 5, 6, 7), 112 | Array.fill(10)(util.Random.nextInt()), 113 | Array.fill(20)(util.Random.nextInt()) 114 | ) 115 | for (input <- inputs) tester.test{ 116 | ArrayTest.bubbleSort(input) 117 | } 118 | } 119 | 120 | } 121 | "multi dim arrays" - { 122 | val buffer = new BufferLog(4000) 123 | "make2D" in { 124 | for(i <- Seq(0, 1, 2)) tester.test{ 125 | metascala.features.arrays.MultiDimArrays.make2D(i, i) 126 | } 127 | } 128 | 129 | "make3D" in { 130 | for(i <- Seq(0, 1, 2)) tester.test{ 131 | metascala.features.arrays.MultiDimArrays.make3D( 132 | i, i, i 133 | ) 134 | } 135 | } 136 | "getAndSet" in tester.test{ 137 | metascala.features.arrays.MultiDimArrays.getAndSet 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/test/scala/metascala/features/CastingTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.{BufferLog, Gen, Util} 7 | import Gen.chk 8 | import java.awt.geom.Point2D 9 | 10 | class CastingTest extends FreeSpec { 11 | import Util._ 12 | 13 | val buffer = new BufferLog(4000) 14 | "if else" - { 15 | val tester = new VM() 16 | "basicCasts" in { 17 | for(x <- Seq("omg", Array(1, 2, 3), new Object())){ 18 | tester.test( 19 | try{ 20 | val s = x.asInstanceOf[String] 21 | s.length() 22 | }catch{ case x: ClassCastException => 23 | -1 24 | } 25 | ) 26 | } 27 | } 28 | "arrayCasts" in { 29 | val cases = Seq( 30 | Array(1, 2, 3), 31 | Array("omg", "wtf", "bbq"), 32 | Array(new Object(), new Object()), 33 | Array(new Point2D.Double(), new Point2D.Double()) 34 | ) 35 | for(x <- cases) tester.test{ 36 | try { 37 | val s: Array[Point2D] = x.asInstanceOf[Array[Point2D]] 38 | s.length 39 | } catch { case e: ClassCastException => 40 | -1 41 | } 42 | } 43 | } 44 | 45 | "primArrayCasts" in { 46 | val cases = Seq( 47 | Array(1, 2, 3), 48 | Array(1.0, 2.0, 3.0), 49 | Array("omg", "wtf", "bbq") 50 | ) 51 | for(x <- cases) tester.test{ 52 | try { 53 | val s: Array[Int] = x.asInstanceOf[Array[Int]] 54 | s.length 55 | } catch { case e: ClassCastException => 56 | -1 57 | } 58 | } 59 | } 60 | "instanceOf" in { 61 | val cases = Seq( 62 | "omg", 63 | Array(1, 2, 3), 64 | new Object(), 65 | new Point2D.Double(1, 1) 66 | ) 67 | for(x <- cases) tester.test( 68 | if (x.isInstanceOf[Point2D]) 0 else 1 69 | ) 70 | 71 | } 72 | "instanceOfArray" in { 73 | val cases = Seq( 74 | Array(1, 2, 3), 75 | Array("omg", "wtf", "bbq"), 76 | Array(new Object(), new Object()), 77 | Array(new Point2D.Double(), new Point2D.Double()) 78 | ) 79 | for(x <- cases) tester.test( 80 | if (x.isInstanceOf[Array[Point2D]]) 0 else 1 81 | ) 82 | } 83 | "instanceOfPrimArray" in { 84 | val cases = Seq( 85 | Array(1, 2, 3), 86 | Array(1.0, 2.0, 3.0), 87 | Array("omg", "wtf", "bbq") 88 | ) 89 | for(x <- cases) tester.test( 90 | if (x.isInstanceOf[Array[Int]]) 0 else 1 91 | ) 92 | } 93 | 94 | } 95 | 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/ClassesTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | import org.scalatest.FreeSpec 5 | import metascala.Util 6 | import java.lang.String 7 | import scala.Predef.String 8 | 9 | class ClassesTest extends FreeSpec { 10 | import Util._ 11 | 12 | "classes" - { 13 | val tester = new VM() 14 | "customClass" in tester.test{ 15 | val c1 = new Cow() 16 | val c2 = new Cow() 17 | c1.moo.length() + c2.moo.length() 18 | } 19 | "stringConcat" in tester.test{ 20 | val x = new StringBuilder() 21 | x.append('a') 22 | x.toString() 23 | } 24 | "inheritance" in tester.test{ 25 | val b = new Bull 26 | b.mooTwice 27 | } 28 | "constructor" in tester.test{ 29 | val m = new Matrix(5, 7, -1, 3) 30 | m.determinant 31 | } 32 | "superConstructor" in tester.test{ 33 | val m = new DoubleMatrix(2, 4, -8, 4) 34 | m.determinant 35 | } 36 | "override" in tester.test{ 37 | val m = new DoubleDetMatrix(1, 2, 3, 4) 38 | m.determinant 39 | } 40 | "innerClass" in tester.test{ 41 | val l: LinkedList = new LinkedList 42 | var i: Int = 0 43 | while (i < 2) { 44 | l.push(i) 45 | i += 1 46 | } 47 | 48 | l.sum 49 | } 50 | 51 | } 52 | "inheritance" - { 53 | val tester = new Tester("metascala.features.classes.Inheritance") 54 | "implement" in tester.run("implement", 10) 55 | "abstractClass" in tester.run("abstractClass") 56 | "shadowedInheritedGet" in tester.run("shadowedInheritedGet") 57 | "shadowedInheritedSet" in tester.run("shadowedInheritedSet") 58 | "superMethod" in tester.run("superMethod") 59 | "staticInheritance" in tester.run("staticInheritance") 60 | } 61 | } 62 | 63 | class Cow { 64 | def moo: String = { 65 | return "moooo" 66 | } 67 | } 68 | 69 | class Bull extends Cow { 70 | def mooTwice: String = { 71 | return moo + moo 72 | } 73 | } 74 | 75 | class Matrix(aa: Float, ab: Float, ba: Float, bb: Float) { 76 | def determinant: Float = { 77 | return aa * bb - ab * ba 78 | } 79 | } 80 | 81 | class DoubleMatrix(aa: Float, ab: Float, ba: Float, bb: Float) 82 | extends Matrix(aa*2, ab*2, ba*2, bb*2) 83 | 84 | class DoubleDetMatrix(aa: Float, ab: Float, ba: Float, bb: Float) 85 | extends Matrix(aa*2, ab*2, ba*2, bb*2){ 86 | 87 | override def determinant: Float = { 88 | return super.determinant * 2 89 | } 90 | } 91 | 92 | class LinkedList { 93 | def push(i: Int) { 94 | val n = new Inner(i, head) 95 | head = n 96 | } 97 | 98 | def sum: Int = { 99 | var curr: Inner = head 100 | var total: Int = 0 101 | while (curr != null) { 102 | total = total + head.value 103 | curr = curr.next 104 | } 105 | return total 106 | } 107 | 108 | var head: Inner = null 109 | 110 | class Inner(val value: Int, val next: Inner) 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/ControlFlowTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | 5 | import org.scalatest.{Tag, FreeSpec} 6 | 7 | import metascala.Util 8 | import Gen.chk 9 | class ControlFlowTest extends FreeSpec { 10 | import Util._ 11 | 12 | 13 | "if else" - { 14 | val tester = new Tester("metascala.features.controlflow.IfElse") 15 | "basicIf" in tester.run("basicIf") 16 | "ifNonIntZero" in tester.run("ifNonIntZero") 17 | "ifNonIntBinary" in tester.run("ifNonIntBinary") 18 | "ifElseIf" in tester.run("ifElseIf") 19 | "ifElseIfBig" in tester.run("ifElseIfBig") 20 | "mathMin" in tester.run("mathMin") 21 | } 22 | "loops" - { 23 | val tester = new Tester("metascala.features.controlflow.Loops") 24 | "nullFor" in tester.run("nullFor", 100) 25 | "basicFor" in chk(tester.run("basicFor", _: Int))(Seq.fill(0)(Gen.int(256))) 26 | "nullWhile" in tester.run("nullWhile", 100) 27 | "basicWhile" in chk(tester.run("basicWhile", _: Int))(Seq.fill(0)(Gen.int(256))) 28 | "sqrtFinder" in chk(tester.run("sqrtFinder", _: Double))(Seq.fill(10)(Math.random() * 1000)) 29 | } 30 | "switches" - { 31 | val tester = new Tester("metascala.features.controlflow.Switches") 32 | "smallSwitch" in chk(tester.run("smallSwitch", _: Int))(Seq(0, 1, 2)) 33 | "bigDenseSwitch" in chk(tester.run("bigDenseSwitch", _: Int))(0 to 30) 34 | "bigSparseSwitch" in chk(tester.run("bigSparseSwitch", _: Int))((0 to 23).map(x => Math.pow(2, x).toInt)) 35 | "charSwitch" in chk(tester.run("charSwitch", _: Char))('a' to 'k') 36 | "byteSwitch" in chk(tester.run("byteSwitch", _: Byte))((0 to 8).map(x=>Math.pow(2, x).toByte)) 37 | "stringSwitch" in chk(tester.run("stringSwitch", _: Int))(Seq(1)) 38 | "stringSwitchTwo" in chk(tester.run("stringSwitchTwo", _: String))(Seq("omg", "wtf", "bbq" ,"lol")) 39 | } 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/ExceptionTest.scala: -------------------------------------------------------------------------------- 1 | package metascala.features 2 | 3 | import org.scalatest.FreeSpec 4 | 5 | import metascala.{BufferLog, Gen, Util} 6 | import Gen.chk 7 | class ExceptionTest extends FreeSpec { 8 | import Util._ 9 | "if else" - { 10 | val tester = new Tester("metascala.features.exceptions.Exceptions") 11 | 12 | "throwCatch" in chk(tester.run("throwCatch", _: Int))(Seq(-1, 0, 1, 2)) 13 | "multiCatch" in chk(tester.run("multiCatch", _: Int))(Seq(0, 1, 2, 3, 4)) 14 | "nullPointer" in chk(tester.run("nullPointer", _: Object))(Seq("omg", null)) 15 | "arrayIndexOutOfBounds" in { 16 | chk(tester.run("arrayIndexOutOfBounds", _: Int))(Seq(-1, 0, 1, 2, 3, 4, 5, 100)) 17 | } 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/IOTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.Gen._ 7 | import util.{Failure, Try} 8 | import metascala.{UncaughtVmException, BufferLog, Gen, Util} 9 | import metascala.Util.SingleClassVM 10 | 11 | class IOTest extends FreeSpec { 12 | 13 | import Util._ 14 | implicit val intAll10 = 10 ** Gen.intAll 15 | val tester = new VM() 16 | "primitives" - { 17 | "retInt" in tester.testFunc(() => 1337) 18 | "retDouble" in tester.testFunc(() => 3.1337) 19 | "argInt" in tester.testFunc((i: Int) => i)(10) 20 | "argDouble" in tester.testFunc((i: Double) => i)(10.01) 21 | "multiArgD" in tester.testFunc((i: Int, d: Double) => d)(27, 3.14) 22 | "multiArgI" in tester.testFunc((i: Int, d: Double) => i)(27, 3.14) 23 | 24 | "stringLiteral" in tester.testFunc(() => "omgwtfbbq") 25 | "strings" in tester.testFunc((s: String) => s + "a")("mooo") 26 | "nullReturn" in tester.testFunc(() => null) 27 | "arrayObj" in tester.testFunc{() => 28 | val arr = new Array[Int](3) 29 | arr(0) = 1 30 | arr(1) = 2 31 | arr(2) = 4 32 | arr 33 | } 34 | 35 | } 36 | "exceptions" -{ 37 | "runtime" in { 38 | val svmRes = Try(tester.testFunc{() => 39 | val s: String = null; 40 | s.charAt(0); 41 | 10 42 | }) 43 | 44 | val Failure(u @ UncaughtVmException(wrapped)) = svmRes 45 | 46 | } 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/MathTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | 5 | import org.scalatest.FreeSpec 6 | import Gen._ 7 | import metascala.Util 8 | import util.Random 9 | 10 | 11 | 12 | class MathTest extends FreeSpec { 13 | import Util._ 14 | implicit def intAll10 = 10 ** Gen.intAll 15 | implicit def floatAll110 = 10 ** Gen.floatAll 16 | implicit def longAll10 = 10 ** Gen.longAll 17 | implicit def doubleAll110 = 10 ** Gen.doubleAll 18 | val tester = new VM() 19 | 20 | "single precision" - { 21 | "hello math" - { 22 | "imain" in tester.test{ 1337 } 23 | "fmain" in tester.test{ 1.337f } 24 | } 25 | "basic math" - { 26 | "int" - { 27 | "ineg" in chk(tester.testFunc( -(_: Int) ) _) 28 | "iadd" in chk(tester.testFunc( (_: Int) + (_: Int) ) _) 29 | "isub" in chk(tester.testFunc( (_: Int) - (_: Int) ) _) 30 | "imul" in chk(tester.testFunc( (_: Int) * (_: Int) ) _) 31 | "idiv" in chk(tester.testFunc( (_: Int) / (_: Int) ) _) 32 | "imod" in chk(tester.testFunc( (_: Int) % (_: Int) ) _) 33 | } 34 | "float" - { 35 | "fneg" in chk(tester.testFunc( -(_: Float) ) _) 36 | "fadd" in chk(tester.testFunc( (_: Float) + (_: Float) ) _) 37 | "fsub" in chk(tester.testFunc( (_: Float) - (_: Float) ) _) 38 | "fmul" in chk(tester.testFunc( (_: Float) * (_: Float) ) _) 39 | "fdiv" in chk(tester.testFunc( (_: Float) / (_: Float) ) _) 40 | "fmod" in chk(tester.testFunc( (_: Float) % (_: Float) ) _) 41 | } 42 | "more int stuff" - { 43 | "ishl" in chk(tester.testFunc( (_: Int) << (_: Int)) _)(10 ** Gen.intAll, 10 ** Gen.int(5)) 44 | "ishr" in chk(tester.testFunc( (_: Int) >> (_: Int)) _)(10 ** Gen.intAll, 10 ** Gen.int(5)) 45 | "iushr" in chk(tester.testFunc( (_: Int) >>> (_: Int)) _)(10 ** Gen.intAll, 10 ** Gen.int(5)) 46 | "iand" in chk(tester.testFunc( (_: Int) & (_: Int)) _) 47 | "ior" in chk(tester.testFunc( (_: Int) | (_: Int)) _) 48 | "ixor" in chk(tester.testFunc( (_: Int) ^ (_: Int)) _) 49 | } 50 | } 51 | } 52 | "double precision" - { 53 | 54 | "hello math" - { 55 | "lmain" in tester.testFunc{ () => 313373133731337L } 56 | "dmain" in tester.testFunc{ () => 31.337 } 57 | } 58 | "basic math" - { 59 | "long" - { 60 | "lneg" in chk(tester.testFunc( -(_: Long) ) _) 61 | "ladd" in chk(tester.testFunc( (_: Long) + (_: Long) ) _) 62 | "lsub" in chk(tester.testFunc( (_: Long) - (_: Long) ) _) 63 | "lmul" in chk(tester.testFunc( (_: Long) * (_: Long) ) _) 64 | "ldiv" in chk(tester.testFunc( (_: Long) / (_: Long) ) _) 65 | "lmod" in chk(tester.testFunc( (_: Long) % (_: Long) ) _) 66 | } 67 | "double" - { 68 | "dneg" in chk(tester.testFunc( -(_: Double) ) _) 69 | "dadd" in chk(tester.testFunc( (_: Double) + (_: Double) ) _) 70 | "dsub" in chk(tester.testFunc( (_: Double) - (_: Double) ) _) 71 | "dmul" in chk(tester.testFunc( (_: Double) * (_: Double) ) _) 72 | "ddiv" in chk(tester.testFunc( (_: Double) / (_: Double) ) _) 73 | "dmod" in chk(tester.testFunc( (_: Double) % (_: Double) ) _) 74 | } 75 | "more long stuff" - { 76 | "ishl" in chk(tester.testFunc( (_: Long) << (_: Int)) _)(10 ** Gen.longAll, 10 ** Gen.int(5)) 77 | "ishr" in chk(tester.testFunc( (_: Long) >> (_: Int)) _)(10 ** Gen.longAll, 10 ** Gen.int(5)) 78 | "iushr" in chk(tester.testFunc( (_: Long) >>> (_: Int)) _)(10 ** Gen.longAll, 10 ** Gen.int(5)) 79 | "iand" in chk(tester.testFunc( (_: Long) & (_: Long)) _) 80 | "ior" in chk(tester.testFunc( (_: Long) | (_: Long)) _) 81 | "ixor" in chk(tester.testFunc( (_: Long) ^ (_: Long)) _) 82 | } 83 | } 84 | } 85 | 86 | "combined" - { 87 | "hmsToDays" in 88 | chk(tester.testFunc( 89 | (h: Double, m: Double, s: Double) => (((h*60)+m)*60+s) / 86400 90 | ) _)( 91 | Seq.fill(0)(Random.nextInt(24).toDouble), 92 | Seq.fill(0)(Random.nextInt(60).toDouble), 93 | Seq.fill(0)(Random.nextInt(60).toDouble) 94 | ) 95 | 96 | 97 | } 98 | 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/test/scala/metascala/features/MethodTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package features 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.{Gen, Util} 7 | import metascala.Gen._ 8 | import java.awt.Point 9 | 10 | class MethodTest extends FreeSpec { 11 | import Util._ 12 | 13 | implicit val intAll10 = 10 ** Gen.intAll 14 | 15 | "static" - { 16 | val tester = new Tester("metascala.features.methods.Statics") 17 | "helloWorld" in chk(tester.run("helloWorld", _: Int)) 18 | 19 | "helloWorld2" in chk(tester.run("helloWorld2", _: Int, _: Int)) 20 | "tailFactorial" in chk(tester.run("tailFactorial", _: Int))(Seq(2, 5, 10, 20, 50)) 21 | "fibonacci" in chk(tester.run("fibonacci", _: Int))(Seq(2, 5, 10)) 22 | "callAtPhiBoundary" in tester.run("callAtPhiBoundary", 0) 23 | } 24 | "natives" - { 25 | val tester = new VM() 26 | "intBitsToFloat" in { 27 | for(n <- intAll10){ 28 | tester.test(java.lang.Float.intBitsToFloat(n)) 29 | } 30 | } 31 | "currentTimeMillis" in tester.test( 32 | System.currentTimeMillis() / 100000 33 | ) 34 | "inheritedNative" in tester.test( 35 | "omg".getClass().getName() 36 | ) 37 | "arrayCopy" in tester.test{ 38 | val x = new Array[Int](5) 39 | x(0) = 1 40 | x(1) = 2 41 | x(2) = 3 42 | x(3) = 4 43 | x(4) = 5 44 | 45 | 46 | val y = new Array[Int](5) 47 | y(0) = 1 48 | y(1) = 2 49 | y(2) = 3 50 | y(3) = 4 51 | y(4) = 5 52 | 53 | System.arraycopy(x, 1, y, 2, 2) 54 | 55 | y(0) * 10000 + y(1) * 1000 + y(2) * 100 * y(3) * 10 + y(4) 56 | } 57 | } 58 | "objects" - { 59 | val tester = new VM() 60 | "helloWorld" in tester.test{ 61 | val d = new DumbObject(5) 62 | d.getTwoN 63 | } 64 | "stringEquals" in { 65 | val a = 0 66 | val b = "0" 67 | tester.test(""+a == b) 68 | } 69 | "inheritance" in tester.test{ 70 | val d = new DumbObjectSubClass(5) 71 | d.getTwoN 72 | } 73 | 74 | "points" in ( 75 | for(i <- intAll10){ 76 | tester.test{ 77 | val p = new Point(i, i) 78 | p.getX 79 | } 80 | } 81 | ) 82 | "points2" in ( 83 | for(n <- intAll10){ 84 | tester.test{ 85 | val p = new Point(10, 10) 86 | p.translate(5, -5) 87 | p.setLocation(p.x * n, p.y * n) 88 | p.setLocation(p.getY, p.getX) 89 | p.translate(n, -n) 90 | p.distanceSq(0, n) 91 | } 92 | } 93 | ) 94 | } 95 | 96 | } 97 | 98 | class DumbObjectSubClass(n: Int) extends DumbObject(n * 2) 99 | 100 | class DumbObject(val n: Int) { 101 | 102 | def getTwoN: Int = { 103 | return n * 2 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/test/scala/metascala/full/ClassTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.Util 7 | import scala.Some 8 | import java.util.Arrays 9 | import metascala.Gen._ 10 | 11 | class ClassTest extends FreeSpec { 12 | import Util._ 13 | "class stuff" - { 14 | val tester = new VM() 15 | 16 | "name" in tester.test{ new Object().getClass.getName } 17 | "namePrim" in tester.test{ classOf[Int].getName } 18 | "nameArray" in tester.test{ new Array[Object](10).getClass.getName } 19 | "nameObjArray" in tester.test{ new Array[Long](100).getClass.getName } 20 | 21 | "forName" in { 22 | val cases = Seq( 23 | "java.lang.Object", 24 | "java.lang.Object", 25 | "java.util.AbstractCollection", 26 | "[I", 27 | "[Ljava.lang.Object;" 28 | ) 29 | for(s <- cases) tester.test{ 30 | val x = Class.forName(s) 31 | x.getCanonicalName 32 | } 33 | } 34 | "forNameBad" in tester.test{ 35 | try{ 36 | val cls = Class.forName("lol") 37 | cls.getName 38 | }catch {case x: ClassNotFoundException => 39 | x.getMessage 40 | } 41 | } 42 | 43 | "isPrimitive" in tester.test{ 44 | Array( 45 | new Object().getClass.isPrimitive, 46 | new java.lang.Float(10).getClass.isPrimitive, 47 | new java.lang.Integer(12).getClass.isPrimitive, 48 | new java.lang.Boolean(true).getClass.isPrimitive, 49 | new Array[Int](0).getClass.isPrimitive 50 | ) 51 | } 52 | "isArray" in tester.test{ 53 | Array( 54 | new Object().getClass.isArray, 55 | new java.lang.Float(10).getClass.isArray, 56 | new java.lang.Integer(12).getClass.isArray, 57 | new java.lang.Boolean(true).getClass.isArray, 58 | new Array[Int](0).getClass.isArray 59 | ) 60 | } 61 | } 62 | "classloaders" - { 63 | val tester = new VM() 64 | "name" in tester.test{ 65 | val cl = classOf[String].getClassLoader 66 | "omg" + cl 67 | } 68 | //"create" in tester.run("create") 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/test/scala/metascala/full/GCTests.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import metascala.{BufferLog, Util} 5 | import metascala.Util._ 6 | import org.scalatest.FreeSpec 7 | 8 | object StaticHolder{ 9 | val x = new Array[Int](2) 10 | } 11 | class Cons(val value: Int, var next: Cons) 12 | 13 | class GCTests extends FreeSpec { 14 | import Util._ 15 | 16 | "helloObj" in { 17 | for{ 18 | memory <- List(30, 67, 121) 19 | count <- List(0, 1, 5, 19, 30, 67) 20 | }{ 21 | val tester = new VM(memorySize = memory) 22 | tester.test{ 23 | var i = 0 24 | while(i < count){ 25 | val p = new Object() 26 | i += 1 27 | } 28 | i 29 | } 30 | } 31 | } 32 | 33 | "helloArr" in { 34 | for{ 35 | memory <- List(30, 65, 93, 123) 36 | count <- List(0, 3, 9, 12, 30) 37 | }{ 38 | println(memory + " " + count) 39 | val tester = new VM(memorySize = memory) 40 | tester.test{ 41 | var i = 0 42 | val p = new Array[Int](2) 43 | p(0) = 5 44 | p(1) = 6 45 | val p2 = new Array[Array[Int]](1) 46 | p2(0) = p 47 | 48 | while(i < count){ 49 | val p = new Array[Int](3) 50 | p(0) = 9 51 | p(1) = 8 52 | p(2) = 7 53 | i += 1 54 | } 55 | p2(0)(1) 56 | } 57 | } 58 | } 59 | 60 | "chain" in { 61 | for { 62 | memory <- 40 to 45 by 2 63 | count <- 20 to 30 by 3 64 | }{ 65 | val tester = new VM(memorySize = 40) 66 | tester.test{ 67 | var i = 0 68 | var front = new Cons(1, null) 69 | var back = new Cons(5, new Cons(4, new Cons(3, new Cons(2, front)))) 70 | while (i < count){ 71 | front.next = new Cons(front.value + 1, null) 72 | front = front.next 73 | back = back.next 74 | i += 1 75 | } 76 | front.value 77 | } 78 | } 79 | } 80 | 81 | "interned" in { 82 | for { 83 | memory <- 40 to 45 by 2 84 | count <- 20 to 30 by 3 85 | }{ 86 | val tester = new VM(memorySize = memory) 87 | tester.test{ 88 | var i = 0 89 | while(i < count){ 90 | val p = new Object() 91 | i += 1 92 | } 93 | "aaaaa" 94 | } 95 | } 96 | } 97 | 98 | "static" in { 99 | val tester = new VM(memorySize = 40) 100 | tester.test{ 101 | var o = StaticHolder.x 102 | var i = 0 103 | while(i < 10){ 104 | o = new Array[Int](2) 105 | i += 1 106 | } 107 | o = StaticHolder.x 108 | o 109 | } 110 | } 111 | "parseClass" in { 112 | val bl = new BufferLog(4000) 113 | val tester = new Tester("metascala.full.ScalaLib", log=bl, memorySize = 12 * 1024) 114 | try{ 115 | for(i <- 1 to 5) tester.run("parseClass") 116 | } catch{case e => 117 | // bl.lines.foreach(println) 118 | throw e 119 | } 120 | } 121 | 122 | "gcInterrupt" in { 123 | val tester = new Tester("metascala.full.ScalaLib", memorySize = 10) 124 | implicit val vm = tester.svm 125 | import metascala.pimpedString 126 | var initialSet: Set[Int] = null 127 | val (p1, p2, p3) = vm.alloc{ implicit r => 128 | val p1 = "java/lang/Object".allocObj() 129 | val p2 = "java/lang/Object".allocObj() 130 | val p3 = "java/lang/Object".allocObj() 131 | initialSet = Set(p1(), p2(), p3()) 132 | assert(initialSet.forall(p => vm.heap(p) == -1)) 133 | vm.heap.collect(vm.heap.start) 134 | // These guys got moved together to the new space (maybe in a new order) 135 | assert(Set(p1(), p2(), p3()) == initialSet.map(_+10)) 136 | assert(Set(p1, p2, p3).forall(p => vm.heap(p()) == -1)) 137 | vm.heap.collect(vm.heap.start) 138 | assert(Set(p1(), p2(), p3()) == initialSet) 139 | assert(Set(p1, p2, p3).forall(p => vm.heap(p()) == -1)) 140 | (p1, p2, p3) 141 | } 142 | 143 | vm.heap.collect(vm.heap.start) 144 | vm.heap.collect(vm.heap.start) 145 | // after exiting the `alloc{}` block, the refs become meaningless 146 | // as the things they are pointing to get GCed 147 | assert(Set(p1(), p2(), p3()) == initialSet) 148 | assert(Set(p1, p2, p3).forall(p => vm.heap(p()) == 0)) 149 | } 150 | } -------------------------------------------------------------------------------- /src/test/scala/metascala/full/JavaLibTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.Gen._ 7 | import java.math.BigInteger 8 | import java.util.regex.{Matcher, Pattern} 9 | import java.util.concurrent.atomic.{AtomicLong, AtomicInteger, AtomicBoolean} 10 | import java.util.concurrent.{ConcurrentLinkedQueue, ConcurrentHashMap} 11 | import java.util.concurrent.locks.ReentrantLock 12 | 13 | 14 | class JavaLibTest extends FreeSpec { 15 | implicit val intAll10 = 10 ** Gen.intAll 16 | import Util._ 17 | "sudoku" in { 18 | val tester = new Tester("metascala.full.Sudoku") 19 | tester.run("run") 20 | } 21 | "stuff" - { 22 | val tester = new VM() 23 | "sorting" in tester.test{ 24 | val arr: Array[Int] = new Array[Int](250) 25 | 26 | var current: Int = 94664704 27 | for(i <- 0 until arr.length){ 28 | current = 23 * current % 100000000 + 1 29 | arr(i) = current % 100000 30 | } 31 | 32 | java.util.Arrays.sort(arr) 33 | arr(52) 34 | } 35 | "collections" in tester.test{ 36 | val vec = new java.util.Vector[Integer]() 37 | for(i <- 0 until 10 ){ 38 | vec.add(i) 39 | } 40 | val map = new java.util.HashMap[Integer, String]() 41 | var total = 0 42 | for(i <- 0 until 10){ 43 | total = total + vec.get(i) 44 | map.put(vec.get(i), ""+total) 45 | } 46 | 47 | Integer.parseInt(map.get(10/2)) 48 | } 49 | 50 | "bigInteger" in tester.test{ 51 | val a: BigInteger = new BigInteger("1237923896771092385") 52 | val b: BigInteger = new BigInteger("498658982734992345912340") 53 | val c: BigInteger = new BigInteger("08968240235478367717203984123") 54 | 55 | val d: BigInteger = a.add(b) 56 | val e: BigInteger = d.subtract(c) 57 | val f: BigInteger = e.multiply(b) 58 | val g: BigInteger = f.divide(a) 59 | 60 | g.toString 61 | } 62 | "regex" in tester.test{ 63 | val p: Pattern = Pattern.compile("\\d+([_-]\\d+)*(:? )") 64 | val m: Matcher = p.matcher("123_321_12 i am a cow 123_3-" + "12_990 but my ip is 192-168-1-1 lolz") 65 | 66 | var s: String = "" 67 | while (m.find) { 68 | s += m.group(0) 69 | } 70 | 71 | s 72 | } 73 | "atomicBooleans" in tester.test{ 74 | val b: AtomicBoolean = new AtomicBoolean 75 | val values: Array[Boolean] = new Array[Boolean](4) 76 | 77 | b.set(true) 78 | values(0) = b.get 79 | b.compareAndSet(false, false) 80 | values(1) = b.get 81 | values(2) = b.getAndSet(false) 82 | b.compareAndSet(false, true) 83 | values(3) = b.get 84 | 85 | values 86 | } 87 | "atomicIntegers" in tester.test{ 88 | val b: AtomicInteger = new AtomicInteger 89 | val values: Array[Int] = new Array[Int](4) 90 | 91 | b.set(192) 92 | values(0) = b.get 93 | b.compareAndSet(12, 3123) 94 | values(1) = b.get 95 | values(2) = b.getAndSet(31241) 96 | b.compareAndSet(31241, 12451) 97 | values(3) = b.get 98 | 99 | values 100 | } 101 | "atomicLongs" in tester.test{ 102 | val b: AtomicLong = new AtomicLong 103 | val values: Array[Long] = new Array[Long](4) 104 | 105 | b.set(1921231231234124124L) 106 | values(0) = b.get 107 | b.compareAndSet(12124124164865234L, 34934198359342123L) 108 | values(1) = b.get 109 | values(2) = b.getAndSet(98172271923198732L) 110 | b.compareAndSet(981724127399231987L, 123517894187923123L) 111 | values(3) = b.get 112 | 113 | values 114 | } 115 | "randoms" in tester.test{ 116 | val r = new java.util.Random(241231241251241123L) 117 | for(i <- 0 until 100){ 118 | r.nextLong() 119 | } 120 | r.nextLong() 121 | } 122 | "concurrentLinkedQueue" in tester.test{ 123 | val struct = new ConcurrentLinkedQueue[Int]() 124 | struct.add(123) 125 | struct.add(456) 126 | val i = struct.poll() 127 | struct.peek() + i + struct.peek() 128 | } 129 | "locks" in tester.test{ 130 | val lock = new ReentrantLock() 131 | lock.lock() 132 | lock.unlock() 133 | } 134 | "concurrentHashMap" in tester.test{ 135 | val map = new ConcurrentHashMap[Int, Int]() 136 | map.put(123, 456) 137 | } 138 | // "rhino" in tester.test{ 139 | // classOf[VMBridge_jdk15].newInstance() 140 | // /*val cx = Context.enter() 141 | // val scope = cx.initStandardObjects() 142 | // val script = "var s = 'omg'" 143 | // val obj = cx.evaluateString(scope, script, "Test Script", 1, null) 144 | // println(obj) 145 | // 1*/ 146 | // } 147 | } 148 | 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/test/scala/metascala/full/MetacircularTest.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import org.scalatest.FreeSpec 5 | import metascala.features.Bull 6 | 7 | 8 | class MetacircularTest extends FreeSpec { 9 | import Util._ 10 | 11 | val buffer = new BufferLog(1900) 12 | var count = 0 13 | 14 | "sqrtFinder" in { 15 | new VM(memorySize = 225 * 1024).test{ 16 | val x = new VM(memorySize = 1024) 17 | x.invoke("metascala/features/controlflow/Loops", "sqrtFinder", Seq(5.0)) 18 | } 19 | } 20 | 21 | "fibonacci" in { 22 | new VM(memorySize = 3 * 1024 * 1024).test{ 23 | val x = new metascala.VM() 24 | x.invoke("metascala/features/methods/Statics", "fibonacci", Seq(12)) 25 | } 26 | } 27 | 28 | "inheritance" in { 29 | val vm = new VM(memorySize = 8 * 1014 * 1024) 30 | vm.test{ 31 | val vm = new VM() 32 | val x = vm.exec{ 33 | val b = new Bull 34 | b.mooTwice 35 | } 36 | println(vm.threads(0).count) 37 | x 38 | } 39 | println(vm.threads(0).count) 40 | } 41 | 42 | "bubbleSort" in { 43 | new VM(memorySize = 24 * 1014 * 1024).test{ 44 | val x = new metascala.VM() 45 | x.invoke("metascala/features/ArrayTest", "bubbleSort", Seq(Array(6, 5, 2, 7, 3, 4, 9, 1, 8))) 46 | .cast[Array[Int]] 47 | .toSeq 48 | } 49 | } 50 | 51 | "getAndSet" in { 52 | new VM(memorySize = 15 * 1014 * 1024).test{ 53 | val x = new metascala.VM() 54 | x.invoke("metascala/features/arrays/MultiDimArrays", "getAndSet") 55 | } 56 | } 57 | 58 | "multiCatch" in { 59 | new VM(memorySize = 16 * 1014 * 1024).test{ 60 | val x = new metascala.VM() 61 | x.invoke("metascala/features/exceptions/Exceptions", "multiCatch", Seq(2)) 62 | } 63 | } 64 | 65 | "reflectField" in { 66 | val vm = new VM(memorySize = 16 * 1014 * 1024) 67 | vm.test{ 68 | val vm = new VM() 69 | vm.exec{ 70 | val string = new String("i am a cow") 71 | val f = classOf[String].getDeclaredField("value") 72 | f.setAccessible(true) 73 | f.set(string, Array('o', 'm', 'g')) 74 | f.get(string) 75 | } 76 | } 77 | } 78 | 79 | // "predef" in { 80 | // new VM(memorySize = 16 * 1014 * 1024).test{ 81 | // new VM(log = x => println(x)).exec{ 82 | // Predef 83 | // } 84 | // } 85 | // } 86 | 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/test/scala/metascala/full/ReflectTests.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import org.scalatest.FreeSpec 5 | 6 | import metascala.Gen._ 7 | import java.awt.geom.Point2D 8 | 9 | 10 | class ReflectTests extends FreeSpec { 11 | implicit val intAll10 = 10 ** Gen.intAll 12 | import Util._ 13 | val tester = new VM() 14 | "getSetX" - { 15 | 16 | "obj" in tester.testFunc{ () => 17 | val string = new String("i am a cow") 18 | val f = classOf[String].getDeclaredField("value") 19 | f.setAccessible(true) 20 | f.set(string, Array('o', 'm', 'g')) 21 | f.get(string) 22 | } 23 | "bool" in tester.testFunc{ () => 24 | val bool = new java.lang.Boolean(false) 25 | val f = classOf[java.lang.Boolean].getDeclaredField("value") 26 | f.setAccessible(true) 27 | f.setBoolean(bool, true) 28 | f.getBoolean(bool) 29 | } 30 | "byte" in tester.testFunc{ () => 31 | val byte = new java.lang.Byte(123.toByte) 32 | val f = classOf[java.lang.Byte].getDeclaredField("value") 33 | f.setAccessible(true) 34 | f.setByte(byte, 314.toByte) 35 | f.getByte(byte) 36 | } 37 | "char" in tester.testFunc{ () => 38 | val char = new java.lang.Character('o') 39 | val f = classOf[java.lang.Character].getDeclaredField("value") 40 | f.setAccessible(true) 41 | f.setChar(char, '1') 42 | f.getChar(char) 43 | } 44 | "int" in tester.testFunc{ () => 45 | val string = new String("i am a cow") 46 | string.hashCode 47 | val f = classOf[String].getDeclaredField("hash") 48 | f.setAccessible(true) 49 | f.setInt(string, 123456789) 50 | f.getInt(string) 51 | } 52 | "float" in tester.testFunc{ () => 53 | val point = new Point2D.Float(1.337f, 2.7182f) 54 | val f = classOf[Point2D.Float].getDeclaredField("x") 55 | f.setAccessible(true) 56 | f.setFloat(point, 123123123f) 57 | f.getFloat(point) 58 | } 59 | "long" in tester.testFunc{ () => 60 | val long = new java.lang.Long(10) 61 | val f = classOf[java.lang.Long].getDeclaredField("value") 62 | f.setAccessible(true) 63 | f.setLong(long, 1234567890l) 64 | f.getLong(long) 65 | } 66 | "double" in tester.testFunc{ () => 67 | val point = new Point2D.Double(31.337, 27.182) 68 | val f = classOf[Point2D.Double].getDeclaredField("x") 69 | f.setAccessible(true) 70 | f.setDouble(point, 3133.7) 71 | f.getDouble(point) 72 | } 73 | } 74 | "getSetStatic" - { 75 | // "double" in tester.testFunc{ () => 76 | // val f = classOf[Point2D.Double].getDeclaredField("serialVersionUID") 77 | // f.setAccessible(true) 78 | // f.getDouble(null) 79 | // } 80 | } 81 | "allocate" in { 82 | val p = Virtualizer.unsafe 83 | .allocateInstance(classOf[Point2D.Float]) 84 | .asInstanceOf[Point2D.Float] 85 | p.x = 10 86 | p.y = 20 87 | p.x * p.x + p.y * p.y 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/test/scala/metascala/full/ScalaLib.scala: -------------------------------------------------------------------------------- 1 | package metascala 2 | package full 3 | 4 | import org.scalatest.FreeSpec 5 | import scala.concurrent.{Await, Promise} 6 | import java.io.DataInputStream 7 | import org.objectweb.asm.ClassReader 8 | import org.objectweb.asm.tree.ClassNode 9 | import scala.collection.JavaConversions._ 10 | import org.mozilla.javascript.Context 11 | 12 | object ScalaLib{ 13 | 14 | 15 | def parseClass() = { 16 | 17 | val slashName = "/java/lang/Object.class" 18 | 19 | val loaded = getClass.getResourceAsStream(slashName) 20 | val stream = new DataInputStream(loaded) 21 | val bytes = new Array[Byte](stream.available()) 22 | stream.readFully(bytes) 23 | val cr = new ClassReader(bytes) 24 | val classNode = new ClassNode() 25 | 26 | cr.accept(classNode, ClassReader.EXPAND_FRAMES) 27 | val s = classNode.name; 28 | val methods = for(m <- classNode.methods) yield { 29 | m.name + m.desc 30 | } 31 | s + "\n" + methods.mkString("\n") 32 | } 33 | 34 | def futures = { 35 | import scala.concurrent.ExecutionContext.Implicits.global 36 | val a = Promise[Promise[Int]]() 37 | val b = Promise[List[String]]() 38 | val c = for{ 39 | ar <- a.future 40 | br <- b.future 41 | i <- ar.future 42 | } yield i.toString :: br 43 | a.success(Promise.successful(1)) 44 | b.success(List("2", "3", "4")) 45 | import scala.concurrent.duration._ 46 | Await.result(c, 10 seconds) 47 | } 48 | 49 | def lol = { 50 | println("AAA") 51 | val ctx = Context.enter() 52 | println("BBB") 53 | val scope = ctx.initStandardObjects() 54 | println("CCC") 55 | println(ctx.evaluateString(scope, "1 + 1", "", 0, null)) 56 | println("DDD") 57 | 0 58 | } 59 | } 60 | class ScalaLib extends FreeSpec { 61 | import Util._ 62 | val buffer = new BufferLog(4000) 63 | 64 | val tester = new VM(memorySize = 128 * 1024 * 1024) 65 | 66 | "predef" in { 67 | val x = 5 68 | tester.test{ 69 | Predef 70 | 0 71 | } 72 | } 73 | "palindrome" in { 74 | val min = 100 75 | val max = 130 76 | tester.test{ 77 | def isPalindrome(s: String): Boolean = s.reverse.mkString == s 78 | val palindromes = for { 79 | a <- (min until max) 80 | b <- (a until max) 81 | p = a*b 82 | if isPalindrome(p.toString) 83 | } yield p 84 | palindromes 85 | 86 | } 87 | } 88 | "bigFibonacci" in { 89 | val n = 10 90 | tester.test{ 91 | lazy val fs: Stream[BigInt] = 92 | 0 #:: 1 #:: fs.zip(fs.tail).map(p => p._1 + p._2) 93 | 94 | fs.view.takeWhile(_.toString.length < n).size 95 | } 96 | } 97 | "Euler1" in { 98 | tester.test { 99 | val r = (1 until 1000).view.filter(n => n % 3 == 0 || n % 5 == 0).sum 100 | r 101 | } 102 | 103 | } 104 | "Euler2" in { 105 | tester.test { 106 | lazy val fs: Stream[Int] = 0 #:: 1 #:: fs.zip(fs.tail).map(p => p._1 + p._2) 107 | 108 | val r = fs.view.takeWhile(_ <= 4000000).filter(_ % 2 == 0).sum 109 | r 110 | } 111 | } 112 | "Euler4" in { 113 | tester.test { 114 | (10 to 99).view 115 | .flatMap(i => (i to 99).map(i *)) 116 | .filter(n => n.toString == n.toString.reverse) 117 | .max 118 | } 119 | } 120 | "Euler10" in { 121 | tester.test { 122 | lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i => 123 | ps.takeWhile(j => j * j <= i).forall(i % _ > 0)) 124 | ps.view.takeWhile(_ < 2000).foldLeft(0L)(_ + _) 125 | } 126 | } 127 | 128 | "Euler16" in { 129 | tester.test { 130 | BigInt(2).pow(1000).toString.view.map(_.asDigit).sum 131 | } 132 | } 133 | "Euler29" in { 134 | tester.test { 135 | (2 to 10).flatMap(a => (2 to 10) 136 | .map(b => BigInt(a).pow(b))) 137 | .distinct 138 | .size 139 | 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/scala/metascala/imm/Misc.scala: -------------------------------------------------------------------------------- 1 | package metascala.imm 2 | 3 | import org.scalatest.FreeSpec 4 | import collection.mutable 5 | 6 | 7 | import org.objectweb.asm.{Opcodes, ClassReader} 8 | import Opcodes._ 9 | import org.objectweb.asm.tree._ 10 | import org.objectweb.asm.tree.analysis._ 11 | import metascala.opcodes.{Insn, BasicBlock, Code} 12 | import scala.collection.JavaConverters._ 13 | import metascala.opcodes.Code 14 | import metascala.opcodes.Insn._ 15 | import metascala.opcodes.Insn.GetArray 16 | import metascala.opcodes.Insn.Push 17 | import metascala.opcodes.Insn.BinOp 18 | 19 | import metascala.opcodes.Insn.PutArray 20 | 21 | import metascala.opcodes.Code 22 | import metascala._ 23 | import metascala.imm.Type.Prim 24 | import metascala.Gen._ 25 | import metascala.imm.Type.Prim._ 26 | 27 | class Misc extends FreeSpec { 28 | import Util._ 29 | val arr = new Array[Int](2) 30 | def test[T](p: Prim[T])(cases: Iterable[T]){ 31 | chk{ x: T => 32 | p.write(x, writer(arr, 0)) 33 | assert(p.read(reader(arr, 0)) === x) 34 | }(cases) 35 | } 36 | "making sure Prim[T] write & pops preserve the value T" - { 37 | "testZ" in test(Z)(Seq(true, false)) 38 | "testB" in test(B)(30 ** Gen.intAll.toByte) 39 | "testC" in test(C)(30 ** Gen.intAll.toChar) 40 | "testS" in test(S)(30 ** Gen.intAll.toShort) 41 | "testF" in test(F)(30 ** java.lang.Float.intBitsToFloat(Gen.intAll)) 42 | "testL" in test(J)(30 ** Gen.longAll) 43 | "testD" in test(D)(30 ** Gen.doubleAll) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/scala/metascala/imm/Type.scala: -------------------------------------------------------------------------------- 1 | package metascala.imm 2 | 3 | import org.scalatest.FreeSpec 4 | import metascala.Util 5 | import metascala.imm.Type.Prim._ 6 | import metascala.imm.Type.Prim 7 | 8 | class TypeTest extends FreeSpec { 9 | "helloObj" in { 10 | def test(p: Prim[_]) = { 11 | assert(p.javaName == p.primClass.getName, s"${p.javaName} != ${p.realCls.getName}") 12 | } 13 | Seq(Z, B, C, S, I, F, J, D).map(test) 14 | } 15 | } 16 | --------------------------------------------------------------------------------