├── .gitignore ├── .gitpod.yml ├── .settings ├── org.eclipse.jdt.apt.core.prefs ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.ui.prefs ├── .devcontainer ├── maven-settings.xml ├── Dockerfile └── devcontainer.json ├── .vscode └── tasks.json ├── .github └── workflows │ └── maven.yaml ├── src ├── main │ └── java │ │ └── com │ │ └── bandlem │ │ └── jvm │ │ └── jvmulator │ │ ├── ui │ │ ├── StepAction.java │ │ ├── OutTextArea.java │ │ ├── InvokeAction.java │ │ ├── EmulateAction.java │ │ ├── CompileAction.java │ │ ├── GUI.java │ │ └── JVMulator.java │ │ ├── JVMType.java │ │ ├── compiler │ │ ├── SourceFile.java │ │ ├── InMemoryClassLoader.java │ │ ├── ClassFile.java │ │ ├── NotifyingByteArrayOutputStream.java │ │ ├── InMemoryFileManager.java │ │ └── JavaC.java │ │ ├── classfile │ │ ├── Attribute.java │ │ ├── Member.java │ │ ├── JavaClass.java │ │ └── ConstantPool.java │ │ ├── Stack.java │ │ ├── Slot.java │ │ └── Opcodes.java └── test │ └── java │ └── com │ └── bandlem │ └── jvm │ └── jvmulator │ ├── compiler │ ├── InMemoryFileManagerTest.java │ ├── ClassFileTest.java │ ├── NotifyingByteArrayOutputStreamTest.java │ └── JavaCTest.java │ ├── JVMTypeTest.java │ ├── classfile │ ├── AttributeTest.java │ ├── MemberTest.java │ ├── JavaClassTest.java │ └── ConstantPoolTest.java │ ├── StackTest.java │ ├── SlotTest.java │ ├── OpcodesTest.java │ ├── JVMClassTest.java │ └── JVMTest.java ├── .project ├── .classpath ├── README.md ├── pom.xml └── LICENSE.html /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .devcontainer/Dockerfile 3 | context: .devcontainer 4 | 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /.devcontainer/maven-settings.xml: -------------------------------------------------------------------------------- 1 | 5 | /usr/share/maven/ref/repository 6 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "verify", 8 | "type": "shell", 9 | "command": "mvn -B verify", 10 | "group": "build" 11 | }, 12 | { 13 | "label": "test", 14 | "type": "shell", 15 | "command": "mvn -B test", 16 | "group": "test" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.github/workflows/maven.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Build Automatically with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Java 11 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | - name: Build with Maven 24 | run: mvn -B package verify --file pom.xml 25 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/StepAction.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.event.ActionEvent; 3 | import javax.swing.AbstractAction; 4 | import javax.swing.JOptionPane; 5 | class StepAction extends AbstractAction { 6 | private static final long serialVersionUID = 1L; 7 | private final JVMulator jvmulator; 8 | StepAction(final JVMulator jvmulator) { 9 | super("Step"); 10 | this.jvmulator = jvmulator; 11 | } 12 | @Override 13 | public void actionPerformed(final ActionEvent event) { 14 | try { 15 | jvmulator.step(); 16 | } catch (final Exception e) { 17 | JOptionPane.showMessageDialog(null, e.toString(), "Runtime Error", JOptionPane.ERROR_MESSAGE); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/JVMType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | public enum JVMType { 11 | BOOLEAN('Z', 1), // 12 | BYTE('B', 8), // 13 | CHAR('C', 16), // 14 | DOUBLE('D', 64), // 15 | FLOAT('F', 32), // 16 | INTEGER('I', 32), // 17 | LONG('L', 64), // 18 | SHORT('S', 16), // 19 | VOID('V', 0); 20 | public final char id; 21 | public final int size; 22 | private JVMType(final char id, final int size) { 23 | this.id = id; 24 | this.size = size; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/compiler/InMemoryFileManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import static org.junit.jupiter.api.Assertions.assertNull; 11 | import java.io.IOException; 12 | import org.junit.jupiter.api.Test; 13 | public class InMemoryFileManagerTest { 14 | @Test 15 | void testInMemory() throws IOException { 16 | try (final InMemoryFileManager inMemory = new InMemoryFileManager()) { 17 | assertNull(inMemory.getBytes("Missing")); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/SourceFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.io.IOException; 11 | import java.net.URI; 12 | import javax.tools.SimpleJavaFileObject; 13 | public class SourceFile extends SimpleJavaFileObject { 14 | private final String source; 15 | public SourceFile(final String name, final String source) { 16 | super(URI.create("memory:///" + name + Kind.SOURCE.extension), Kind.SOURCE); 17 | this.source = source; 18 | } 19 | @Override 20 | public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException { 21 | return source; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | JVMulator 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | 25 | VSCode 26 | VSCode filters 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/InMemoryClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.util.Map; 11 | public class InMemoryClassLoader extends ClassLoader { 12 | private final Map map; 13 | public InMemoryClassLoader(final Map map, final ClassLoader parent) { 14 | super(parent); 15 | this.map = map; 16 | } 17 | @Override 18 | protected Class findClass(final String name) throws ClassNotFoundException { 19 | final byte[] bytes = map.get(name); 20 | if (bytes == null) { 21 | throw new ClassNotFoundException(name); 22 | } 23 | return defineClass(name, bytes, 0, bytes.length); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/OutTextArea.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.io.IOException; 3 | import java.io.PrintStream; 4 | import javax.swing.JTextArea; 5 | public class OutTextArea extends PrintStream { 6 | private final JTextArea console; 7 | public OutTextArea(final JTextArea console, final PrintStream wrapped) { 8 | super(wrapped); 9 | this.console = console; 10 | } 11 | @Override 12 | public void write(final byte[] bytes) throws IOException { 13 | console.append(new String(bytes)); 14 | console.setVisible(true); 15 | super.write(bytes); 16 | } 17 | @Override 18 | public void write(final byte[] bytes, final int offset, final int length) { 19 | console.append(new String(bytes, offset, length)); 20 | console.setVisible(true); 21 | super.write(bytes, offset, length); 22 | } 23 | @Override 24 | public void write(final int b) { 25 | console.append(new String(new byte[] { 26 | (byte) b 27 | })); 28 | console.setVisible(true); 29 | super.write(b); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/InvokeAction.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.event.ActionEvent; 3 | import java.lang.reflect.Method; 4 | import javax.swing.AbstractAction; 5 | import javax.swing.JOptionPane; 6 | class InvokeAction extends AbstractAction { 7 | private static final long serialVersionUID = 1L; 8 | private final GUI gui; 9 | InvokeAction(final GUI gui) { 10 | super("Invoke"); 11 | setEnabled(false); 12 | this.gui = gui; 13 | } 14 | @Override 15 | public void actionPerformed(final ActionEvent event) { 16 | try { 17 | gui.clearConsole(); 18 | final Method method = gui.getSelectedMethod(); 19 | final Object[] arguments = gui.promptForArguments(); 20 | final Object result = method.invoke(null, arguments); 21 | if (result != null) 22 | JOptionPane.showMessageDialog(gui, result.toString(), "Result", JOptionPane.INFORMATION_MESSAGE); 23 | } catch (final Exception e) { 24 | JOptionPane.showMessageDialog(gui, e.getMessage(), "Unable to invoke method", JOptionPane.ERROR_MESSAGE); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/ClassFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | import java.net.URI; 14 | import java.util.function.Consumer; 15 | import javax.tools.SimpleJavaFileObject; 16 | public class ClassFile extends SimpleJavaFileObject { 17 | private final ByteArrayOutputStream baos; 18 | ClassFile(final String name, final Consumer listener) { 19 | super(URI.create("memory:///" + name + Kind.CLASS.extension), Kind.CLASS); 20 | baos = new NotifyingByteArrayOutputStream(1024, listener); 21 | } 22 | @Override 23 | public OutputStream openOutputStream() throws IOException { 24 | return baos; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/NotifyingByteArrayOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.util.function.Consumer; 13 | public class NotifyingByteArrayOutputStream extends ByteArrayOutputStream { 14 | private final Consumer listener; 15 | public NotifyingByteArrayOutputStream(final int intialSize, final Consumer listener) { 16 | super(intialSize); 17 | if (listener == null) { 18 | throw new IllegalArgumentException("Listener must be supplied"); 19 | } 20 | this.listener = listener; 21 | } 22 | @Override 23 | public void close() throws IOException { 24 | super.close(); 25 | listener.accept(toByteArray()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/EmulateAction.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.event.ActionEvent; 3 | import java.io.ByteArrayInputStream; 4 | import java.io.DataInputStream; 5 | import javax.swing.AbstractAction; 6 | import javax.swing.JFrame; 7 | import com.bandlem.jvm.jvmulator.classfile.JavaClass; 8 | class EmulateAction extends AbstractAction { 9 | private static final long serialVersionUID = 1L; 10 | private final GUI gui; 11 | EmulateAction(final GUI gui) { 12 | super("Emulate"); 13 | setEnabled(false); 14 | this.gui = gui; 15 | } 16 | @Override 17 | public void actionPerformed(final ActionEvent e) { 18 | final JavaClass javaClass = new JavaClass(new DataInputStream(new ByteArrayInputStream(gui.getClassBytes()))); 19 | final JVMulator jvmulator = new JVMulator(javaClass); 20 | final JFrame jvmulatorFrame = new JFrame("JVMulator"); 21 | jvmulatorFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 22 | jvmulatorFrame.add(jvmulator); 23 | jvmulatorFrame.pack(); 24 | jvmulatorFrame.setVisible(true); 25 | jvmulatorFrame.setSize(jvmulator.getMinimumSize()); 26 | jvmulator.setName(gui.getSelectedMethod().getName()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.194.0/containers/java/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Java version: 11, 16 4 | ARG VARIANT="16" 5 | FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} 6 | 7 | # [Option] Install Maven 8 | ARG INSTALL_MAVEN="false" 9 | ARG MAVEN_VERSION="" 10 | # [Option] Install Gradle 11 | ARG INSTALL_GRADLE="false" 12 | ARG GRADLE_VERSION="" 13 | RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ 14 | && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi 15 | 16 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 17 | ARG NODE_VERSION="none" 18 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 19 | 20 | # [Optional] Uncomment this section to install additional OS packages. 21 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 22 | && apt-get -y install --no-install-recommends vim 23 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.194.0/containers/java 3 | { 4 | "name": "Java", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Update the VARIANT arg to pick a Java version: 11, 16 9 | "VARIANT": "11", 10 | // Options 11 | "INSTALL_MAVEN": "true", 12 | "INSTALL_GRADLE": "false", 13 | "NODE_VERSION": "lts/*" 14 | } 15 | }, 16 | 17 | // Set *default* container specific settings.json values on container create. 18 | "settings": { 19 | "java.home": "/docker-java-home" 20 | }, 21 | 22 | // Add the IDs of extensions you want installed when the container is created. 23 | "extensions": [ 24 | "vscjava.vscode-java-pack" 25 | ], 26 | 27 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 28 | // "forwardPorts": [], 29 | 30 | // Use 'postCreateCommand' to run commands after the container is created. 31 | // "postCreateCommand": "java -version", 32 | 33 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 34 | "remoteUser": "vscode" 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/compiler/ClassFileTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.Writer; 14 | import org.junit.jupiter.api.Test; 15 | public class ClassFileTest { 16 | @Test 17 | void testClassFile() throws IOException { 18 | final byte[][] datas = new byte[1][1]; 19 | final ClassFile classFile = new ClassFile("Output", (data) -> { 20 | datas[0] = data; 21 | }); 22 | final Writer writer = classFile.openWriter(); 23 | final String message = "Hello World!"; 24 | writer.write(message); 25 | writer.close(); 26 | assertEquals(message.getBytes().length, datas[0].length); 27 | } 28 | @Test 29 | void testSourceFile() throws IOException { 30 | final String depressedMoose = "Depressed Moose"; 31 | final SourceFile file = new SourceFile("Input", depressedMoose); 32 | final String line = new BufferedReader(file.openReader(true)).readLine(); 33 | assertEquals(depressedMoose, line); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/compiler/NotifyingByteArrayOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNull; 12 | import static org.junit.jupiter.api.Assertions.assertThrows; 13 | import java.io.IOException; 14 | import java.util.function.Consumer; 15 | import org.junit.jupiter.api.Test; 16 | public class NotifyingByteArrayOutputStreamTest { 17 | @Test 18 | void testNBAOS() throws IOException { 19 | final byte[][] result = new byte[1][]; 20 | final Consumer listener = (data) -> { 21 | result[0] = data; 22 | }; 23 | try (final NotifyingByteArrayOutputStream nbaos = new NotifyingByteArrayOutputStream(0, listener)) { 24 | final String message = "Hello World!"; 25 | nbaos.write(message.getBytes()); 26 | assertNull(result[0]); 27 | nbaos.flush(); 28 | assertNull(result[0]); 29 | nbaos.close(); 30 | assertArrayEquals(message.getBytes(), result[0]); 31 | } 32 | } 33 | @Test 34 | void testNBAOSNullThrows() { 35 | assertThrows(IllegalArgumentException.class, () -> new NotifyingByteArrayOutputStream(0, null)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/JVMTypeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static com.bandlem.jvm.jvmulator.JVMType.BOOLEAN; 11 | import static com.bandlem.jvm.jvmulator.JVMType.BYTE; 12 | import static com.bandlem.jvm.jvmulator.JVMType.CHAR; 13 | import static com.bandlem.jvm.jvmulator.JVMType.DOUBLE; 14 | import static com.bandlem.jvm.jvmulator.JVMType.INTEGER; 15 | import static com.bandlem.jvm.jvmulator.JVMType.LONG; 16 | import static com.bandlem.jvm.jvmulator.JVMType.SHORT; 17 | import static com.bandlem.jvm.jvmulator.JVMType.VOID; 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import org.junit.jupiter.api.Test; 20 | public class JVMTypeTest { 21 | @Test 22 | void testTypeIDs() { 23 | assertEquals('D', DOUBLE.id); 24 | assertEquals('L', LONG.id); 25 | assertEquals('I', INTEGER.id); 26 | assertEquals('S', SHORT.id); 27 | assertEquals('C', CHAR.id); 28 | assertEquals('B', BYTE.id); 29 | assertEquals('Z', BOOLEAN.id); 30 | assertEquals('V', VOID.id); 31 | } 32 | @Test 33 | void testTypeSizes() { 34 | assertEquals(64, DOUBLE.size); 35 | assertEquals(64, LONG.size); 36 | assertEquals(32, INTEGER.size); 37 | assertEquals(16, SHORT.size); 38 | assertEquals(16, CHAR.size); 39 | assertEquals(8, BYTE.size); 40 | assertEquals(1, BOOLEAN.size); 41 | assertEquals(0, VOID.size); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/CompileAction.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.event.ActionEvent; 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | import javax.swing.AbstractAction; 6 | import javax.swing.JOptionPane; 7 | import javax.tools.Diagnostic; 8 | import javax.tools.JavaFileObject; 9 | import com.bandlem.jvm.jvmulator.compiler.JavaC; 10 | import com.bandlem.jvm.jvmulator.compiler.SourceFile; 11 | class CompileAction extends AbstractAction { 12 | private static final long serialVersionUID = 1L; 13 | private final GUI gui; 14 | CompileAction(final GUI gui) { 15 | super("Compile"); 16 | this.gui = gui; 17 | } 18 | @Override 19 | public void actionPerformed(final ActionEvent event) { 20 | gui.clearMethods(); 21 | final JavaC compiler = new JavaC(); 22 | final boolean success = compiler.compile(new SourceFile("Example", gui.getSource())); 23 | final List> diagnostics = compiler.getDiagnostics(); 24 | if (!success || !diagnostics.isEmpty()) { 25 | final List messages = diagnostics.stream().map(Object::toString).collect(Collectors.toList()); 26 | JOptionPane.showMessageDialog(gui, String.join("\n", messages), "Compiler Error", 27 | JOptionPane.ERROR_MESSAGE); 28 | } else { 29 | try { 30 | final Class exampleClass = compiler.newClassLoader().loadClass("Example"); 31 | for (final var method : exampleClass.getMethods()) { 32 | gui.addMethod(method); 33 | } 34 | gui.setClassBytes(compiler.getBytes("Example")); 35 | } catch (final ClassNotFoundException e) { 36 | JOptionPane.showMessageDialog(gui, "Unable to load class", "Compiler Error", JOptionPane.ERROR_MESSAGE); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/InMemoryFileManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.io.IOException; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Collections; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import javax.tools.FileObject; 16 | import javax.tools.ForwardingJavaFileManager; 17 | import javax.tools.JavaCompiler; 18 | import javax.tools.JavaFileManager; 19 | import javax.tools.JavaFileObject; 20 | import javax.tools.ToolProvider; 21 | public class InMemoryFileManager extends ForwardingJavaFileManager { 22 | private final Map classes = Collections.synchronizedMap(new HashMap<>()); 23 | public InMemoryFileManager() { 24 | this(ToolProvider.getSystemJavaCompiler()); 25 | } 26 | public InMemoryFileManager(final JavaCompiler javaCompiler) { 27 | super(javaCompiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)); 28 | } 29 | public byte[] getBytes(final String name) { 30 | return classes.get(name); 31 | } 32 | @Override 33 | public JavaFileObject getJavaFileForOutput(final Location location, final String className, 34 | final JavaFileObject.Kind kind, final FileObject sibling) throws IOException { 35 | return new ClassFile(className, bytes -> classes.put(className, bytes)); 36 | } 37 | public ClassLoader newClassLoader() { 38 | return new InMemoryClassLoader(classes, getClass().getClassLoader()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/compiler/JavaC.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import javax.tools.Diagnostic; 14 | import javax.tools.DiagnosticCollector; 15 | import javax.tools.JavaCompiler; 16 | import javax.tools.JavaCompiler.CompilationTask; 17 | import javax.tools.JavaFileObject; 18 | import javax.tools.ToolProvider; 19 | public class JavaC { 20 | private final DiagnosticCollector collector; 21 | private final JavaCompiler compiler; 22 | private final InMemoryFileManager fileManager; 23 | public JavaC() { 24 | this(new DiagnosticCollector(), ToolProvider.getSystemJavaCompiler(), 25 | new InMemoryFileManager(ToolProvider.getSystemJavaCompiler())); 26 | } 27 | public JavaC(final DiagnosticCollector collector, final JavaCompiler compiler, 28 | final InMemoryFileManager fileManager) { 29 | this.collector = Objects.requireNonNull(collector); 30 | this.compiler = Objects.requireNonNull(compiler); 31 | this.fileManager = Objects.requireNonNull(fileManager); 32 | } 33 | public boolean compile(final SourceFile... source) { 34 | final CompilationTask task = compiler.getTask(null, fileManager, collector, null, null, Arrays.asList(source)); 35 | return task.call(); 36 | } 37 | public byte[] getBytes(final String name) { 38 | return fileManager.getBytes(name); 39 | } 40 | public List> getDiagnostics() { 41 | return collector.getDiagnostics(); 42 | } 43 | public ClassLoader newClassLoader() { 44 | return fileManager.newClassLoader(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/compiler/JavaCTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.compiler; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | import static org.junit.jupiter.api.Assertions.assertThrows; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | import org.junit.jupiter.api.Test; 15 | public class JavaCTest { 16 | private static final String TEST_PROPERTY = "com.bandlem.jvm.jvmulator.example.run"; 17 | private SourceFile getSourceFile() { 18 | return new SourceFile("HelloWorld", // 19 | "package com.bandlem.jvm.jvmulator.example;" // 20 | + "public class HelloWorld implements Runnable {" // 21 | + " public void run() {" // 22 | + " System.setProperty(\"" + TEST_PROPERTY + "\",\"true\");" // 23 | + " }" // 24 | + "}"); 25 | } 26 | @Test 27 | void testCompile() throws ReflectiveOperationException { 28 | final JavaC javac = new JavaC(); 29 | assertTrue(javac.compile(getSourceFile())); 30 | assertTrue(javac.getDiagnostics().isEmpty()); 31 | assertNotNull(javac.getBytes("com.bandlem.jvm.jvmulator.example.HelloWorld")); 32 | final ClassLoader loader = javac.newClassLoader(); 33 | final Class helloWorld = loader.loadClass("com.bandlem.jvm.jvmulator.example.HelloWorld"); 34 | assertNotNull(helloWorld); 35 | final Object instance = helloWorld.getDeclaredConstructor().newInstance(); 36 | assertNotNull(instance); 37 | assertTrue(instance instanceof Runnable); 38 | System.setProperty(TEST_PROPERTY, "false"); 39 | ((Runnable) instance).run(); 40 | assertEquals("true", System.getProperty(TEST_PROPERTY)); 41 | } 42 | @Test 43 | void testNotFound() { 44 | final JavaC javac = new JavaC(); 45 | assertThrows(ClassNotFoundException.class, () -> javac.newClassLoader().loadClass("missing")); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/classfile/AttributeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.DataInputStream; 14 | import java.io.IOException; 15 | import org.junit.jupiter.api.Test; 16 | import com.bandlem.jvm.jvmulator.classfile.Attribute.Code; 17 | import com.bandlem.jvm.jvmulator.classfile.Attribute.SourceFile; 18 | import com.bandlem.jvm.jvmulator.classfile.Attribute.Unknown; 19 | public class AttributeTest { 20 | @Test 21 | void testCodeAttribute() { 22 | final Code code = (Code) Attribute.of("Code", null, new byte[] { 23 | 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, (byte) 0xca, (byte) 0xfe 24 | }); 25 | assertEquals("Code", code.attributeName); 26 | assertEquals(1, code.getMaxStack()); 27 | assertEquals(2, code.getMaxLocals()); 28 | final byte[] bytecode = code.getBytecode(); 29 | assertEquals(2, bytecode.length); 30 | assertEquals((byte) 0xca, bytecode[0]); 31 | assertEquals((byte) 0xfe, bytecode[1]); 32 | } 33 | @Test 34 | void testIncompleteData() { 35 | assertThrows(IllegalArgumentException.class, () -> Attribute.of("Code", null, new byte[] {})); 36 | } 37 | @Test 38 | void testSoruceFileAttribute() throws IOException { 39 | final DataInputStream dis = new DataInputStream(new ByteArrayInputStream(new byte[] { 40 | ConstantPool.UTFConstant.TYPE, 0x00, 0x02, 'O', 'K' 41 | })); 42 | final ConstantPool pool = new ConstantPool((short) 2, dis); 43 | final SourceFile file = (SourceFile) Attribute.of("SourceFile", pool, new byte[] { 44 | 0x00, 0x01 45 | }); 46 | assertEquals("OK", file.file); 47 | assertEquals("SourceFile", file.attributeName); 48 | } 49 | @Test 50 | void testUnknown() { 51 | final byte[] input = new byte[] { 52 | 0x61, 0x6c, 0x62, 0x6c, 0x75, 0x65 53 | }; 54 | final Unknown attribute = (Unknown) Attribute.of("alblue", null, input); 55 | assertEquals("alblue", attribute.attributeName); 56 | assertEquals(6, attribute.data.length); 57 | for (int i = 0; i < input.length; i++) { 58 | assertEquals(input[i], attribute.data[i]); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/classfile/Attribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import java.io.ByteArrayInputStream; 11 | import java.io.DataInputStream; 12 | import java.io.IOException; 13 | public abstract class Attribute { 14 | public static class Code extends Attribute { 15 | public static final String NAME = "Code"; 16 | private final byte[] bytecode; 17 | private final short maxLocals; 18 | private final short maxStack; 19 | public Code(final DataInputStream dis) throws IOException { 20 | super(NAME); 21 | maxStack = dis.readShort(); 22 | maxLocals = dis.readShort(); 23 | bytecode = new byte[dis.readInt()]; 24 | dis.readFully(bytecode); 25 | // exception table 26 | // code attributes 27 | } 28 | public byte[] getBytecode() { 29 | return bytecode; 30 | } 31 | public short getMaxLocals() { 32 | return maxLocals; 33 | } 34 | public short getMaxStack() { 35 | return maxStack; 36 | } 37 | } 38 | public static class SourceFile extends Attribute { 39 | public static final String NAME = "SourceFile"; 40 | public final String file; 41 | public SourceFile(final DataInputStream dis, final ConstantPool pool) throws IOException { 42 | super(NAME); 43 | this.file = pool.getString(dis.readShort()); 44 | } 45 | @Override 46 | public String toString() { 47 | return file; 48 | } 49 | } 50 | public static class Unknown extends Attribute { 51 | public final byte[] data; 52 | public Unknown(final String attributeName, final byte[] data) { 53 | super(attributeName); 54 | this.data = data; 55 | } 56 | } 57 | public static Attribute of(final String attributeName, final ConstantPool pool, final byte[] data) { 58 | try { 59 | final DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data)); 60 | if (Code.NAME.equals(attributeName)) { 61 | return new Code(dis); 62 | } else if (SourceFile.NAME.equals(attributeName)) { 63 | return new SourceFile(dis, pool); 64 | } else { 65 | return new Unknown(attributeName, data); 66 | } 67 | } catch (final IOException e) { 68 | throw new IllegalArgumentException("Unable to parse " + attributeName, e); 69 | } 70 | } 71 | public final String attributeName; 72 | public Attribute(final String attributeName) { 73 | this.attributeName = attributeName; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/classfile/MemberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | import static org.junit.jupiter.api.Assertions.assertNull; 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | import java.nio.charset.StandardCharsets; 15 | import org.junit.jupiter.api.Test; 16 | import com.bandlem.jvm.jvmulator.classfile.Member.Field; 17 | import com.bandlem.jvm.jvmulator.classfile.Member.Method; 18 | public class MemberTest { 19 | @Test 20 | void testDescriptor() { 21 | final Class[] types = Method.argumentTypes("(ZSCIJFDLjava/lang/String;Z)V", getClass().getClassLoader()); 22 | assertEquals(9, types.length); 23 | assertEquals(Boolean.TYPE, types[0]); 24 | assertEquals(Short.TYPE, types[1]); 25 | assertEquals(Character.TYPE, types[2]); 26 | assertEquals(Integer.TYPE, types[3]); 27 | assertEquals(Long.TYPE, types[4]); 28 | assertEquals(Float.TYPE, types[5]); 29 | assertEquals(Double.TYPE, types[6]); 30 | assertEquals(String.class, types[7]); 31 | assertEquals(Boolean.TYPE, types[8]); 32 | assertThrows(IllegalArgumentException.class, () -> Method.argumentTypes("(?)V", null)); 33 | assertThrows(IllegalStateException.class, () -> Method.argumentTypes("(I", null)); 34 | assertThrows(RuntimeException.class, () -> Method.argumentTypes("(Lmissingclass;)V", null)); 35 | } 36 | @Test 37 | void testField() { 38 | final Field field = new Field((short) 12, "MyField", "[I", new Attribute[] {}); 39 | assertEquals(12, field.flags); 40 | assertEquals("MyField", field.name); 41 | assertEquals("[I", field.descriptor); 42 | assertNotNull(field.attributes); 43 | assertNull(field.getAttribute("Unknown")); 44 | } 45 | @Test 46 | void testMethod() { 47 | final Attribute[] attributes = new Attribute[] { 48 | Attribute.of(Attribute.Code.NAME, null, new byte[] { 49 | 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00 50 | }), Attribute.of("Unknown", null, "Unknown".getBytes(StandardCharsets.UTF_8)) 51 | }; 52 | final Method method = new Method((short) 34, "MyMethod", "(I)V", attributes); 53 | final Class[] args = method.argumentTypes(MemberTest.class.getClassLoader()); 54 | assertEquals(1, args.length); 55 | assertEquals(Integer.TYPE, args[0]); 56 | assertEquals(34, method.flags); 57 | assertEquals("MyMethod", method.name); 58 | assertEquals("(I)V", method.descriptor); 59 | assertNotNull(method.attributes); 60 | assertEquals("Unknown", method.getAttribute("Unknown").attributeName); 61 | assertEquals("Code", method.getCodeAttribute().attributeName); 62 | assertEquals(0, method.getCodeAttribute().getBytecode().length); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/alblue/jvmulator) [Open in GitHub Codespaces](https://github.com/codespaces/alblue/alblue-jvmulator-master) 2 | 3 | JVMulator 4 | ========= 5 | 6 | This provides a simple emulator for Java bytecode as well as an in-memory Java 7 | compiler to allow bytecode to be generated. The generated code can be executed 8 | as well as emulated to allow stepping through bytecode line by line, and seeing 9 | what the content of the local variables or stack happens to be. 10 | 11 | Usage 12 | ----- 13 | 14 | The project can be built with Maven or IDEs supporting Maven. There is a Swing 15 | GUI which can be run by executing the Main-Class from the JAR manifest, or 16 | with Maven directly: 17 | 18 | $ mvn exec:java 19 | $ java -jar target/jvmulator.jar 20 | 21 | The source pane shows the Java source code that will be compiled when the 22 | compile button is pressed. Java source must be compiled before it can be 23 | executed or interpreted. If there are errors when the code is compiled they 24 | will be presented as a dialog. It is intentionally a lightweight text field; 25 | it is not intended that it will be a fully fledged editor. 26 | 27 | After the source has been compiled, the drop-down list of methods will be 28 | filled. A method can be chosen and then executed or interpreted by clicking 29 | the appropriate button. 30 | 31 | If the interpret button is chosen, a new window will come up with that method's 32 | bytecodes, and the 'step' will allow stepping over an instruction line by line. 33 | When the method returns, the return value will be displayed. 34 | 35 | Limitations 36 | ----------- 37 | 38 | * This isn't intended to be a fully fledged IDE. Think Notepad, not NetBeans. 39 | * The class name Example is hard-coded in some parts; using a different class 40 | name is not supported at the moment. 41 | * Only static methods can be invoked or emulated. 42 | 43 | Limitations for emulation 44 | ------------------------- 45 | 46 | There are many bytecodes not yet supported, which will cause some failures. 47 | 48 | * New isn't supported, which means that many implicit operations like + fail 49 | * Anewarray isn't supported either, so no new Object[] for you 50 | * Anything to do with exception processing (throw, try, catch) isn't present 51 | * Casting (`checkcast`) 52 | * Switch statements (that use `tableswitch` and `lookupswitch`) 53 | * Invokeinterface isn't implemented yet, so `Runnable.run()` won't work 54 | * `invokespecial` used by constructors doesn't work yet 55 | * Synchronisation (`monitorenter` and `monitorexit`) don't work yet 56 | * `wide` operations (used by larger code examples) won't work 57 | 58 | If an uninterpreted bytecode occurs, an exception will be generated. So feel 59 | free to try out what you want; just be happy when it works as expected. 60 | 61 | Support 62 | ------- 63 | 64 | There isn't any. Use at your own risk. It is an educational project, originally 65 | created for the presentation for the LJCJUG on 9 June 2020. 66 | 67 | That said, if you enjoyed it, feel free to say thanks to @alblue! 68 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/Stack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.function.IntFunction; 13 | public class Stack { 14 | private final List internal = new ArrayList<>(); 15 | public Slot at(final int i) { 16 | return internal.get(i); 17 | } 18 | public void dup() { 19 | internal.add(internal.get(internal.size() - 1)); 20 | } 21 | public void dup_x1() { 22 | internal.add(internal.get(internal.size() - 2)); 23 | } 24 | public void dup_x2() { 25 | internal.add(internal.get(internal.size() - 3)); 26 | } 27 | public void dup2() { 28 | internal.add(internal.get(internal.size() - 2)); 29 | internal.add(internal.get(internal.size() - 2)); 30 | } 31 | public void dup2_x1() { 32 | internal.add(internal.get(internal.size() - 3)); 33 | internal.add(internal.get(internal.size() - 3)); 34 | } 35 | public void dup2_x2() { 36 | internal.add(internal.get(internal.size() - 4)); 37 | internal.add(internal.get(internal.size() - 4)); 38 | } 39 | public Slot peek() { 40 | return top(internal::get); 41 | } 42 | public Slot pop() { 43 | return top(internal::remove); 44 | } 45 | public double popDouble() { 46 | return pop().doubleValue(); 47 | } 48 | public float popFloat() { 49 | return pop().floatValue(); 50 | } 51 | public int popInt() { 52 | return pop().intValue(); 53 | } 54 | public long popLong() { 55 | return pop().longValue(); 56 | } 57 | public Object popReference() { 58 | return pop().referenceValue(); 59 | } 60 | public void push(final boolean b) { 61 | pushSlot(Slot.of(b)); 62 | } 63 | public void push(final double d) { 64 | pushSlot(Slot.of(d)); 65 | } 66 | public void push(final float f) { 67 | pushSlot(Slot.of(f)); 68 | } 69 | public void push(final int i) { 70 | pushSlot(Slot.of(i)); 71 | } 72 | public void push(final long l) { 73 | pushSlot(Slot.of(l)); 74 | } 75 | public void push(final Object value) { 76 | pushSlot(Slot.of(value)); 77 | } 78 | void pushSlot(final Slot s) { 79 | if (s == null) { 80 | throw new IllegalArgumentException("Cannot push a null slot"); 81 | } else { 82 | internal.add(s); 83 | if (s.isWide()) { 84 | internal.add(Slot.empty()); 85 | } 86 | } 87 | } 88 | public int size() { 89 | return internal.size(); 90 | } 91 | private Slot top(final IntFunction op) { 92 | final int pos = internal.size() - 1; 93 | Slot topslot = op.apply(pos); 94 | if (topslot == Slot.empty()) { 95 | topslot = op.apply(pos - 1); 96 | if (!topslot.isWide()) { 97 | throw new IllegalStateException("Top slot was empty, but next was not wide"); 98 | } 99 | } 100 | return topslot; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/classfile/JavaClassTest.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.classfile; 2 | import static org.junit.jupiter.api.Assertions.assertEquals; 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | import static org.junit.jupiter.api.Assertions.assertNull; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import java.io.ByteArrayInputStream; 7 | import java.io.DataInput; 8 | import java.io.DataInputStream; 9 | import java.io.InputStream; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import com.bandlem.jvm.jvmulator.Opcodes; 13 | public class JavaClassTest { 14 | private static class ClassUnderTest implements Runnable { 15 | private String field; 16 | @Override 17 | public void run() { 18 | field = "Executed"; 19 | } 20 | @Override 21 | public String toString() { 22 | return field; 23 | } 24 | } 25 | private JavaClass classUnderTest; 26 | private ConstantPool pool; 27 | private DataInput dis(final byte... bytes) { 28 | return new DataInputStream(new ByteArrayInputStream(bytes)); 29 | } 30 | @BeforeEach 31 | void setupClass() { 32 | final String name = ClassUnderTest.class.getName().replace('.', '/') + ".class"; 33 | final InputStream stream = ClassUnderTest.class.getClassLoader().getResourceAsStream(name); 34 | classUnderTest = new JavaClass(new DataInputStream(stream)); 35 | assertNotNull(classUnderTest); 36 | pool = classUnderTest.pool; 37 | } 38 | @Test 39 | void testClass() { 40 | assertEquals(ClassUnderTest.class.getName().replace('.', '/'), classUnderTest.this_class); 41 | assertEquals(Object.class.getName().replace('.', '/'), classUnderTest.super_class); 42 | assertEquals(1, classUnderTest.interfaces.length); 43 | assertEquals(Runnable.class.getName().replace('.', '/'), classUnderTest.interfaces[0]); 44 | assertEquals(1, classUnderTest.fields.length); 45 | assertEquals("field", classUnderTest.fields[0].name); 46 | assertEquals(3, classUnderTest.methods.length); 47 | assertEquals("", classUnderTest.methods[0].name); 48 | final Attribute.Code code = (Attribute.Code) classUnderTest.methods[0].getAttribute("Code"); 49 | assertEquals("Code", code.attributeName); 50 | final byte[] bytecode = code.getBytecode(); 51 | // Default constructor is: 52 | // aload_0 53 | // invokespecial 54 | // return 55 | assertEquals(5, bytecode.length); 56 | assertEquals(Opcodes.ALOAD_0, bytecode[0]); 57 | assertEquals(Opcodes.INVOKESPECIAL, bytecode[1]); 58 | assertEquals(Opcodes.RETURN, bytecode[4]); 59 | assertEquals(JavaClassTest.class.getSimpleName() + ".java", 60 | classUnderTest.getAttribute("SourceFile").toString()); 61 | assertNull(classUnderTest.getAttribute("WhoNose")); 62 | assertThrows(IllegalArgumentException.class, () -> pool.getItem(0)); 63 | assertEquals(System.getProperty("java.class.version"), classUnderTest.major + "." + classUnderTest.minor); 64 | } 65 | @Test 66 | void testInvalidClass() { 67 | assertThrows(IllegalArgumentException.class, 68 | () -> new JavaClass(dis((byte) 0xb0, (byte) 0x00, (byte) 0xb0, (byte) 0x00))); 69 | assertThrows(IllegalArgumentException.class, () -> new JavaClass(dis())); 70 | } 71 | @Test 72 | void testMember() { 73 | assertNull(classUnderTest.getField("Missing field")); 74 | assertNotNull(classUnderTest.getField("field")); 75 | assertNull(classUnderTest.getMethod("Not present")); 76 | assertNotNull(classUnderTest.getMethod("run")); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/classfile/Member.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import com.bandlem.jvm.jvmulator.classfile.Attribute.Code; 13 | public abstract class Member { 14 | public static class Field extends Member { 15 | public Field(final short flags, final String name, final String descriptor, final Attribute[] attributes) { 16 | super(flags, name, descriptor, attributes); 17 | } 18 | } 19 | public static class Method extends Member { 20 | public static Class[] argumentTypes(final String descriptor, ClassLoader loader) { 21 | if (loader == null) { 22 | loader = Method.class.getClassLoader(); 23 | } 24 | final byte[] bytes = descriptor.getBytes(); 25 | final List> types = new ArrayList<>(); 26 | for (int i = 0; i < bytes.length; i++) { 27 | switch (bytes[i]) { 28 | case '(': 29 | continue; 30 | case ')': 31 | return types.toArray(new Class[0]); 32 | case 'Z': 33 | types.add(Boolean.TYPE); 34 | break; 35 | case 'S': 36 | types.add(Short.TYPE); 37 | break; 38 | case 'C': 39 | types.add(Character.TYPE); 40 | break; 41 | case 'I': 42 | types.add(Integer.TYPE); 43 | break; 44 | case 'J': 45 | types.add(Long.TYPE); 46 | break; 47 | case 'F': 48 | types.add(Float.TYPE); 49 | break; 50 | case 'D': 51 | types.add(Double.TYPE); 52 | break; 53 | case 'L': 54 | final int start = i + 1; 55 | // Converts java/lang/String to java.lang.String 56 | while (bytes[i] != ';') { 57 | if (bytes[i] == '/') 58 | bytes[i] = '.'; 59 | i++; 60 | } 61 | final String clazz = new String(bytes, start, i - start); 62 | try { 63 | types.add(loader.loadClass(clazz)); 64 | } catch (final ClassNotFoundException e) { 65 | throw new RuntimeException("Cannot load class " + clazz, e); 66 | } 67 | break; 68 | default: 69 | throw new IllegalArgumentException("Unknown type " + (char) bytes[i]); 70 | } 71 | } 72 | throw new IllegalStateException("Read to end of " + descriptor + " without closing )"); 73 | } 74 | public Method(final short flags, final String name, final String descriptor, final Attribute[] attributes) { 75 | super(flags, name, descriptor, attributes); 76 | } 77 | public Class[] argumentTypes(final ClassLoader classLoader) { 78 | return argumentTypes(descriptor, classLoader); 79 | } 80 | } 81 | public final Attribute[] attributes; 82 | public final String descriptor; 83 | public final short flags; 84 | public final String name; 85 | public Member(final short flags, final String name, final String descriptor, final Attribute[] attributes) { 86 | this.flags = flags; 87 | this.name = name; 88 | this.descriptor = descriptor; 89 | this.attributes = attributes; 90 | } 91 | public Attribute getAttribute(final String name) { 92 | for (final Attribute attribute : attributes) { 93 | if (name.equals(attribute.attributeName)) { 94 | return attribute; 95 | } 96 | } 97 | return null; 98 | } 99 | public Code getCodeAttribute() { 100 | return (Code) getAttribute(Code.NAME); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/classfile/JavaClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import java.io.DataInput; 11 | import java.io.IOException; 12 | import com.bandlem.jvm.jvmulator.classfile.Member.Field; 13 | import com.bandlem.jvm.jvmulator.classfile.Member.Method; 14 | public class JavaClass { 15 | public final Attribute[] classAttributes; 16 | public final Field[] fields; 17 | public final short flags; 18 | public final String[] interfaces; 19 | public final short major; 20 | public final Method[] methods; 21 | public final short minor; 22 | public final ConstantPool pool; 23 | public final String super_class; 24 | public final String this_class; 25 | public JavaClass(final DataInput di) throws IllegalArgumentException { 26 | try { 27 | if (di.readInt() != 0xcafebabe) { 28 | throw new IllegalArgumentException("Content is not a class file"); 29 | } 30 | this.minor = di.readShort(); 31 | this.major = di.readShort(); 32 | this.pool = new ConstantPool(di.readShort(), di); 33 | this.flags = di.readShort(); 34 | this.this_class = pool.getClassName(di.readShort()); 35 | this.super_class = pool.getClassName(di.readShort()); 36 | this.interfaces = new String[di.readShort() & 0xffff]; 37 | for (int i = 0; i < interfaces.length; i++) { 38 | interfaces[i] = pool.getClassName(di.readShort()); 39 | } 40 | this.fields = new Field[di.readShort() & 0xffff]; 41 | for (int i = 0; i < fields.length; i++) { 42 | final short flags = di.readShort(); 43 | final String name = pool.getString(di.readShort()); 44 | final String descriptor = pool.getString(di.readShort()); 45 | fields[i] = new Field(flags, name, descriptor, readAttributes(di, pool)); 46 | } 47 | this.methods = new Method[di.readShort() & 0xffff]; 48 | for (int i = 0; i < methods.length; i++) { 49 | final short flags = di.readShort(); 50 | final String name = pool.getString(di.readShort()); 51 | final String descriptor = pool.getString(di.readShort()); 52 | methods[i] = new Method(flags, name, descriptor, readAttributes(di, pool)); 53 | } 54 | this.classAttributes = readAttributes(di, pool); 55 | } catch (final IOException e) { 56 | throw new IllegalArgumentException("Unable to parse bytecode", e); 57 | } 58 | } 59 | public Attribute getAttribute(final String name) { 60 | for (final var attribute : classAttributes) { 61 | if (name.equals(attribute.attributeName)) { 62 | return attribute; 63 | } 64 | } 65 | return null; 66 | } 67 | public Field getField(final String name) { 68 | for (final Field field : fields) { 69 | if (name.equals(field.name)) { 70 | return field; 71 | } 72 | } 73 | return null; 74 | } 75 | public Method getMethod(final String name) { 76 | for (final Method method : methods) { 77 | if (name.equals(method.name)) { 78 | return method; 79 | } 80 | } 81 | return null; 82 | } 83 | private Attribute[] readAttributes(final DataInput di, final ConstantPool pool) throws IOException { 84 | final Attribute[] attributes = new Attribute[di.readShort()]; 85 | for (int i = 0; i < attributes.length; i++) { 86 | final String name = pool.getString(di.readShort()); 87 | final int length = di.readInt(); 88 | final byte[] data = new byte[length]; 89 | di.readFully(data); 90 | attributes[i] = Attribute.of(name, pool, data); 91 | } 92 | return attributes; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true 3 | formatter_profile=_jvmulator 4 | formatter_settings_version=18 5 | org.eclipse.jdt.ui.exception.name=e 6 | org.eclipse.jdt.ui.gettersetter.use.is=true 7 | org.eclipse.jdt.ui.keywordthis=false 8 | org.eclipse.jdt.ui.overrideannotation=true 9 | sp_cleanup.add_default_serial_version_id=true 10 | sp_cleanup.add_generated_serial_version_id=false 11 | sp_cleanup.add_missing_annotations=true 12 | sp_cleanup.add_missing_deprecated_annotations=true 13 | sp_cleanup.add_missing_methods=false 14 | sp_cleanup.add_missing_nls_tags=false 15 | sp_cleanup.add_missing_override_annotations=true 16 | sp_cleanup.add_missing_override_annotations_interface_methods=true 17 | sp_cleanup.add_serial_version_id=false 18 | sp_cleanup.always_use_blocks=true 19 | sp_cleanup.always_use_parentheses_in_expressions=false 20 | sp_cleanup.always_use_this_for_non_static_field_access=false 21 | sp_cleanup.always_use_this_for_non_static_method_access=false 22 | sp_cleanup.convert_functional_interfaces=false 23 | sp_cleanup.convert_to_enhanced_for_loop=false 24 | sp_cleanup.correct_indentation=true 25 | sp_cleanup.format_source_code=true 26 | sp_cleanup.format_source_code_changes_only=false 27 | sp_cleanup.insert_inferred_type_arguments=false 28 | sp_cleanup.make_local_variable_final=true 29 | sp_cleanup.make_parameters_final=true 30 | sp_cleanup.make_private_fields_final=true 31 | sp_cleanup.make_type_abstract_if_missing_method=false 32 | sp_cleanup.make_variable_declarations_final=true 33 | sp_cleanup.never_use_blocks=false 34 | sp_cleanup.never_use_parentheses_in_expressions=true 35 | sp_cleanup.number_suffix=true 36 | sp_cleanup.on_save_use_additional_actions=true 37 | sp_cleanup.organize_imports=true 38 | sp_cleanup.push_down_negation=false 39 | sp_cleanup.qualify_static_field_accesses_with_declaring_class=false 40 | sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true 41 | sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true 42 | sp_cleanup.qualify_static_member_accesses_with_declaring_class=false 43 | sp_cleanup.qualify_static_method_accesses_with_declaring_class=false 44 | sp_cleanup.remove_private_constructors=true 45 | sp_cleanup.remove_redundant_modifiers=false 46 | sp_cleanup.remove_redundant_semicolons=false 47 | sp_cleanup.remove_redundant_type_arguments=true 48 | sp_cleanup.remove_trailing_whitespaces=true 49 | sp_cleanup.remove_trailing_whitespaces_all=true 50 | sp_cleanup.remove_trailing_whitespaces_ignore_empty=false 51 | sp_cleanup.remove_unnecessary_array_creation=false 52 | sp_cleanup.remove_unnecessary_casts=true 53 | sp_cleanup.remove_unnecessary_nls_tags=true 54 | sp_cleanup.remove_unused_imports=true 55 | sp_cleanup.remove_unused_local_variables=false 56 | sp_cleanup.remove_unused_private_fields=true 57 | sp_cleanup.remove_unused_private_members=false 58 | sp_cleanup.remove_unused_private_methods=true 59 | sp_cleanup.remove_unused_private_types=true 60 | sp_cleanup.simplify_lambda_expression_and_method_ref=false 61 | sp_cleanup.sort_members=true 62 | sp_cleanup.sort_members_all=true 63 | sp_cleanup.use_anonymous_class_creation=false 64 | sp_cleanup.use_autoboxing=true 65 | sp_cleanup.use_blocks=false 66 | sp_cleanup.use_blocks_only_for_return_and_throw=false 67 | sp_cleanup.use_directly_map_method=false 68 | sp_cleanup.use_lambda=true 69 | sp_cleanup.use_parentheses_in_expressions=false 70 | sp_cleanup.use_this_for_non_static_field_access=false 71 | sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true 72 | sp_cleanup.use_this_for_non_static_method_access=false 73 | sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true 74 | sp_cleanup.use_unboxing=true 75 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/Slot.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | public abstract class Slot { 11 | private static class DoubleSlot extends Slot { 12 | public DoubleSlot(final double value) { 13 | super(value, true); 14 | } 15 | @Override 16 | protected Object toObject() { 17 | return value; 18 | } 19 | @Override 20 | public String toString() { 21 | return String.valueOf(value); 22 | } 23 | } 24 | public static class Empty extends Slot { 25 | protected Empty() { 26 | super(null, false); 27 | } 28 | @Override 29 | public String toString() { 30 | return "---"; 31 | } 32 | } 33 | private static class FloatSlot extends Slot { 34 | public FloatSlot(final float value) { 35 | super(value, false); 36 | } 37 | @Override 38 | public String toString() { 39 | return String.valueOf(value); 40 | } 41 | } 42 | private static class IntSlot extends Slot { 43 | public IntSlot(final int value) { 44 | super(value, false); 45 | } 46 | @Override 47 | public String toString() { 48 | return String.valueOf(value); 49 | } 50 | } 51 | private static class LongSlot extends Slot { 52 | public LongSlot(final long value) { 53 | super(value, true); 54 | } 55 | @Override 56 | public String toString() { 57 | return String.valueOf(value); 58 | } 59 | } 60 | private static class ReferenceSlot extends Slot { 61 | public ReferenceSlot(final Object value) { 62 | super(value, false); 63 | } 64 | @Override 65 | public String toString() { 66 | return String.valueOf(value); 67 | } 68 | } 69 | private static final Slot EMPTY = new Empty(); 70 | public static Slot empty() { 71 | return EMPTY; 72 | } 73 | public static Slot of(final boolean b) { 74 | return new IntSlot(b ? 1 : 0); 75 | } 76 | public static Slot of(final double d) { 77 | return new DoubleSlot(d); 78 | } 79 | public static Slot of(final float f) { 80 | return new FloatSlot(f); 81 | } 82 | public static Slot of(final int i) { 83 | return new IntSlot(i); 84 | } 85 | public static Slot of(final long l) { 86 | return new LongSlot(l); 87 | } 88 | public static Slot of(final Object object) { 89 | if (object instanceof Slot) { 90 | throw new IllegalStateException("Attempted to wrap slot in slot"); 91 | } 92 | return new ReferenceSlot(object); 93 | } 94 | protected final Object value; 95 | private final boolean wide; 96 | protected Slot(final Object value, final boolean wide) { 97 | this.value = value; 98 | this.wide = wide; 99 | } 100 | public boolean booleanValue() { 101 | return 0 != (int) ((IntSlot) this).value; 102 | } 103 | public double doubleValue() { 104 | return (double) ((DoubleSlot) this).value; 105 | } 106 | public float floatValue() { 107 | return (float) ((FloatSlot) this).value; 108 | } 109 | public int intValue() { 110 | return (int) ((IntSlot) this).value; 111 | } 112 | public final boolean isWide() { 113 | return wide; 114 | } 115 | public long longValue() { 116 | return (long) ((LongSlot) this).value; 117 | } 118 | public Object referenceValue() { 119 | // The cast to reference slot ensures this is a reference 120 | // The code could be changed to 'return this.value' but then 121 | // the code wouldn't correctly check that it is a ReferenceSlot type 122 | return ((ReferenceSlot) this).value; 123 | } 124 | protected Object toObject() { 125 | return value; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | com.bandlem.jvm 7 | jvmulator 8 | 1.0-SNAPSHOT 9 | jar 10 | jvm-emulator 11 | 12 | UTF-8 13 | 11 14 | 11 15 | com.bandlem.jvm.jvmulator.ui.GUI 16 | 17 | 18 | Bandlem Limited 19 | 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-api 24 | 5.6.2 25 | test 26 | 27 | 28 | org.junit.jupiter 29 | junit-jupiter-engine 30 | 5.6.2 31 | test 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-surefire-plugin 39 | 2.22.2 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-jar-plugin 44 | 2.4 45 | 46 | 47 | 48 | ${exec.mainClass} 49 | 50 | 51 | 52 | 53 | 54 | org.jacoco 55 | jacoco-maven-plugin 56 | 0.8.5 57 | 58 | 59 | default-prepare-agent 60 | 61 | prepare-agent 62 | 63 | 64 | 65 | default-report 66 | 67 | report 68 | 69 | 70 | 71 | default-check 72 | 73 | check 74 | 75 | 76 | 77 | **/ui/** 78 | 79 | 80 | 81 | BUNDLE 82 | 83 | 84 | LINE 85 | COVEREDRATIO 86 | 1.00 87 | 88 | 89 | BRANCH 90 | COVEREDRATIO 91 | 1.00 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-enforcer-plugin 103 | 3.0.0-M3 104 | 105 | 106 | enforce-maven 107 | 108 | enforce 109 | 110 | 111 | 112 | 113 | 3.3.9 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/StackTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertFalse; 13 | import static org.junit.jupiter.api.Assertions.assertNull; 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.function.Executable; 18 | public class StackTest { 19 | private Stack stack; 20 | @BeforeEach 21 | void newStack() { 22 | stack = new Stack(); 23 | } 24 | @Test 25 | void testBadStack() { 26 | stack.push(0); 27 | assertEquals(0, stack.at(0).intValue()); 28 | assertEquals(1, stack.size()); 29 | stack.pushSlot(Slot.empty()); 30 | assertThrows(IllegalStateException.class, stack::peek); 31 | assertThrows(IllegalStateException.class, stack::pop); 32 | } 33 | @Test 34 | void testDoublePushPeekPop() { 35 | stack.push(8.0D); 36 | final Slot s = stack.peek(); 37 | assertFalse(s == Slot.empty()); 38 | assertEquals(8.0D, s.doubleValue()); 39 | } 40 | @Test 41 | void testIncompatiblePop() { 42 | final Slot[] slots = new Slot[] { 43 | Slot.of(1), Slot.of(2f), Slot.of(3L), Slot.of(4d) 44 | }; 45 | final Executable[] ops = new Executable[] { 46 | stack::popInt, stack::popFloat, stack::popLong, stack::popDouble 47 | }; 48 | for (final Slot s : slots) { 49 | stack.pushSlot(s); 50 | final Slot same = stack.pop(); 51 | assertEquals(s, same); 52 | } 53 | for (int s = 0; s < 4; s++) { 54 | for (int o = 0; o < 4; o++) { 55 | stack.pushSlot(slots[s]); 56 | final Executable op = ops[o]; 57 | if (s == o) { 58 | assertDoesNotThrow(op); 59 | } else { 60 | assertThrows(ClassCastException.class, op); 61 | } 62 | } 63 | } 64 | } 65 | @Test 66 | void testPeek() { 67 | stack.push(2.0D); 68 | assertEquals(2.0D, stack.peek().doubleValue()); 69 | assertEquals(2.0D, stack.pop().doubleValue()); 70 | stack.push(2L); 71 | assertEquals(2L, stack.peek().longValue()); 72 | assertEquals(2L, stack.pop().longValue()); 73 | stack.push(2F); 74 | assertEquals(2F, stack.peek().floatValue()); 75 | assertEquals(2F, stack.pop().floatValue()); 76 | stack.push(2); 77 | assertEquals(2, stack.peek().intValue()); 78 | assertEquals(2, stack.pop().intValue()); 79 | } 80 | @Test 81 | void testPushNull() { 82 | stack.push(null); 83 | assertNull(stack.popReference()); 84 | assertThrows(IllegalArgumentException.class, () -> stack.pushSlot(null)); 85 | } 86 | @Test 87 | void testReference() { 88 | stack.push("Hello World"); 89 | stack.push(null); 90 | assertNull(stack.popReference()); 91 | assertEquals("Hello World", stack.popReference()); 92 | } 93 | @Test 94 | void testStackPopEmpty() { 95 | assertThrows(IndexOutOfBoundsException.class, stack::pop); 96 | } 97 | @Test 98 | void testStackPushBoolean() { 99 | stack.push(true); 100 | assertEquals(true, stack.pop().booleanValue()); 101 | stack.push(false); 102 | assertEquals(false, stack.pop().booleanValue()); 103 | } 104 | @Test 105 | void testStackPushDouble() { 106 | stack.push(2.0d); 107 | assertEquals(2.0d, stack.pop().doubleValue()); 108 | } 109 | @Test 110 | void testStackPushFloat() { 111 | stack.push(2.0f); 112 | assertEquals(2.0f, stack.pop().floatValue()); 113 | } 114 | @Test 115 | void testStackPushInt() { 116 | stack.push(1); 117 | assertEquals(1, stack.pop().intValue()); 118 | } 119 | @Test 120 | void testStackPushLong() { 121 | stack.push(3L); 122 | assertEquals(3L, stack.pop().longValue()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/SlotTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | import static org.junit.jupiter.api.Assertions.assertNull; 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | import org.junit.jupiter.api.Test; 16 | public class SlotTest { 17 | private static final Slot booleanFalseSlot = Slot.of(false); 18 | private static final Slot booleanTrueSlot = Slot.of(true); 19 | private static final Slot doubleSlot = Slot.of(4.0d); 20 | private static final Slot empty = Slot.empty(); 21 | private static final Slot floatSlot = Slot.of(2.0f); 22 | private static final Slot intSlot = Slot.of(1); 23 | private static final Slot longSlot = Slot.of(3L); 24 | static void wrapSlotInSlot() { 25 | Slot.of(Slot.of("Hello World")); 26 | } 27 | @Test 28 | void testBooleanFalseSlot() { 29 | final Slot slot = booleanFalseSlot; 30 | assertEquals(0, slot.intValue()); 31 | assertEquals(false, slot.booleanValue()); 32 | assertEquals(0, slot.toObject()); 33 | assertEquals("0", slot.toString()); 34 | assertFalse(slot.isWide()); 35 | assertThrows(ClassCastException.class, slot::longValue); 36 | assertThrows(ClassCastException.class, slot::floatValue); 37 | assertThrows(ClassCastException.class, slot::doubleValue); 38 | } 39 | @Test 40 | void testBooleanTrueSlot() { 41 | final Slot slot = booleanTrueSlot; 42 | assertEquals(1, slot.intValue()); 43 | assertEquals(true, slot.booleanValue()); 44 | assertEquals(1, slot.toObject()); 45 | assertEquals("1", slot.toString()); 46 | assertFalse(slot.isWide()); 47 | assertThrows(ClassCastException.class, slot::longValue); 48 | assertThrows(ClassCastException.class, slot::floatValue); 49 | assertThrows(ClassCastException.class, slot::doubleValue); 50 | } 51 | @Test 52 | void testDoubleSlot() { 53 | final Slot slot = doubleSlot; 54 | assertEquals(4, slot.doubleValue()); 55 | assertEquals(4.0D, slot.toObject()); 56 | assertEquals("4.0", slot.toString()); 57 | assertTrue(slot.isWide()); 58 | assertThrows(ClassCastException.class, slot::intValue); 59 | assertThrows(ClassCastException.class, slot::longValue); 60 | assertThrows(ClassCastException.class, slot::floatValue); 61 | } 62 | @Test 63 | void testEmptySlot() { 64 | final Slot slot = empty; 65 | assertEquals("---", slot.toString()); 66 | assertEquals(null, slot.toObject()); 67 | assertFalse(slot.isWide()); 68 | assertThrows(ClassCastException.class, slot::intValue); 69 | assertThrows(ClassCastException.class, slot::longValue); 70 | assertThrows(ClassCastException.class, slot::floatValue); 71 | assertThrows(ClassCastException.class, slot::doubleValue); 72 | } 73 | @Test 74 | void testFloatSlot() { 75 | final Slot slot = floatSlot; 76 | assertEquals(2F, slot.floatValue()); 77 | assertEquals(2.0F, slot.toObject()); 78 | assertEquals("2.0", slot.toString()); 79 | assertFalse(slot.isWide()); 80 | assertThrows(ClassCastException.class, slot::intValue); 81 | assertThrows(ClassCastException.class, slot::longValue); 82 | assertThrows(ClassCastException.class, slot::doubleValue); 83 | } 84 | @Test 85 | void testIntSlot() { 86 | final Slot slot = intSlot; 87 | assertEquals(1, slot.intValue()); 88 | assertEquals(true, slot.booleanValue()); 89 | assertEquals(1, slot.toObject()); 90 | assertEquals("1", slot.toString()); 91 | assertFalse(slot.isWide()); 92 | assertThrows(ClassCastException.class, slot::longValue); 93 | assertThrows(ClassCastException.class, slot::floatValue); 94 | assertThrows(ClassCastException.class, slot::doubleValue); 95 | } 96 | @Test 97 | void testLongSlot() { 98 | final Slot slot = longSlot; 99 | assertEquals(3L, slot.longValue()); 100 | assertEquals(3L, slot.toObject()); 101 | assertEquals("3", slot.toString()); 102 | assertTrue(slot.isWide()); 103 | assertThrows(ClassCastException.class, slot::intValue); 104 | assertThrows(ClassCastException.class, slot::floatValue); 105 | assertThrows(ClassCastException.class, slot::doubleValue); 106 | } 107 | @Test 108 | void testReferenceSlot() { 109 | final Slot slot = Slot.of("Hello World"); 110 | assertEquals("Hello World", slot.referenceValue()); 111 | assertEquals("Hello World", slot.toString()); 112 | assertEquals("Hello World", slot.toObject()); 113 | assertNull(Slot.of(null).referenceValue()); 114 | assertThrows(IllegalStateException.class, SlotTest::wrapSlotInSlot); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/GUI.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.Font; 3 | import java.awt.GridBagConstraints; 4 | import java.awt.GridBagLayout; 5 | import java.awt.Insets; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Modifier; 8 | import javax.swing.Action; 9 | import javax.swing.BorderFactory; 10 | import javax.swing.JButton; 11 | import javax.swing.JComboBox; 12 | import javax.swing.JFrame; 13 | import javax.swing.JOptionPane; 14 | import javax.swing.JPanel; 15 | import javax.swing.JTextArea; 16 | public class GUI extends JPanel { 17 | private static final long serialVersionUID = 1L; 18 | static GridBagConstraints constraints(final int x, final int y) { 19 | return new GridBagConstraints(x, y, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, 20 | new Insets(0, 0, 0, 0), 0, 0); 21 | } 22 | public static void main(final String[] args) { 23 | final JFrame frame = new JFrame("JVMulator"); 24 | final GUI gui = new GUI(); 25 | frame.add(gui); 26 | frame.setSize(gui.getMinimumSize()); 27 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 28 | frame.setVisible(true); 29 | } 30 | private byte[] classBytes; 31 | private final Action compile = new CompileAction(this); 32 | private final JTextArea console = new JTextArea("", 100, 100); 33 | private final Action emulate = new EmulateAction(this); 34 | private final Action invoke = new InvokeAction(this); 35 | private final JComboBox methods = new JComboBox<>(); 36 | private final OutTextArea out = new OutTextArea(console, System.out); 37 | private final JTextArea source = new JTextArea(getExample(), 100, 100); 38 | public GUI() { 39 | final Font monospaced = new Font(Font.MONOSPACED, Font.PLAIN, 24); 40 | if (monospaced != null) { 41 | source.setFont(monospaced); 42 | console.setFont(monospaced); 43 | } 44 | source.setBorder(BorderFactory.createTitledBorder("Source")); 45 | console.setBorder(BorderFactory.createTitledBorder("Console")); 46 | setLayout(new GridBagLayout()); 47 | add(source, constraints(0, 0)); 48 | add(console, constraints(1, 0)); 49 | add(new JButton(compile), constraints(0, 1)); 50 | add(new JButton(invoke), constraints(1, 1)); 51 | add(methods, constraints(0, 2)); 52 | add(new JButton(emulate), constraints(1, 2)); 53 | System.setOut(out); 54 | } 55 | public void addMethod(final Method method) { 56 | if (method.getDeclaringClass() != Object.class && (method.getModifiers() & Modifier.STATIC) != 0) { 57 | methods.addItem(method); 58 | enableButtons(true); 59 | } 60 | } 61 | public void clearConsole() { 62 | console.setText(""); 63 | } 64 | public void clearMethods() { 65 | methods.removeAllItems(); 66 | enableButtons(false); 67 | } 68 | private void enableButtons(final boolean enabled) { 69 | invoke.setEnabled(enabled); 70 | methods.setEnabled(enabled); 71 | emulate.setEnabled(enabled); 72 | } 73 | public byte[] getClassBytes() { 74 | return classBytes; 75 | } 76 | private String getExample() { 77 | return "public class Example {\n" // 78 | + " public static void gc() { System.gc(); }\n" // 79 | + " public static void sayHello() {\n" // 80 | + " System.out.println(\"Hello World\");\n" // 81 | + " }\n" // 82 | + " public static int add(int a, int b) {\n" // 83 | + " return a + b;\n" // 84 | + " }\n" // 85 | + "}\n"; 86 | } 87 | public Method getSelectedMethod() { 88 | return (Method) methods.getSelectedItem(); 89 | } 90 | public String getSource() { 91 | return source.getText(); 92 | } 93 | private Object[] getValues(final String methodName, final Class[] types) { 94 | final Object[] values = new Object[types.length]; 95 | for (int i = 0; i < values.length; i++) { 96 | final String answer = JOptionPane.showInputDialog(null, 97 | "Argument " + i + " (" + types[i].getSimpleName() + ")", methodName, JOptionPane.QUESTION_MESSAGE); 98 | values[i] = toObject(types[i], answer); 99 | } 100 | return values; 101 | } 102 | public Object[] promptForArguments() { 103 | final Method method = getSelectedMethod(); 104 | final Class[] types = method.getParameterTypes(); 105 | return getValues(method.getName(), types); 106 | } 107 | public void setClassBytes(final byte[] bytes) { 108 | this.classBytes = bytes; 109 | } 110 | private Object toObject(final Class type, final String value) { 111 | if (type == Integer.TYPE || type == Integer.class // 112 | || type == Short.TYPE || type == Short.class // 113 | || type == Byte.TYPE || type == Byte.class // 114 | || type == Character.TYPE || type == Character.class) { 115 | return Integer.valueOf(value); 116 | } else if (type == Boolean.TYPE || type == Boolean.class) { 117 | return Boolean.valueOf(value); 118 | } else if (type == Long.TYPE || type == Long.class) { 119 | return Long.valueOf(value); 120 | } else if (type == Float.TYPE || type == Float.class) { 121 | return Float.valueOf(value); 122 | } else if (type == Double.TYPE || type == Double.class) { 123 | return Double.valueOf(value); 124 | } else { 125 | return value; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/ui/JVMulator.java: -------------------------------------------------------------------------------- 1 | package com.bandlem.jvm.jvmulator.ui; 2 | import java.awt.Dimension; 3 | import java.awt.Font; 4 | import java.awt.GridBagLayout; 5 | import javax.swing.BorderFactory; 6 | import javax.swing.JButton; 7 | import javax.swing.JOptionPane; 8 | import javax.swing.JPanel; 9 | import javax.swing.JTextArea; 10 | import com.bandlem.jvm.jvmulator.JVMFrame; 11 | import com.bandlem.jvm.jvmulator.Opcodes; 12 | import com.bandlem.jvm.jvmulator.Slot; 13 | import com.bandlem.jvm.jvmulator.Stack; 14 | import com.bandlem.jvm.jvmulator.classfile.Attribute.Code; 15 | import com.bandlem.jvm.jvmulator.classfile.JavaClass; 16 | import com.bandlem.jvm.jvmulator.classfile.Member.Method; 17 | public class JVMulator extends JPanel { 18 | private static final long serialVersionUID = 1L; 19 | private final JTextArea bytecode = new JTextArea("", 100, 100); 20 | private byte[] code; 21 | private JVMFrame frame; 22 | private final JavaClass javaClass; 23 | private final JTextArea locals = new JTextArea("", 100, 100); 24 | private int pc; 25 | private final JTextArea stack = new JTextArea("", 100, 100); 26 | public JVMulator(final JavaClass javaClass) { 27 | this.javaClass = javaClass; 28 | final Font monospaced = new Font(Font.MONOSPACED, Font.PLAIN, 24); 29 | if (monospaced != null) { 30 | bytecode.setFont(monospaced); 31 | locals.setFont(monospaced); 32 | stack.setFont(monospaced); 33 | } 34 | bytecode.setEditable(false); 35 | locals.setEditable(false); 36 | stack.setEditable(false); 37 | bytecode.setBorder(BorderFactory.createTitledBorder("Bytecode")); 38 | locals.setBorder(BorderFactory.createTitledBorder("Locals")); 39 | stack.setBorder(BorderFactory.createTitledBorder("Stack")); 40 | setLayout(new GridBagLayout()); 41 | add(bytecode, GUI.constraints(0, 0)); 42 | add(locals, GUI.constraints(1, 0)); 43 | add(stack, GUI.constraints(2, 0)); 44 | final Dimension minimumSize = new Dimension(400, 400); 45 | bytecode.setMinimumSize(minimumSize); 46 | locals.setMinimumSize(minimumSize); 47 | stack.setMinimumSize(minimumSize); 48 | add(new JButton(new StepAction(this)), GUI.constraints(0, 1)); 49 | } 50 | private void displayCode() { 51 | final StringBuilder builder = new StringBuilder(code.length * 10); 52 | int i = 0; 53 | while (i < code.length) { 54 | final byte opcode = code[i]; 55 | builder.append(String.format("%3s%3d: %2x %s\n", pc == i ? "=>" : "", i, opcode, Opcodes.name(opcode))); 56 | i++; 57 | final int operands = Opcodes.operands(opcode); 58 | if (operands < 0) { 59 | throw new UnsupportedOperationException("Unknown opcode " + opcode); 60 | } else { 61 | i += operands; 62 | } 63 | } 64 | bytecode.setText(builder.toString()); 65 | } 66 | private void displayLocals() { 67 | final Slot[] l = frame.getLocals(); 68 | final StringBuilder builder = new StringBuilder(l.length * 10); 69 | for (int s = 0; s < l.length; s++) { 70 | builder.append(String.format("[%02d] %s\n", s, l[s].toString())); 71 | } 72 | locals.setText(builder.toString()); 73 | } 74 | private void displayStack() { 75 | final Stack s = frame.getStack(); 76 | final StringBuilder builder = new StringBuilder(s.size() * 10); 77 | for (int ss = 0; ss < s.size(); ss++) { 78 | builder.append(String.format("[%02d] %s\n", ss, s.at(ss).toString())); 79 | } 80 | stack.setText(builder.toString()); 81 | } 82 | private String[] getValues(final String methodName, final Class[] types) { 83 | final String[] values = new String[types.length]; 84 | for (int i = 0; i < values.length; i++) { 85 | final String answer = JOptionPane.showInputDialog(null, 86 | "Argument " + i + " (" + types[i].getSimpleName() + ")", methodName, JOptionPane.QUESTION_MESSAGE); 87 | values[i] = answer; 88 | } 89 | return values; 90 | } 91 | @Override 92 | public void setName(final String name) { 93 | final Method method = javaClass.getMethod(name); 94 | if (method == null) { 95 | code = new byte[0]; 96 | } else { 97 | final Code codeAttribute = method.getCodeAttribute(); 98 | code = codeAttribute.getBytecode(); 99 | frame = new JVMFrame(javaClass, codeAttribute.getMaxLocals(), code); 100 | getArguments(name, method); 101 | displayCode(); 102 | displayLocals(); 103 | displayStack(); 104 | } 105 | } 106 | private void getArguments(final String name, final Method method) { 107 | final Class[] types = Method.argumentTypes(method.descriptor, frame.getClass().getClassLoader()); 108 | final String[] values = getValues(name, types); 109 | final Slot[] l = frame.getLocals(); 110 | for (int i = 0; i < l.length; i++) { 111 | l[l.length - i - 1] = toSlot(types[i], values[i]); 112 | } 113 | } 114 | public void step() { 115 | if (pc >= 0 && frame.step()) { 116 | pc = frame.getPC(); 117 | displayCode(); 118 | displayLocals(); 119 | displayStack(); 120 | } else { 121 | pc = -1; 122 | JOptionPane.showMessageDialog(null, "Return value: " + frame.getReturnValue(), "Returned", 123 | JOptionPane.INFORMATION_MESSAGE); 124 | } 125 | } 126 | private Slot toSlot(final Class type, final String value) { 127 | if (type == Integer.TYPE || type == Short.TYPE || type == Byte.TYPE || type == Character.TYPE) { 128 | return Slot.of(Integer.parseInt(value)); 129 | } else if (type == Boolean.TYPE) { 130 | return Slot.of(Boolean.parseBoolean(value)); 131 | } else if (type == Long.TYPE) { 132 | return Slot.of(Long.parseLong(value)); 133 | } else if (type == Float.TYPE) { 134 | return Slot.of(Float.parseFloat(value)); 135 | } else if (type == Double.TYPE) { 136 | return Slot.of(Double.parseDouble(value)); 137 | } else { 138 | return Slot.of(value); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/classfile/ConstantPoolTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.DataInput; 14 | import java.io.DataInputStream; 15 | import java.io.IOException; 16 | import org.junit.jupiter.api.Test; 17 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.ClassConstant; 18 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.DoubleConstant; 19 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.FieldRef; 20 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.FloatConstant; 21 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.IntConstant; 22 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.InterfaceMethodRef; 23 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.InvokeDynamic; 24 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.Item; 25 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.LongConstant; 26 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.MethodHandle; 27 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.MethodRef; 28 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.MethodType; 29 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.Module; 30 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.NameAndType; 31 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.Package; 32 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.StringConstant; 33 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.UTFConstant; 34 | public class ConstantPoolTest { 35 | Item item(final int... data) throws IOException { 36 | final byte[] bytes = new byte[data.length]; 37 | for (int i = 0; i < data.length; i++) { 38 | bytes[i] = (byte) data[i]; 39 | } 40 | final ConstantPool pool = new ConstantPool((short) 2, with(bytes)); 41 | return pool.getItem(1); 42 | } 43 | @Test 44 | void testItems() throws IOException { 45 | final UTFConstant utfItem = (UTFConstant) item(0x01, 0x00, 0x06, 0x61, 0x6c, 0x62, 0x6c, 0x75, 0x65); 46 | assertEquals(1, utfItem.type); 47 | assertEquals("alblue", utfItem.value); 48 | assertEquals("alblue", utfItem.stringValue()); 49 | final IntConstant intItem = (IntConstant) item(0x03, 0x00, 0x00, 0x00, 0x01); 50 | assertEquals(3, intItem.type); 51 | assertEquals(1, intItem.value); 52 | final FloatConstant floatItem = (FloatConstant) item(0x04, 0x7f, 0x80, 0x00, 0x00); 53 | assertEquals(4, floatItem.type); 54 | assertEquals(Float.POSITIVE_INFINITY, floatItem.value); 55 | final LongConstant longItem = (LongConstant) item(0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02); 56 | assertEquals(5, longItem.type); 57 | assertEquals(2, longItem.value); 58 | final DoubleConstant doubleItem = (DoubleConstant) item(0x06, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 59 | assertEquals(6, doubleItem.type); 60 | assertEquals(Double.NEGATIVE_INFINITY, doubleItem.value); 61 | final ClassConstant classItem = (ClassConstant) item(0x07, 0x01, 0x04); 62 | assertEquals(7, classItem.type); 63 | assertEquals(0x104, classItem.index); 64 | final StringConstant stringItem = (StringConstant) item(0x08, 0x03, 0x04); 65 | assertEquals(8, stringItem.type); 66 | assertEquals(0x304, stringItem.index); 67 | final FieldRef fieldRefItem = (FieldRef) item(0x09, 0x01, 0x12, 0x03, 0x14); 68 | assertEquals(9, fieldRefItem.type); 69 | assertEquals(0x112, fieldRefItem.classIndex); 70 | assertEquals(0x314, fieldRefItem.nameAndTypeIndex); 71 | final MethodRef methodRefItem = (MethodRef) item(0x0a, 0x01, 0x22, 0x03, 0x24); 72 | assertEquals(10, methodRefItem.type); 73 | assertEquals(0x122, methodRefItem.classIndex); 74 | assertEquals(0x324, methodRefItem.nameAndTypeIndex); 75 | final InterfaceMethodRef interfaceMethodRefItem = (InterfaceMethodRef) item(0x0b, 0x01, 0x22, 0x03, 0x24); 76 | assertEquals(11, interfaceMethodRefItem.type); 77 | assertEquals(0x122, interfaceMethodRefItem.classIndex); 78 | assertEquals(0x324, interfaceMethodRefItem.nameAndTypeIndex); 79 | final NameAndType natItem = (NameAndType) item(0x0c, 0x01, 0x02, 0x03, 0x04); 80 | assertEquals(12, natItem.type); 81 | assertEquals(0x102, natItem.nameIndex); 82 | assertEquals(0x304, natItem.descriptorIndex); 83 | final MethodHandle methodHandleItem = (MethodHandle) item(0x0f, 0x03, 0x05, 0x04); 84 | assertEquals(15, methodHandleItem.type); 85 | assertEquals(0x3, methodHandleItem.referenceKind); 86 | assertEquals(0x504, methodHandleItem.referenceIndex); 87 | final MethodType methodTypeItem = (MethodType) item(0x10, 0x05, 0x04); 88 | assertEquals(16, methodTypeItem.type); 89 | assertEquals(0x504, methodTypeItem.descriptorIndex); 90 | final InvokeDynamic invokeDynamicItem = (InvokeDynamic) item(0x12, 0x02, 0x22, 0x04, 0x24); 91 | assertEquals(18, invokeDynamicItem.type); 92 | assertEquals(0x222, invokeDynamicItem.bootstrapIndex); 93 | assertEquals(0x424, invokeDynamicItem.nameAndTypeIndex); 94 | assertThrows(IllegalArgumentException.class, invokeDynamicItem::stringValue); 95 | assertThrows(IllegalArgumentException.class, () -> item(0x2)); 96 | final Module moduleItem = (Module) item(0x13, 0x04, 0x02); 97 | assertEquals(19, moduleItem.type); 98 | assertEquals(0x402, moduleItem.nameIndex); 99 | final Package packageItem = (Package) item(0x14, 0x07, 0x47); 100 | assertEquals(20, packageItem.type); 101 | assertEquals(0x747, packageItem.nameIndex); 102 | } 103 | @Test 104 | void testPool() throws IOException { 105 | final ConstantPool empty = new ConstantPool((short) 1, with(new byte[] {})); 106 | assertThrows(IllegalArgumentException.class, () -> empty.getItem(0)); 107 | final ConstantPool single = new ConstantPool((short) 3, with(new byte[] { 108 | 0x01, 0x00, 0x06, 0x61, 0x6c, 0x62, 0x6c, 0x75, 0x65, // UTF-8 item 109 | 0x07, 0x00, 0x01 // Class item 110 | })); 111 | assertEquals("alblue", single.getString(1)); 112 | assertEquals("alblue", single.getClassName(2)); 113 | assertEquals(1, empty.size()); 114 | assertEquals(3, single.size()); 115 | } 116 | DataInput with(final byte... data) throws IOException { 117 | return new DataInputStream(new ByteArrayInputStream(data)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/classfile/ConstantPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator.classfile; 10 | import java.io.DataInput; 11 | import java.io.IOException; 12 | public class ConstantPool { 13 | public static class ClassConstant extends Item { 14 | public static final int TYPE = 7; 15 | public final short index; 16 | ClassConstant(final short index) { 17 | super(TYPE); 18 | this.index = index; 19 | } 20 | } 21 | public static class DoubleConstant extends Item { 22 | public static final int TYPE = 6; 23 | public final double value; 24 | DoubleConstant(final double value) { 25 | super(TYPE); 26 | this.value = value; 27 | } 28 | @Override 29 | public boolean isWide() { 30 | return true; 31 | } 32 | } 33 | public static class FieldRef extends Item { 34 | public static final int TYPE = 9; 35 | public final short classIndex; 36 | public final short nameAndTypeIndex; 37 | FieldRef(final short classIndex, final short nameAndTypeIndex) { 38 | super(TYPE); 39 | this.classIndex = classIndex; 40 | this.nameAndTypeIndex = nameAndTypeIndex; 41 | } 42 | } 43 | public static class FloatConstant extends Item { 44 | public static final int TYPE = 4; 45 | public final float value; 46 | FloatConstant(final float value) { 47 | super(TYPE); 48 | this.value = value; 49 | } 50 | } 51 | public static class IntConstant extends Item { 52 | public static final int TYPE = 3; 53 | public final int value; 54 | IntConstant(final int value) { 55 | super(TYPE); 56 | this.value = value; 57 | } 58 | } 59 | public static class InterfaceMethodRef extends Item { 60 | public static final int TYPE = 11; 61 | public final short classIndex; 62 | public final short nameAndTypeIndex; 63 | InterfaceMethodRef(final short classIndex, final short nameAndTypeIndex) { 64 | super(TYPE); 65 | this.classIndex = classIndex; 66 | this.nameAndTypeIndex = nameAndTypeIndex; 67 | } 68 | } 69 | public static class InvokeDynamic extends Item { 70 | public static final int TYPE = 18; 71 | public final short bootstrapIndex; 72 | public final short nameAndTypeIndex; 73 | InvokeDynamic(final short bootstrapIndex, final short nameAndTypeIndex) { 74 | super(TYPE); 75 | this.bootstrapIndex = bootstrapIndex; 76 | this.nameAndTypeIndex = nameAndTypeIndex; 77 | } 78 | } 79 | public static class Item { 80 | public static Item read(final DataInput di) throws IOException { 81 | final byte type = di.readByte(); 82 | switch (type) { 83 | case UTFConstant.TYPE: // 1 84 | return new UTFConstant(di.readUTF()); 85 | case IntConstant.TYPE: // 3 86 | return new IntConstant(di.readInt()); 87 | case FloatConstant.TYPE: // 4 88 | return new FloatConstant(di.readFloat()); 89 | case LongConstant.TYPE: // 5 90 | return new LongConstant(di.readLong()); 91 | case DoubleConstant.TYPE: // 6 92 | return new DoubleConstant(di.readDouble()); 93 | case ClassConstant.TYPE: // 7 94 | return new ClassConstant(di.readShort()); 95 | case StringConstant.TYPE: // 8 96 | return new StringConstant(di.readShort()); 97 | case FieldRef.TYPE: // 9 98 | return new FieldRef(di.readShort(), di.readShort()); 99 | case MethodRef.TYPE: // 10 100 | return new MethodRef(di.readShort(), di.readShort()); 101 | case InterfaceMethodRef.TYPE: // 11 102 | return new InterfaceMethodRef(di.readShort(), di.readShort()); 103 | case NameAndType.TYPE: // 12 104 | return new NameAndType(di.readShort(), di.readShort()); 105 | case MethodHandle.TYPE: // 15 106 | return new MethodHandle(di.readByte(), di.readShort()); 107 | case MethodType.TYPE: // 16 108 | return new MethodType(di.readShort()); 109 | case InvokeDynamic.TYPE: // 18 110 | return new InvokeDynamic(di.readShort(), di.readShort()); 111 | case Module.TYPE: // 19 112 | return new Module(di.readShort()); 113 | case Package.TYPE: // 20 114 | return new Package(di.readShort()); 115 | default: 116 | throw new IllegalArgumentException("Unknown type " + type); 117 | } 118 | } 119 | public final int type; 120 | Item(final int type) { 121 | this.type = type; 122 | } 123 | public boolean isWide() { 124 | return false; 125 | } 126 | public String stringValue() { 127 | throw new IllegalArgumentException("Wrong type"); 128 | } 129 | } 130 | public static class LongConstant extends Item { 131 | public static final int TYPE = 5; 132 | public final long value; 133 | LongConstant(final long value) { 134 | super(TYPE); 135 | this.value = value; 136 | } 137 | @Override 138 | public boolean isWide() { 139 | return true; 140 | } 141 | } 142 | public static class MethodHandle extends Item { 143 | public static final int TYPE = 15; 144 | public final short referenceIndex; 145 | public final byte referenceKind; 146 | MethodHandle(final byte referenceKind, final short referenceIndex) { 147 | super(TYPE); 148 | this.referenceKind = referenceKind; 149 | this.referenceIndex = referenceIndex; 150 | } 151 | } 152 | public static class MethodRef extends Item { 153 | public static final int TYPE = 10; 154 | public final short classIndex; 155 | public final short nameAndTypeIndex; 156 | MethodRef(final short classIndex, final short nameAndTypeIndex) { 157 | super(TYPE); 158 | this.classIndex = classIndex; 159 | this.nameAndTypeIndex = nameAndTypeIndex; 160 | } 161 | } 162 | public static class MethodType extends Item { 163 | public static final int TYPE = 16; 164 | public final short descriptorIndex; 165 | MethodType(final short descriptorIndex) { 166 | super(TYPE); 167 | this.descriptorIndex = descriptorIndex; 168 | } 169 | } 170 | public static class Module extends Item { 171 | public static final int TYPE = 19; 172 | public final short nameIndex; 173 | Module(final short nameIndex) { 174 | super(TYPE); 175 | this.nameIndex = nameIndex; 176 | } 177 | } 178 | public static class NameAndType extends Item { 179 | public static final int TYPE = 12; 180 | public final short descriptorIndex; 181 | public final short nameIndex; 182 | NameAndType(final short nameIndex, final short descriptorIndex) { 183 | super(TYPE); 184 | this.nameIndex = nameIndex; 185 | this.descriptorIndex = descriptorIndex; 186 | } 187 | } 188 | public static class Package extends Item { 189 | public static final int TYPE = 20; 190 | public final short nameIndex; 191 | Package(final short nameIndex) { 192 | super(TYPE); 193 | this.nameIndex = nameIndex; 194 | } 195 | } 196 | public static class StringConstant extends Item { 197 | public static final int TYPE = 8; 198 | public final short index; 199 | StringConstant(final short index) { 200 | super(TYPE); 201 | this.index = index; 202 | } 203 | } 204 | public static class UTFConstant extends Item { 205 | public static final int TYPE = 1; 206 | public final String value; 207 | UTFConstant(final String value) { 208 | super(TYPE); 209 | this.value = value; 210 | } 211 | @Override 212 | public String stringValue() { 213 | return value; 214 | } 215 | } 216 | private final Item[] items; 217 | public ConstantPool(final short size, final DataInput di) throws IOException { 218 | items = new Item[size & 0xffff]; 219 | for (int i = 1; i < items.length; i++) { 220 | items[i] = Item.read(di); 221 | if (items[i].isWide()) { 222 | i++; 223 | } 224 | } 225 | } 226 | public String getClassName(final int index) { 227 | return getString(((ClassConstant) getItem(index)).index); 228 | } 229 | public Item getItem(final int index) { 230 | if (index == 0) { 231 | throw new IllegalArgumentException("Constant Pool is 1-indexed"); 232 | } 233 | return items[index & 0xfff]; 234 | } 235 | public String getString(final int index) { 236 | return getItem(index).stringValue(); 237 | } 238 | public int size() { 239 | return items.length; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/OpcodesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static com.bandlem.jvm.jvmulator.Opcodes.ALOAD; 11 | import static com.bandlem.jvm.jvmulator.Opcodes.ANEWARRAY; 12 | import static com.bandlem.jvm.jvmulator.Opcodes.ASTORE; 13 | import static com.bandlem.jvm.jvmulator.Opcodes.BIPUSH; 14 | import static com.bandlem.jvm.jvmulator.Opcodes.CHECKCAST; 15 | import static com.bandlem.jvm.jvmulator.Opcodes.DLOAD; 16 | import static com.bandlem.jvm.jvmulator.Opcodes.DSTORE; 17 | import static com.bandlem.jvm.jvmulator.Opcodes.FLOAD; 18 | import static com.bandlem.jvm.jvmulator.Opcodes.FSTORE; 19 | import static com.bandlem.jvm.jvmulator.Opcodes.GETFIELD; 20 | import static com.bandlem.jvm.jvmulator.Opcodes.GETSTATIC; 21 | import static com.bandlem.jvm.jvmulator.Opcodes.GOTO; 22 | import static com.bandlem.jvm.jvmulator.Opcodes.GOTO_W; 23 | import static com.bandlem.jvm.jvmulator.Opcodes.IFEQ; 24 | import static com.bandlem.jvm.jvmulator.Opcodes.IFGE; 25 | import static com.bandlem.jvm.jvmulator.Opcodes.IFGT; 26 | import static com.bandlem.jvm.jvmulator.Opcodes.IFLE; 27 | import static com.bandlem.jvm.jvmulator.Opcodes.IFLT; 28 | import static com.bandlem.jvm.jvmulator.Opcodes.IFNONNULL; 29 | import static com.bandlem.jvm.jvmulator.Opcodes.IFNULL; 30 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ACMPEQ; 31 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ACMPNE; 32 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPEQ; 33 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPGE; 34 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPGT; 35 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPLE; 36 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPLT; 37 | import static com.bandlem.jvm.jvmulator.Opcodes.IF_ICMPNE; 38 | import static com.bandlem.jvm.jvmulator.Opcodes.IINC; 39 | import static com.bandlem.jvm.jvmulator.Opcodes.ILOAD; 40 | import static com.bandlem.jvm.jvmulator.Opcodes.INSTANCEOF; 41 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKEDYNAMIC; 42 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKEINTERFACE; 43 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKESPECIAL; 44 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKESTATIC; 45 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKEVIRTUAL; 46 | import static com.bandlem.jvm.jvmulator.Opcodes.ISTORE; 47 | import static com.bandlem.jvm.jvmulator.Opcodes.JSR; 48 | import static com.bandlem.jvm.jvmulator.Opcodes.JSR_W; 49 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC; 50 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC2_W; 51 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC_W; 52 | import static com.bandlem.jvm.jvmulator.Opcodes.LLOAD; 53 | import static com.bandlem.jvm.jvmulator.Opcodes.LOOKUPSWITCH; 54 | import static com.bandlem.jvm.jvmulator.Opcodes.LSTORE; 55 | import static com.bandlem.jvm.jvmulator.Opcodes.MULTIANEWARRAY; 56 | import static com.bandlem.jvm.jvmulator.Opcodes.NEW; 57 | import static com.bandlem.jvm.jvmulator.Opcodes.NEWARRAY; 58 | import static com.bandlem.jvm.jvmulator.Opcodes.PUTFIELD; 59 | import static com.bandlem.jvm.jvmulator.Opcodes.PUTSTATIC; 60 | import static com.bandlem.jvm.jvmulator.Opcodes.RET; 61 | import static com.bandlem.jvm.jvmulator.Opcodes.SIPUSH; 62 | import static com.bandlem.jvm.jvmulator.Opcodes.TABLESWITCH; 63 | import static com.bandlem.jvm.jvmulator.Opcodes.WIDE; 64 | import static org.junit.jupiter.api.Assertions.assertEquals; 65 | import static org.junit.jupiter.api.Assertions.assertNull; 66 | import org.junit.jupiter.api.Test; 67 | class OpcodesTest { 68 | @Test 69 | void testOpcodeName() { 70 | assertEquals("nop", Opcodes.name((byte) 0x0)); 71 | assertEquals("aconst_null", Opcodes.name((byte) 0x1)); 72 | assertEquals("iconst_m1", Opcodes.name((byte) 0x2)); 73 | assertEquals("iconst_0", Opcodes.name((byte) 0x3)); 74 | assertEquals("iconst_1", Opcodes.name((byte) 0x4)); 75 | assertEquals("iconst_2", Opcodes.name((byte) 0x5)); 76 | assertEquals("iconst_3", Opcodes.name((byte) 0x6)); 77 | assertEquals("iconst_4", Opcodes.name((byte) 0x7)); 78 | assertEquals("iconst_5", Opcodes.name((byte) 0x8)); 79 | assertEquals("lconst_0", Opcodes.name((byte) 0x9)); 80 | assertEquals("lconst_1", Opcodes.name((byte) 0x0a)); 81 | assertEquals("fconst_0", Opcodes.name((byte) 0x0b)); 82 | assertEquals("fconst_1", Opcodes.name((byte) 0x0c)); 83 | assertEquals("fconst_2", Opcodes.name((byte) 0x0d)); 84 | assertEquals("dconst_0", Opcodes.name((byte) 0x0e)); 85 | assertEquals("dconst_1", Opcodes.name((byte) 0x0f)); 86 | assertEquals("bipush", Opcodes.name((byte) 0x10)); 87 | assertEquals("sipush", Opcodes.name((byte) 0x11)); 88 | assertEquals("ldc", Opcodes.name((byte) 0x12)); 89 | assertEquals("ldc_w", Opcodes.name((byte) 0x13)); 90 | assertEquals("ldc2_w", Opcodes.name((byte) 0x14)); 91 | assertEquals("iload", Opcodes.name((byte) 0x15)); 92 | assertEquals("lload", Opcodes.name((byte) 0x16)); 93 | assertEquals("fload", Opcodes.name((byte) 0x17)); 94 | assertEquals("dload", Opcodes.name((byte) 0x18)); 95 | assertEquals("aload", Opcodes.name((byte) 0x19)); 96 | assertEquals("iload_0", Opcodes.name((byte) 0x1a)); 97 | assertEquals("iload_1", Opcodes.name((byte) 0x1b)); 98 | assertEquals("iload_2", Opcodes.name((byte) 0x1c)); 99 | assertEquals("iload_3", Opcodes.name((byte) 0x1d)); 100 | assertEquals("lload_0", Opcodes.name((byte) 0x1e)); 101 | assertEquals("lload_1", Opcodes.name((byte) 0x1f)); 102 | assertEquals("lload_2", Opcodes.name((byte) 0x20)); 103 | assertEquals("lload_3", Opcodes.name((byte) 0x21)); 104 | assertEquals("fload_0", Opcodes.name((byte) 0x22)); 105 | assertEquals("fload_1", Opcodes.name((byte) 0x23)); 106 | assertEquals("fload_2", Opcodes.name((byte) 0x24)); 107 | assertEquals("fload_3", Opcodes.name((byte) 0x25)); 108 | assertEquals("dload_0", Opcodes.name((byte) 0x26)); 109 | assertEquals("dload_1", Opcodes.name((byte) 0x27)); 110 | assertEquals("dload_2", Opcodes.name((byte) 0x28)); 111 | assertEquals("dload_3", Opcodes.name((byte) 0x29)); 112 | assertEquals("aload_0", Opcodes.name((byte) 0x2a)); 113 | assertEquals("aload_1", Opcodes.name((byte) 0x2b)); 114 | assertEquals("aload_2", Opcodes.name((byte) 0x2c)); 115 | assertEquals("aload_3", Opcodes.name((byte) 0x2d)); 116 | assertEquals("iaload", Opcodes.name((byte) 0x2e)); 117 | assertEquals("laload", Opcodes.name((byte) 0x2f)); 118 | assertEquals("faload", Opcodes.name((byte) 0x30)); 119 | assertEquals("daload", Opcodes.name((byte) 0x31)); 120 | assertEquals("aaload", Opcodes.name((byte) 0x32)); 121 | assertEquals("baload", Opcodes.name((byte) 0x33)); 122 | assertEquals("caload", Opcodes.name((byte) 0x34)); 123 | assertEquals("saload", Opcodes.name((byte) 0x35)); 124 | assertEquals("istore", Opcodes.name((byte) 0x36)); 125 | assertEquals("lstore", Opcodes.name((byte) 0x37)); 126 | assertEquals("fstore", Opcodes.name((byte) 0x38)); 127 | assertEquals("dstore", Opcodes.name((byte) 0x39)); 128 | assertEquals("astore", Opcodes.name((byte) 0x3a)); 129 | assertEquals("istore_0", Opcodes.name((byte) 0x3b)); 130 | assertEquals("istore_1", Opcodes.name((byte) 0x3c)); 131 | assertEquals("istore_2", Opcodes.name((byte) 0x3d)); 132 | assertEquals("istore_3", Opcodes.name((byte) 0x3e)); 133 | assertEquals("lstore_0", Opcodes.name((byte) 0x3f)); 134 | assertEquals("lstore_1", Opcodes.name((byte) 0x40)); 135 | assertEquals("lstore_2", Opcodes.name((byte) 0x41)); 136 | assertEquals("lstore_3", Opcodes.name((byte) 0x42)); 137 | assertEquals("fstore_0", Opcodes.name((byte) 0x43)); 138 | assertEquals("fstore_1", Opcodes.name((byte) 0x44)); 139 | assertEquals("fstore_2", Opcodes.name((byte) 0x45)); 140 | assertEquals("fstore_3", Opcodes.name((byte) 0x46)); 141 | assertEquals("dstore_0", Opcodes.name((byte) 0x47)); 142 | assertEquals("dstore_1", Opcodes.name((byte) 0x48)); 143 | assertEquals("dstore_2", Opcodes.name((byte) 0x49)); 144 | assertEquals("dstore_3", Opcodes.name((byte) 0x4a)); 145 | assertEquals("astore_0", Opcodes.name((byte) 0x4b)); 146 | assertEquals("astore_1", Opcodes.name((byte) 0x4c)); 147 | assertEquals("astore_2", Opcodes.name((byte) 0x4d)); 148 | assertEquals("astore_3", Opcodes.name((byte) 0x4e)); 149 | assertEquals("iastore", Opcodes.name((byte) 0x4f)); 150 | assertEquals("lastore", Opcodes.name((byte) 0x50)); 151 | assertEquals("fastore", Opcodes.name((byte) 0x51)); 152 | assertEquals("dastore", Opcodes.name((byte) 0x52)); 153 | assertEquals("aastore", Opcodes.name((byte) 0x53)); 154 | assertEquals("bastore", Opcodes.name((byte) 0x54)); 155 | assertEquals("castore", Opcodes.name((byte) 0x55)); 156 | assertEquals("sastore", Opcodes.name((byte) 0x56)); 157 | assertEquals("pop", Opcodes.name((byte) 0x57)); 158 | assertEquals("pop2", Opcodes.name((byte) 0x58)); 159 | assertEquals("dup", Opcodes.name((byte) 0x59)); 160 | assertEquals("dup_x1", Opcodes.name((byte) 0x5a)); 161 | assertEquals("dup_x2", Opcodes.name((byte) 0x5b)); 162 | assertEquals("dup2", Opcodes.name((byte) 0x5c)); 163 | assertEquals("dup2_x1", Opcodes.name((byte) 0x5d)); 164 | assertEquals("dup2_x2", Opcodes.name((byte) 0x5e)); 165 | assertEquals("swap", Opcodes.name((byte) 0x5f)); 166 | assertEquals("iadd", Opcodes.name((byte) 0x60)); 167 | assertEquals("ladd", Opcodes.name((byte) 0x61)); 168 | assertEquals("fadd", Opcodes.name((byte) 0x62)); 169 | assertEquals("dadd", Opcodes.name((byte) 0x63)); 170 | assertEquals("isub", Opcodes.name((byte) 0x64)); 171 | assertEquals("lsub", Opcodes.name((byte) 0x65)); 172 | assertEquals("fsub", Opcodes.name((byte) 0x66)); 173 | assertEquals("dsub", Opcodes.name((byte) 0x67)); 174 | assertEquals("imul", Opcodes.name((byte) 0x68)); 175 | assertEquals("lmul", Opcodes.name((byte) 0x69)); 176 | assertEquals("fmul", Opcodes.name((byte) 0x6a)); 177 | assertEquals("dmul", Opcodes.name((byte) 0x6b)); 178 | assertEquals("idiv", Opcodes.name((byte) 0x6c)); 179 | assertEquals("ldiv", Opcodes.name((byte) 0x6d)); 180 | assertEquals("fdiv", Opcodes.name((byte) 0x6e)); 181 | assertEquals("ddiv", Opcodes.name((byte) 0x6f)); 182 | assertEquals("irem", Opcodes.name((byte) 0x70)); 183 | assertEquals("lrem", Opcodes.name((byte) 0x71)); 184 | assertEquals("frem", Opcodes.name((byte) 0x72)); 185 | assertEquals("drem", Opcodes.name((byte) 0x73)); 186 | assertEquals("ineg", Opcodes.name((byte) 0x74)); 187 | assertEquals("lneg", Opcodes.name((byte) 0x75)); 188 | assertEquals("fneg", Opcodes.name((byte) 0x76)); 189 | assertEquals("dneg", Opcodes.name((byte) 0x77)); 190 | assertEquals("ishl", Opcodes.name((byte) 0x78)); 191 | assertEquals("lshl", Opcodes.name((byte) 0x79)); 192 | assertEquals("ishr", Opcodes.name((byte) 0x7a)); 193 | assertEquals("lshr", Opcodes.name((byte) 0x7b)); 194 | assertEquals("iushr", Opcodes.name((byte) 0x7c)); 195 | assertEquals("lushr", Opcodes.name((byte) 0x7d)); 196 | assertEquals("iand", Opcodes.name((byte) 0x7e)); 197 | assertEquals("land", Opcodes.name((byte) 0x7f)); 198 | assertEquals("ior", Opcodes.name((byte) 0x80)); 199 | assertEquals("lor", Opcodes.name((byte) 0x81)); 200 | assertEquals("ixor", Opcodes.name((byte) 0x82)); 201 | assertEquals("lxor", Opcodes.name((byte) 0x83)); 202 | assertEquals("iinc", Opcodes.name((byte) 0x84)); 203 | assertEquals("i2l", Opcodes.name((byte) 0x85)); 204 | assertEquals("i2f", Opcodes.name((byte) 0x86)); 205 | assertEquals("i2d", Opcodes.name((byte) 0x87)); 206 | assertEquals("l2i", Opcodes.name((byte) 0x88)); 207 | assertEquals("l2f", Opcodes.name((byte) 0x89)); 208 | assertEquals("l2d", Opcodes.name((byte) 0x8a)); 209 | assertEquals("f2i", Opcodes.name((byte) 0x8b)); 210 | assertEquals("f2l", Opcodes.name((byte) 0x8c)); 211 | assertEquals("f2d", Opcodes.name((byte) 0x8d)); 212 | assertEquals("d2i", Opcodes.name((byte) 0x8e)); 213 | assertEquals("d2l", Opcodes.name((byte) 0x8f)); 214 | assertEquals("d2f", Opcodes.name((byte) 0x90)); 215 | assertEquals("i2b", Opcodes.name((byte) 0x91)); 216 | assertEquals("i2c", Opcodes.name((byte) 0x92)); 217 | assertEquals("i2s", Opcodes.name((byte) 0x93)); 218 | assertEquals("lcmp", Opcodes.name((byte) 0x94)); 219 | assertEquals("fcmpl", Opcodes.name((byte) 0x95)); 220 | assertEquals("fcmpg", Opcodes.name((byte) 0x96)); 221 | assertEquals("dcmpl", Opcodes.name((byte) 0x97)); 222 | assertEquals("dcmpg", Opcodes.name((byte) 0x98)); 223 | assertEquals("ifeq", Opcodes.name((byte) 0x99)); 224 | assertEquals("ifne", Opcodes.name((byte) 0x9a)); 225 | assertEquals("iflt", Opcodes.name((byte) 0x9b)); 226 | assertEquals("ifge", Opcodes.name((byte) 0x9c)); 227 | assertEquals("ifgt", Opcodes.name((byte) 0x9d)); 228 | assertEquals("ifle", Opcodes.name((byte) 0x9e)); 229 | assertEquals("if_icmpeq", Opcodes.name((byte) 0x9f)); 230 | assertEquals("if_icmpne", Opcodes.name((byte) 0xa0)); 231 | assertEquals("if_icmplt", Opcodes.name((byte) 0xa1)); 232 | assertEquals("if_icmpge", Opcodes.name((byte) 0xa2)); 233 | assertEquals("if_icmpgt", Opcodes.name((byte) 0xa3)); 234 | assertEquals("if_icmple", Opcodes.name((byte) 0xa4)); 235 | assertEquals("if_acmpeq", Opcodes.name((byte) 0xa5)); 236 | assertEquals("if_acmpne", Opcodes.name((byte) 0xa6)); 237 | assertEquals("goto", Opcodes.name((byte) 0xa7)); 238 | assertEquals("jsr", Opcodes.name((byte) 0xa8)); 239 | assertEquals("ret", Opcodes.name((byte) 0xa9)); 240 | assertEquals("tableswitch", Opcodes.name((byte) 0xaa)); 241 | assertEquals("lookupswitch", Opcodes.name((byte) 0xab)); 242 | assertEquals("ireturn", Opcodes.name((byte) 0xac)); 243 | assertEquals("lreturn", Opcodes.name((byte) 0xad)); 244 | assertEquals("freturn", Opcodes.name((byte) 0xae)); 245 | assertEquals("dreturn", Opcodes.name((byte) 0xaf)); 246 | assertEquals("areturn", Opcodes.name((byte) 0xb0)); 247 | assertEquals("return", Opcodes.name((byte) 0xb1)); 248 | assertEquals("getstatic", Opcodes.name((byte) 0xb2)); 249 | assertEquals("putstatic", Opcodes.name((byte) 0xb3)); 250 | assertEquals("getfield", Opcodes.name((byte) 0xb4)); 251 | assertEquals("putfield", Opcodes.name((byte) 0xb5)); 252 | assertEquals("invokevirtual", Opcodes.name((byte) 0xb6)); 253 | assertEquals("invokespecial", Opcodes.name((byte) 0xb7)); 254 | assertEquals("invokestatic", Opcodes.name((byte) 0xb8)); 255 | assertEquals("invokeinterface", Opcodes.name((byte) 0xb9)); 256 | assertEquals("invokedynamic", Opcodes.name((byte) 0xba)); 257 | assertEquals("new", Opcodes.name((byte) 0xbb)); 258 | assertEquals("newarray", Opcodes.name((byte) 0xbc)); 259 | assertEquals("anewarray", Opcodes.name((byte) 0xbd)); 260 | assertEquals("arraylength", Opcodes.name((byte) 0xbe)); 261 | assertEquals("athrow", Opcodes.name((byte) 0xbf)); 262 | assertEquals("checkcast", Opcodes.name((byte) 0xc0)); 263 | assertEquals("instanceof", Opcodes.name((byte) 0xc1)); 264 | assertEquals("monitorenter", Opcodes.name((byte) 0xc2)); 265 | assertEquals("monitorexit", Opcodes.name((byte) 0xc3)); 266 | assertEquals("wide", Opcodes.name((byte) 0xc4)); 267 | assertEquals("multianewarray", Opcodes.name((byte) 0xc5)); 268 | assertEquals("ifnull", Opcodes.name((byte) 0xc6)); 269 | assertEquals("ifnonnull", Opcodes.name((byte) 0xc7)); 270 | assertEquals("goto_w", Opcodes.name((byte) 0xc8)); 271 | assertEquals("jsr_w", Opcodes.name((byte) 0xc9)); 272 | assertEquals("breakpoint", Opcodes.name((byte) 0xca)); 273 | assertEquals("impdep1", Opcodes.name((byte) 0xfe)); 274 | assertEquals("impdep2", Opcodes.name((byte) 0xff)); 275 | for (byte b = (byte) 0xcb; b < (byte) 0xfe; b++) { 276 | assertNull(Opcodes.name(b)); 277 | } 278 | } 279 | @Test 280 | void testOpcodeOperands() { 281 | for (int i = 0; i < 256; i++) { 282 | final byte opcode = (byte) (i & 0xff); 283 | int operands; 284 | switch (opcode) { 285 | case ALOAD: 286 | case ASTORE: 287 | case BIPUSH: 288 | case DLOAD: 289 | case DSTORE: 290 | case FLOAD: 291 | case FSTORE: 292 | case ILOAD: 293 | case ISTORE: 294 | case LDC: 295 | case LLOAD: 296 | case LSTORE: 297 | case NEWARRAY: 298 | case RET: 299 | operands = 1; 300 | break; 301 | case ANEWARRAY: 302 | case CHECKCAST: 303 | case GETFIELD: 304 | case GETSTATIC: 305 | case GOTO: 306 | case IF_ACMPEQ: 307 | case IF_ACMPNE: 308 | case IF_ICMPEQ: 309 | case IF_ICMPGE: 310 | case IF_ICMPGT: 311 | case IF_ICMPLE: 312 | case IF_ICMPLT: 313 | case IF_ICMPNE: 314 | case IFEQ: 315 | case IFGE: 316 | case IFGT: 317 | case IFLE: 318 | case IFLT: 319 | case IFNONNULL: 320 | case IFNULL: 321 | case IINC: 322 | case INSTANCEOF: 323 | case INVOKESPECIAL: 324 | case INVOKESTATIC: 325 | case INVOKEVIRTUAL: 326 | case JSR: 327 | case LDC_W: 328 | case LDC2_W: 329 | case NEW: 330 | case PUTFIELD: 331 | case PUTSTATIC: 332 | case SIPUSH: 333 | operands = 2; 334 | break; 335 | case MULTIANEWARRAY: 336 | operands = 3; 337 | break; 338 | case GOTO_W: 339 | case INVOKEDYNAMIC: 340 | case INVOKEINTERFACE: 341 | case JSR_W: 342 | operands = 4; 343 | break; 344 | case WIDE: 345 | case TABLESWITCH: 346 | case LOOKUPSWITCH: 347 | operands = -1; 348 | default: 349 | operands = 0; 350 | } 351 | assertEquals(operands, Opcodes.operands(opcode), "Opcode " + opcode); 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/main/java/com/bandlem/jvm/jvmulator/Opcodes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | public interface Opcodes { 11 | public static final byte AALOAD = (byte) 50; 12 | public static final byte AASTORE = (byte) 83; 13 | public static final byte ACONST_NULL = (byte) 1; 14 | public static final byte ALOAD = (byte) 25; 15 | public static final byte ALOAD_0 = (byte) 42; 16 | public static final byte ALOAD_1 = (byte) 43; 17 | public static final byte ALOAD_2 = (byte) 44; 18 | public static final byte ALOAD_3 = (byte) 45; 19 | public static final byte ANEWARRAY = (byte) 189; 20 | public static final byte ARETURN = (byte) 176; 21 | public static final byte ARRAYLENGTH = (byte) 190; 22 | public static final byte ASTORE = (byte) 58; 23 | public static final byte ASTORE_0 = (byte) 75; 24 | public static final byte ASTORE_1 = (byte) 76; 25 | public static final byte ASTORE_2 = (byte) 77; 26 | public static final byte ASTORE_3 = (byte) 78; 27 | public static final byte ATHROW = (byte) 191; 28 | public static final byte BALOAD = (byte) 51; 29 | public static final byte BASTORE = (byte) 84; 30 | public static final byte BIPUSH = (byte) 16; 31 | public static final byte BREAKPOINT = (byte) 202; 32 | public static final byte CALOAD = (byte) 52; 33 | public static final byte CASTORE = (byte) 85; 34 | public static final byte CHECKCAST = (byte) 192; 35 | public static final byte D2F = (byte) 144; 36 | public static final byte D2I = (byte) 142; 37 | public static final byte D2L = (byte) 143; 38 | public static final byte DADD = (byte) 99; 39 | public static final byte DALOAD = (byte) 49; 40 | public static final byte DASTORE = (byte) 82; 41 | public static final byte DCMPG = (byte) 152; 42 | public static final byte DCMPL = (byte) 151; 43 | public static final byte DCONST_0 = (byte) 14; 44 | public static final byte DCONST_1 = (byte) 15; 45 | public static final byte DDIV = (byte) 111; 46 | public static final byte DLOAD = (byte) 24; 47 | public static final byte DLOAD_0 = (byte) 38; 48 | public static final byte DLOAD_1 = (byte) 39; 49 | public static final byte DLOAD_2 = (byte) 40; 50 | public static final byte DLOAD_3 = (byte) 41; 51 | public static final byte DMUL = (byte) 107; 52 | public static final byte DNEG = (byte) 119; 53 | public static final byte DREM = (byte) 115; 54 | public static final byte DRETURN = (byte) 175; 55 | public static final byte DSTORE = (byte) 57; 56 | public static final byte DSTORE_0 = (byte) 71; 57 | public static final byte DSTORE_1 = (byte) 72; 58 | public static final byte DSTORE_2 = (byte) 73; 59 | public static final byte DSTORE_3 = (byte) 74; 60 | public static final byte DSUB = (byte) 103; 61 | public static final byte DUP = (byte) 89; 62 | public static final byte DUP_X1 = (byte) 90; 63 | public static final byte DUP_X2 = (byte) 91; 64 | public static final byte DUP2 = (byte) 92; 65 | public static final byte DUP2_X1 = (byte) 93; 66 | public static final byte DUP2_X2 = (byte) 94; 67 | public static final byte F2D = (byte) 141; 68 | public static final byte F2I = (byte) 139; 69 | public static final byte F2L = (byte) 140; 70 | public static final byte FADD = (byte) 98; 71 | public static final byte FALOAD = (byte) 48; 72 | public static final byte FASTORE = (byte) 81; 73 | public static final byte FCMPG = (byte) 150; 74 | public static final byte FCMPL = (byte) 149; 75 | public static final byte FCONST_0 = (byte) 11; 76 | public static final byte FCONST_1 = (byte) 12; 77 | public static final byte FCONST_2 = (byte) 13; 78 | public static final byte FDIV = (byte) 110; 79 | public static final byte FLOAD = (byte) 23; 80 | public static final byte FLOAD_0 = (byte) 34; 81 | public static final byte FLOAD_1 = (byte) 35; 82 | public static final byte FLOAD_2 = (byte) 36; 83 | public static final byte FLOAD_3 = (byte) 37; 84 | public static final byte FMUL = (byte) 106; 85 | public static final byte FNEG = (byte) 118; 86 | public static final byte FREM = (byte) 114; 87 | public static final byte FRETURN = (byte) 174; 88 | public static final byte FSTORE = (byte) 56; 89 | public static final byte FSTORE_0 = (byte) 67; 90 | public static final byte FSTORE_1 = (byte) 68; 91 | public static final byte FSTORE_2 = (byte) 69; 92 | public static final byte FSTORE_3 = (byte) 70; 93 | public static final byte FSUB = (byte) 102; 94 | public static final byte GETFIELD = (byte) 180; 95 | public static final byte GETSTATIC = (byte) 178; 96 | public static final byte GOTO = (byte) 167; 97 | public static final byte GOTO_W = (byte) 200; 98 | public static final byte I2B = (byte) 145; 99 | public static final byte I2C = (byte) 146; 100 | public static final byte I2D = (byte) 135; 101 | public static final byte I2F = (byte) 134; 102 | public static final byte I2L = (byte) 133; 103 | public static final byte I2S = (byte) 147; 104 | public static final byte IADD = (byte) 96; 105 | public static final byte IALOAD = (byte) 46; 106 | public static final byte IAND = (byte) 126; 107 | public static final byte IASTORE = (byte) 79; 108 | public static final byte ICONST_0 = (byte) 3; 109 | public static final byte ICONST_1 = (byte) 4; 110 | public static final byte ICONST_2 = (byte) 5; 111 | public static final byte ICONST_3 = (byte) 6; 112 | public static final byte ICONST_4 = (byte) 7; 113 | public static final byte ICONST_5 = (byte) 8; 114 | public static final byte ICONST_M1 = (byte) 2; 115 | public static final byte IDIV = (byte) 108; 116 | public static final byte IF_ACMPEQ = (byte) 165; 117 | public static final byte IF_ACMPNE = (byte) 166; 118 | public static final byte IF_ICMPEQ = (byte) 159; 119 | public static final byte IF_ICMPGE = (byte) 162; 120 | public static final byte IF_ICMPGT = (byte) 163; 121 | public static final byte IF_ICMPLE = (byte) 164; 122 | public static final byte IF_ICMPLT = (byte) 161; 123 | public static final byte IF_ICMPNE = (byte) 160; 124 | public static final byte IFEQ = (byte) 153; 125 | public static final byte IFGE = (byte) 156; 126 | public static final byte IFGT = (byte) 157; 127 | public static final byte IFLE = (byte) 158; 128 | public static final byte IFLT = (byte) 155; 129 | public static final byte IFNE = (byte) 154; 130 | public static final byte IFNONNULL = (byte) 199; 131 | public static final byte IFNULL = (byte) 198; 132 | public static final byte IINC = (byte) 132; 133 | public static final byte ILOAD = (byte) 21; 134 | public static final byte ILOAD_0 = (byte) 26; 135 | public static final byte ILOAD_1 = (byte) 27; 136 | public static final byte ILOAD_2 = (byte) 28; 137 | public static final byte ILOAD_3 = (byte) 29; 138 | public static final byte IMPDEP1 = (byte) 254; 139 | public static final byte IMPDEP2 = (byte) 255; 140 | public static final byte IMUL = (byte) 104; 141 | public static final byte INEG = (byte) 116; 142 | public static final byte INSTANCEOF = (byte) 193; 143 | public static final byte INVOKEDYNAMIC = (byte) 186; 144 | public static final byte INVOKEINTERFACE = (byte) 185; 145 | public static final byte INVOKESPECIAL = (byte) 183; 146 | public static final byte INVOKESTATIC = (byte) 184; 147 | public static final byte INVOKEVIRTUAL = (byte) 182; 148 | public static final byte IOR = (byte) 128; 149 | public static final byte IREM = (byte) 112; 150 | public static final byte IRETURN = (byte) 172; 151 | public static final byte ISHL = (byte) 120; 152 | public static final byte ISHR = (byte) 122; 153 | public static final byte ISTORE = (byte) 54; 154 | public static final byte ISTORE_0 = (byte) 59; 155 | public static final byte ISTORE_1 = (byte) 60; 156 | public static final byte ISTORE_2 = (byte) 61; 157 | public static final byte ISTORE_3 = (byte) 62; 158 | public static final byte ISUB = (byte) 100; 159 | public static final byte IUSHR = (byte) 124; 160 | public static final byte IXOR = (byte) 130; 161 | public static final byte JSR = (byte) 168; 162 | public static final byte JSR_W = (byte) 201; 163 | public static final byte L2D = (byte) 138; 164 | public static final byte L2F = (byte) 137; 165 | public static final byte L2I = (byte) 136; 166 | public static final byte LADD = (byte) 97; 167 | public static final byte LALOAD = (byte) 47; 168 | public static final byte LAND = (byte) 127; 169 | public static final byte LASTORE = (byte) 80; 170 | public static final byte LCMP = (byte) 148; 171 | public static final byte LCONST_0 = (byte) 9; 172 | public static final byte LCONST_1 = (byte) 10; 173 | public static final byte LDC = (byte) 18; 174 | public static final byte LDC_W = (byte) 19; 175 | public static final byte LDC2_W = (byte) 20; 176 | public static final byte LDIV = (byte) 109; 177 | public static final byte LLOAD = (byte) 22; 178 | public static final byte LLOAD_0 = (byte) 30; 179 | public static final byte LLOAD_1 = (byte) 31; 180 | public static final byte LLOAD_2 = (byte) 32; 181 | public static final byte LLOAD_3 = (byte) 33; 182 | public static final byte LMUL = (byte) 105; 183 | public static final byte LNEG = (byte) 117; 184 | public static final byte LOOKUPSWITCH = (byte) 171; 185 | public static final byte LOR = (byte) 129; 186 | public static final byte LREM = (byte) 113; 187 | public static final byte LRETURN = (byte) 173; 188 | public static final byte LSHL = (byte) 121; 189 | public static final byte LSHR = (byte) 123; 190 | public static final byte LSTORE = (byte) 55; 191 | public static final byte LSTORE_0 = (byte) 63; 192 | public static final byte LSTORE_1 = (byte) 64; 193 | public static final byte LSTORE_2 = (byte) 65; 194 | public static final byte LSTORE_3 = (byte) 66; 195 | public static final byte LSUB = (byte) 101; 196 | public static final byte LUSHR = (byte) 125; 197 | public static final byte LXOR = (byte) 131; 198 | public static final byte MONITORENTER = (byte) 194; 199 | public static final byte MONITOREXIT = (byte) 195; 200 | public static final byte MULTIANEWARRAY = (byte) 197; 201 | public static final String[] name = new String[] { 202 | "nop", // 0 203 | "aconst_null", // 1 204 | "iconst_m1", // 2 205 | "iconst_0", // 3 206 | "iconst_1", // 4 207 | "iconst_2", // 5 208 | "iconst_3", // 6 209 | "iconst_4", // 7 210 | "iconst_5", // 8 211 | "lconst_0", // 9 212 | "lconst_1", // 10 213 | "fconst_0", // 11 214 | "fconst_1", // 12 215 | "fconst_2", // 13 216 | "dconst_0", // 14 217 | "dconst_1", // 15 218 | "bipush", // 16 219 | "sipush", // 17 220 | "ldc", // 18 221 | "ldc_w", // 19 222 | "ldc2_w", // 20 223 | "iload", // 21 224 | "lload", // 22 225 | "fload", // 23 226 | "dload", // 24 227 | "aload", // 25 228 | "iload_0", // 26 229 | "iload_1", // 27 230 | "iload_2", // 28 231 | "iload_3", // 29 232 | "lload_0", // 30 233 | "lload_1", // 31 234 | "lload_2", // 32 235 | "lload_3", // 33 236 | "fload_0", // 34 237 | "fload_1", // 35 238 | "fload_2", // 36 239 | "fload_3", // 37 240 | "dload_0", // 38 241 | "dload_1", // 39 242 | "dload_2", // 40 243 | "dload_3", // 41 244 | "aload_0", // 42 245 | "aload_1", // 43 246 | "aload_2", // 44 247 | "aload_3", // 45 248 | "iaload", // 46 249 | "laload", // 47 250 | "faload", // 48 251 | "daload", // 49 252 | "aaload", // 50 253 | "baload", // 51 254 | "caload", // 52 255 | "saload", // 53 256 | "istore", // 54 257 | "lstore", // 55 258 | "fstore", // 56 259 | "dstore", // 57 260 | "astore", // 58 261 | "istore_0", // 59 262 | "istore_1", // 60 263 | "istore_2", // 61 264 | "istore_3", // 62 265 | "lstore_0", // 63 266 | "lstore_1", // 64 267 | "lstore_2", // 65 268 | "lstore_3", // 66 269 | "fstore_0", // 67 270 | "fstore_1", // 68 271 | "fstore_2", // 69 272 | "fstore_3", // 70 273 | "dstore_0", // 71 274 | "dstore_1", // 72 275 | "dstore_2", // 73 276 | "dstore_3", // 74 277 | "astore_0", // 75 278 | "astore_1", // 76 279 | "astore_2", // 77 280 | "astore_3", // 78 281 | "iastore", // 79 282 | "lastore", // 80 283 | "fastore", // 81 284 | "dastore", // 82 285 | "aastore", // 83 286 | "bastore", // 84 287 | "castore", // 85 288 | "sastore", // 86 289 | "pop", // 87 290 | "pop2", // 88 291 | "dup", // 89 292 | "dup_x1", // 90 293 | "dup_x2", // 91 294 | "dup2", // 92 295 | "dup2_x1", // 93 296 | "dup2_x2", // 94 297 | "swap", // 95 298 | "iadd", // 96 299 | "ladd", // 97 300 | "fadd", // 98 301 | "dadd", // 99 302 | "isub", // 100 303 | "lsub", // 101 304 | "fsub", // 102 305 | "dsub", // 103 306 | "imul", // 104 307 | "lmul", // 105 308 | "fmul", // 106 309 | "dmul", // 107 310 | "idiv", // 108 311 | "ldiv", // 109 312 | "fdiv", // 110 313 | "ddiv", // 111 314 | "irem", // 112 315 | "lrem", // 113 316 | "frem", // 114 317 | "drem", // 115 318 | "ineg", // 116 319 | "lneg", // 117 320 | "fneg", // 118 321 | "dneg", // 119 322 | "ishl", // 120 323 | "lshl", // 121 324 | "ishr", // 122 325 | "lshr", // 123 326 | "iushr", // 124 327 | "lushr", // 125 328 | "iand", // 126 329 | "land", // 127 330 | "ior", // 128 331 | "lor", // 129 332 | "ixor", // 130 333 | "lxor", // 131 334 | "iinc", // 132 335 | "i2l", // 133 336 | "i2f", // 134 337 | "i2d", // 135 338 | "l2i", // 136 339 | "l2f", // 137 340 | "l2d", // 138 341 | "f2i", // 139 342 | "f2l", // 140 343 | "f2d", // 141 344 | "d2i", // 142 345 | "d2l", // 143 346 | "d2f", // 144 347 | "i2b", // 145 348 | "i2c", // 146 349 | "i2s", // 147 350 | "lcmp", // 148 351 | "fcmpl", // 149 352 | "fcmpg", // 150 353 | "dcmpl", // 151 354 | "dcmpg", // 152 355 | "ifeq", // 153 356 | "ifne", // 154 357 | "iflt", // 155 358 | "ifge", // 156 359 | "ifgt", // 157 360 | "ifle", // 158 361 | "if_icmpeq", // 159 362 | "if_icmpne", // 160 363 | "if_icmplt", // 161 364 | "if_icmpge", // 162 365 | "if_icmpgt", // 163 366 | "if_icmple", // 164 367 | "if_acmpeq", // 165 368 | "if_acmpne", // 166 369 | "goto", // 167 370 | "jsr", // 168 371 | "ret", // 169 372 | "tableswitch", // 170 373 | "lookupswitch", // 171 374 | "ireturn", // 172 375 | "lreturn", // 173 376 | "freturn", // 174 377 | "dreturn", // 175 378 | "areturn", // 176 379 | "return", // 177 380 | "getstatic", // 178 381 | "putstatic", // 179 382 | "getfield", // 180 383 | "putfield", // 181 384 | "invokevirtual", // 182 385 | "invokespecial", // 183 386 | "invokestatic", // 184 387 | "invokeinterface", // 185 388 | "invokedynamic", // 186 389 | "new", // 187 390 | "newarray", // 188 391 | "anewarray", // 189 392 | "arraylength", // 190 393 | "athrow", // 191 394 | "checkcast", // 192 395 | "instanceof", // 193 396 | "monitorenter", // 194 397 | "monitorexit", // 195 398 | "wide", // 196 399 | "multianewarray", // 197 400 | "ifnull", // 198 401 | "ifnonnull", // 199 402 | "goto_w", // 200 403 | "jsr_w", // 201 404 | "breakpoint", // 202 405 | null, // 203 406 | null, // 204 407 | null, // 205 408 | null, // 206 409 | null, // 207 410 | null, // 208 411 | null, // 209 412 | null, // 210 413 | null, // 211 414 | null, // 212 415 | null, // 213 416 | null, // 214 417 | null, // 215 418 | null, // 216 419 | null, // 217 420 | null, // 218 421 | null, // 219 422 | null, // 220 423 | null, // 221 424 | null, // 222 425 | null, // 223 426 | null, // 224 427 | null, // 225 428 | null, // 226 429 | null, // 227 430 | null, // 228 431 | null, // 229 432 | null, // 230 433 | null, // 231 434 | null, // 232 435 | null, // 233 436 | null, // 234 437 | null, // 235 438 | null, // 236 439 | null, // 237 440 | null, // 238 441 | null, // 239 442 | null, // 240 443 | null, // 241 444 | null, // 242 445 | null, // 243 446 | null, // 244 447 | null, // 245 448 | null, // 246 449 | null, // 247 450 | null, // 248 451 | null, // 249 452 | null, // 250 453 | null, // 251 454 | null, // 252 455 | null, // 253 456 | "impdep1", // 254 457 | "impdep2", // 255 458 | }; 459 | public static final byte NEW = (byte) 187; 460 | public static final byte NEWARRAY = (byte) 188; 461 | public static final byte NOP = (byte) 0; 462 | public static final byte POP = (byte) 87; 463 | public static final byte POP2 = (byte) 88; 464 | public static final byte PUTFIELD = (byte) 181; 465 | public static final byte PUTSTATIC = (byte) 179; 466 | public static final byte RET = (byte) 169; 467 | public static final byte RETURN = (byte) 177; 468 | public static final byte SALOAD = (byte) 53; 469 | public static final byte SASTORE = (byte) 86; 470 | public static final byte SIPUSH = (byte) 17; 471 | public static final byte SWAP = (byte) 95; 472 | public static final byte TABLESWITCH = (byte) 170; 473 | public static final byte WIDE = (byte) 196; 474 | public static String name(final byte i) { 475 | return name[i & 0xff]; 476 | } 477 | static int operands(final byte opcode) { 478 | int operands; 479 | switch (opcode) { 480 | case ALOAD: 481 | case ASTORE: 482 | case BIPUSH: 483 | case DLOAD: 484 | case DSTORE: 485 | case FLOAD: 486 | case FSTORE: 487 | case ILOAD: 488 | case ISTORE: 489 | case LDC: 490 | case LLOAD: 491 | case LSTORE: 492 | case NEWARRAY: 493 | case RET: 494 | operands = 1; 495 | break; 496 | case ANEWARRAY: 497 | case CHECKCAST: 498 | case GETFIELD: 499 | case GETSTATIC: 500 | case GOTO: 501 | case IF_ACMPEQ: 502 | case IF_ACMPNE: 503 | case IF_ICMPEQ: 504 | case IF_ICMPGE: 505 | case IF_ICMPGT: 506 | case IF_ICMPLE: 507 | case IF_ICMPLT: 508 | case IF_ICMPNE: 509 | case IFEQ: 510 | case IFGE: 511 | case IFGT: 512 | case IFLE: 513 | case IFLT: 514 | case IFNONNULL: 515 | case IFNULL: 516 | case IINC: 517 | case INSTANCEOF: 518 | case INVOKESPECIAL: 519 | case INVOKESTATIC: 520 | case INVOKEVIRTUAL: 521 | case JSR: 522 | case LDC_W: 523 | case LDC2_W: 524 | case NEW: 525 | case PUTFIELD: 526 | case PUTSTATIC: 527 | case SIPUSH: 528 | operands = 2; 529 | break; 530 | case MULTIANEWARRAY: 531 | operands = 3; 532 | break; 533 | case GOTO_W: 534 | case INVOKEDYNAMIC: 535 | case INVOKEINTERFACE: 536 | case JSR_W: 537 | operands = 4; 538 | break; 539 | case WIDE: 540 | case TABLESWITCH: 541 | case LOOKUPSWITCH: 542 | operands = -1; 543 | default: 544 | operands = 0; 545 | } 546 | return operands; 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/JVMClassTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static com.bandlem.jvm.jvmulator.Opcodes.ACONST_NULL; 11 | import static com.bandlem.jvm.jvmulator.Opcodes.ARETURN; 12 | import static com.bandlem.jvm.jvmulator.Opcodes.DCONST_1; 13 | import static com.bandlem.jvm.jvmulator.Opcodes.DRETURN; 14 | import static com.bandlem.jvm.jvmulator.Opcodes.FCONST_1; 15 | import static com.bandlem.jvm.jvmulator.Opcodes.FRETURN; 16 | import static com.bandlem.jvm.jvmulator.Opcodes.GETFIELD; 17 | import static com.bandlem.jvm.jvmulator.Opcodes.GETSTATIC; 18 | import static com.bandlem.jvm.jvmulator.Opcodes.ICONST_1; 19 | import static com.bandlem.jvm.jvmulator.Opcodes.INSTANCEOF; 20 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKESTATIC; 21 | import static com.bandlem.jvm.jvmulator.Opcodes.INVOKEVIRTUAL; 22 | import static com.bandlem.jvm.jvmulator.Opcodes.IRETURN; 23 | import static com.bandlem.jvm.jvmulator.Opcodes.LCONST_1; 24 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC; 25 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC2_W; 26 | import static com.bandlem.jvm.jvmulator.Opcodes.LDC_W; 27 | import static com.bandlem.jvm.jvmulator.Opcodes.LRETURN; 28 | import static com.bandlem.jvm.jvmulator.Opcodes.PUTFIELD; 29 | import static com.bandlem.jvm.jvmulator.Opcodes.PUTSTATIC; 30 | import static com.bandlem.jvm.jvmulator.Opcodes.RETURN; 31 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 32 | import static org.junit.jupiter.api.Assertions.assertEquals; 33 | import static org.junit.jupiter.api.Assertions.assertNotNull; 34 | import static org.junit.jupiter.api.Assertions.assertNull; 35 | import static org.junit.jupiter.api.Assertions.assertThrows; 36 | import static org.junit.jupiter.api.Assertions.assertTrue; 37 | import java.io.DataInputStream; 38 | import java.io.InputStream; 39 | import org.junit.jupiter.api.BeforeAll; 40 | import org.junit.jupiter.api.Test; 41 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool; 42 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.ClassConstant; 43 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.DoubleConstant; 44 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.FieldRef; 45 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.FloatConstant; 46 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.IntConstant; 47 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.Item; 48 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.LongConstant; 49 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.MethodRef; 50 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.NameAndType; 51 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.StringConstant; 52 | import com.bandlem.jvm.jvmulator.classfile.ConstantPool.UTFConstant; 53 | import com.bandlem.jvm.jvmulator.classfile.JavaClass; 54 | class JVMClassTest { 55 | static class Sample { 56 | public static boolean bb = false; 57 | public static double dd = 3.141; 58 | public static float ff = 2.718f; 59 | public static int ii = 0xff00; 60 | public static long ll = 37L; 61 | public static String ss = "alex.blewitt@gmail.com"; 62 | public boolean b = false; 63 | public double d = 3.141; 64 | public float f = 2.718f; 65 | public int i = 0xff00; 66 | public long l = 37L; 67 | public String s = "alex.blewitt@gmail.com"; 68 | public float floaty() { 69 | return 3.141f; 70 | } 71 | public long longy() { 72 | return 4200L; 73 | } 74 | public void reset() { 75 | dd = d; 76 | ff = f; 77 | ii = i; 78 | ll = l; 79 | bb = b; 80 | ss = s; 81 | } 82 | public double run() { 83 | System.gc(); 84 | return Math.random(); 85 | } 86 | } 87 | private static byte constant_double; 88 | private static byte constant_email; 89 | private static byte constant_email_utf; 90 | private static byte constant_field_d; 91 | private static byte constant_field_dd; 92 | private static byte constant_field_f; 93 | private static byte constant_field_ff; 94 | private static byte constant_field_i; 95 | private static byte constant_field_ii; 96 | private static byte constant_field_l; 97 | private static byte constant_field_ll; 98 | private static byte constant_field_s; 99 | private static byte constant_field_ss; 100 | private static byte constant_float; 101 | private static byte constant_gc; 102 | private static byte constant_int; 103 | private static byte constant_long; 104 | private static byte constant_object; 105 | private static byte constant_random; 106 | private static byte constant_system; 107 | private static JavaClass javaClass; 108 | private static ConstantPool pool; 109 | static short find(final Object value, final Class type) { 110 | for (short i = 1; i < pool.size(); i++) { 111 | final Item item = pool.getItem(i); 112 | // Double and Long values have a missing slot 113 | if (item == null) { 114 | continue; 115 | } 116 | if (type == StringConstant.class && item instanceof StringConstant) { 117 | if (value.equals(pool.getString(((StringConstant) item).index))) { 118 | return i; 119 | } 120 | } else if (type == UTFConstant.class && item instanceof UTFConstant) { 121 | if (value.equals((((UTFConstant) item).value))) { 122 | return i; 123 | } 124 | } else if (type == IntConstant.class && item instanceof IntConstant) { 125 | if (value.equals((((IntConstant) item).value))) { 126 | return i; 127 | } 128 | } else if (type == LongConstant.class && item instanceof LongConstant) { 129 | if (value.equals((((LongConstant) item).value))) { 130 | return i; 131 | } 132 | } else if (type == FloatConstant.class && item instanceof FloatConstant) { 133 | if (value.equals((((FloatConstant) item).value))) { 134 | return i; 135 | } 136 | } else if (type == DoubleConstant.class && item instanceof DoubleConstant) { 137 | if (value.equals((((DoubleConstant) item).value))) { 138 | return i; 139 | } 140 | } else if (type == ClassConstant.class && item instanceof ClassConstant) { 141 | if (value.equals(pool.getString(((ClassConstant) item).index))) { 142 | return i; 143 | } 144 | } else if (type == MethodRef.class && item instanceof MethodRef) { 145 | // NB only considers name 146 | final short nat = ((MethodRef) item).nameAndTypeIndex; 147 | final short name = ((NameAndType) pool.getItem(nat)).nameIndex; 148 | if (value.equals(pool.getString(name))) { 149 | return i; 150 | } 151 | } else if (type == FieldRef.class && item instanceof FieldRef) { 152 | // NB only considers name 153 | final short nat = ((FieldRef) item).nameAndTypeIndex; 154 | final short name = ((NameAndType) pool.getItem(nat)).nameIndex; 155 | if (value.equals(pool.getString(name))) { 156 | return i; 157 | } 158 | } 159 | } 160 | throw new IllegalArgumentException("Cannot find " + value + " of type " + type); 161 | } 162 | @BeforeAll 163 | static void setup() { 164 | final InputStream in = Sample.class 165 | .getResourceAsStream("/" + Sample.class.getName().replace('.', '/') + ".class"); 166 | javaClass = new JavaClass(new DataInputStream(in)); 167 | pool = javaClass.pool; 168 | // If the pool is above 256 we have problems with constant resolution 169 | assertTrue(pool.size() < 256); 170 | constant_email_utf = (byte) find(Sample.ss, UTFConstant.class); 171 | constant_email = (byte) find(Sample.ss, StringConstant.class); 172 | constant_int = (byte) find(Sample.ii, IntConstant.class); 173 | constant_long = (byte) find(Sample.ll, LongConstant.class); 174 | constant_float = (byte) find(Sample.ff, FloatConstant.class); 175 | constant_double = (byte) find(Sample.dd, DoubleConstant.class); 176 | constant_object = (byte) find("java/lang/Object", ClassConstant.class); 177 | constant_system = (byte) find("java/lang/System", ClassConstant.class); 178 | constant_gc = (byte) find("gc", MethodRef.class); 179 | constant_random = (byte) find("random", MethodRef.class); 180 | constant_field_i = (byte) find("i", FieldRef.class); 181 | constant_field_l = (byte) find("l", FieldRef.class); 182 | constant_field_f = (byte) find("f", FieldRef.class); 183 | constant_field_d = (byte) find("d", FieldRef.class); 184 | constant_field_s = (byte) find("s", FieldRef.class); 185 | constant_field_ii = (byte) find("ii", FieldRef.class); 186 | constant_field_ll = (byte) find("ll", FieldRef.class); 187 | constant_field_ff = (byte) find("ff", FieldRef.class); 188 | constant_field_dd = (byte) find("dd", FieldRef.class); 189 | constant_field_ss = (byte) find("ss", FieldRef.class); 190 | } 191 | private void expect(final Class expected, final JavaClass javaClass, final int locals, 192 | final Slot slot, final byte[] code) { 193 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 194 | if (slot != null) { 195 | frame.stack.pushSlot(slot); 196 | } 197 | assertThrows(expected, frame::run); 198 | } 199 | private void expect(final double result, final JavaClass javaClass, final int locals, final Slot slot, 200 | final byte[] code) { 201 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 202 | if (slot != null) { 203 | frame.stack.pushSlot(slot); 204 | } 205 | assertEquals(result, frame.run().doubleValue()); 206 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 207 | } 208 | private void expect(final float result, final JavaClass javaClass, final int locals, final Slot slot, 209 | final byte[] code) { 210 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 211 | if (slot != null) { 212 | frame.stack.pushSlot(slot); 213 | } 214 | assertEquals(result, frame.run().floatValue()); 215 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 216 | } 217 | private void expect(final int result, final JavaClass javaClass, final int locals, final Slot slot, 218 | final byte[] code) { 219 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 220 | if (slot != null) { 221 | frame.stack.pushSlot(slot); 222 | } 223 | assertEquals(result, frame.run().intValue()); 224 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 225 | } 226 | private void expect(final long result, final JavaClass javaClass, final int locals, final Slot slot, 227 | final byte[] code) { 228 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 229 | if (slot != null) { 230 | frame.stack.pushSlot(slot); 231 | } 232 | assertEquals(result, frame.run().longValue()); 233 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 234 | } 235 | private void expect(final Object result, final JavaClass javaClass, final int locals, final Slot slot, 236 | final byte[] code) { 237 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 238 | if (slot != null) { 239 | frame.stack.pushSlot(slot); 240 | } 241 | final Slot answer = frame.run(); 242 | if (answer != null) { 243 | assertEquals(result, answer.referenceValue()); 244 | } 245 | assertEquals(answer, frame.getReturnValue()); 246 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 247 | } 248 | @Test 249 | void testClassData() { 250 | new Sample().reset(); 251 | assertEquals("alex.blewitt@gmail.com", pool.getString(constant_email_utf)); 252 | assertEquals(constant_email_utf, ((StringConstant) pool.getItem(constant_email)).index); 253 | assertEquals("alex.blewitt@gmail.com", pool.getString(((StringConstant) pool.getItem(constant_email)).index)); 254 | assertEquals(Sample.ii, ((IntConstant) pool.getItem(constant_int)).value); 255 | assertEquals(Sample.ll, ((LongConstant) pool.getItem(constant_long)).value); 256 | assertEquals(Sample.ff, ((FloatConstant) pool.getItem(constant_float)).value); 257 | assertEquals(Sample.dd, ((DoubleConstant) pool.getItem(constant_double)).value); 258 | expect("alex.blewitt@gmail.com", javaClass, 0, null, new byte[] { 259 | LDC, constant_email, ARETURN 260 | }); 261 | expect(Sample.ii, javaClass, 0, null, new byte[] { 262 | LDC, constant_int, IRETURN 263 | }); 264 | expect(Sample.ll, javaClass, 0, null, new byte[] { 265 | LDC2_W, 0x00, constant_long, LRETURN 266 | }); 267 | expect(Sample.ff, javaClass, 0, null, new byte[] { 268 | LDC_W, 0x00, constant_float, FRETURN 269 | }); 270 | expect(Sample.dd, javaClass, 0, null, new byte[] { 271 | LDC2_W, 0x00, constant_double, DRETURN 272 | }); 273 | expect(UnsupportedOperationException.class, javaClass, 0, null, new byte[] { 274 | LDC, 0x01, IRETURN 275 | }); 276 | } 277 | @Test 278 | void testFields() { 279 | final Sample original = new Sample(); 280 | original.reset(); 281 | assertNotNull(JVMFrame.getfield(null, "out", "Ljava/io/PrintStream;", System.class.getName(), null)); 282 | assertEquals(original.d, JVMFrame.getfield(original, "d", "D", Sample.class.getName(), null).doubleValue()); 283 | assertEquals(original.f, JVMFrame.getfield(original, "f", "F", Sample.class.getName(), null).floatValue()); 284 | assertEquals(original.i, JVMFrame.getfield(original, "i", "I", Sample.class.getName(), null).intValue()); 285 | assertEquals(original.l, JVMFrame.getfield(original, "l", "J", Sample.class.getName(), null).longValue()); 286 | assertEquals(Sample.dd, JVMFrame.getfield(null, "dd", "D", Sample.class.getName(), null).doubleValue()); 287 | assertEquals(Sample.ff, JVMFrame.getfield(null, "ff", "F", Sample.class.getName(), null).floatValue()); 288 | assertEquals(Sample.ii, JVMFrame.getfield(null, "ii", "I", Sample.class.getName(), null).intValue()); 289 | assertEquals(Sample.ll, JVMFrame.getfield(null, "ll", "J", Sample.class.getName(), null).longValue()); 290 | assertEquals(Sample.ii, JVMFrame.getfield(null, "ii", "Z", Sample.class.getName(), null).intValue()); 291 | assertEquals(Sample.ii, JVMFrame.getfield(null, "ii", "B", Sample.class.getName(), null).intValue()); 292 | assertEquals(Sample.ii, JVMFrame.getfield(null, "ii", "S", Sample.class.getName(), null).intValue()); 293 | assertEquals(Sample.ii, JVMFrame.getfield(null, "ii", "C", Sample.class.getName(), null).intValue()); 294 | // instance fields 295 | JVMFrame.putfield(Slot.of(1.23D), original, "d", "D", Sample.class.getName(), null); 296 | assertEquals(1.23D, original.d); 297 | JVMFrame.putfield(Slot.of(2.34F), original, "f", "F", Sample.class.getName(), null); 298 | assertEquals(2.34f, original.f); 299 | JVMFrame.putfield(Slot.of(5), original, "i", "I", Sample.class.getName(), null); 300 | assertEquals(5, original.i); 301 | JVMFrame.putfield(Slot.of(6L), original, "l", "J", Sample.class.getName(), null); 302 | assertEquals(6L, original.l); 303 | JVMFrame.putfield(Slot.of("Hello World"), original, "s", "Lsomething;", Sample.class.getName(), null); 304 | assertEquals("Hello World", original.s); 305 | // static fields 306 | JVMFrame.putfield(Slot.of(2.23D), null, "dd", "D", Sample.class.getName(), null); 307 | assertEquals(2.23D, Sample.dd); 308 | JVMFrame.putfield(Slot.of(3.34F), null, "ff", "F", Sample.class.getName(), null); 309 | assertEquals(3.34f, Sample.ff); 310 | JVMFrame.putfield(Slot.of(7), null, "ii", "I", Sample.class.getName(), null); 311 | assertEquals(7, Sample.ii); 312 | JVMFrame.putfield(Slot.of(8L), null, "ll", "J", Sample.class.getName(), null); 313 | assertEquals(8L, Sample.ll); 314 | JVMFrame.putfield(Slot.of("Goodbye World"), null, "ss", "Lsomething;", Sample.class.getName(), null); 315 | assertEquals("Goodbye World", Sample.ss); 316 | // Short types 317 | JVMFrame.putfield(Slot.of(-2), original, "i", "C", Sample.class.getName(), null); 318 | assertEquals((char) -2, original.i); 319 | JVMFrame.putfield(Slot.of(-3), original, "i", "S", Sample.class.getName(), null); 320 | assertEquals((short) -3, original.i); 321 | JVMFrame.putfield(Slot.of(-4), original, "i", "B", Sample.class.getName(), null); 322 | assertEquals((byte) -4, original.i); 323 | JVMFrame.putfield(Slot.of(true), original, "b", "Z", Sample.class.getName(), null); 324 | assertEquals(true, original.b); 325 | JVMFrame.putfield(Slot.of(-2), null, "ii", "C", Sample.class.getName(), null); 326 | assertEquals((char) -2, Sample.ii); 327 | JVMFrame.putfield(Slot.of(-3), null, "ii", "S", Sample.class.getName(), null); 328 | assertEquals((short) -3, Sample.ii); 329 | JVMFrame.putfield(Slot.of(-4), null, "ii", "B", Sample.class.getName(), null); 330 | assertEquals((byte) -4, Sample.ii); 331 | JVMFrame.putfield(Slot.of(true), null, "bb", "Z", Sample.class.getName(), null); 332 | assertEquals(true, Sample.bb); 333 | assertThrows(UnsupportedOperationException.class, 334 | () -> JVMFrame.getfield(null, "foobar", "V", "missing class", null)); 335 | assertThrows(UnsupportedOperationException.class, 336 | () -> JVMFrame.putfield(Slot.of(true), null, "foobar", "V", "missing class", null)); 337 | } 338 | @Test 339 | void testGet() { 340 | final Sample sample = new Sample(); 341 | final Slot sampleSlot = Slot.of(sample); 342 | expect(sample.i, javaClass, 0, sampleSlot, new byte[] { 343 | GETFIELD, 0x00, constant_field_i, IRETURN 344 | }); 345 | expect(sample.l, javaClass, 0, sampleSlot, new byte[] { 346 | GETFIELD, 0x00, constant_field_l, LRETURN 347 | }); 348 | expect(sample.f, javaClass, 0, sampleSlot, new byte[] { 349 | GETFIELD, 0x00, constant_field_f, FRETURN 350 | }); 351 | expect(sample.d, javaClass, 0, sampleSlot, new byte[] { 352 | GETFIELD, 0x00, constant_field_d, DRETURN 353 | }); 354 | expect(sample.s, javaClass, 0, sampleSlot, new byte[] { 355 | GETFIELD, 0x00, constant_field_s, ARETURN 356 | }); 357 | expect(Sample.ii, javaClass, 0, null, new byte[] { 358 | GETSTATIC, 0x00, constant_field_ii, IRETURN 359 | }); 360 | expect(Sample.ll, javaClass, 0, null, new byte[] { 361 | GETSTATIC, 0x00, constant_field_ll, LRETURN 362 | }); 363 | expect(Sample.ff, javaClass, 0, null, new byte[] { 364 | GETSTATIC, 0x00, constant_field_ff, FRETURN 365 | }); 366 | expect(Sample.dd, javaClass, 0, null, new byte[] { 367 | GETSTATIC, 0x00, constant_field_dd, DRETURN 368 | }); 369 | expect(Sample.ss, javaClass, 0, null, new byte[] { 370 | GETSTATIC, 0x00, constant_field_ss, ARETURN 371 | }); 372 | } 373 | @Test 374 | void testInstanceOf() { 375 | assertEquals(0, new JVMFrame(javaClass, 0, new byte[] { 376 | ACONST_NULL, INSTANCEOF, 0x00, constant_object, IRETURN 377 | }).run().intValue()); 378 | assertEquals(1, new JVMFrame(javaClass, 0, new byte[] { 379 | LDC, constant_email, INSTANCEOF, 0x00, constant_object, IRETURN 380 | }).run().intValue()); 381 | assertEquals(0, new JVMFrame(javaClass, 0, new byte[] { 382 | LDC, constant_email, INSTANCEOF, 0x00, constant_system, IRETURN 383 | }).run().intValue()); 384 | assertThrows(UnsupportedOperationException.class, () -> JVMFrame.instanceOf("foobar", "foobar")); 385 | } 386 | @Test 387 | void testInvoke() { 388 | assertNotNull(new JVMFrame(javaClass, 0, new byte[] { 389 | INVOKESTATIC, 0x00, constant_random, DRETURN 390 | }).run()); 391 | assertNull(new JVMFrame(javaClass, 0, new byte[] { 392 | INVOKEVIRTUAL, 0x00, constant_gc, RETURN 393 | }).run()); 394 | } 395 | @Test 396 | void testInvokeDirect() { 397 | final JVMFrame frame = new JVMFrame(javaClass, 0, new byte[] { 398 | RETURN 399 | }); 400 | final ClassLoader classLoader = getClass().getClassLoader(); 401 | frame.stack.push("Hello World"); 402 | final Slot resultInt = frame.invoke("length", "()I", String.class.getName(), classLoader); 403 | assertNotNull(resultInt); 404 | assertDoesNotThrow(resultInt::intValue); 405 | frame.stack.push(new Sample()); 406 | final Slot resultLong = frame.invoke("longy", "()J", Sample.class.getName(), classLoader); 407 | assertNotNull(resultLong); 408 | assertDoesNotThrow(resultLong::longValue); 409 | frame.stack.push(new Sample()); 410 | final Slot resultFloat = frame.invoke("floaty", "()F", Sample.class.getName(), classLoader); 411 | assertNotNull(resultFloat); 412 | assertDoesNotThrow(resultFloat::floatValue); 413 | final Slot resultDouble = frame.invoke("random", "()D", Math.class.getName(), classLoader); 414 | assertNotNull(resultDouble); 415 | assertDoesNotThrow(resultDouble::doubleValue); 416 | frame.stack.push("Alex"); 417 | final Slot stringSlot = frame.invoke("toUpperCase", "()Ljava/lang/String;", String.class.getName(), 418 | classLoader); 419 | assertNotNull(stringSlot); 420 | assertEquals("ALEX", stringSlot.toObject()); 421 | assertThrows(UnsupportedOperationException.class, 422 | () -> frame.invoke("toSnakeCase", "()Ljava/lang/String;", String.class.getName(), classLoader)); 423 | frame.stack.push(123); 424 | final Slot negatedSlot = frame.invoke("negateExact", "(I)I", Math.class.getName(), classLoader); 425 | assertEquals(-123, negatedSlot.intValue()); 426 | } 427 | @Test 428 | void testPut() { 429 | final Sample sample = new Sample(); 430 | final Slot sampleSlot = Slot.of(sample); 431 | sample.i = 0; 432 | sample.l = 0; 433 | sample.f = 0; 434 | sample.d = 0; 435 | sample.s = "Not Null"; 436 | expect((Object) null, javaClass, 0, sampleSlot, new byte[] { 437 | ICONST_1, PUTFIELD, 0x00, constant_field_i, RETURN 438 | }); 439 | assertEquals(1, sample.i); 440 | expect((Object) null, javaClass, 0, sampleSlot, new byte[] { 441 | LCONST_1, PUTFIELD, 0x00, constant_field_l, RETURN 442 | }); 443 | assertEquals(1, sample.l); 444 | expect((Object) null, javaClass, 0, sampleSlot, new byte[] { 445 | FCONST_1, PUTFIELD, 0x00, constant_field_f, RETURN 446 | }); 447 | assertEquals(1.0f, sample.f); 448 | expect((Object) null, javaClass, 0, sampleSlot, new byte[] { 449 | DCONST_1, PUTFIELD, 0x00, constant_field_d, RETURN 450 | }); 451 | assertEquals(1.0d, sample.d); 452 | expect((Object) null, javaClass, 0, sampleSlot, new byte[] { 453 | ACONST_NULL, PUTFIELD, 0x00, constant_field_s, RETURN 454 | }); 455 | assertNull(sample.s); 456 | Sample.ss = "Not null"; 457 | Sample.ii = 0; 458 | Sample.ll = 0; 459 | Sample.ff = 0; 460 | Sample.dd = 0; 461 | expect((Object) null, javaClass, 0, null, new byte[] { 462 | ICONST_1, PUTSTATIC, 0x00, constant_field_ii, RETURN 463 | }); 464 | assertEquals(1, Sample.ii); 465 | expect((Object) null, javaClass, 0, null, new byte[] { 466 | LCONST_1, PUTSTATIC, 0x00, constant_field_ll, RETURN 467 | }); 468 | assertEquals(1L, Sample.ll); 469 | expect((Object) null, javaClass, 0, null, new byte[] { 470 | FCONST_1, PUTSTATIC, 0x00, constant_field_ff, RETURN 471 | }); 472 | assertEquals(1.0f, Sample.ff); 473 | expect((Object) null, javaClass, 0, null, new byte[] { 474 | DCONST_1, PUTSTATIC, 0x00, constant_field_dd, RETURN 475 | }); 476 | assertEquals(1.0d, Sample.dd); 477 | expect((Object) null, javaClass, 0, null, new byte[] { 478 | ACONST_NULL, PUTSTATIC, 0x00, constant_field_ss, RETURN 479 | }); 480 | assertNull(Sample.ss); 481 | sample.reset(); 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /src/test/java/com/bandlem/jvm/jvmulator/JVMTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Alex Blewitt, Bandlem Ltd 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package com.bandlem.jvm.jvmulator; 10 | import static com.bandlem.jvm.jvmulator.Opcodes.*; 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertFalse; 13 | import static org.junit.jupiter.api.Assertions.assertNotNull; 14 | import static org.junit.jupiter.api.Assertions.assertNull; 15 | import static org.junit.jupiter.api.Assertions.assertThrows; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | import static org.junit.jupiter.api.Assertions.fail; 18 | import org.junit.jupiter.api.Test; 19 | import com.bandlem.jvm.jvmulator.classfile.JavaClass; 20 | class JVMTest { 21 | private void expect(final Class expected, final JavaClass javaClass, final int locals, 22 | final byte[] code) { 23 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 24 | assertThrows(expected, frame::run); 25 | } 26 | private void expect(final double result, final JavaClass javaClass, final int locals, final byte[] code) { 27 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 28 | assertEquals(result, frame.run().doubleValue()); 29 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 30 | } 31 | private void expect(final float result, final JavaClass javaClass, final int locals, final byte[] code) { 32 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 33 | assertEquals(result, frame.run().floatValue()); 34 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 35 | } 36 | private void expect(final int result, final JavaClass javaClass, final int locals, final byte[] code) { 37 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 38 | assertEquals(result, frame.run().intValue()); 39 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 40 | } 41 | private void expect(final long result, final JavaClass javaClass, final int locals, final byte[] code) { 42 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 43 | assertEquals(result, frame.run().longValue()); 44 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 45 | } 46 | private void expect(final Object result, final JavaClass javaClass, final int locals, final byte[] code) { 47 | final JVMFrame frame = new JVMFrame(javaClass, locals, code); 48 | final Slot slot = frame.run(); 49 | if (slot != null) { 50 | assertEquals(result, slot.referenceValue()); 51 | } 52 | assertEquals(slot, frame.getReturnValue()); 53 | assertThrows(IndexOutOfBoundsException.class, frame.stack::peek); 54 | } 55 | @Test 56 | void testArray() { 57 | for (final byte b : new byte[] { 58 | 'Z', 'B', 'S', 'C', 'I', 'L', 'F', 'D' 59 | }) { 60 | expect(2, null, 0, new byte[] { 61 | ICONST_2, NEWARRAY, b, ARRAYLENGTH, IRETURN 62 | }); 63 | } 64 | expect(1, null, 0, new byte[] { 65 | ICONST_1, NEWARRAY, 'Z', DUP, ICONST_0, ICONST_1, BASTORE, ICONST_0, BALOAD, IRETURN 66 | }); 67 | expect(0, null, 0, new byte[] { 68 | ICONST_1, NEWARRAY, 'Z', DUP, ICONST_0, ICONST_0, BASTORE, ICONST_0, BALOAD, IRETURN 69 | }); 70 | expect(1, null, 0, new byte[] { 71 | ICONST_1, NEWARRAY, 'B', DUP, ICONST_0, ICONST_1, BASTORE, ICONST_0, BALOAD, IRETURN 72 | }); 73 | expect(1, null, 0, new byte[] { 74 | ICONST_1, NEWARRAY, 'S', DUP, ICONST_0, ICONST_1, SASTORE, ICONST_0, SALOAD, IRETURN 75 | }); 76 | expect(1, null, 0, new byte[] { 77 | ICONST_1, NEWARRAY, 'C', DUP, ICONST_0, ICONST_1, CASTORE, ICONST_0, CALOAD, IRETURN 78 | }); 79 | expect(1, null, 0, new byte[] { 80 | ICONST_1, NEWARRAY, 'I', DUP, ICONST_0, ICONST_1, IASTORE, ICONST_0, IALOAD, IRETURN 81 | }); 82 | expect(1L, null, 0, new byte[] { 83 | ICONST_1, NEWARRAY, 'L', DUP, ICONST_0, LCONST_1, LASTORE, ICONST_0, LALOAD, LRETURN 84 | }); 85 | expect(1F, null, 0, new byte[] { 86 | ICONST_1, NEWARRAY, 'F', DUP, ICONST_0, FCONST_1, FASTORE, ICONST_0, FALOAD, FRETURN 87 | }); 88 | expect(1D, null, 0, new byte[] { 89 | ICONST_1, NEWARRAY, 'D', DUP, ICONST_0, DCONST_1, DASTORE, ICONST_0, DALOAD, DRETURN 90 | }); 91 | expect(IllegalStateException.class, null, 0, new byte[] { 92 | ICONST_0, NEWARRAY, '?' 93 | }); 94 | expect(IllegalStateException.class, null, 0, new byte[] { 95 | ACONST_NULL, ARRAYLENGTH 96 | }); 97 | expect(IllegalStateException.class, null, 0, new byte[] { 98 | ACONST_NULL, ICONST_0, AALOAD 99 | }); 100 | expect(IllegalStateException.class, null, 0, new byte[] { 101 | ACONST_NULL, ICONST_0, ICONST_1, AASTORE 102 | }); 103 | expect(2, null, 0, new byte[] { 104 | ICONST_1, NEWARRAY, 'I', DUP, ICONST_0, ICONST_2, IASTORE, ICONST_0, IALOAD, IRETURN 105 | }); 106 | } 107 | @Test 108 | void testBadArrayCombinations() { 109 | final byte[] types = new byte[] { 110 | 'Z', 'B', 'S', 'C', 'I', 'L', 'F', 'D' 111 | }; 112 | final byte[] load = new byte[] { 113 | BALOAD, BALOAD, SALOAD, CALOAD, IALOAD, LALOAD, FALOAD, DALOAD 114 | }; 115 | final byte[] one = new byte[] { 116 | ICONST_1, ICONST_1, ICONST_1, ICONST_1, ICONST_1, LCONST_1, FCONST_1, DCONST_1 117 | }; 118 | final byte[] store = new byte[] { 119 | BASTORE, BASTORE, SASTORE, CASTORE, IASTORE, LASTORE, FASTORE, DASTORE 120 | }; 121 | assertEquals(types.length, load.length); 122 | assertEquals(types.length, store.length); 123 | assertEquals(types.length, one.length); 124 | for (int type = 0; type < types.length; type++) { 125 | for (int target = 0; target < types.length; target++) { 126 | if (store[target] == store[type]) { 127 | continue; 128 | } 129 | expect(IllegalStateException.class, null, 0, new byte[] { 130 | ICONST_1, NEWARRAY, types[type], ICONST_0, one[target], store[target], RETURN 131 | }); 132 | } 133 | } 134 | for (int type = 0; type < types.length; type++) { 135 | for (int target = 0; target < types.length; target++) { 136 | if (load[target] == load[type]) { 137 | continue; 138 | } 139 | expect(IllegalStateException.class, null, 0, new byte[] { 140 | ICONST_1, NEWARRAY, types[type], ICONST_0, load[target], RETURN 141 | }); 142 | } 143 | } 144 | } 145 | @Test 146 | void testBadReturnStack() { 147 | expect(IllegalStateException.class, null, 0, new byte[] { 148 | ICONST_1, RETURN 149 | }); 150 | expect(IllegalStateException.class, null, 0, new byte[] { 151 | ICONST_1, ICONST_1, IRETURN 152 | }); 153 | expect(IllegalStateException.class, null, 0, new byte[] { 154 | ICONST_1, LCONST_1, LRETURN 155 | }); 156 | expect(IllegalStateException.class, null, 0, new byte[] { 157 | ICONST_1, FCONST_1, FRETURN 158 | }); 159 | expect(IllegalStateException.class, null, 0, new byte[] { 160 | ICONST_1, DCONST_1, DRETURN 161 | }); 162 | expect(IllegalStateException.class, null, 0, new byte[] { 163 | ICONST_1, ACONST_NULL, ARETURN 164 | }); 165 | expect((Object) null, null, 0, new byte[] { 166 | ACONST_NULL, ARETURN, NOP 167 | }); 168 | } 169 | @Test 170 | void testBadStackSwap() { 171 | expect(IllegalStateException.class, null, 0, new byte[] { 172 | LCONST_0, DCONST_0, SWAP, RETURN 173 | }); 174 | } 175 | @Test 176 | void testComparisons() { 177 | expect(0, null, 0, new byte[] { 178 | LCONST_1, LCONST_1, LCMP, IRETURN 179 | }); 180 | expect(1, null, 0, new byte[] { 181 | LCONST_0, LCONST_1, LCMP, IRETURN 182 | }); 183 | expect(-1, null, 0, new byte[] { 184 | LCONST_1, LCONST_0, LCMP, IRETURN 185 | }); 186 | expect(0, null, 0, new byte[] { 187 | FCONST_1, FCONST_1, FCMPL, IRETURN 188 | }); 189 | expect(1, null, 0, new byte[] { 190 | FCONST_0, FCONST_1, FCMPL, IRETURN 191 | }); 192 | expect(-1, null, 0, new byte[] { 193 | FCONST_1, FCONST_0, FCMPL, IRETURN 194 | }); 195 | expect(-1, null, 0, new byte[] { 196 | FCONST_0, FCONST_0, FDIV, FCONST_1, FCMPL, IRETURN 197 | }); 198 | expect(0, null, 0, new byte[] { 199 | DCONST_1, DCONST_1, DCMPL, IRETURN 200 | }); 201 | expect(1, null, 0, new byte[] { 202 | DCONST_0, DCONST_1, DCMPL, IRETURN 203 | }); 204 | expect(-1, null, 0, new byte[] { 205 | DCONST_1, DCONST_0, DCMPL, IRETURN 206 | }); 207 | expect(-1, null, 0, new byte[] { 208 | DCONST_0, DCONST_0, DDIV, DCONST_1, DCMPL, IRETURN 209 | }); 210 | expect(0, null, 0, new byte[] { 211 | FCONST_1, FCONST_1, FCMPG, IRETURN 212 | }); 213 | expect(1, null, 0, new byte[] { 214 | FCONST_0, FCONST_1, FCMPG, IRETURN 215 | }); 216 | expect(-1, null, 0, new byte[] { 217 | FCONST_1, FCONST_0, FCMPG, IRETURN 218 | }); 219 | expect(1, null, 0, new byte[] { 220 | FCONST_0, FCONST_0, FDIV, FCONST_1, FCMPG, IRETURN 221 | }); 222 | expect(0, null, 0, new byte[] { 223 | DCONST_1, DCONST_1, DCMPG, IRETURN 224 | }); 225 | expect(1, null, 0, new byte[] { 226 | DCONST_0, DCONST_1, DCMPG, IRETURN 227 | }); 228 | expect(-1, null, 0, new byte[] { 229 | DCONST_1, DCONST_0, DCMPG, IRETURN 230 | }); 231 | expect(1, null, 0, new byte[] { 232 | DCONST_0, DCONST_0, DDIV, DCONST_1, DCMPG, IRETURN 233 | }); 234 | } 235 | @Test 236 | void testConstantPush() { 237 | expect(10, null, 0, new byte[] { 238 | BIPUSH, 0x0a, IRETURN 239 | }); 240 | expect(314, null, 0, new byte[] { 241 | SIPUSH, 0x01, 0x3a, IRETURN 242 | }); 243 | } 244 | @Test 245 | void testConversions() { 246 | expect(1L, null, 0, new byte[] { 247 | ICONST_1, I2L, LRETURN 248 | }); 249 | expect(1F, null, 0, new byte[] { 250 | ICONST_1, I2F, FRETURN 251 | }); 252 | expect(1D, null, 0, new byte[] { 253 | ICONST_1, I2D, DRETURN 254 | }); 255 | expect((short) -1, null, 0, new byte[] { 256 | ICONST_M1, I2S, IRETURN 257 | }); 258 | expect((char) -1, null, 0, new byte[] { 259 | ICONST_M1, I2C, IRETURN 260 | }); 261 | expect((byte) -1, null, 0, new byte[] { 262 | ICONST_M1, I2B, IRETURN 263 | }); 264 | expect(1, null, 0, new byte[] { 265 | LCONST_1, L2I, IRETURN 266 | }); 267 | expect(1F, null, 0, new byte[] { 268 | LCONST_1, L2F, FRETURN 269 | }); 270 | expect(1D, null, 0, new byte[] { 271 | LCONST_1, L2D, DRETURN 272 | }); 273 | expect(1, null, 0, new byte[] { 274 | FCONST_1, F2I, IRETURN 275 | }); 276 | expect(1L, null, 0, new byte[] { 277 | FCONST_1, F2L, LRETURN 278 | }); 279 | expect(1D, null, 0, new byte[] { 280 | FCONST_1, F2D, DRETURN 281 | }); 282 | expect(1, null, 0, new byte[] { 283 | DCONST_1, D2I, IRETURN 284 | }); 285 | expect(1L, null, 0, new byte[] { 286 | DCONST_1, D2L, LRETURN 287 | }); 288 | expect(1F, null, 0, new byte[] { 289 | DCONST_1, D2F, FRETURN 290 | }); 291 | } 292 | @Test 293 | void testDouble() { 294 | expect(1.0D, null, 0, new byte[] { 295 | DCONST_0, DCONST_1, DADD, DRETURN 296 | }); 297 | expect(-1.0D, null, 0, new byte[] { 298 | DCONST_1, DCONST_0, DSUB, DRETURN 299 | }); 300 | expect(4.0D, null, 0, new byte[] { 301 | DCONST_1, DCONST_1, DADD, DCONST_1, DCONST_1, DADD, DMUL, DRETURN 302 | }); 303 | expect(1.0D, null, 0, new byte[] { 304 | DCONST_1, DCONST_1, DADD, DCONST_1, DCONST_1, DADD, DDIV, DRETURN 305 | }); 306 | expect(0.0D, null, 0, new byte[] { 307 | DCONST_1, DCONST_1, DADD, DCONST_1, DCONST_1, DADD, DREM, DRETURN 308 | }); 309 | expect(1.0D, null, 0, new byte[] { 310 | DCONST_1, DCONST_1, DADD, DCONST_1, DSUB, DNEG, DRETURN 311 | }); 312 | } 313 | @Test 314 | void testFloat() { 315 | expect(3.0F, null, 0, new byte[] { 316 | FCONST_0, FCONST_1, FCONST_2, FADD, FADD, FRETURN 317 | }); 318 | expect(4.0F, null, 0, new byte[] { 319 | FCONST_1, FCONST_1, FADD, FCONST_1, FCONST_1, FADD, FMUL, FRETURN 320 | }); 321 | expect(1.0F, null, 0, new byte[] { 322 | FCONST_1, FCONST_1, FADD, FCONST_1, FCONST_1, FADD, FDIV, FRETURN 323 | }); 324 | expect(0.0F, null, 0, new byte[] { 325 | FCONST_1, FCONST_1, FADD, FCONST_1, FCONST_1, FADD, FREM, FRETURN 326 | }); 327 | expect(1.0F, null, 0, new byte[] { 328 | FCONST_1, FCONST_1, FADD, FCONST_1, FSUB, FNEG, FRETURN 329 | }); 330 | } 331 | @Test 332 | void testGoto() { 333 | expect(4, null, 0, new byte[] { 334 | ICONST_1, GOTO, 0x00, 0x07, ICONST_2, GOTO, 0x00, 0x03, ICONST_3, IADD, IRETURN 335 | }); 336 | expect(4, null, 0, new byte[] { 337 | ICONST_1, GOTO_W, 0x00, 0x00, 0x00, 0x0b, ICONST_2, GOTO_W, 0x00, 0x00, 0x00, 0x05, ICONST_3, IADD, 338 | IRETURN 339 | }); 340 | expect(5, null, 0, new byte[] { 341 | GOTO, 0x00, 0x08, ICONST_1, ICONST_2, GOTO, 0x00, 0x06, GOTO, (byte) 0xff, (byte) -4, ICONST_3, IADD, 342 | IRETURN 343 | }); 344 | expect(5, null, 0, new byte[] { 345 | GOTO_W, 0x00, 0x00, 0x00, 0x0c, // 346 | ICONST_1, ICONST_2, // 347 | GOTO_W, 0x00, 0x00, 0x00, 0x0a, // 348 | GOTO_W, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) -6, // 349 | ICONST_3, IADD, IRETURN 350 | }); 351 | } 352 | @Test 353 | void testIf() { 354 | expect(2, null, 0, new byte[] { 355 | ICONST_0, IFEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 356 | }); 357 | expect(3, null, 0, new byte[] { 358 | ICONST_1, IFEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 359 | }); 360 | expect(3, null, 0, new byte[] { 361 | ICONST_0, IFNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 362 | }); 363 | expect(2, null, 0, new byte[] { 364 | ICONST_1, IFNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 365 | }); 366 | expect(2, null, 0, new byte[] { 367 | ICONST_0, IFLE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 368 | }); 369 | expect(3, null, 0, new byte[] { 370 | ICONST_1, IFLE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 371 | }); 372 | expect(3, null, 0, new byte[] { 373 | ICONST_0, IFLT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 374 | }); 375 | expect(2, null, 0, new byte[] { 376 | ICONST_M1, IFLE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 377 | }); 378 | expect(2, null, 0, new byte[] { 379 | ICONST_M1, IFLT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 380 | }); 381 | expect(2, null, 0, new byte[] { 382 | ICONST_0, IFGE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 383 | }); 384 | expect(3, null, 0, new byte[] { 385 | ICONST_M1, IFGE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 386 | }); 387 | expect(3, null, 0, new byte[] { 388 | ICONST_0, IFGT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 389 | }); 390 | expect(2, null, 0, new byte[] { 391 | ICONST_1, IFGE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 392 | }); 393 | expect(2, null, 0, new byte[] { 394 | ICONST_1, IFGT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 395 | }); 396 | } 397 | @Test 398 | void testIfCmp() { 399 | expect(2, null, 0, new byte[] { 400 | ICONST_0, ICONST_0, IF_ICMPEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 401 | }); 402 | expect(3, null, 0, new byte[] { 403 | ICONST_0, ICONST_1, IF_ICMPEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 404 | }); 405 | expect(3, null, 0, new byte[] { 406 | ICONST_0, ICONST_0, IF_ICMPNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 407 | }); 408 | expect(2, null, 0, new byte[] { 409 | ICONST_1, ICONST_0, IF_ICMPNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 410 | }); 411 | expect(2, null, 0, new byte[] { 412 | ICONST_0, ICONST_0, IF_ICMPLE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 413 | }); 414 | expect(3, null, 0, new byte[] { 415 | ICONST_0, ICONST_0, IF_ICMPLT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 416 | }); 417 | expect(3, null, 0, new byte[] { 418 | ICONST_M1, ICONST_0, IF_ICMPLE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 419 | IRETURN 420 | }); 421 | expect(2, null, 0, new byte[] { 422 | ICONST_1, ICONST_0, IF_ICMPLT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 423 | }); 424 | expect(2, null, 0, new byte[] { 425 | ICONST_0, ICONST_0, IF_ICMPGE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 426 | }); 427 | expect(3, null, 0, new byte[] { 428 | ICONST_0, ICONST_0, IF_ICMPGT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 429 | }); 430 | expect(3, null, 0, new byte[] { 431 | ICONST_1, ICONST_0, IF_ICMPGE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 432 | }); 433 | expect(2, null, 0, new byte[] { 434 | ICONST_M1, ICONST_0, IF_ICMPGT, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 435 | IRETURN 436 | }); 437 | expect(2, null, 0, new byte[] { 438 | ACONST_NULL, ACONST_NULL, IF_ACMPEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 439 | IRETURN 440 | }); 441 | expect(3, null, 0, new byte[] { 442 | ICONST_0, NEWARRAY, 'Z', ACONST_NULL, IF_ACMPEQ, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, 443 | ICONST_0, IADD, IRETURN 444 | }); 445 | expect(3, null, 0, new byte[] { 446 | ACONST_NULL, ACONST_NULL, IF_ACMPNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 447 | IRETURN 448 | }); 449 | expect(2, null, 0, new byte[] { 450 | ICONST_0, NEWARRAY, 'Z', ACONST_NULL, IF_ACMPNE, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, 451 | ICONST_0, IADD, IRETURN 452 | }); 453 | expect(2, null, 0, new byte[] { 454 | ACONST_NULL, IFNULL, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 455 | }); 456 | expect(3, null, 0, new byte[] { 457 | ACONST_NULL, IFNONNULL, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, IRETURN 458 | }); 459 | expect(3, null, 0, new byte[] { 460 | ICONST_0, NEWARRAY, 'Z', IFNULL, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 461 | IRETURN 462 | }); 463 | expect(2, null, 0, new byte[] { 464 | ICONST_0, NEWARRAY, 'Z', IFNONNULL, 0x00, 0x07, ICONST_3, GOTO, 0x00, 0x04, ICONST_2, ICONST_0, IADD, 465 | IRETURN 466 | }); 467 | } 468 | @Test 469 | void testInteger() { 470 | expect(24, null, 0, new byte[] { 471 | ICONST_4, ICONST_3, ICONST_1, ICONST_0, ICONST_M1, IADD, ISUB, IMUL, IMUL, INEG, IRETURN 472 | }); 473 | expect(2, null, 0, new byte[] { 474 | ICONST_5, NOP, ICONST_2, IREM, IRETURN 475 | }); 476 | expect(1, null, 0, new byte[] { 477 | ICONST_2, ICONST_5, IREM, IRETURN 478 | }); 479 | expect(0, null, 0, new byte[] { 480 | ICONST_5, ICONST_2, IDIV, IRETURN 481 | }); 482 | expect(2, null, 0, new byte[] { 483 | ICONST_2, ICONST_5, IDIV, IRETURN 484 | }); 485 | expect(2, null, 0, new byte[] { 486 | ICONST_1, ICONST_1, ISHL, IRETURN 487 | }); 488 | expect(-1, null, 0, new byte[] { 489 | ICONST_M1, ICONST_1, ISHR, IRETURN 490 | }); 491 | expect(-1 >>> 1, null, 0, new byte[] { 492 | ICONST_M1, ICONST_1, IUSHR, IRETURN 493 | }); 494 | expect(0, null, 0, new byte[] { 495 | ICONST_1, ICONST_1, ICONST_1, IADD, IAND, IRETURN 496 | }); 497 | expect(3, null, 0, new byte[] { 498 | ICONST_1, ICONST_1, ICONST_1, IADD, IOR, IRETURN 499 | }); 500 | expect(3, null, 0, new byte[] { 501 | ICONST_1, ICONST_1, ICONST_1, IADD, IXOR, IRETURN 502 | }); 503 | } 504 | @Test 505 | void testJSR() { 506 | expect(3, null, 2, new byte[] { 507 | ICONST_3, JSR, 0x00, 0x04, IRETURN, ASTORE_1, RET, 0x01 508 | }); 509 | expect(2, null, 2, new byte[] { 510 | ICONST_2, JSR_W, 0x00, 0x00, 0x00, 0x06, IRETURN, ASTORE_0, RET, 0x00 511 | }); 512 | } 513 | @Test 514 | void testLocals() { 515 | expect(1, null, 5, new byte[] { 516 | ICONST_1, ISTORE_0, ILOAD_0, ISTORE_1, ILOAD_1, ISTORE_2, ILOAD_2, ISTORE_3, ILOAD_3, ISTORE, 4, ILOAD, 517 | 4, IRETURN 518 | }); 519 | expect(1, null, 0xff, new byte[] { 520 | ICONST_1, ISTORE, (byte) 0xfe, ILOAD, (byte) 0xfe, IRETURN 521 | }); 522 | expect(2, null, 1, new byte[] { 523 | ICONST_1, ISTORE_0, IINC, 0, 1, ILOAD_0, IRETURN 524 | }); 525 | expect(0, null, 1, new byte[] { 526 | ICONST_1, ISTORE_0, IINC, 0, (byte) 0xff, ILOAD_0, IRETURN 527 | }); 528 | expect(1L, null, 5, new byte[] { 529 | LCONST_1, LSTORE_0, LLOAD_0, LSTORE_1, LLOAD_1, LSTORE_2, LLOAD_2, LSTORE_3, LLOAD_3, LSTORE, 4, LLOAD, 530 | 4, LRETURN 531 | }); 532 | expect(1L, null, 0xff, new byte[] { 533 | LCONST_1, LSTORE, (byte) 0xfe, LLOAD, (byte) 0xfe, LRETURN 534 | }); 535 | expect(1.0F, null, 5, new byte[] { 536 | FCONST_1, FSTORE_0, FLOAD_0, FSTORE_1, FLOAD_1, FSTORE_2, FLOAD_2, FSTORE_3, FLOAD_3, FSTORE, 4, FLOAD, 537 | 4, FRETURN 538 | }); 539 | expect(1.0F, null, 0xff, new byte[] { 540 | FCONST_1, FSTORE, (byte) 0xfe, FLOAD, (byte) 0xfe, FRETURN 541 | }); 542 | expect(1.0D, null, 5, new byte[] { 543 | DCONST_1, DSTORE_0, DLOAD_0, DSTORE_1, DLOAD_1, DSTORE_2, DLOAD_2, DSTORE_3, DLOAD_3, DSTORE, 4, DLOAD, 544 | 4, DRETURN 545 | }); 546 | expect(1.0D, null, 0xff, new byte[] { 547 | DCONST_1, DSTORE, (byte) 0xfe, DLOAD, (byte) 0xfe, DRETURN 548 | }); 549 | expect((Object) null, null, 5, new byte[] { 550 | ACONST_NULL, ASTORE_0, ALOAD_0, ASTORE_1, ALOAD_1, ASTORE_2, ALOAD_2, ASTORE_3, ALOAD_3, ASTORE, 4, 551 | ALOAD, 4, ARETURN 552 | }); 553 | expect((Object) null, null, 0xff, new byte[] { 554 | ACONST_NULL, ASTORE, (byte) 0xfe, ALOAD, (byte) 0xfe, ARETURN 555 | }); 556 | } 557 | @Test 558 | void testLong() { 559 | expect(1L, null, 0, new byte[] { 560 | LCONST_0, LCONST_1, LADD, LRETURN 561 | }); 562 | expect(4L, null, 0, new byte[] { 563 | LCONST_1, LCONST_1, LADD, LCONST_1, LCONST_1, LADD, LMUL, LRETURN 564 | }); 565 | expect(1L, null, 0, new byte[] { 566 | LCONST_1, LCONST_1, LADD, LCONST_1, LCONST_1, LADD, LDIV, LRETURN 567 | }); 568 | expect(0L, null, 0, new byte[] { 569 | LCONST_1, LCONST_1, LADD, LCONST_1, LCONST_1, LADD, LREM, LRETURN 570 | }); 571 | expect(1L, null, 0, new byte[] { 572 | LCONST_1, LCONST_1, LADD, LCONST_1, LSUB, LNEG, LRETURN 573 | }); 574 | expect(2L, null, 0, new byte[] { 575 | LCONST_1, ICONST_1, LSHL, LRETURN 576 | }); 577 | expect(-1L, null, 0, new byte[] { 578 | LCONST_1, LNEG, ICONST_1, LSHR, LRETURN 579 | }); 580 | expect(-1L >>> 1, null, 0, new byte[] { 581 | LCONST_1, LNEG, ICONST_1, LUSHR, LRETURN 582 | }); 583 | expect(0L, null, 0, new byte[] { 584 | LCONST_1, LCONST_1, LCONST_1, LADD, LAND, LRETURN 585 | }); 586 | expect(3L, null, 0, new byte[] { 587 | LCONST_1, LCONST_1, LCONST_1, LADD, LOR, LRETURN 588 | }); 589 | expect(3L, null, 0, new byte[] { 590 | LCONST_1, LCONST_1, LCONST_1, LADD, LXOR, LRETURN 591 | }); 592 | } 593 | @Test 594 | void testMisc() { 595 | expect(IllegalArgumentException.class, null, 0, new byte[] { 596 | BREAKPOINT 597 | }); 598 | expect(IllegalArgumentException.class, null, 0, new byte[] { 599 | IMPDEP1 600 | }); 601 | expect(IllegalArgumentException.class, null, 0, new byte[] { 602 | IMPDEP2 603 | }); 604 | expect(IllegalStateException.class, null, 0, new byte[] { 605 | (byte) 0xf0 606 | }); 607 | } 608 | @Test 609 | void testReturn() { 610 | expect(1, null, 0, new byte[] { 611 | ICONST_1, IRETURN 612 | }); 613 | expect(1L, null, 0, new byte[] { 614 | LCONST_1, LRETURN 615 | }); 616 | expect(1F, null, 0, new byte[] { 617 | FCONST_1, FRETURN 618 | }); 619 | expect(1D, null, 0, new byte[] { 620 | DCONST_1, DRETURN 621 | }); 622 | expect((Object) null, null, 0, new byte[] { 623 | ACONST_NULL, ARETURN 624 | }); 625 | expect((Object) null, null, 0, new byte[] { 626 | RETURN 627 | }); 628 | } 629 | @Test 630 | void testStackManipulation() { 631 | expect(1, null, 0, new byte[] { 632 | ICONST_1, ICONST_0, POP, IRETURN 633 | }); 634 | expect(1, null, 0, new byte[] { 635 | ICONST_1, ICONST_2, ICONST_3, POP2, IRETURN 636 | }); 637 | expect(1.0D, null, 0, new byte[] { 638 | DCONST_1, DCONST_0, POP2, DRETURN 639 | }); 640 | expect(-1, null, 0, new byte[] { 641 | ICONST_0, ICONST_1, SWAP, ISUB, IRETURN 642 | }); 643 | expect(2, null, 0, new byte[] { 644 | ICONST_1, DUP, IADD, IRETURN 645 | }); 646 | expect(2, null, 0, new byte[] { 647 | ICONST_1, ICONST_0, DUP_X1, IADD, IADD, IRETURN 648 | }); 649 | expect(2, null, 0, new byte[] { 650 | ICONST_1, ICONST_0, ICONST_0, DUP_X2, IADD, IADD, IADD, IRETURN 651 | }); 652 | expect(2.0D, null, 0, new byte[] { 653 | DCONST_1, DUP2, DADD, DRETURN 654 | }); 655 | expect(4, null, 0, new byte[] { 656 | ICONST_1, ICONST_1, DUP2, IADD, IADD, IADD, IRETURN 657 | }); 658 | expect(1.0D, null, 0, new byte[] { 659 | DCONST_1, ICONST_5, DUP2_X1, POP2, POP, DRETURN 660 | }); 661 | expect(2.0D, null, 0, new byte[] { 662 | DCONST_1, DCONST_0, DUP2_X2, DADD, DADD, DRETURN 663 | }); 664 | } 665 | @Test 666 | void testStep() { 667 | final JVMFrame frame = new JVMFrame(null, 0, new byte[] { 668 | ICONST_1, IRETURN 669 | }); 670 | assertEquals(0, frame.getLocals().length); 671 | assertEquals(0, frame.getStack().size()); 672 | assertEquals(0, frame.getPC()); 673 | assertNull(frame.getReturnValue()); 674 | assertTrue(frame.step()); 675 | assertEquals(0, frame.getLocals().length); 676 | assertEquals(1, frame.getStack().size()); 677 | assertEquals(1, frame.getPC()); 678 | assertNull(frame.getReturnValue()); 679 | assertFalse(frame.step()); 680 | assertEquals(2, frame.getPC()); 681 | assertNotNull(frame.getReturnValue()); 682 | } 683 | @Test 684 | void testSupportedBytecodes() { 685 | // Contains the high water mark of implemented features 686 | final int max = 256; 687 | for (int b = 0; b < max; b++) { 688 | final String name = Opcodes.name((byte) b); 689 | // Not defined bytecodes 690 | if (name == null) 691 | continue; 692 | // Not supported yet - object 693 | if (name.startsWith("get") || name.startsWith("put") || name.startsWith("new") || name.contains("anew")) 694 | continue; 695 | // Not supported yet - switch 696 | if (name.endsWith("switch")) 697 | continue; 698 | // Not supported yet - invoke, throw and monitor 699 | if (name.startsWith("invoke") || name.startsWith("monitor") || name.equals("athrow")) 700 | continue; 701 | // Not supported yet - type casting 702 | if (name.equals("instanceof") || name.equals("checkcast")) 703 | continue; 704 | // Not supported yet - wide 705 | if (name.equals("wide")) 706 | continue; 707 | try { 708 | final JVMFrame frame = new JVMFrame(null, 0, new byte[] { 709 | (byte) b 710 | }); 711 | frame.step(); 712 | assertTrue(frame.getPC() > 0); 713 | } catch (final Exception e) { 714 | final String message = e.getMessage(); 715 | final boolean unsupported = message.startsWith("Unknown opcode:"); 716 | if (unsupported) { 717 | fail(message); 718 | } 719 | } 720 | } 721 | } 722 | } 723 | --------------------------------------------------------------------------------