mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
ENT-1906: Modify the DJVM to wrap Java primitive types. (#4035)
* WIP - sandbox classloading * Fix handling of Appendable in the sandbox. * WIP - Load wrapped Java types into SandboxClassLoader. * Add explicit toDJVM() invocation after invoking Object.toString(). * Add simple ThreadLocal to the sandbox to complete AbstractStringBuilder. * Add support for Enum types inside the sandbox. * Simplify type conversions into and out of the sandbox. * Small refactors and comments to tidy up code. * Fix Enum support to include EnumSet and EnumMap. * Fix use of "$" in whitelist regexps. * Add extra methods (i.e. bridges) to stitched interfaces. * Rename ToDJVMStringWrapper to StringReturnTypeWrapper. * Support lambdas within the sandbox. * Fix mapping of java.lang.System into the sandbox. * Don't remap exception classes that we catch into sandbox classes. * Remove unnecessary "bootstrap" classes from the DJVM jar. * Ensure that Character.UnicodeScript is available inside the sandbox. * Tweak sandboxed implementations of System and Runtime. * Ensure that Character.UnicodeScript is loaded correctly as Enum type. * Disallow invoking methods of ClassLoader inside the sandbox. * Apply updates after review. * More review fixes.
This commit is contained in:
parent
0e68f26c0f
commit
825c544cac
@ -52,6 +52,20 @@ shadowJar {
|
||||
baseName 'corda-djvm'
|
||||
classifier ''
|
||||
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
|
||||
|
||||
// These particular classes are only needed to "bootstrap"
|
||||
// the compilation of the other sandbox classes. At runtime,
|
||||
// we will generate better versions from deterministic-rt.jar.
|
||||
exclude 'sandbox/java/lang/Appendable.class'
|
||||
exclude 'sandbox/java/lang/CharSequence.class'
|
||||
exclude 'sandbox/java/lang/Character\$*.class'
|
||||
exclude 'sandbox/java/lang/Comparable.class'
|
||||
exclude 'sandbox/java/lang/Enum.class'
|
||||
exclude 'sandbox/java/lang/Iterable.class'
|
||||
exclude 'sandbox/java/lang/StringBuffer.class'
|
||||
exclude 'sandbox/java/lang/StringBuilder.class'
|
||||
exclude 'sandbox/java/nio/**'
|
||||
exclude 'sandbox/java/util/**'
|
||||
}
|
||||
assemble.dependsOn shadowJar
|
||||
|
||||
|
19
djvm/src/main/java/sandbox/java/lang/Appendable.java
Normal file
19
djvm/src/main/java/sandbox/java/lang/Appendable.java
Normal file
@ -0,0 +1,19 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.Appendable]
|
||||
* to keep [sandbox.java.lang.StringBuilder], [sandbox.java.lang.StringBuffer]
|
||||
* and [sandbox.java.lang.String] honest.
|
||||
* Note that it does not extend [java.lang.Appendable].
|
||||
*/
|
||||
public interface Appendable {
|
||||
|
||||
Appendable append(CharSequence csq, int start, int end) throws IOException;
|
||||
|
||||
Appendable append(CharSequence csq) throws IOException;
|
||||
|
||||
Appendable append(char c) throws IOException;
|
||||
|
||||
}
|
100
djvm/src/main/java/sandbox/java/lang/Boolean.java
Normal file
100
djvm/src/main/java/sandbox/java/lang/Boolean.java
Normal file
@ -0,0 +1,100 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Boolean extends Object implements Comparable<Boolean>, Serializable {
|
||||
|
||||
public static final Boolean TRUE = new Boolean(true);
|
||||
public static final Boolean FALSE = new Boolean(false);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Boolean> TYPE = (Class) java.lang.Boolean.TYPE;
|
||||
|
||||
private final boolean value;
|
||||
|
||||
public Boolean(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Boolean(String s) {
|
||||
this(parseBoolean(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Boolean) && ((Boolean) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(boolean value) {
|
||||
return java.lang.Boolean.hashCode(value);
|
||||
}
|
||||
|
||||
public boolean booleanValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Boolean.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return toString(value);
|
||||
}
|
||||
|
||||
public static String toString(boolean b) {
|
||||
return String.valueOf(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Boolean fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Boolean other) {
|
||||
return compare(value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(boolean x, boolean y) {
|
||||
return java.lang.Boolean.compare(x, y);
|
||||
}
|
||||
|
||||
public static boolean parseBoolean(String s) {
|
||||
return java.lang.Boolean.parseBoolean(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Boolean valueOf(boolean b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public static Boolean valueOf(String s) {
|
||||
return valueOf(parseBoolean(s));
|
||||
}
|
||||
|
||||
public static boolean logicalAnd(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalAnd(a, b);
|
||||
}
|
||||
|
||||
public static boolean logicalOr(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalOr(a, b);
|
||||
}
|
||||
|
||||
public static boolean logicalXor(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalXor(a, b);
|
||||
}
|
||||
|
||||
public static Boolean toDJVM(java.lang.Boolean b) { return (b == null) ? null : new Boolean(b); }
|
||||
}
|
129
djvm/src/main/java/sandbox/java/lang/Byte.java
Normal file
129
djvm/src/main/java/sandbox/java/lang/Byte.java
Normal file
@ -0,0 +1,129 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Byte extends Number implements Comparable<Byte> {
|
||||
public static final byte MIN_VALUE = java.lang.Byte.MIN_VALUE;
|
||||
public static final byte MAX_VALUE = java.lang.Byte.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Byte.BYTES;
|
||||
public static final int SIZE = java.lang.Byte.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Byte> TYPE = (Class) java.lang.Byte.TYPE;
|
||||
|
||||
private final byte value;
|
||||
|
||||
public Byte(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Byte(String s) throws NumberFormatException {
|
||||
this.value = parseByte(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(byte b) {
|
||||
return java.lang.Byte.hashCode(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Byte) && ((Byte) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Byte.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Byte fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Byte other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(byte x, byte y) {
|
||||
return java.lang.Byte.compare(x, y);
|
||||
}
|
||||
|
||||
public static String toString(byte b) {
|
||||
return Integer.toString(b);
|
||||
}
|
||||
|
||||
public static Byte valueOf(byte b) {
|
||||
return new Byte(b);
|
||||
}
|
||||
|
||||
public static byte parseByte(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Byte.parseByte(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static byte parseByte(String s) throws NumberFormatException {
|
||||
return java.lang.Byte.parseByte(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Byte valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Byte valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Byte decode(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.decode(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static int toUnsignedInt(byte b) {
|
||||
return java.lang.Byte.toUnsignedInt(b);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(byte b) {
|
||||
return java.lang.Byte.toUnsignedLong(b);
|
||||
}
|
||||
|
||||
public static Byte toDJVM(java.lang.Byte b) {
|
||||
return (b == null) ? null : valueOf(b);
|
||||
}
|
||||
}
|
21
djvm/src/main/java/sandbox/java/lang/CharSequence.java
Normal file
21
djvm/src/main/java/sandbox/java/lang/CharSequence.java
Normal file
@ -0,0 +1,21 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.CharSequence]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public interface CharSequence extends java.lang.CharSequence {
|
||||
|
||||
@Override
|
||||
CharSequence subSequence(int start, int end);
|
||||
|
||||
@NotNull
|
||||
String toDJVMString();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.String toString();
|
||||
|
||||
}
|
481
djvm/src/main/java/sandbox/java/lang/Character.java
Normal file
481
djvm/src/main/java/sandbox/java/lang/Character.java
Normal file
@ -0,0 +1,481 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Character extends Object implements Comparable<Character>, Serializable {
|
||||
public static final int MIN_RADIX = java.lang.Character.MIN_RADIX;
|
||||
public static final int MAX_RADIX = java.lang.Character.MAX_RADIX;
|
||||
public static final char MIN_VALUE = java.lang.Character.MIN_VALUE;
|
||||
public static final char MAX_VALUE = java.lang.Character.MAX_VALUE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Character> TYPE = (Class) java.lang.Character.TYPE;
|
||||
|
||||
public static final byte UNASSIGNED = java.lang.Character.UNASSIGNED;
|
||||
public static final byte UPPERCASE_LETTER = java.lang.Character.UPPERCASE_LETTER;
|
||||
public static final byte LOWERCASE_LETTER = java.lang.Character.LOWERCASE_LETTER;
|
||||
public static final byte TITLECASE_LETTER = java.lang.Character.TITLECASE_LETTER;
|
||||
public static final byte MODIFIER_LETTER = java.lang.Character.MODIFIER_LETTER;
|
||||
public static final byte OTHER_LETTER = java.lang.Character.OTHER_LETTER;
|
||||
public static final byte NON_SPACING_MARK = java.lang.Character.NON_SPACING_MARK;
|
||||
public static final byte ENCLOSING_MARK = java.lang.Character.ENCLOSING_MARK;
|
||||
public static final byte COMBINING_SPACING_MARK = java.lang.Character.COMBINING_SPACING_MARK;
|
||||
public static final byte DECIMAL_DIGIT_NUMBER = java.lang.Character.DECIMAL_DIGIT_NUMBER;
|
||||
public static final byte LETTER_NUMBER = java.lang.Character.LETTER_NUMBER;
|
||||
public static final byte OTHER_NUMBER = java.lang.Character.OTHER_NUMBER;
|
||||
public static final byte SPACE_SEPARATOR = java.lang.Character.SPACE_SEPARATOR;
|
||||
public static final byte LINE_SEPARATOR = java.lang.Character.LINE_SEPARATOR;
|
||||
public static final byte PARAGRAPH_SEPARATOR = java.lang.Character.PARAGRAPH_SEPARATOR;
|
||||
public static final byte CONTROL = java.lang.Character.CONTROL;
|
||||
public static final byte FORMAT = java.lang.Character.FORMAT;
|
||||
public static final byte PRIVATE_USE = java.lang.Character.PRIVATE_USE;
|
||||
public static final byte SURROGATE = java.lang.Character.SURROGATE;
|
||||
public static final byte DASH_PUNCTUATION = java.lang.Character.DASH_PUNCTUATION;
|
||||
public static final byte START_PUNCTUATION = java.lang.Character.START_PUNCTUATION;
|
||||
public static final byte END_PUNCTUATION = java.lang.Character.END_PUNCTUATION;
|
||||
public static final byte CONNECTOR_PUNCTUATION = java.lang.Character.CONNECTOR_PUNCTUATION;
|
||||
public static final byte OTHER_PUNCTUATION = java.lang.Character.OTHER_PUNCTUATION;
|
||||
public static final byte MATH_SYMBOL = java.lang.Character.MATH_SYMBOL;
|
||||
public static final byte CURRENCY_SYMBOL = java.lang.Character.CURRENCY_SYMBOL;
|
||||
public static final byte MODIFIER_SYMBOL = java.lang.Character.MODIFIER_SYMBOL;
|
||||
public static final byte OTHER_SYMBOL = java.lang.Character.OTHER_SYMBOL;
|
||||
public static final byte INITIAL_QUOTE_PUNCTUATION = java.lang.Character.INITIAL_QUOTE_PUNCTUATION;
|
||||
public static final byte FINAL_QUOTE_PUNCTUATION = java.lang.Character.FINAL_QUOTE_PUNCTUATION;
|
||||
public static final byte DIRECTIONALITY_UNDEFINED = java.lang.Character.DIRECTIONALITY_UNDEFINED;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR;
|
||||
public static final byte DIRECTIONALITY_ARABIC_NUMBER = java.lang.Character.DIRECTIONALITY_ARABIC_NUMBER;
|
||||
public static final byte DIRECTIONALITY_COMMON_NUMBER_SEPARATOR = java.lang.Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_NONSPACING_MARK = java.lang.Character.DIRECTIONALITY_NONSPACING_MARK;
|
||||
public static final byte DIRECTIONALITY_BOUNDARY_NEUTRAL = java.lang.Character.DIRECTIONALITY_BOUNDARY_NEUTRAL;
|
||||
public static final byte DIRECTIONALITY_PARAGRAPH_SEPARATOR = java.lang.Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_SEGMENT_SEPARATOR = java.lang.Character.DIRECTIONALITY_SEGMENT_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_WHITESPACE = java.lang.Character.DIRECTIONALITY_WHITESPACE;
|
||||
public static final byte DIRECTIONALITY_OTHER_NEUTRALS = java.lang.Character.DIRECTIONALITY_OTHER_NEUTRALS;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE;
|
||||
public static final byte DIRECTIONALITY_POP_DIRECTIONAL_FORMAT = java.lang.Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT;
|
||||
public static final char MIN_HIGH_SURROGATE = java.lang.Character.MIN_HIGH_SURROGATE;
|
||||
public static final char MAX_HIGH_SURROGATE = java.lang.Character.MAX_HIGH_SURROGATE;
|
||||
public static final char MIN_LOW_SURROGATE = java.lang.Character.MIN_LOW_SURROGATE;
|
||||
public static final char MAX_LOW_SURROGATE = java.lang.Character.MAX_LOW_SURROGATE;
|
||||
public static final char MIN_SURROGATE = java.lang.Character.MIN_SURROGATE;
|
||||
public static final char MAX_SURROGATE = java.lang.Character.MAX_SURROGATE;
|
||||
public static final int MIN_SUPPLEMENTARY_CODE_POINT = java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
|
||||
public static final int MIN_CODE_POINT = java.lang.Character.MIN_CODE_POINT;
|
||||
public static final int MAX_CODE_POINT = java.lang.Character.MAX_CODE_POINT;
|
||||
public static final int BYTES = java.lang.Character.BYTES;
|
||||
public static final int SIZE = java.lang.Character.SIZE;
|
||||
|
||||
private final char value;
|
||||
|
||||
public Character(char c) {
|
||||
this.value = c;
|
||||
}
|
||||
|
||||
public char charValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(this.value);
|
||||
}
|
||||
|
||||
public static int hashCode(char value) {
|
||||
return java.lang.Character.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Character) && ((Character) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Character.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Character fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Character var1) {
|
||||
return compare(this.value, var1.value);
|
||||
}
|
||||
|
||||
public static int compare(char x, char y) {
|
||||
return java.lang.Character.compare(x, y);
|
||||
}
|
||||
|
||||
public static String toString(char c) {
|
||||
return String.toDJVM(java.lang.Character.toString(c));
|
||||
}
|
||||
|
||||
public static Character valueOf(char c) {
|
||||
return (c <= 127) ? Cache.cache[(int)c] : new Character(c);
|
||||
}
|
||||
|
||||
public static boolean isValidCodePoint(int codePoint) {
|
||||
return java.lang.Character.isValidCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isBmpCodePoint(int codePoint) {
|
||||
return java.lang.Character.isBmpCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isSupplementaryCodePoint(int codePoint) {
|
||||
return java.lang.Character.isSupplementaryCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isHighSurrogate(char ch) {
|
||||
return java.lang.Character.isHighSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isLowSurrogate(char ch) {
|
||||
return java.lang.Character.isLowSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isSurrogate(char ch) {
|
||||
return java.lang.Character.isSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isSurrogatePair(char high, char low) {
|
||||
return java.lang.Character.isSurrogatePair(high, low);
|
||||
}
|
||||
|
||||
public static int charCount(int codePoint) {
|
||||
return java.lang.Character.charCount(codePoint);
|
||||
}
|
||||
|
||||
public static int toCodePoint(char high, char low) {
|
||||
return java.lang.Character.toCodePoint(high, low);
|
||||
}
|
||||
|
||||
public static int codePointAt(CharSequence seq, int index) {
|
||||
return java.lang.Character.codePointAt(seq, index);
|
||||
}
|
||||
|
||||
public static int codePointAt(char[] a, int index) {
|
||||
return java.lang.Character.codePointAt(a, index);
|
||||
}
|
||||
|
||||
public static int codePointAt(char[] a, int index, int limit) {
|
||||
return java.lang.Character.codePointAt(a, index, limit);
|
||||
}
|
||||
|
||||
public static int codePointBefore(CharSequence seq, int index) {
|
||||
return java.lang.Character.codePointBefore(seq, index);
|
||||
}
|
||||
|
||||
public static int codePointBefore(char[] a, int index) {
|
||||
return java.lang.Character.codePointBefore(a, index);
|
||||
}
|
||||
|
||||
public static int codePointBefore(char[] a, int index, int limit) {
|
||||
return java.lang.Character.codePointBefore(a, index, limit);
|
||||
}
|
||||
|
||||
public static char highSurrogate(int codePoint) {
|
||||
return java.lang.Character.highSurrogate(codePoint);
|
||||
}
|
||||
|
||||
public static char lowSurrogate(int codePoint) {
|
||||
return java.lang.Character.lowSurrogate(codePoint);
|
||||
}
|
||||
|
||||
public static int toChars(int codePoint, char[] dst, int dstIndex) {
|
||||
return java.lang.Character.toChars(codePoint, dst, dstIndex);
|
||||
}
|
||||
|
||||
public static char[] toChars(int codePoint) {
|
||||
return java.lang.Character.toChars(codePoint);
|
||||
}
|
||||
|
||||
public static int codePointCount(CharSequence seq, int beginIndex, int endIndex) {
|
||||
return java.lang.Character.codePointCount(seq, beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public static int codePointCount(char[] a, int offset, int count) {
|
||||
return java.lang.Character.codePointCount(a, offset, count);
|
||||
}
|
||||
|
||||
public static int offsetByCodePoints(CharSequence seq, int index, int codePointOffset) {
|
||||
return java.lang.Character.offsetByCodePoints(seq, index, codePointOffset);
|
||||
}
|
||||
|
||||
public static int offsetByCodePoints(char[] a, int start, int count, int index, int codePointOffset) {
|
||||
return java.lang.Character.offsetByCodePoints(a, start, count, index, codePointOffset);
|
||||
}
|
||||
|
||||
public static boolean isLowerCase(char ch) {
|
||||
return java.lang.Character.isLowerCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isLowerCase(int codePoint) {
|
||||
return java.lang.Character.isLowerCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUpperCase(char ch) {
|
||||
return java.lang.Character.isUpperCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isUpperCase(int codePoint) {
|
||||
return java.lang.Character.isUpperCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isTitleCase(char ch) {
|
||||
return java.lang.Character.isTitleCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isTitleCase(int codePoint) {
|
||||
return java.lang.Character.isTitleCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isDigit(char ch) {
|
||||
return java.lang.Character.isDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isDigit(int codePoint) {
|
||||
return java.lang.Character.isDigit(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isDefined(char ch) {
|
||||
return java.lang.Character.isDefined(ch);
|
||||
}
|
||||
|
||||
public static boolean isDefined(int codePoint) {
|
||||
return java.lang.Character.isDefined(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isLetter(char ch) {
|
||||
return java.lang.Character.isLetter(ch);
|
||||
}
|
||||
|
||||
public static boolean isLetter(int codePoint) {
|
||||
return java.lang.Character.isLetter(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isLetterOrDigit(char ch) {
|
||||
return java.lang.Character.isLetterOrDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isLetterOrDigit(int codePoint) {
|
||||
return java.lang.Character.isLetterOrDigit(codePoint);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isJavaLetter(char ch) {
|
||||
return java.lang.Character.isJavaLetter(ch);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isJavaLetterOrDigit(char ch) {
|
||||
return java.lang.Character.isJavaLetterOrDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isAlphabetic(int codePoint) {
|
||||
return java.lang.Character.isAlphabetic(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isIdeographic(int codePoint) {
|
||||
return java.lang.Character.isIdeographic(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierStart(char ch) {
|
||||
return java.lang.Character.isJavaIdentifierStart(ch);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierStart(int codePoint) {
|
||||
return java.lang.Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierPart(char ch) {
|
||||
return java.lang.Character.isJavaIdentifierPart(ch);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierPart(int codePoint) {
|
||||
return java.lang.Character.isJavaIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierStart(char ch) {
|
||||
return java.lang.Character.isUnicodeIdentifierStart(ch);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierStart(int codePoint) {
|
||||
return java.lang.Character.isUnicodeIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierPart(char ch) {
|
||||
return java.lang.Character.isUnicodeIdentifierPart(ch);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierPart(int codePoint) {
|
||||
return java.lang.Character.isUnicodeIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isIdentifierIgnorable(char ch) {
|
||||
return java.lang.Character.isIdentifierIgnorable(ch);
|
||||
}
|
||||
|
||||
public static boolean isIdentifierIgnorable(int codePoint) {
|
||||
return java.lang.Character.isIdentifierIgnorable(codePoint);
|
||||
}
|
||||
|
||||
public static char toLowerCase(char ch) {
|
||||
return java.lang.Character.toLowerCase(ch);
|
||||
}
|
||||
|
||||
public static int toLowerCase(int codePoint) {
|
||||
return java.lang.Character.toLowerCase(codePoint);
|
||||
}
|
||||
|
||||
public static char toUpperCase(char ch) {
|
||||
return java.lang.Character.toUpperCase(ch);
|
||||
}
|
||||
|
||||
public static int toUpperCase(int codePoint) {
|
||||
return java.lang.Character.toUpperCase(codePoint);
|
||||
}
|
||||
|
||||
public static char toTitleCase(char ch) {
|
||||
return java.lang.Character.toTitleCase(ch);
|
||||
}
|
||||
|
||||
public static int toTitleCase(int codePoint) {
|
||||
return java.lang.Character.toTitleCase(codePoint);
|
||||
}
|
||||
|
||||
public static int digit(char ch, int radix) {
|
||||
return java.lang.Character.digit(ch, radix);
|
||||
}
|
||||
|
||||
public static int digit(int codePoint, int radix) {
|
||||
return java.lang.Character.digit(codePoint, radix);
|
||||
}
|
||||
|
||||
public static int getNumericValue(char ch) {
|
||||
return java.lang.Character.getNumericValue(ch);
|
||||
}
|
||||
|
||||
public static int getNumericValue(int codePoint) {
|
||||
return java.lang.Character.getNumericValue(codePoint);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isSpace(char ch) {
|
||||
return java.lang.Character.isSpace(ch);
|
||||
}
|
||||
|
||||
public static boolean isSpaceChar(char ch) {
|
||||
return java.lang.Character.isSpaceChar(ch);
|
||||
}
|
||||
|
||||
public static boolean isSpaceChar(int codePoint) {
|
||||
return java.lang.Character.isSpaceChar(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isWhitespace(char ch) {
|
||||
return java.lang.Character.isWhitespace(ch);
|
||||
}
|
||||
|
||||
public static boolean isWhitespace(int codePoint) {
|
||||
return java.lang.Character.isWhitespace(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isISOControl(char ch) {
|
||||
return java.lang.Character.isISOControl(ch);
|
||||
}
|
||||
|
||||
public static boolean isISOControl(int codePoint) {
|
||||
return java.lang.Character.isISOControl(codePoint);
|
||||
}
|
||||
|
||||
public static int getType(char ch) {
|
||||
return java.lang.Character.getType(ch);
|
||||
}
|
||||
|
||||
public static int getType(int codePoint) {
|
||||
return java.lang.Character.getType(codePoint);
|
||||
}
|
||||
|
||||
public static char forDigit(int digit, int radix) {
|
||||
return java.lang.Character.forDigit(digit, radix);
|
||||
}
|
||||
|
||||
public static byte getDirectionality(char ch) {
|
||||
return java.lang.Character.getDirectionality(ch);
|
||||
}
|
||||
|
||||
public static byte getDirectionality(int codePoint) {
|
||||
return java.lang.Character.getDirectionality(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isMirrored(char ch) {
|
||||
return java.lang.Character.isMirrored(ch);
|
||||
}
|
||||
|
||||
public static boolean isMirrored(int codePoint) {
|
||||
return java.lang.Character.isMirrored(codePoint);
|
||||
}
|
||||
|
||||
public static String getName(int codePoint) {
|
||||
return String.toDJVM(java.lang.Character.getName(codePoint));
|
||||
}
|
||||
|
||||
public static Character toDJVM(java.lang.Character c) {
|
||||
return (c == null) ? null : valueOf(c);
|
||||
}
|
||||
|
||||
// These three nested classes are placeholders to ensure that
|
||||
// the Character class bytecode is generated correctly. The
|
||||
// real classes will be loaded from the from the bootstrap jar
|
||||
// and then mapped into the sandbox.* namespace.
|
||||
public static final class UnicodeScript extends Enum<UnicodeScript> {
|
||||
private UnicodeScript(String name, int index) {
|
||||
super(name, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull UnicodeScript other) {
|
||||
throw new UnsupportedOperationException("Bootstrap implementation");
|
||||
}
|
||||
}
|
||||
public static final class UnicodeBlock extends Subset {}
|
||||
public static class Subset extends Object {}
|
||||
|
||||
/**
|
||||
* Keep pre-allocated instances of the first 128 characters
|
||||
* on the basis that these will be used most frequently.
|
||||
*/
|
||||
private static class Cache {
|
||||
private static final Character[] cache = new Character[128];
|
||||
|
||||
static {
|
||||
for (int c = 0; c < cache.length; ++c) {
|
||||
cache[c] = new Character((char) c);
|
||||
}
|
||||
}
|
||||
|
||||
private Cache() {}
|
||||
}
|
||||
}
|
8
djvm/src/main/java/sandbox/java/lang/Comparable.java
Normal file
8
djvm/src/main/java/sandbox/java/lang/Comparable.java
Normal file
@ -0,0 +1,8 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.Comparable]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public interface Comparable<T> extends java.lang.Comparable<T> {
|
||||
}
|
163
djvm/src/main/java/sandbox/java/lang/Double.java
Normal file
163
djvm/src/main/java/sandbox/java/lang/Double.java
Normal file
@ -0,0 +1,163 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Double extends Number implements Comparable<Double> {
|
||||
public static final double POSITIVE_INFINITY = java.lang.Double.POSITIVE_INFINITY;
|
||||
public static final double NEGATIVE_INFINITY = java.lang.Double.NEGATIVE_INFINITY;
|
||||
public static final double NaN = java.lang.Double.NaN;
|
||||
public static final double MAX_VALUE = java.lang.Double.MAX_VALUE;
|
||||
public static final double MIN_NORMAL = java.lang.Double.MIN_NORMAL;
|
||||
public static final double MIN_VALUE = java.lang.Double.MIN_VALUE;
|
||||
public static final int MAX_EXPONENT = java.lang.Double.MAX_EXPONENT;
|
||||
public static final int MIN_EXPONENT = java.lang.Double.MIN_EXPONENT;
|
||||
public static final int BYTES = java.lang.Double.BYTES;
|
||||
public static final int SIZE = java.lang.Double.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Double> TYPE = (Class) java.lang.Double.TYPE;
|
||||
|
||||
private final double value;
|
||||
|
||||
public Double(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Double(String s) throws NumberFormatException {
|
||||
this.value = parseDouble(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return java.lang.Double.isNaN(value);
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return isInfinite(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Double) && doubleToLongBits(((Double)other).value) == doubleToLongBits(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(double d) {
|
||||
return java.lang.Double.hashCode(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Double.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Double fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Double other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static String toString(double d) {
|
||||
return String.toDJVM(java.lang.Double.toString(d));
|
||||
}
|
||||
|
||||
public static String toHexString(double d) {
|
||||
return String.toDJVM(java.lang.Double.toHexString(d));
|
||||
}
|
||||
|
||||
public static Double valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Double.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Double valueOf(double d) {
|
||||
return new Double(d);
|
||||
}
|
||||
|
||||
public static double parseDouble(String s) throws NumberFormatException {
|
||||
return java.lang.Double.parseDouble(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static boolean isNaN(double d) {
|
||||
return java.lang.Double.isNaN(d);
|
||||
}
|
||||
|
||||
public static boolean isInfinite(double d) {
|
||||
return java.lang.Double.isInfinite(d);
|
||||
}
|
||||
|
||||
public static boolean isFinite(double d) {
|
||||
return java.lang.Double.isFinite(d);
|
||||
}
|
||||
|
||||
public static long doubleToLongBits(double d) {
|
||||
return java.lang.Double.doubleToLongBits(d);
|
||||
}
|
||||
|
||||
public static long doubleToRawLongBits(double d) {
|
||||
return java.lang.Double.doubleToRawLongBits(d);
|
||||
}
|
||||
|
||||
public static double longBitsToDouble(long bits) {
|
||||
return java.lang.Double.longBitsToDouble(bits);
|
||||
}
|
||||
|
||||
public static int compare(double d1, double d2) {
|
||||
return java.lang.Double.compare(d1, d2);
|
||||
}
|
||||
|
||||
public static double sum(double a, double b) {
|
||||
return java.lang.Double.sum(a, b);
|
||||
}
|
||||
|
||||
public static double max(double a, double b) {
|
||||
return java.lang.Double.max(a, b);
|
||||
}
|
||||
|
||||
public static double min(double a, double b) {
|
||||
return java.lang.Double.min(a, b);
|
||||
}
|
||||
|
||||
public static Double toDJVM(java.lang.Double d) {
|
||||
return (d == null) ? null : valueOf(d);
|
||||
}
|
||||
}
|
27
djvm/src/main/java/sandbox/java/lang/Enum.java
Normal file
27
djvm/src/main/java/sandbox/java/lang/Enum.java
Normal file
@ -0,0 +1,27 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class. We will load the actual Enum class at run-time.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable {
|
||||
|
||||
private final String name;
|
||||
private final int ordinal;
|
||||
|
||||
protected Enum(String name, int ordinal) {
|
||||
this.name = name;
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int ordinal() {
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
}
|
163
djvm/src/main/java/sandbox/java/lang/Float.java
Normal file
163
djvm/src/main/java/sandbox/java/lang/Float.java
Normal file
@ -0,0 +1,163 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Float extends Number implements Comparable<Float> {
|
||||
public static final float POSITIVE_INFINITY = java.lang.Float.POSITIVE_INFINITY;
|
||||
public static final float NEGATIVE_INFINITY = java.lang.Float.NEGATIVE_INFINITY;
|
||||
public static final float NaN = java.lang.Float.NaN;
|
||||
public static final float MAX_VALUE = java.lang.Float.MAX_VALUE;
|
||||
public static final float MIN_NORMAL = java.lang.Float.MIN_NORMAL;
|
||||
public static final float MIN_VALUE = java.lang.Float.MIN_VALUE;
|
||||
public static final int MAX_EXPONENT = java.lang.Float.MAX_EXPONENT;
|
||||
public static final int MIN_EXPONENT = java.lang.Float.MIN_EXPONENT;
|
||||
public static final int BYTES = java.lang.Float.BYTES;
|
||||
public static final int SIZE = java.lang.Float.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Float> TYPE = (Class) java.lang.Float.TYPE;
|
||||
|
||||
private final float value;
|
||||
|
||||
public Float(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Float(String s) throws NumberFormatException {
|
||||
this.value = parseFloat(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(float f) {
|
||||
return java.lang.Float.hashCode(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return other instanceof Float && floatToIntBits(((Float)other).value) == floatToIntBits(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Float.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Float fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Float other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return isNaN(value);
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return isInfinite(value);
|
||||
}
|
||||
|
||||
public static String toString(float f) {
|
||||
return String.valueOf(f);
|
||||
}
|
||||
|
||||
public static String toHexString(float f) {
|
||||
return String.toDJVM(java.lang.Float.toHexString(f));
|
||||
}
|
||||
|
||||
public static Float valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Float.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Float valueOf(float f) {
|
||||
return new Float(f);
|
||||
}
|
||||
|
||||
public static float parseFloat(String s) throws NumberFormatException {
|
||||
return java.lang.Float.parseFloat(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static boolean isNaN(float f) {
|
||||
return java.lang.Float.isNaN(f);
|
||||
}
|
||||
|
||||
public static boolean isInfinite(float f) {
|
||||
return java.lang.Float.isInfinite(f);
|
||||
}
|
||||
|
||||
public static boolean isFinite(float f) {
|
||||
return java.lang.Float.isFinite(f);
|
||||
}
|
||||
|
||||
public static int floatToIntBits(float f) {
|
||||
return java.lang.Float.floatToIntBits(f);
|
||||
}
|
||||
|
||||
public static int floatToRawIntBits(float f) {
|
||||
return java.lang.Float.floatToIntBits(f);
|
||||
}
|
||||
|
||||
public static float intBitsToFloat(int bits) {
|
||||
return java.lang.Float.intBitsToFloat(bits);
|
||||
}
|
||||
|
||||
public static int compare(float f1, float f2) {
|
||||
return java.lang.Float.compare(f1, f2);
|
||||
}
|
||||
|
||||
public static float sum(float a, float b) {
|
||||
return java.lang.Float.sum(a, b);
|
||||
}
|
||||
|
||||
public static float max(float a, float b) {
|
||||
return java.lang.Float.max(a, b);
|
||||
}
|
||||
|
||||
public static float min(float a, float b) {
|
||||
return java.lang.Float.min(a, b);
|
||||
}
|
||||
|
||||
public static Float toDJVM(java.lang.Float f) {
|
||||
return (f == null) ? null : valueOf(f);
|
||||
}
|
||||
}
|
241
djvm/src/main/java/sandbox/java/lang/Integer.java
Normal file
241
djvm/src/main/java/sandbox/java/lang/Integer.java
Normal file
@ -0,0 +1,241 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Integer extends Number implements Comparable<Integer> {
|
||||
|
||||
public static final int MIN_VALUE = java.lang.Integer.MIN_VALUE;
|
||||
public static final int MAX_VALUE = java.lang.Integer.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Integer.BYTES;
|
||||
public static final int SIZE = java.lang.Integer.SIZE;
|
||||
|
||||
static final int[] SIZE_TABLE = new int[] { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, MAX_VALUE };
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Integer> TYPE = (Class) java.lang.Integer.TYPE;
|
||||
|
||||
private final int value;
|
||||
|
||||
public Integer(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Integer(String s) throws NumberFormatException {
|
||||
this.value = parseInt(s, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(int i) {
|
||||
return java.lang.Integer.hashCode(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Integer) && (value == ((Integer) other).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Integer other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Integer fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String toString(int i, int radix) {
|
||||
return String.toDJVM(java.lang.Integer.toString(i, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(int i, int radix) {
|
||||
return String.toDJVM(java.lang.Integer.toUnsignedString(i, radix));
|
||||
}
|
||||
|
||||
public static String toHexString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toHexString(i));
|
||||
}
|
||||
|
||||
public static String toOctalString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toOctalString(i));
|
||||
}
|
||||
|
||||
public static String toBinaryString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toBinaryString(i));
|
||||
}
|
||||
|
||||
public static String toString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toString(i));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toUnsignedString(i));
|
||||
}
|
||||
|
||||
public static int parseInt(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Integer.parseInt(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static int parseInt(String s) throws NumberFormatException {
|
||||
return java.lang.Integer.parseInt(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static int parseUnsignedInt(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Integer.parseUnsignedInt(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static int parseUnsignedInt(String s) throws NumberFormatException {
|
||||
return java.lang.Integer.parseUnsignedInt(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Integer valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Integer.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Integer valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Integer.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Integer valueOf(int i) {
|
||||
return new Integer(i);
|
||||
}
|
||||
|
||||
public static Integer decode(String nm) throws NumberFormatException {
|
||||
return new Integer(java.lang.Integer.decode(String.fromDJVM(nm)));
|
||||
}
|
||||
|
||||
public static int compare(int x, int y) {
|
||||
return java.lang.Integer.compare(x, y);
|
||||
}
|
||||
|
||||
public static int compareUnsigned(int x, int y) {
|
||||
return java.lang.Integer.compareUnsigned(x, y);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(int x) {
|
||||
return java.lang.Integer.toUnsignedLong(x);
|
||||
}
|
||||
|
||||
public static int divideUnsigned(int dividend, int divisor) {
|
||||
return java.lang.Integer.divideUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static int remainderUnsigned(int dividend, int divisor) {
|
||||
return java.lang.Integer.remainderUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static int highestOneBit(int i) {
|
||||
return java.lang.Integer.highestOneBit(i);
|
||||
}
|
||||
|
||||
public static int lowestOneBit(int i) {
|
||||
return java.lang.Integer.lowestOneBit(i);
|
||||
}
|
||||
|
||||
public static int numberOfLeadingZeros(int i) {
|
||||
return java.lang.Integer.numberOfLeadingZeros(i);
|
||||
}
|
||||
|
||||
public static int numberOfTrailingZeros(int i) {
|
||||
return java.lang.Integer.numberOfTrailingZeros(i);
|
||||
}
|
||||
|
||||
public static int bitCount(int i) {
|
||||
return java.lang.Integer.bitCount(i);
|
||||
}
|
||||
|
||||
public static int rotateLeft(int i, int distance) {
|
||||
return java.lang.Integer.rotateLeft(i, distance);
|
||||
}
|
||||
|
||||
public static int rotateRight(int i, int distance) {
|
||||
return java.lang.Integer.rotateRight(i, distance);
|
||||
}
|
||||
|
||||
public static int reverse(int i) {
|
||||
return java.lang.Integer.reverse(i);
|
||||
}
|
||||
|
||||
public static int signum(int i) {
|
||||
return java.lang.Integer.signum(i);
|
||||
}
|
||||
|
||||
public static int reverseBytes(int i) {
|
||||
return java.lang.Integer.reverseBytes(i);
|
||||
}
|
||||
|
||||
public static int sum(int a, int b) {
|
||||
return java.lang.Integer.sum(a, b);
|
||||
}
|
||||
|
||||
public static int max(int a, int b) {
|
||||
return java.lang.Integer.max(a, b);
|
||||
}
|
||||
|
||||
public static int min(int a, int b) {
|
||||
return java.lang.Integer.min(a, b);
|
||||
}
|
||||
|
||||
public static Integer toDJVM(java.lang.Integer i) {
|
||||
return (i == null) ? null : valueOf(i);
|
||||
}
|
||||
|
||||
static int stringSize(final int number) {
|
||||
int i = 0;
|
||||
while (number > SIZE_TABLE[i]) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void getChars(final int number, int index, char[] buffer) {
|
||||
java.lang.String s = java.lang.Integer.toString(number);
|
||||
int length = s.length();
|
||||
|
||||
while (length > 0) {
|
||||
buffer[--index] = s.charAt(--length);
|
||||
}
|
||||
}
|
||||
}
|
15
djvm/src/main/java/sandbox/java/lang/Iterable.java
Normal file
15
djvm/src/main/java/sandbox/java/lang/Iterable.java
Normal file
@ -0,0 +1,15 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.Iterable]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public interface Iterable<T> extends java.lang.Iterable<T> {
|
||||
@Override
|
||||
@NotNull
|
||||
Iterator<T> iterator();
|
||||
}
|
239
djvm/src/main/java/sandbox/java/lang/Long.java
Normal file
239
djvm/src/main/java/sandbox/java/lang/Long.java
Normal file
@ -0,0 +1,239 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Long extends Number implements Comparable<Long> {
|
||||
|
||||
public static final long MIN_VALUE = java.lang.Long.MIN_VALUE;
|
||||
public static final long MAX_VALUE = java.lang.Long.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Long.BYTES;
|
||||
public static final int SIZE = java.lang.Long.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Long> TYPE = (Class) java.lang.Long.TYPE;
|
||||
|
||||
private final long value;
|
||||
|
||||
public Long(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Long(String s) throws NumberFormatException {
|
||||
this.value = parseLong(s, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Long) && ((Long) other).longValue() == value;
|
||||
}
|
||||
|
||||
public static int hashCode(long l) {
|
||||
return java.lang.Long.hashCode(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Long other) {
|
||||
return compare(value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(long x, long y) {
|
||||
return java.lang.Long.compare(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Long fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Long.toString(value);
|
||||
}
|
||||
|
||||
public static String toString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toString(l));
|
||||
}
|
||||
|
||||
public static String toString(long l, int radix) {
|
||||
return String.toDJVM(java.lang.Long.toString(l, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(long l, int radix) {
|
||||
return String.toDJVM(java.lang.Long.toUnsignedString(l, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toUnsignedString(l));
|
||||
}
|
||||
|
||||
public static String toHexString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toHexString(l));
|
||||
}
|
||||
|
||||
public static String toOctalString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toOctalString(l));
|
||||
}
|
||||
|
||||
public static String toBinaryString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toBinaryString(l));
|
||||
}
|
||||
|
||||
public static long parseLong(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Long.parseLong(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static long parseLong(String s) throws NumberFormatException {
|
||||
return java.lang.Long.parseLong(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static long parseUnsignedLong(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Long.parseUnsignedLong(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static long parseUnsignedLong(String s) throws NumberFormatException {
|
||||
return java.lang.Long.parseUnsignedLong(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Long valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Long valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Long valueOf(long l) {
|
||||
return new Long(l);
|
||||
}
|
||||
|
||||
public static Long decode(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.decode(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static int compareUnsigned(long x, long y) {
|
||||
return java.lang.Long.compareUnsigned(x, y);
|
||||
}
|
||||
|
||||
public static long divideUnsigned(long dividend, long divisor) {
|
||||
return java.lang.Long.divideUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static long remainderUnsigned(long dividend, long divisor) {
|
||||
return java.lang.Long.remainderUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static long highestOneBit(long l) {
|
||||
return java.lang.Long.highestOneBit(l);
|
||||
}
|
||||
|
||||
public static long lowestOneBit(long l) {
|
||||
return java.lang.Long.lowestOneBit(l);
|
||||
}
|
||||
|
||||
public static int numberOfLeadingZeros(long l) {
|
||||
return java.lang.Long.numberOfLeadingZeros(l);
|
||||
}
|
||||
|
||||
public static int numberOfTrailingZeros(long l) {
|
||||
return java.lang.Long.numberOfTrailingZeros(l);
|
||||
}
|
||||
|
||||
public static int bitCount(long l) {
|
||||
return java.lang.Long.bitCount(l);
|
||||
}
|
||||
|
||||
public static long rotateLeft(long i, int distance) {
|
||||
return java.lang.Long.rotateLeft(i, distance);
|
||||
}
|
||||
|
||||
public static long rotateRight(long i, int distance) {
|
||||
return java.lang.Long.rotateRight(i, distance);
|
||||
}
|
||||
|
||||
public static long reverse(long l) {
|
||||
return java.lang.Long.reverse(l);
|
||||
}
|
||||
|
||||
public static int signum(long l) {
|
||||
return java.lang.Long.signum(l);
|
||||
}
|
||||
|
||||
public static long reverseBytes(long l) {
|
||||
return java.lang.Long.reverseBytes(l);
|
||||
}
|
||||
|
||||
public static long sum(long a, long b) {
|
||||
return java.lang.Long.sum(a, b);
|
||||
}
|
||||
|
||||
public static long max(long a, long b) {
|
||||
return java.lang.Long.max(a, b);
|
||||
}
|
||||
|
||||
public static long min(long a, long b) {
|
||||
return java.lang.Long.min(a, b);
|
||||
}
|
||||
|
||||
public static Long toDJVM(java.lang.Long l) {
|
||||
return (l == null) ? null : valueOf(l);
|
||||
}
|
||||
|
||||
static int stringSize(final long number) {
|
||||
long l = 10;
|
||||
int i = 1;
|
||||
|
||||
while ((i < 19) && (number >= l)) {
|
||||
l *= 10;
|
||||
++i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void getChars(final long number, int index, char[] buffer) {
|
||||
java.lang.String s = java.lang.Long.toString(number);
|
||||
int length = s.length();
|
||||
|
||||
while (length > 0) {
|
||||
buffer[--index] = s.charAt(--length);
|
||||
}
|
||||
}
|
||||
}
|
21
djvm/src/main/java/sandbox/java/lang/Number.java
Normal file
21
djvm/src/main/java/sandbox/java/lang/Number.java
Normal file
@ -0,0 +1,21 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Number extends Object implements Serializable {
|
||||
|
||||
public abstract double doubleValue();
|
||||
public abstract float floatValue();
|
||||
public abstract long longValue();
|
||||
public abstract int intValue();
|
||||
public abstract short shortValue();
|
||||
public abstract byte byteValue();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return String.toDJVM(toString());
|
||||
}
|
||||
}
|
71
djvm/src/main/java/sandbox/java/lang/Object.java
Normal file
71
djvm/src/main/java/sandbox/java/lang/Object.java
Normal file
@ -0,0 +1,71 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sandbox.net.corda.djvm.rules.RuleViolationError;
|
||||
|
||||
public class Object {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return sandbox.java.lang.System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return toDJVMString().toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return String.toDJVM("sandbox.java.lang.Object@" + java.lang.Integer.toString(hashCode(), 16));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
java.lang.Object fromDJVM() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static java.lang.Object[] fromDJVM(java.lang.Object[] args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
java.lang.Object[] unwrapped = (java.lang.Object[]) java.lang.reflect.Array.newInstance(
|
||||
fromDJVM(args.getClass().getComponentType()), args.length
|
||||
);
|
||||
int i = 0;
|
||||
for (java.lang.Object arg : args) {
|
||||
unwrapped[i] = unwrap(arg);
|
||||
++i;
|
||||
}
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
private static java.lang.Object unwrap(java.lang.Object arg) {
|
||||
if (arg instanceof Object) {
|
||||
return ((Object) arg).fromDJVM();
|
||||
} else if (Object[].class.isAssignableFrom(arg.getClass())) {
|
||||
return fromDJVM((Object[]) arg);
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> fromDJVM(Class<?> type) {
|
||||
try {
|
||||
java.lang.String name = type.getName();
|
||||
return Class.forName(name.startsWith("sandbox.") ? name.substring(8) : name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuleViolationError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static java.util.Locale fromDJVM(sandbox.java.util.Locale locale) {
|
||||
return java.util.Locale.forLanguageTag(locale.toLanguageTag().fromDJVM());
|
||||
}
|
||||
|
||||
static java.nio.charset.Charset fromDJVM(sandbox.java.nio.charset.Charset charset) {
|
||||
return java.nio.charset.Charset.forName(charset.name().fromDJVM());
|
||||
}
|
||||
}
|
27
djvm/src/main/java/sandbox/java/lang/Runtime.java
Normal file
27
djvm/src/main/java/sandbox/java/lang/Runtime.java
Normal file
@ -0,0 +1,27 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class Runtime extends Object {
|
||||
private static final Runtime RUNTIME = new Runtime();
|
||||
|
||||
private Runtime() {}
|
||||
|
||||
public static Runtime getRuntime() {
|
||||
return RUNTIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything inside the sandbox is single-threaded.
|
||||
* @return 1
|
||||
*/
|
||||
public int availableProcessors() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void loadLibrary(String libraryName) {}
|
||||
|
||||
public void load(String fileName) {}
|
||||
|
||||
public void runFinalization() {}
|
||||
public void gc() {}
|
||||
}
|
128
djvm/src/main/java/sandbox/java/lang/Short.java
Normal file
128
djvm/src/main/java/sandbox/java/lang/Short.java
Normal file
@ -0,0 +1,128 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Short extends Number implements Comparable<Short> {
|
||||
public static final short MIN_VALUE = java.lang.Short.MIN_VALUE;
|
||||
public static final short MAX_VALUE = java.lang.Short.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Short.BYTES;
|
||||
public static final int SIZE = java.lang.Short.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Short> TYPE = (Class) java.lang.Short.TYPE;
|
||||
|
||||
private final short value;
|
||||
|
||||
public Short(short value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Short(String s) throws NumberFormatException {
|
||||
this.value = parseShort(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Short fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(short value) {
|
||||
return java.lang.Short.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Short) && ((Short) other).value == value;
|
||||
}
|
||||
|
||||
public int compareTo(@NotNull Short other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(short x, short y) {
|
||||
return java.lang.Short.compare(x, y);
|
||||
}
|
||||
|
||||
public static short reverseBytes(short value) {
|
||||
return java.lang.Short.reverseBytes(value);
|
||||
}
|
||||
|
||||
public static int toUnsignedInt(short x) {
|
||||
return java.lang.Short.toUnsignedInt(x);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(short x) {
|
||||
return java.lang.Short.toUnsignedLong(x);
|
||||
}
|
||||
|
||||
public static short parseShort(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Short.parseShort(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static short parseShort(String s) throws NumberFormatException {
|
||||
return java.lang.Short.parseShort(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Short valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Short valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Short valueOf(short s) {
|
||||
return new Short(s);
|
||||
}
|
||||
|
||||
public static Short decode(String nm) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.decode(String.fromDJVM(nm)));
|
||||
}
|
||||
|
||||
public static Short toDJVM(java.lang.Short i) {
|
||||
return (i == null) ? null : valueOf(i);
|
||||
}
|
||||
}
|
398
djvm/src/main/java/sandbox/java/lang/String.java
Normal file
398
djvm/src/main/java/sandbox/java/lang/String.java
Normal file
@ -0,0 +1,398 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sandbox.java.nio.charset.Charset;
|
||||
import sandbox.java.util.Comparator;
|
||||
import sandbox.java.util.Locale;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class String extends Object implements Comparable<String>, CharSequence, Serializable {
|
||||
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
|
||||
|
||||
private static class CaseInsensitiveComparator extends Object implements Comparator<String>, Serializable {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
return java.lang.String.CASE_INSENSITIVE_ORDER.compare(String.fromDJVM(s1), String.fromDJVM(s2));
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TRUE = new String("true");
|
||||
private static final String FALSE = new String("false");
|
||||
|
||||
private final java.lang.String value;
|
||||
|
||||
public String() {
|
||||
this.value = "";
|
||||
}
|
||||
|
||||
public String(java.lang.String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String(char value[]) {
|
||||
this.value = new java.lang.String(value);
|
||||
}
|
||||
|
||||
public String(char value[], int offset, int count) {
|
||||
this.value = new java.lang.String(value, offset, count);
|
||||
}
|
||||
|
||||
public String(int[] codePoints, int offset, int count) {
|
||||
this.value = new java.lang.String(codePoints, offset, count);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String(byte ascii[], int hibyte, int offset, int count) {
|
||||
this.value = new java.lang.String(ascii, hibyte, offset, count);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String(byte ascii[], int hibyte) {
|
||||
this.value = new java.lang.String(ascii, hibyte);
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length, String charsetName)
|
||||
throws UnsupportedEncodingException {
|
||||
this.value = new java.lang.String(bytes, offset, length, fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length, Charset charset) {
|
||||
this.value = new java.lang.String(bytes, offset, length, fromDJVM(charset));
|
||||
}
|
||||
|
||||
public String(byte bytes[], String charsetName)
|
||||
throws UnsupportedEncodingException {
|
||||
this.value = new java.lang.String(bytes, fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public String(byte bytes[], Charset charset) {
|
||||
this.value = new java.lang.String(bytes, fromDJVM(charset));
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length) {
|
||||
this.value = new java.lang.String(bytes, offset, length);
|
||||
}
|
||||
|
||||
public String(byte bytes[]) {
|
||||
this.value = new java.lang.String(bytes);
|
||||
}
|
||||
|
||||
public String(StringBuffer buffer) {
|
||||
this.value = buffer.toString();
|
||||
}
|
||||
|
||||
public String(StringBuilder builder) {
|
||||
this.value = builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return value.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return value.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return value.isEmpty();
|
||||
}
|
||||
|
||||
public int codePointAt(int index) {
|
||||
return value.codePointAt(index);
|
||||
}
|
||||
|
||||
public int codePointBefore(int index) {
|
||||
return value.codePointBefore(index);
|
||||
}
|
||||
|
||||
public int codePointCount(int beginIndex, int endIndex) {
|
||||
return value.codePointCount(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public int offsetByCodePoints(int index, int codePointOffset) {
|
||||
return value.offsetByCodePoints(index, codePointOffset);
|
||||
}
|
||||
|
||||
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
|
||||
value.getChars(srcBegin, srcEnd, dst, dstBegin);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
|
||||
value.getBytes(srcBegin, srcEnd, dst, dstBegin);
|
||||
}
|
||||
|
||||
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
|
||||
return value.getBytes(fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public byte[] getBytes(Charset charset) {
|
||||
return value.getBytes(fromDJVM(charset));
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return value.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof String) && ((String) other).value.equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.String fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean contentEquals(StringBuffer sb) {
|
||||
return value.contentEquals((CharSequence) sb);
|
||||
}
|
||||
|
||||
public boolean contentEquals(CharSequence cs) {
|
||||
return value.contentEquals(cs);
|
||||
}
|
||||
|
||||
public boolean equalsIgnoreCase(String anotherString) {
|
||||
return value.equalsIgnoreCase(fromDJVM(anotherString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return toDJVM((java.lang.String) value.subSequence(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull String other) {
|
||||
return value.compareTo(other.toString());
|
||||
}
|
||||
|
||||
public int compareToIgnoreCase(String str) {
|
||||
return value.compareToIgnoreCase(fromDJVM(str));
|
||||
}
|
||||
|
||||
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
|
||||
return value.regionMatches(toffset, fromDJVM(other), ooffset, len);
|
||||
}
|
||||
|
||||
public boolean regionMatches(boolean ignoreCase, int toffset,
|
||||
String other, int ooffset, int len) {
|
||||
return value.regionMatches(ignoreCase, toffset, fromDJVM(other), ooffset, len);
|
||||
}
|
||||
|
||||
public boolean startsWith(String prefix, int toffset) {
|
||||
return value.startsWith(fromDJVM(prefix), toffset);
|
||||
}
|
||||
|
||||
public boolean startsWith(String prefix) {
|
||||
return value.startsWith(fromDJVM(prefix));
|
||||
}
|
||||
|
||||
public boolean endsWith(String suffix) {
|
||||
return value.endsWith(fromDJVM(suffix));
|
||||
}
|
||||
|
||||
public int indexOf(int ch) {
|
||||
return value.indexOf(ch);
|
||||
}
|
||||
|
||||
public int indexOf(int ch, int fromIndex) {
|
||||
return value.indexOf(ch, fromIndex);
|
||||
}
|
||||
|
||||
public int lastIndexOf(int ch) {
|
||||
return value.lastIndexOf(ch);
|
||||
}
|
||||
|
||||
public int lastIndexOf(int ch, int fromIndex) {
|
||||
return value.lastIndexOf(ch, fromIndex);
|
||||
}
|
||||
|
||||
public int indexOf(String str) {
|
||||
return value.indexOf(fromDJVM(str));
|
||||
}
|
||||
|
||||
public int indexOf(String str, int fromIndex) {
|
||||
return value.indexOf(fromDJVM(str), fromIndex);
|
||||
}
|
||||
|
||||
public int lastIndexOf(String str) {
|
||||
return value.lastIndexOf(fromDJVM(str));
|
||||
}
|
||||
|
||||
public int lastIndexOf(String str, int fromIndex) {
|
||||
return value.lastIndexOf(fromDJVM(str), fromIndex);
|
||||
}
|
||||
|
||||
public String substring(int beginIndex) {
|
||||
return toDJVM(value.substring(beginIndex));
|
||||
}
|
||||
|
||||
public String substring(int beginIndex, int endIndex) {
|
||||
return toDJVM(value.substring(beginIndex, endIndex));
|
||||
}
|
||||
|
||||
public String concat(String str) {
|
||||
return toDJVM(value.concat(fromDJVM(str)));
|
||||
}
|
||||
|
||||
public String replace(char oldChar, char newChar) {
|
||||
return toDJVM(value.replace(oldChar, newChar));
|
||||
}
|
||||
|
||||
public boolean matches(String regex) {
|
||||
return value.matches(fromDJVM(regex));
|
||||
}
|
||||
|
||||
public boolean contains(CharSequence s) {
|
||||
return value.contains(s);
|
||||
}
|
||||
|
||||
public String replaceFirst(String regex, String replacement) {
|
||||
return toDJVM(value.replaceFirst(fromDJVM(regex), fromDJVM(replacement)));
|
||||
}
|
||||
|
||||
public String replaceAll(String regex, String replacement) {
|
||||
return toDJVM(value.replaceAll(fromDJVM(regex), fromDJVM(replacement)));
|
||||
}
|
||||
|
||||
public String replace(CharSequence target, CharSequence replacement) {
|
||||
return toDJVM(value.replace(target, replacement));
|
||||
}
|
||||
|
||||
public String[] split(String regex, int limit) {
|
||||
return toDJVM(value.split(fromDJVM(regex), limit));
|
||||
}
|
||||
|
||||
public String[] split(String regex) {
|
||||
return toDJVM(value.split(fromDJVM(regex)));
|
||||
}
|
||||
|
||||
public String toLowerCase(Locale locale) {
|
||||
return toDJVM(value.toLowerCase(fromDJVM(locale)));
|
||||
}
|
||||
|
||||
public String toLowerCase() {
|
||||
return toDJVM(value.toLowerCase());
|
||||
}
|
||||
|
||||
public String toUpperCase(Locale locale) {
|
||||
return toDJVM(value.toUpperCase(fromDJVM(locale)));
|
||||
}
|
||||
|
||||
public String toUpperCase() {
|
||||
return toDJVM(value.toUpperCase());
|
||||
}
|
||||
|
||||
public String trim() {
|
||||
return toDJVM(value.trim());
|
||||
}
|
||||
|
||||
public char[] toCharArray() {
|
||||
return value.toCharArray();
|
||||
}
|
||||
|
||||
public static String format(String format, java.lang.Object... args) {
|
||||
return toDJVM(java.lang.String.format(fromDJVM(format), fromDJVM(args)));
|
||||
}
|
||||
|
||||
public static String format(Locale locale, String format, java.lang.Object... args) {
|
||||
return toDJVM(java.lang.String.format(fromDJVM(locale), fromDJVM(format), fromDJVM(args)));
|
||||
}
|
||||
|
||||
public static String join(CharSequence delimiter, CharSequence... elements) {
|
||||
return toDJVM(java.lang.String.join(delimiter, elements));
|
||||
}
|
||||
|
||||
public static String join(CharSequence delimiter,
|
||||
Iterable<? extends CharSequence> elements) {
|
||||
return toDJVM(java.lang.String.join(delimiter, elements));
|
||||
}
|
||||
|
||||
public static String valueOf(java.lang.Object obj) {
|
||||
return (obj instanceof Object) ? ((Object) obj).toDJVMString() : toDJVM(java.lang.String.valueOf(obj));
|
||||
}
|
||||
|
||||
public static String valueOf(char data[]) {
|
||||
return toDJVM(java.lang.String.valueOf(data));
|
||||
}
|
||||
|
||||
public static String valueOf(char data[], int offset, int count) {
|
||||
return toDJVM(java.lang.String.valueOf(data, offset, count));
|
||||
}
|
||||
|
||||
public static String copyValueOf(char data[], int offset, int count) {
|
||||
return toDJVM(java.lang.String.copyValueOf(data, offset, count));
|
||||
}
|
||||
|
||||
public static String copyValueOf(char data[]) {
|
||||
return toDJVM(java.lang.String.copyValueOf(data));
|
||||
}
|
||||
|
||||
public static String valueOf(boolean b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public static String valueOf(char c) {
|
||||
return toDJVM(java.lang.String.valueOf(c));
|
||||
}
|
||||
|
||||
public static String valueOf(int i) {
|
||||
return toDJVM(java.lang.String.valueOf(i));
|
||||
}
|
||||
|
||||
public static String valueOf(long l) {
|
||||
return toDJVM(java.lang.String.valueOf(l));
|
||||
}
|
||||
|
||||
public static String valueOf(float f) {
|
||||
return toDJVM(java.lang.String.valueOf(f));
|
||||
}
|
||||
|
||||
public static String valueOf(double d) {
|
||||
return toDJVM(java.lang.String.valueOf(d));
|
||||
}
|
||||
|
||||
static String[] toDJVM(java.lang.String[] value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String[] result = new String[value.length];
|
||||
int i = 0;
|
||||
for (java.lang.String v : value) {
|
||||
result[i] = toDJVM(v);
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String toDJVM(java.lang.String value) {
|
||||
return (value == null) ? null : new String(value);
|
||||
}
|
||||
|
||||
public static java.lang.String fromDJVM(String value) {
|
||||
return (value == null) ? null : value.fromDJVM();
|
||||
}
|
||||
}
|
20
djvm/src/main/java/sandbox/java/lang/StringBuffer.java
Normal file
20
djvm/src/main/java/sandbox/java/lang/StringBuffer.java
Normal file
@ -0,0 +1,20 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.StringBuffer]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public abstract class StringBuffer extends Object implements CharSequence, Appendable, Serializable {
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(CharSequence seq);
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(CharSequence seq, int start, int end);
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(char c);
|
||||
|
||||
}
|
20
djvm/src/main/java/sandbox/java/lang/StringBuilder.java
Normal file
20
djvm/src/main/java/sandbox/java/lang/StringBuilder.java
Normal file
@ -0,0 +1,20 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.lang.StringBuilder]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public abstract class StringBuilder extends Object implements Appendable, CharSequence, Serializable {
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(CharSequence seq);
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(CharSequence seq, int start, int end);
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(char c);
|
||||
|
||||
}
|
28
djvm/src/main/java/sandbox/java/lang/System.java
Normal file
28
djvm/src/main/java/sandbox/java/lang/System.java
Normal file
@ -0,0 +1,28 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public final class System extends Object {
|
||||
|
||||
private System() {}
|
||||
|
||||
/*
|
||||
* This class is duplicated into every sandbox, where everything is single-threaded.
|
||||
*/
|
||||
private static final java.util.Map<java.lang.Integer, java.lang.Integer> objectHashCodes = new java.util.LinkedHashMap<>();
|
||||
private static int objectCounter = 0;
|
||||
|
||||
public static int identityHashCode(java.lang.Object obj) {
|
||||
int nativeHashCode = java.lang.System.identityHashCode(obj);
|
||||
// TODO Instead of using a magic offset below, one could take in a per-context seed
|
||||
return objectHashCodes.computeIfAbsent(nativeHashCode, i -> ++objectCounter + 0xfed_c0de);
|
||||
}
|
||||
|
||||
public static final String lineSeparator = String.toDJVM("\n");
|
||||
|
||||
public static void arraycopy(java.lang.Object src, int srcPos, java.lang.Object dest, int destPos, int length) {
|
||||
java.lang.System.arraycopy(src, srcPos, dest, destPos, length);
|
||||
}
|
||||
|
||||
public static void runFinalization() {}
|
||||
public static void gc() {}
|
||||
}
|
59
djvm/src/main/java/sandbox/java/lang/ThreadLocal.java
Normal file
59
djvm/src/main/java/sandbox/java/lang/ThreadLocal.java
Normal file
@ -0,0 +1,59 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import sandbox.java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Everything inside the sandbox is single-threaded, so this
|
||||
* implementation of ThreadLocal<T> is sufficient.
|
||||
* @param <T>
|
||||
*/
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class ThreadLocal<T> extends Object {
|
||||
|
||||
private T value;
|
||||
private boolean isSet;
|
||||
|
||||
public ThreadLocal() {
|
||||
}
|
||||
|
||||
protected T initialValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (!isSet) {
|
||||
set(initialValue());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
this.value = value;
|
||||
this.isSet = true;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
value = null;
|
||||
isSet = false;
|
||||
}
|
||||
|
||||
public static <V> ThreadLocal<V> withInitial(Supplier<? extends V> supplier) {
|
||||
return new SuppliedThreadLocal<>(supplier);
|
||||
}
|
||||
|
||||
// Stub class for compiling ThreadLocal. The sandbox will import the
|
||||
// actual SuppliedThreadLocal class at run-time. Having said that, we
|
||||
// still need a working implementation here for the sake of our tests.
|
||||
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
|
||||
private final Supplier<? extends T> supplier;
|
||||
|
||||
SuppliedThreadLocal(Supplier<? extends T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T initialValue() {
|
||||
return supplier.get();
|
||||
}
|
||||
}
|
||||
}
|
18
djvm/src/main/java/sandbox/java/nio/charset/Charset.java
Normal file
18
djvm/src/main/java/sandbox/java/nio/charset/Charset.java
Normal file
@ -0,0 +1,18 @@
|
||||
package sandbox.java.nio.charset;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.nio.charset.Charset]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Charset extends sandbox.java.lang.Object {
|
||||
private final sandbox.java.lang.String canonicalName;
|
||||
|
||||
protected Charset(sandbox.java.lang.String canonicalName, sandbox.java.lang.String[] aliases) {
|
||||
this.canonicalName = canonicalName;
|
||||
}
|
||||
|
||||
public final sandbox.java.lang.String name() {
|
||||
return canonicalName;
|
||||
}
|
||||
}
|
9
djvm/src/main/java/sandbox/java/util/Comparator.java
Normal file
9
djvm/src/main/java/sandbox/java/util/Comparator.java
Normal file
@ -0,0 +1,9 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.util.Comparator]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Comparator<T> extends java.util.Comparator<T> {
|
||||
}
|
13
djvm/src/main/java/sandbox/java/util/LinkedHashMap.java
Normal file
13
djvm/src/main/java/sandbox/java/util/LinkedHashMap.java
Normal file
@ -0,0 +1,13 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class to bootstrap us into the sandbox.
|
||||
*/
|
||||
public class LinkedHashMap<K, V> extends java.util.LinkedHashMap<K, V> implements Map<K, V> {
|
||||
public LinkedHashMap(int initialSize) {
|
||||
super(initialSize);
|
||||
}
|
||||
|
||||
public LinkedHashMap() {
|
||||
}
|
||||
}
|
9
djvm/src/main/java/sandbox/java/util/Locale.java
Normal file
9
djvm/src/main/java/sandbox/java/util/Locale.java
Normal file
@ -0,0 +1,9 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.util.Locale]
|
||||
* to allow us to compile [sandbox.java.lang.String].
|
||||
*/
|
||||
public abstract class Locale extends sandbox.java.lang.Object {
|
||||
public abstract sandbox.java.lang.String toLanguageTag();
|
||||
}
|
7
djvm/src/main/java/sandbox/java/util/Map.java
Normal file
7
djvm/src/main/java/sandbox/java/util/Map.java
Normal file
@ -0,0 +1,7 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class to bootstrap us into the sandbox.
|
||||
*/
|
||||
public interface Map<K, V> extends java.util.Map<K, V> {
|
||||
}
|
10
djvm/src/main/java/sandbox/java/util/function/Function.java
Normal file
10
djvm/src/main/java/sandbox/java/util/function/Function.java
Normal file
@ -0,0 +1,10 @@
|
||||
package sandbox.java.util.function;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.util.function.Function]
|
||||
* to allow us to compile [sandbox.Task].
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Function<T, R> {
|
||||
R apply(T item);
|
||||
}
|
10
djvm/src/main/java/sandbox/java/util/function/Supplier.java
Normal file
10
djvm/src/main/java/sandbox/java/util/function/Supplier.java
Normal file
@ -0,0 +1,10 @@
|
||||
package sandbox.java.util.function;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of [java.util.function.Supplier]
|
||||
* to allow us to compile [sandbox.java.lang.ThreadLocal].
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier<T> {
|
||||
T get();
|
||||
}
|
10
djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java
Normal file
10
djvm/src/main/java/sandbox/sun/misc/JavaLangAccess.java
Normal file
@ -0,0 +1,10 @@
|
||||
package sandbox.sun.misc;
|
||||
|
||||
import sandbox.java.lang.Enum;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface JavaLangAccess {
|
||||
|
||||
<E extends Enum<E>> E[] getEnumConstantsShared(Class<E> enumClass);
|
||||
|
||||
}
|
20
djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java
Normal file
20
djvm/src/main/java/sandbox/sun/misc/SharedSecrets.java
Normal file
@ -0,0 +1,20 @@
|
||||
package sandbox.sun.misc;
|
||||
|
||||
import sandbox.java.lang.Enum;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SharedSecrets extends sandbox.java.lang.Object {
|
||||
private static final JavaLangAccess javaLangAccess = new JavaLangAccessImpl();
|
||||
|
||||
private static class JavaLangAccessImpl implements JavaLangAccess {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <E extends Enum<E>> E[] getEnumConstantsShared(Class<E> enumClass) {
|
||||
return (E[]) sandbox.java.lang.DJVM.getEnumConstantsShared(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static JavaLangAccess getJavaLangAccess() {
|
||||
return javaLangAccess;
|
||||
}
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.code.EmitterModule
|
||||
import net.corda.djvm.code.ruleViolationError
|
||||
import net.corda.djvm.code.thresholdViolationError
|
||||
import net.corda.djvm.messages.Severity
|
||||
import net.corda.djvm.references.ClassModule
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.references.MemberModule
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import net.corda.djvm.source.BootstrapClassLoader
|
||||
import net.corda.djvm.source.SourceClassLoader
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import org.objectweb.asm.Type
|
||||
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
@ -41,19 +46,22 @@ class AnalysisConfiguration(
|
||||
|
||||
/**
|
||||
* Classes that have already been declared in the sandbox namespace and that should be made
|
||||
* available inside the sandboxed environment.
|
||||
* available inside the sandboxed environment. These classes belong to the application
|
||||
* classloader and so are shared across all sandboxes.
|
||||
*/
|
||||
val pinnedClasses: Set<String> = setOf(
|
||||
SANDBOXED_OBJECT,
|
||||
RuntimeCostAccounter.TYPE_NAME,
|
||||
ruleViolationError,
|
||||
thresholdViolationError
|
||||
) + additionalPinnedClasses
|
||||
val pinnedClasses: Set<String> = MANDATORY_PINNED_CLASSES + additionalPinnedClasses
|
||||
|
||||
/**
|
||||
* These interfaces are modified as they are mapped into the sandbox by
|
||||
* having their unsandboxed version "stitched in" as a super-interface.
|
||||
* And in some cases, we need to add some synthetic bridge methods as well.
|
||||
*/
|
||||
val stitchedInterfaces: Map<String, List<Member>> get() = STITCHED_INTERFACES
|
||||
|
||||
/**
|
||||
* Functionality used to resolve the qualified name and relevant information about a class.
|
||||
*/
|
||||
val classResolver: ClassResolver = ClassResolver(pinnedClasses, whitelist, SANDBOX_PREFIX)
|
||||
val classResolver: ClassResolver = ClassResolver(pinnedClasses, TEMPLATE_CLASSES, whitelist, SANDBOX_PREFIX)
|
||||
|
||||
private val bootstrapClassLoader = bootstrapJar?.let { BootstrapClassLoader(it, classResolver) }
|
||||
val supportingClassLoader = SourceClassLoader(classPath, classResolver, bootstrapClassLoader)
|
||||
@ -65,13 +73,114 @@ class AnalysisConfiguration(
|
||||
}
|
||||
}
|
||||
|
||||
fun isTemplateClass(className: String): Boolean = className in TEMPLATE_CLASSES
|
||||
fun isPinnedClass(className: String): Boolean = className in pinnedClasses
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The package name prefix to use for classes loaded into a sandbox.
|
||||
*/
|
||||
private const val SANDBOX_PREFIX: String = "sandbox/"
|
||||
|
||||
private const val SANDBOXED_OBJECT = SANDBOX_PREFIX + "java/lang/Object"
|
||||
/**
|
||||
* These class must belong to the application class loader.
|
||||
* They should already exist within the sandbox namespace.
|
||||
*/
|
||||
private val MANDATORY_PINNED_CLASSES: Set<String> = setOf(
|
||||
RuntimeCostAccounter.TYPE_NAME,
|
||||
ruleViolationError,
|
||||
thresholdViolationError
|
||||
)
|
||||
|
||||
/**
|
||||
* These classes will be duplicated into every sandbox's
|
||||
* classloader.
|
||||
*/
|
||||
private val TEMPLATE_CLASSES: Set<String> = setOf(
|
||||
java.lang.Boolean::class.java,
|
||||
java.lang.Byte::class.java,
|
||||
java.lang.Character::class.java,
|
||||
java.lang.Double::class.java,
|
||||
java.lang.Float::class.java,
|
||||
java.lang.Integer::class.java,
|
||||
java.lang.Long::class.java,
|
||||
java.lang.Number::class.java,
|
||||
java.lang.Runtime::class.java,
|
||||
java.lang.Short::class.java,
|
||||
java.lang.String::class.java,
|
||||
java.lang.String.CASE_INSENSITIVE_ORDER::class.java,
|
||||
java.lang.System::class.java,
|
||||
java.lang.ThreadLocal::class.java,
|
||||
kotlin.Any::class.java,
|
||||
sun.misc.JavaLangAccess::class.java,
|
||||
sun.misc.SharedSecrets::class.java
|
||||
).sandboxed() + setOf(
|
||||
"sandbox/Task",
|
||||
"sandbox/java/lang/DJVM",
|
||||
"sandbox/sun/misc/SharedSecrets\$1",
|
||||
"sandbox/sun/misc/SharedSecrets\$JavaLangAccessImpl"
|
||||
)
|
||||
|
||||
/**
|
||||
* These interfaces will be modified as follows when
|
||||
* added to the sandbox:
|
||||
*
|
||||
* <code>interface sandbox.A extends A</code>
|
||||
*/
|
||||
private val STITCHED_INTERFACES: Map<String, List<Member>> = mapOf(
|
||||
sandboxed(CharSequence::class.java) to listOf(
|
||||
object : MethodBuilder(
|
||||
access = ACC_PUBLIC or ACC_SYNTHETIC or ACC_BRIDGE,
|
||||
className = "sandbox/java/lang/CharSequence",
|
||||
memberName = "subSequence",
|
||||
descriptor = "(II)Ljava/lang/CharSequence;"
|
||||
) {
|
||||
override fun writeBody(emitter: EmitterModule) = with(emitter) {
|
||||
pushObject(0)
|
||||
pushInteger(1)
|
||||
pushInteger(2)
|
||||
invokeInterface(className, memberName, "(II)L$className;")
|
||||
returnObject()
|
||||
}
|
||||
}.withBody()
|
||||
.build(),
|
||||
MethodBuilder(
|
||||
access = ACC_PUBLIC or ACC_ABSTRACT,
|
||||
className = "sandbox/java/lang/CharSequence",
|
||||
memberName = "toString",
|
||||
descriptor = "()Ljava/lang/String;"
|
||||
).build()
|
||||
),
|
||||
sandboxed(Comparable::class.java) to emptyList(),
|
||||
sandboxed(Comparator::class.java) to emptyList(),
|
||||
sandboxed(Iterable::class.java) to emptyList()
|
||||
)
|
||||
|
||||
private fun sandboxed(clazz: Class<*>) = SANDBOX_PREFIX + Type.getInternalName(clazz)
|
||||
private fun Set<Class<*>>.sandboxed(): Set<String> = map(Companion::sandboxed).toSet()
|
||||
}
|
||||
|
||||
private open class MethodBuilder(
|
||||
protected val access: Int,
|
||||
protected val className: String,
|
||||
protected val memberName: String,
|
||||
protected val descriptor: String) {
|
||||
private val bodies = mutableListOf<MethodBody>()
|
||||
|
||||
protected open fun writeBody(emitter: EmitterModule) {}
|
||||
|
||||
fun withBody(): MethodBuilder {
|
||||
bodies.add(::writeBody)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build() = Member(
|
||||
access = access,
|
||||
className = className,
|
||||
memberName = memberName,
|
||||
signature = descriptor,
|
||||
genericsDetails = "",
|
||||
body = bodies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +85,12 @@ open class ClassAndMemberVisitor(
|
||||
|
||||
/**
|
||||
* Process class after it has been fully traversed and analyzed.
|
||||
* The [classVisitor] has finished visiting all of the class's
|
||||
* existing elements (i.e. methods, fields, inner classes etc)
|
||||
* and is about to complete. However, it can still add new
|
||||
* elements to the class, if required.
|
||||
*/
|
||||
open fun visitClassEnd(clazz: ClassRepresentation) {}
|
||||
open fun visitClassEnd(classVisitor: ClassVisitor, clazz: ClassRepresentation) {}
|
||||
|
||||
/**
|
||||
* Extract the meta-data indicating the source file of the traversed class (i.e., where it is compiled from).
|
||||
@ -136,7 +140,7 @@ open class ClassAndMemberVisitor(
|
||||
*/
|
||||
protected fun shouldBeProcessed(className: String): Boolean {
|
||||
return !configuration.whitelist.inNamespace(className) &&
|
||||
className !in configuration.pinnedClasses
|
||||
!configuration.isPinnedClass(className)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +245,7 @@ open class ClassAndMemberVisitor(
|
||||
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
|
||||
.forEach(::recordTypeReference)
|
||||
captureExceptions {
|
||||
visitClassEnd(currentClass!!)
|
||||
visitClassEnd(this, currentClass!!)
|
||||
}
|
||||
super.visitEnd()
|
||||
}
|
||||
@ -385,8 +389,10 @@ open class ClassAndMemberVisitor(
|
||||
*/
|
||||
override fun visitCode() {
|
||||
tryReplaceMethodBody()
|
||||
visit(MethodEntry(method)) {
|
||||
super.visitCode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided field access instruction.
|
||||
@ -494,6 +500,15 @@ open class ClassAndMemberVisitor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform values loaded from the constants pool.
|
||||
*/
|
||||
override fun visitLdcInsn(value: Any) {
|
||||
visit(ConstantInstruction(value), defaultFirst = true) {
|
||||
super.visitLdcInsn(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish visiting this method, writing any new method body byte-code
|
||||
* if we haven't written it already. This would (presumably) only happen
|
||||
|
@ -26,6 +26,7 @@ import net.corda.djvm.code.asResourcePath
|
||||
*/
|
||||
class ClassResolver(
|
||||
private val pinnedClasses: Set<String>,
|
||||
private val templateClasses: Set<String>,
|
||||
private val whitelist: Whitelist,
|
||||
private val sandboxPrefix: String
|
||||
) {
|
||||
@ -83,7 +84,7 @@ class ClassResolver(
|
||||
* Reverse the resolution of a class name.
|
||||
*/
|
||||
fun reverse(resolvedClassName: String): String {
|
||||
if (resolvedClassName in pinnedClasses) {
|
||||
if (resolvedClassName in pinnedClasses || resolvedClassName in templateClasses) {
|
||||
return resolvedClassName
|
||||
}
|
||||
if (resolvedClassName.startsWith(sandboxPrefix)) {
|
||||
@ -103,10 +104,10 @@ class ClassResolver(
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve class name from a fully qualified name.
|
||||
* Resolve sandboxed class name from a fully qualified name.
|
||||
*/
|
||||
private fun resolveName(name: String): String {
|
||||
return if (isPinnedOrWhitelistedClass(name)) {
|
||||
return if (isPinnedOrWhitelistedClass(name) || name in templateClasses) {
|
||||
name
|
||||
} else {
|
||||
"$sandboxPrefix$name"
|
||||
@ -122,10 +123,10 @@ class ClassResolver(
|
||||
sandboxRegex.matches(name)
|
||||
}
|
||||
|
||||
private val sandboxRegex = "^$sandboxPrefix.*$".toRegex()
|
||||
private val sandboxRegex = "^$sandboxPrefix.*\$".toRegex()
|
||||
|
||||
companion object {
|
||||
private val complexArrayTypeRegex = "^(\\[+)L(.*);$".toRegex()
|
||||
private val complexArrayTypeRegex = "^(\\[+)L(.*);\$".toRegex()
|
||||
}
|
||||
|
||||
}
|
@ -89,36 +89,25 @@ open class Whitelist private constructor(
|
||||
* Enumerate all the entries of the whitelist.
|
||||
*/
|
||||
val items: Set<String>
|
||||
get() = textEntries + entries.map { it.pattern }
|
||||
get() = textEntries + entries.map(Regex::pattern)
|
||||
|
||||
companion object {
|
||||
private val everythingRegex = setOf(".*".toRegex())
|
||||
|
||||
private val minimumSet = setOf(
|
||||
"^java/lang/Boolean(\\..*)?$".toRegex(),
|
||||
"^java/lang/Byte(\\..*)?$".toRegex(),
|
||||
"^java/lang/Character(\\..*)?$".toRegex(),
|
||||
"^java/lang/Class(\\..*)?$".toRegex(),
|
||||
"^java/lang/ClassLoader(\\..*)?$".toRegex(),
|
||||
"^java/lang/Cloneable(\\..*)?$".toRegex(),
|
||||
"^java/lang/Comparable(\\..*)?$".toRegex(),
|
||||
"^java/lang/Double(\\..*)?$".toRegex(),
|
||||
"^java/lang/Enum(\\..*)?$".toRegex(),
|
||||
"^java/lang/Float(\\..*)?$".toRegex(),
|
||||
"^java/lang/Integer(\\..*)?$".toRegex(),
|
||||
"^java/lang/Iterable(\\..*)?$".toRegex(),
|
||||
"^java/lang/Long(\\..*)?$".toRegex(),
|
||||
"^java/lang/Number(\\..*)?$".toRegex(),
|
||||
"^java/lang/Object(\\..*)?$".toRegex(),
|
||||
"^java/lang/Override(\\..*)?$".toRegex(),
|
||||
"^java/lang/Short(\\..*)?$".toRegex(),
|
||||
"^java/lang/String(\\..*)?$".toRegex(),
|
||||
"^java/lang/ThreadDeath(\\..*)?$".toRegex(),
|
||||
"^java/lang/Throwable(\\..*)?$".toRegex(),
|
||||
"^java/lang/Void(\\..*)?$".toRegex(),
|
||||
"^java/lang/.*Error(\\..*)?$".toRegex(),
|
||||
"^java/lang/.*Exception(\\..*)?$".toRegex(),
|
||||
"^java/lang/reflect/Array(\\..*)?$".toRegex()
|
||||
"^java/lang/Class(\\..*)?\$".toRegex(),
|
||||
"^java/lang/ClassLoader(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Cloneable(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Object(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Override(\\..*)?\$".toRegex(),
|
||||
// TODO: sandbox exception handling!
|
||||
"^java/lang/StackTraceElement\$".toRegex(),
|
||||
"^java/lang/Throwable\$".toRegex(),
|
||||
"^java/lang/Void\$".toRegex(),
|
||||
"^java/lang/invoke/LambdaMetafactory\$".toRegex(),
|
||||
"^java/lang/invoke/MethodHandles(\\\$.*)?\$".toRegex(),
|
||||
"^java/lang/reflect/Array(\\..*)?\$".toRegex(),
|
||||
"^java/io/Serializable\$".toRegex()
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -2,26 +2,48 @@ package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.ClassAndMemberVisitor
|
||||
import net.corda.djvm.code.instructions.MethodEntry
|
||||
import net.corda.djvm.references.ClassRepresentation
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import net.corda.djvm.utilities.Processor
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* Helper class for applying a set of definition providers and emitters to a class or set of classes.
|
||||
*
|
||||
* @param classVisitor Class visitor to use when traversing the structure of classes.
|
||||
* @property configuration The configuration to use for class analysis.
|
||||
* @property definitionProviders A set of providers used to update the name or meta-data of classes and members.
|
||||
* @property emitters A set of code emitters used to modify and instrument method bodies.
|
||||
* @param emitters A set of code emitters used to modify and instrument method bodies.
|
||||
*/
|
||||
class ClassMutator(
|
||||
classVisitor: ClassVisitor,
|
||||
private val configuration: AnalysisConfiguration,
|
||||
private val definitionProviders: List<DefinitionProvider> = emptyList(),
|
||||
private val emitters: List<Emitter> = emptyList()
|
||||
emitters: List<Emitter> = emptyList()
|
||||
) : ClassAndMemberVisitor(configuration, classVisitor) {
|
||||
|
||||
/**
|
||||
* Internal [Emitter] to add static field initializers to
|
||||
* any class constructor method.
|
||||
*/
|
||||
private inner class PrependClassInitializer : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MethodEntry
|
||||
&& instruction.method.memberName == "<clinit>" && instruction.method.signature == "()V"
|
||||
&& initializers.isNotEmpty()) {
|
||||
writeByteCode(initializers)
|
||||
initializers.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val emitters: List<Emitter> = emitters + PrependClassInitializer()
|
||||
private val initializers = mutableListOf<MethodBody>()
|
||||
|
||||
/**
|
||||
* Tracks whether any modifications have been applied to any of the processed class(es) and pertinent members.
|
||||
*/
|
||||
@ -44,6 +66,29 @@ class ClassMutator(
|
||||
return super.visitClass(resultingClass)
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have some static fields to initialise, and haven't already added them
|
||||
* to an existing class initialiser block then we need to create one.
|
||||
*/
|
||||
override fun visitClassEnd(classVisitor: ClassVisitor, clazz: ClassRepresentation) {
|
||||
tryWriteClassInitializer(classVisitor)
|
||||
super.visitClassEnd(classVisitor, clazz)
|
||||
}
|
||||
|
||||
private fun tryWriteClassInitializer(classVisitor: ClassVisitor) {
|
||||
if (initializers.isNotEmpty()) {
|
||||
classVisitor.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null)?.also { mv ->
|
||||
mv.visitCode()
|
||||
EmitterModule(mv).writeByteCode(initializers)
|
||||
mv.visitInsn(RETURN)
|
||||
mv.visitMaxs(-1, -1)
|
||||
mv.visitEnd()
|
||||
}
|
||||
initializers.clear()
|
||||
hasBeenModified = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply definition providers to a method. This can be used to update the name or definition (pertinent meta-data)
|
||||
* of a class member.
|
||||
@ -71,6 +116,7 @@ class ClassMutator(
|
||||
}
|
||||
if (field != resultingField) {
|
||||
logger.trace("Field has been mutated {}", field)
|
||||
initializers += resultingField.body
|
||||
hasBeenModified = true
|
||||
}
|
||||
return super.visitField(clazz, resultingField)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import org.objectweb.asm.Label
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
@ -44,17 +45,9 @@ class EmitterModule(
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for loading an integer constant onto the stack.
|
||||
* Emit instruction for loading a constant onto the stack.
|
||||
*/
|
||||
fun loadConstant(constant: Int) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitLdcInsn(constant)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for loading a string constant onto the stack.
|
||||
*/
|
||||
fun loadConstant(constant: String) {
|
||||
fun loadConstant(constant: Any) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitLdcInsn(constant)
|
||||
}
|
||||
@ -67,6 +60,14 @@ class EmitterModule(
|
||||
methodVisitor.visitMethodInsn(INVOKESTATIC, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a virtual method.
|
||||
*/
|
||||
fun invokeVirtual(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a special method, e.g. a constructor or a method on a super-type.
|
||||
*/
|
||||
@ -82,6 +83,19 @@ class EmitterModule(
|
||||
invokeSpecial(Type.getInternalName(T::class.java), name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
fun invokeInterface(owner: String, name: String, descriptor: String) {
|
||||
methodVisitor.visitMethodInsn(INVOKEINTERFACE, owner, name, descriptor, true)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for storing a value into a static field.
|
||||
*/
|
||||
fun putStatic(owner: String, name: String, descriptor: String) {
|
||||
methodVisitor.visitFieldInsn(PUTSTATIC, owner, name, descriptor)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for popping one element off the stack.
|
||||
*/
|
||||
@ -98,11 +112,52 @@ class EmitterModule(
|
||||
methodVisitor.visitInsn(DUP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for pushing an object reference
|
||||
* from a register onto the stack.
|
||||
*/
|
||||
fun pushObject(regNum: Int) {
|
||||
methodVisitor.visitVarInsn(ALOAD, regNum)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for pushing an integer value
|
||||
* from a register onto the stack.
|
||||
*/
|
||||
fun pushInteger(regNum: Int) {
|
||||
methodVisitor.visitVarInsn(ILOAD, regNum)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions to rearrange the stack as follows:
|
||||
* [W1] [W3]
|
||||
* [W2] -> [W1]
|
||||
* [w3] [W2]
|
||||
*/
|
||||
fun raiseThirdWordToTop() {
|
||||
methodVisitor.visitInsn(DUP2_X1)
|
||||
methodVisitor.visitInsn(POP2)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions to rearrange the stack as follows:
|
||||
* [W1] [W2]
|
||||
* [W2] -> [W3]
|
||||
* [W3] [W1]
|
||||
*/
|
||||
fun sinkTopToThirdWord() {
|
||||
methodVisitor.visitInsn(DUP_X2)
|
||||
methodVisitor.visitInsn(POP)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a sequence of instructions for instantiating and throwing an exception based on the provided message.
|
||||
*/
|
||||
fun <T : Throwable> throwException(exceptionType: Class<T>, message: String) {
|
||||
hasEmittedCustomCode = true
|
||||
val exceptionName = Type.getInternalName(exceptionType)
|
||||
new(exceptionName)
|
||||
methodVisitor.visitInsn(DUP)
|
||||
@ -121,6 +176,14 @@ class EmitterModule(
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for a function that returns an object reference.
|
||||
*/
|
||||
fun returnObject() {
|
||||
methodVisitor.visitInsn(ARETURN)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions for a new line number.
|
||||
*/
|
||||
@ -131,6 +194,15 @@ class EmitterModule(
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bytecode from these [MethodBody] objects as provided.
|
||||
*/
|
||||
fun writeByteCode(bodies: Iterable<MethodBody>) {
|
||||
for (body in bodies) {
|
||||
body(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the code writer not to emit the default instruction.
|
||||
*/
|
||||
|
@ -13,3 +13,5 @@ val thresholdViolationError: String = Type.getInternalName(ThresholdViolationErr
|
||||
*/
|
||||
val String.asPackagePath: String get() = this.replace('/', '.')
|
||||
val String.asResourcePath: String get() = this.replace('.', '/')
|
||||
|
||||
val String.emptyAsNull: String? get() = if (isEmpty()) null else this
|
@ -0,0 +1,6 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
class ConstantInstruction(val value: Any) : Instruction(Opcodes.LDC)
|
@ -0,0 +1,9 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.references.Member
|
||||
|
||||
/**
|
||||
* Pseudo-instruction marking the beginning of a method.
|
||||
* @property method [Member] describing this method.
|
||||
*/
|
||||
class MethodEntry(val method: Member): NoOperationInstruction()
|
@ -5,6 +5,7 @@ import net.corda.djvm.analysis.AnalysisContext
|
||||
import net.corda.djvm.messages.Message
|
||||
import net.corda.djvm.references.ClassReference
|
||||
import net.corda.djvm.references.MemberReference
|
||||
import net.corda.djvm.references.ReferenceWithLocation
|
||||
import net.corda.djvm.rewiring.LoadedClass
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
||||
@ -67,12 +68,24 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
||||
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration).run {
|
||||
validate(context, classLoader, classSources)
|
||||
val loadedClass = classLoader.loadClassAndBytes(runnableClass, context)
|
||||
val instance = loadedClass.type.newInstance()
|
||||
val method = loadedClass.type.getMethod("apply", Any::class.java)
|
||||
|
||||
// Load the "entry-point" task class into the sandbox. This task will marshall
|
||||
// the input and outputs between Java types and sandbox wrapper types.
|
||||
val taskClass = Class.forName("sandbox.Task", false, classLoader)
|
||||
|
||||
// Create the user's task object inside the sandbox.
|
||||
val runnable = classLoader.loadForSandbox(runnableClass, context).type.newInstance()
|
||||
|
||||
// Fetch this sandbox's instance of Class<Function> so we can retrieve Task(Function)
|
||||
// and then instantiate the Task.
|
||||
val functionClass = Class.forName("sandbox.java.util.function.Function", false, classLoader)
|
||||
val task = taskClass.getDeclaredConstructor(functionClass).newInstance(runnable)
|
||||
|
||||
// Execute the task...
|
||||
val method = taskClass.getMethod("apply", Any::class.java)
|
||||
try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
method.invoke(instance, input) as? TOutput
|
||||
method.invoke(task, input) as? TOutput
|
||||
} catch (ex: InvocationTargetException) {
|
||||
throw ex.targetException
|
||||
}
|
||||
@ -101,7 +114,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
||||
fun load(classSource: ClassSource): LoadedClass {
|
||||
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
|
||||
val result = IsolatedTask("LoadClass", configuration).run {
|
||||
classLoader.loadClassAndBytes(classSource, context)
|
||||
classLoader.loadForSandbox(classSource, context)
|
||||
}
|
||||
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
|
||||
}
|
||||
@ -146,7 +159,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
||||
): ReferenceValidationSummary {
|
||||
processClassQueue(*classSources.toTypedArray()) { classSource, className ->
|
||||
val didLoad = try {
|
||||
classLoader.loadClassAndBytes(classSource, context)
|
||||
classLoader.loadForSandbox(classSource, context)
|
||||
true
|
||||
} catch (exception: SandboxClassLoadingException) {
|
||||
// Continue; all warnings and errors are captured in [context.messages]
|
||||
@ -155,7 +168,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
||||
if (didLoad) {
|
||||
context.classes[className]?.apply {
|
||||
context.references.referencesFromLocation(className)
|
||||
.map { it.reference }
|
||||
.map(ReferenceWithLocation::reference)
|
||||
.filterIsInstance<ClassReference>()
|
||||
.filter { it.className != className }
|
||||
.distinct()
|
||||
@ -201,6 +214,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = loggerFor<SandboxExecutor<TInput, TOutput>>()
|
||||
|
||||
private companion object {
|
||||
private val logger = loggerFor<SandboxExecutor<*, *>>()
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
package net.corda.djvm.rewiring
|
||||
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.AnalysisContext
|
||||
import net.corda.djvm.analysis.ClassAndMemberVisitor.Companion.API_VERSION
|
||||
import net.corda.djvm.code.ClassMutator
|
||||
import net.corda.djvm.code.EmitterModule
|
||||
import net.corda.djvm.code.emptyAsNull
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.commons.ClassRemapper
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
|
||||
/**
|
||||
* Functionality for rewrite parts of a class as it is being loaded.
|
||||
* Functionality for rewriting parts of a class as it is being loaded.
|
||||
*
|
||||
* @property configuration The configuration of the sandbox.
|
||||
* @property classLoader The class loader used to load the classes that are to be rewritten.
|
||||
* @property remapper A sandbox-aware remapper for inspecting and correcting type names and descriptors.
|
||||
*/
|
||||
open class ClassRewriter(
|
||||
private val configuration: SandboxConfiguration,
|
||||
private val classLoader: ClassLoader,
|
||||
private val remapper: SandboxRemapper = SandboxRemapper(configuration.analysisConfiguration.classResolver)
|
||||
private val classLoader: ClassLoader
|
||||
) {
|
||||
|
||||
/**
|
||||
@ -29,20 +32,53 @@ open class ClassRewriter(
|
||||
fun rewrite(reader: ClassReader, context: AnalysisContext): ByteCode {
|
||||
logger.debug("Rewriting class {}...", reader.className)
|
||||
val writer = SandboxClassWriter(reader, classLoader)
|
||||
val classRemapper = ClassRemapper(writer, remapper)
|
||||
val analysisConfiguration = configuration.analysisConfiguration
|
||||
val classRemapper = SandboxClassRemapper(InterfaceStitcher(writer, analysisConfiguration), analysisConfiguration)
|
||||
val visitor = ClassMutator(
|
||||
classRemapper,
|
||||
configuration.analysisConfiguration,
|
||||
analysisConfiguration,
|
||||
configuration.definitionProviders,
|
||||
configuration.emitters
|
||||
)
|
||||
visitor.analyze(reader, context, options = ClassReader.EXPAND_FRAMES)
|
||||
val hasBeenModified = visitor.hasBeenModified
|
||||
return ByteCode(writer.toByteArray(), hasBeenModified)
|
||||
return ByteCode(writer.toByteArray(), visitor.hasBeenModified)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val logger = loggerFor<ClassRewriter>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra visitor that is applied after [SandboxRemapper]. This "stitches" the original
|
||||
* unmapped interface as a super-interface of the mapped version.
|
||||
*/
|
||||
private class InterfaceStitcher(parent: ClassVisitor, private val configuration: AnalysisConfiguration)
|
||||
: ClassVisitor(API_VERSION, parent)
|
||||
{
|
||||
private val extraMethods = mutableListOf<Member>()
|
||||
|
||||
override fun visit(version: Int, access: Int, className: String, signature: String?, superName: String?, interfaces: Array<String>?) {
|
||||
val stitchedInterfaces = configuration.stitchedInterfaces[className]?.let { methods ->
|
||||
extraMethods += methods
|
||||
arrayOf(*(interfaces ?: emptyArray()), configuration.classResolver.reverse(className))
|
||||
} ?: interfaces
|
||||
|
||||
super.visit(version, access, className, signature, superName, stitchedInterfaces)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
for (method in extraMethods) {
|
||||
method.apply {
|
||||
visitMethod(access, memberName, signature, genericsDetails.emptyAsNull, exceptions.toTypedArray())?.also { mv ->
|
||||
mv.visitCode()
|
||||
EmitterModule(mv).writeByteCode(body)
|
||||
mv.visitMaxs(-1, -1)
|
||||
mv.visitEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
extraMethods.clear()
|
||||
super.visitEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import net.corda.djvm.validation.RuleValidator
|
||||
class SandboxClassLoader(
|
||||
configuration: SandboxConfiguration,
|
||||
private val context: AnalysisContext
|
||||
) : ClassLoader(null) {
|
||||
) : ClassLoader() {
|
||||
|
||||
private val analysisConfiguration = configuration.analysisConfiguration
|
||||
|
||||
@ -36,11 +36,6 @@ class SandboxClassLoader(
|
||||
val analyzer: ClassAndMemberVisitor
|
||||
get() = ruleValidator
|
||||
|
||||
/**
|
||||
* Set of classes that should be left untouched due to pinning.
|
||||
*/
|
||||
private val pinnedClasses = analysisConfiguration.pinnedClasses
|
||||
|
||||
/**
|
||||
* Set of classes that should be left untouched due to whitelisting.
|
||||
*/
|
||||
@ -61,6 +56,17 @@ class SandboxClassLoader(
|
||||
*/
|
||||
private val rewriter: ClassRewriter = ClassRewriter(configuration, supportingClassLoader)
|
||||
|
||||
/**
|
||||
* Given a class name, provide its corresponding [LoadedClass] for the sandbox.
|
||||
*/
|
||||
fun loadForSandbox(name: String, context: AnalysisContext): LoadedClass {
|
||||
return loadClassAndBytes(ClassSource.fromClassName(analysisConfiguration.classResolver.resolveNormalized(name)), context)
|
||||
}
|
||||
|
||||
fun loadForSandbox(source: ClassSource, context: AnalysisContext): LoadedClass {
|
||||
return loadForSandbox(source.qualifiedClassName, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the class with the specified binary name.
|
||||
*
|
||||
@ -69,69 +75,68 @@ class SandboxClassLoader(
|
||||
*
|
||||
* @return The resulting <tt>Class</tt> object.
|
||||
*/
|
||||
@Throws(ClassNotFoundException::class)
|
||||
override fun loadClass(name: String, resolve: Boolean): Class<*> {
|
||||
return loadClassAndBytes(ClassSource.fromClassName(name), context).type
|
||||
val source = ClassSource.fromClassName(name)
|
||||
return if (name.startsWith("sandbox.") && !analysisConfiguration.isPinnedClass(source.internalClassName)) {
|
||||
loadClassAndBytes(source, context).type
|
||||
} else {
|
||||
super.loadClass(name, resolve)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the class with the specified binary name.
|
||||
*
|
||||
* @param source The class source, including the binary name of the class.
|
||||
* @param request The class request, including the binary name of the class.
|
||||
* @param context The context in which the analysis is conducted.
|
||||
*
|
||||
* @return The resulting <tt>Class</tt> object and its byte code representation.
|
||||
*/
|
||||
fun loadClassAndBytes(source: ClassSource, context: AnalysisContext): LoadedClass {
|
||||
logger.debug("Loading class {}, origin={}...", source.qualifiedClassName, source.origin)
|
||||
val name = analysisConfiguration.classResolver.reverseNormalized(source.qualifiedClassName)
|
||||
val resolvedName = analysisConfiguration.classResolver.resolveNormalized(name)
|
||||
private fun loadClassAndBytes(request: ClassSource, context: AnalysisContext): LoadedClass {
|
||||
logger.debug("Loading class {}, origin={}...", request.qualifiedClassName, request.origin)
|
||||
val requestedPath = request.internalClassName
|
||||
val sourceName = analysisConfiguration.classResolver.reverseNormalized(request.qualifiedClassName)
|
||||
val resolvedName = analysisConfiguration.classResolver.resolveNormalized(sourceName)
|
||||
|
||||
// Check if the class has already been loaded.
|
||||
val loadedClass = loadedClasses[name]
|
||||
val loadedClass = loadedClasses[requestedPath]
|
||||
if (loadedClass != null) {
|
||||
logger.trace("Class {} already loaded", source.qualifiedClassName)
|
||||
logger.trace("Class {} already loaded", request.qualifiedClassName)
|
||||
return loadedClass
|
||||
} else if (analysisConfiguration.isPinnedClass(requestedPath)) {
|
||||
logger.debug("Class {} is loaded unmodified", request.qualifiedClassName)
|
||||
return loadUnmodifiedClass(requestedPath)
|
||||
}
|
||||
|
||||
val byteCode = if (analysisConfiguration.isTemplateClass(requestedPath)) {
|
||||
loadUnmodifiedByteCode(requestedPath)
|
||||
} else {
|
||||
// Load the byte code for the specified class.
|
||||
val reader = supportingClassLoader.classReader(name, context, source.origin)
|
||||
val reader = supportingClassLoader.classReader(sourceName, context, request.origin)
|
||||
|
||||
// Analyse the class if not matching the whitelist.
|
||||
val readClassName = reader.className
|
||||
if (!analysisConfiguration.whitelist.matches(readClassName)) {
|
||||
logger.trace("Class {} does not match with the whitelist", source.qualifiedClassName)
|
||||
logger.trace("Analyzing class {}...", source.qualifiedClassName)
|
||||
logger.trace("Class {} does not match with the whitelist", request.qualifiedClassName)
|
||||
logger.trace("Analyzing class {}...", request.qualifiedClassName)
|
||||
analyzer.analyze(reader, context)
|
||||
}
|
||||
|
||||
// Check if the class should be left untouched.
|
||||
val qualifiedName = name.asResourcePath
|
||||
if (qualifiedName in pinnedClasses) {
|
||||
logger.trace("Class {} is marked as pinned", source.qualifiedClassName)
|
||||
val pinnedClasses = LoadedClass(
|
||||
supportingClassLoader.loadClass(name),
|
||||
ByteCode(ByteArray(0), false)
|
||||
)
|
||||
loadedClasses[name] = pinnedClasses
|
||||
if (source.origin != null) {
|
||||
context.recordClassOrigin(name, ClassReference(source.origin))
|
||||
}
|
||||
return pinnedClasses
|
||||
}
|
||||
|
||||
// Check if any errors were found during analysis.
|
||||
if (context.messages.errorCount > 0) {
|
||||
logger.trace("Errors detected after analyzing class {}", source.qualifiedClassName)
|
||||
logger.debug("Errors detected after analyzing class {}", request.qualifiedClassName)
|
||||
throw SandboxClassLoadingException(context)
|
||||
}
|
||||
|
||||
// Transform the class definition and byte code in accordance with provided rules.
|
||||
val byteCode = rewriter.rewrite(reader, context)
|
||||
rewriter.rewrite(reader, context)
|
||||
}
|
||||
|
||||
// Try to define the transformed class.
|
||||
val clazz = try {
|
||||
when {
|
||||
whitelistedClasses.matches(qualifiedName) -> supportingClassLoader.loadClass(name)
|
||||
whitelistedClasses.matches(sourceName.asResourcePath) -> supportingClassLoader.loadClass(sourceName)
|
||||
else -> defineClass(resolvedName, byteCode.bytes, 0, byteCode.bytes.size)
|
||||
}
|
||||
} catch (exception: SecurityException) {
|
||||
@ -140,19 +145,31 @@ class SandboxClassLoader(
|
||||
|
||||
// Cache transformed class.
|
||||
val classWithByteCode = LoadedClass(clazz, byteCode)
|
||||
loadedClasses[name] = classWithByteCode
|
||||
if (source.origin != null) {
|
||||
context.recordClassOrigin(name, ClassReference(source.origin))
|
||||
loadedClasses[requestedPath] = classWithByteCode
|
||||
if (request.origin != null) {
|
||||
context.recordClassOrigin(sourceName, ClassReference(request.origin))
|
||||
}
|
||||
|
||||
logger.debug("Loaded class {}, bytes={}, isModified={}",
|
||||
source.qualifiedClassName, byteCode.bytes.size, byteCode.isModified)
|
||||
request.qualifiedClassName, byteCode.bytes.size, byteCode.isModified)
|
||||
|
||||
return classWithByteCode
|
||||
}
|
||||
|
||||
private fun loadUnmodifiedByteCode(internalClassName: String): ByteCode {
|
||||
return ByteCode((getSystemClassLoader().getResourceAsStream("$internalClassName.class")
|
||||
?: throw ClassNotFoundException(internalClassName)).readBytes(), false)
|
||||
}
|
||||
|
||||
private fun loadUnmodifiedClass(className: String): LoadedClass {
|
||||
return LoadedClass(supportingClassLoader.loadClass(className), UNMODIFIED).apply {
|
||||
loadedClasses[className] = this
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val logger = loggerFor<SandboxClassLoader>()
|
||||
private val UNMODIFIED = ByteCode(ByteArray(0), false)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package net.corda.djvm.rewiring
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.ClassAndMemberVisitor.Companion.API_VERSION
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Label
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.commons.ClassRemapper
|
||||
|
||||
class SandboxClassRemapper(cv: ClassVisitor, private val configuration: AnalysisConfiguration)
|
||||
: ClassRemapper(cv, SandboxRemapper(configuration.classResolver, configuration.whitelist)
|
||||
) {
|
||||
override fun createMethodRemapper(mv: MethodVisitor): MethodVisitor {
|
||||
return MethodRemapperWithPinning(mv, super.createMethodRemapper(mv))
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not attempt to remap references to methods and fields on pinned classes.
|
||||
* For example, the methods on [RuntimeCostAccounter] really DO use [java.lang.String]
|
||||
* rather than [sandbox.java.lang.String].
|
||||
*/
|
||||
private inner class MethodRemapperWithPinning(private val nonmapper: MethodVisitor, remapper: MethodVisitor)
|
||||
: MethodVisitor(API_VERSION, remapper) {
|
||||
|
||||
private fun mapperFor(element: Element): MethodVisitor {
|
||||
return if (configuration.isPinnedClass(element.owner) || configuration.isTemplateClass(element.owner) || isUnmapped(element)) {
|
||||
nonmapper
|
||||
} else {
|
||||
mv
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) {
|
||||
val method = Element(owner, name, descriptor)
|
||||
return mapperFor(method).visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
override fun visitTryCatchBlock(start: Label, end: Label, handler: Label, type: String?) {
|
||||
// Don't map caught exception names - these could be thrown by the JVM itself.
|
||||
nonmapper.visitTryCatchBlock(start, end, handler, type)
|
||||
}
|
||||
|
||||
override fun visitFieldInsn(opcode: Int, owner: String, name: String, descriptor: String) {
|
||||
val field = Element(owner, name, descriptor)
|
||||
return mapperFor(field).visitFieldInsn(opcode, owner, name, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUnmapped(element: Element): Boolean = configuration.whitelist.matches(element.owner)
|
||||
|
||||
private data class Element(val owner: String, val name: String, val descriptor: String)
|
||||
}
|
@ -8,13 +8,13 @@ import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
/**
|
||||
* Class writer for sandbox execution, with configurable a [classLoader] to ensure correct deduction of the used class
|
||||
* Class writer for sandbox execution, with a configurable classloader to ensure correct deduction of the used class
|
||||
* hierarchy.
|
||||
*
|
||||
* @param classReader The [ClassReader] used to read the original class. It will be used to copy the entire constant
|
||||
* pool and bootstrap methods from the original class and also to copy other fragments of original byte code where
|
||||
* applicable.
|
||||
* @property classLoader The class loader used to load the classes that are to be rewritten.
|
||||
* @property cloader The class loader used to load the classes that are to be rewritten.
|
||||
* @param flags Option flags that can be used to modify the default behaviour of this class. Must be zero or a
|
||||
* combination of [COMPUTE_MAXS] and [COMPUTE_FRAMES]. These option flags do not affect methods that are copied as is
|
||||
* in the new class. This means that neither the maximum stack size nor the stack frames will be computed for these
|
||||
@ -61,7 +61,7 @@ open class SandboxClassWriter(
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private companion object {
|
||||
|
||||
private const val OBJECT_NAME = "java/lang/Object"
|
||||
|
||||
|
@ -1,15 +1,19 @@
|
||||
package net.corda.djvm.rewiring
|
||||
|
||||
import net.corda.djvm.analysis.ClassResolver
|
||||
import net.corda.djvm.analysis.Whitelist
|
||||
import org.objectweb.asm.*
|
||||
import org.objectweb.asm.commons.Remapper
|
||||
|
||||
/**
|
||||
* Class name and descriptor re-mapper for use in a sandbox.
|
||||
*
|
||||
* @property classResolver Functionality for resolving the class name of a sandboxed or sandboxable class.
|
||||
* @property whitelist Identifies the Java APIs which are not mapped into the sandbox namespace.
|
||||
*/
|
||||
open class SandboxRemapper(
|
||||
private val classResolver: ClassResolver
|
||||
private val classResolver: ClassResolver,
|
||||
private val whitelist: Whitelist
|
||||
) : Remapper() {
|
||||
|
||||
/**
|
||||
@ -26,6 +30,32 @@ open class SandboxRemapper(
|
||||
return rewriteTypeName(super.map(typename))
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapper for [Type] and [Handle] objects.
|
||||
*/
|
||||
override fun mapValue(obj: Any?): Any? {
|
||||
return if (obj is Handle && whitelist.matches(obj.owner)) {
|
||||
obj
|
||||
} else {
|
||||
super.mapValue(obj)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All [Object.toString] methods must be transformed to [sandbox.java.lang.Object.toDJVMString],
|
||||
* to allow the return type to change to [sandbox.java.lang.String].
|
||||
*
|
||||
* The [sandbox.java.lang.Object] class is pinned and not mapped.
|
||||
*/
|
||||
override fun mapMethodName(owner: String, name: String, descriptor: String): String {
|
||||
val newName = if (name == "toString" && descriptor == "()Ljava/lang/String;") {
|
||||
"toDJVMString"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
return super.mapMethodName(owner, newName, descriptor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for rewriting a descriptor.
|
||||
*/
|
||||
|
@ -0,0 +1,35 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.code.EmitterContext
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.instructions.MemberAccessInstruction
|
||||
|
||||
/**
|
||||
* Some whitelisted functions have [java.lang.String] arguments, so we
|
||||
* need to unwrap the [sandbox.java.lang.String] object before invoking.
|
||||
*
|
||||
* There are lots of rabbits in this hole because method arguments are
|
||||
* theoretically arbitrary. However, in practice WE control the whitelist.
|
||||
*/
|
||||
class ArgumentUnwrapper : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MemberAccessInstruction && context.whitelist.matches(instruction.owner)) {
|
||||
fun unwrapString() = invokeStatic("sandbox/java/lang/String", "fromDJVM", "(Lsandbox/java/lang/String;)Ljava/lang/String;")
|
||||
|
||||
if (hasStringArgument(instruction)) {
|
||||
unwrapString()
|
||||
} else if (instruction.owner == "java/lang/Class" && instruction.signature.startsWith("(Ljava/lang/String;ZLjava/lang/ClassLoader;)")) {
|
||||
/**
|
||||
* [kotlin.jvm.internal.Intrinsics.checkHasClass] invokes [Class.forName], so I'm
|
||||
* adding support for both of this function's variants. For now.
|
||||
*/
|
||||
raiseThirdWordToTop()
|
||||
unwrapString()
|
||||
sinkTopToThirdWord()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasStringArgument(method: MemberAccessInstruction) = method.signature.contains("Ljava/lang/String;)")
|
||||
}
|
@ -17,7 +17,7 @@ class DisallowNonDeterministicMethods : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MemberAccessInstruction && isForbidden(instruction)) {
|
||||
when (instruction.operation) {
|
||||
INVOKEVIRTUAL -> {
|
||||
INVOKEVIRTUAL, INVOKESPECIAL -> {
|
||||
throwException<RuleViolationError>("Disallowed reference to API; ${memberFormatter.format(instruction.member)}")
|
||||
preventDefault()
|
||||
}
|
||||
@ -31,12 +31,20 @@ class DisallowNonDeterministicMethods : Emitter {
|
||||
|| instruction.signature.contains("Ljava/lang/reflect/"))
|
||||
)
|
||||
|
||||
private fun isClassLoading(instruction: MemberAccessInstruction): Boolean =
|
||||
(instruction.owner == "java/lang/ClassLoader") && instruction.memberName in CLASSLOADING_METHODS
|
||||
|
||||
private fun isObjectMonitor(instruction: MemberAccessInstruction): Boolean =
|
||||
(instruction.signature == "()V" && (instruction.memberName == "notify" || instruction.memberName == "notifyAll" || instruction.memberName == "wait"))
|
||||
(instruction.signature == "()V" && instruction.memberName in MONITOR_METHODS)
|
||||
|| (instruction.memberName == "wait" && (instruction.signature == "(J)V" || instruction.signature == "(JI)V"))
|
||||
|
||||
private fun isForbidden(instruction: MemberAccessInstruction): Boolean
|
||||
= instruction.isMethod && (isClassReflection(instruction) || isObjectMonitor(instruction))
|
||||
= instruction.isMethod && (isClassReflection(instruction) || isObjectMonitor(instruction) || isClassLoading(instruction))
|
||||
|
||||
private val memberFormatter = MemberFormatter()
|
||||
|
||||
private companion object {
|
||||
private val MONITOR_METHODS = setOf("notify", "notifyAll", "wait")
|
||||
private val CLASSLOADING_METHODS = setOf("defineClass", "loadClass", "findClass")
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.code.EmitterContext
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.instructions.MemberAccessInstruction
|
||||
|
||||
/**
|
||||
* Whitelisted classes may still return [java.lang.String] from some
|
||||
* functions, e.g. [java.lang.Object.toString]. So always explicitly
|
||||
* invoke [sandbox.java.lang.String.toDJVM] after these.
|
||||
*/
|
||||
class ReturnTypeWrapper : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MemberAccessInstruction && context.whitelist.matches(instruction.owner)) {
|
||||
fun invokeMethod() = invokeVirtual(instruction.owner, instruction.memberName, instruction.signature)
|
||||
|
||||
if (hasStringReturnType(instruction)) {
|
||||
preventDefault()
|
||||
invokeMethod()
|
||||
invokeStatic("sandbox/java/lang/String", "toDJVM", "(Ljava/lang/String;)Lsandbox/java/lang/String;")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasStringReturnType(method: MemberAccessInstruction) = method.signature.endsWith(")Ljava/lang/String;")
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.code.EmitterContext
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.instructions.MemberAccessInstruction
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* The enum-related methods on [Class] all require that enums use [java.lang.Enum]
|
||||
* as their super class. So replace their all invocations with ones to equivalent
|
||||
* methods on the DJVM class that require [sandbox.java.lang.Enum] instead.
|
||||
*/
|
||||
class RewriteClassMethods : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MemberAccessInstruction && instruction.owner == "java/lang/Class") {
|
||||
when (instruction.operation) {
|
||||
INVOKEVIRTUAL -> if (instruction.memberName == "enumConstantDirectory" && instruction.signature == "()Ljava/util/Map;") {
|
||||
invokeStatic(
|
||||
owner = "sandbox/java/lang/DJVM",
|
||||
name = "enumConstantDirectory",
|
||||
descriptor = "(Ljava/lang/Class;)Lsandbox/java/util/Map;"
|
||||
)
|
||||
preventDefault()
|
||||
} else if (instruction.memberName == "isEnum" && instruction.signature == "()Z") {
|
||||
invokeStatic(
|
||||
owner = "sandbox/java/lang/DJVM",
|
||||
name = "isEnum",
|
||||
descriptor = "(Ljava/lang/Class;)Z"
|
||||
)
|
||||
preventDefault()
|
||||
} else if (instruction.memberName == "getEnumConstants" && instruction.signature == "()[Ljava/lang/Object;") {
|
||||
invokeStatic(
|
||||
owner = "sandbox/java/lang/DJVM",
|
||||
name = "getEnumConstants",
|
||||
descriptor = "(Ljava/lang/Class;)[Ljava/lang/Object;")
|
||||
preventDefault()
|
||||
}
|
||||
|
||||
INVOKESTATIC -> if (isClassForName(instruction)) {
|
||||
invokeStatic(
|
||||
owner = "sandbox/java/lang/DJVM",
|
||||
name = "classForName",
|
||||
descriptor = instruction.signature
|
||||
)
|
||||
preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isClassForName(instruction: MemberAccessInstruction): Boolean
|
||||
= instruction.memberName == "forName" &&
|
||||
(instruction.signature == "(Ljava/lang/String;)Ljava/lang/Class;" ||
|
||||
instruction.signature == "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;")
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisRuntimeContext
|
||||
import net.corda.djvm.code.EmitterModule
|
||||
import net.corda.djvm.code.MemberDefinitionProvider
|
||||
import net.corda.djvm.references.Member
|
||||
|
||||
/**
|
||||
* Removes static constant objects that are initialised directly in the byte-code.
|
||||
* Currently, the only use-case is for re-initialising [String] fields.
|
||||
*/
|
||||
class StaticConstantRemover : MemberDefinitionProvider {
|
||||
|
||||
override fun define(context: AnalysisRuntimeContext, member: Member): Member = when {
|
||||
isConstantField(member) -> member.copy(body = listOf(StringFieldInitializer(member)::writeInitializer), value = null)
|
||||
else -> member
|
||||
}
|
||||
|
||||
private fun isConstantField(member: Member): Boolean = member.value != null && member.signature == "Ljava/lang/String;"
|
||||
|
||||
class StringFieldInitializer(private val member: Member) {
|
||||
fun writeInitializer(emitter: EmitterModule): Unit = with(emitter) {
|
||||
member.value?.apply {
|
||||
loadConstant(this)
|
||||
invokeStatic("sandbox/java/lang/String", "toDJVM", "(Ljava/lang/String;)Lsandbox/java/lang/String;", false)
|
||||
putStatic(member.className, member.memberName, "Lsandbox/java/lang/String;")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.code.EmitterContext
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.instructions.ConstantInstruction
|
||||
|
||||
/**
|
||||
* Ensure that [String] constants loaded from the Constants
|
||||
* Pool are wrapped into [sandbox.java.lang.String].
|
||||
*/
|
||||
class StringConstantWrapper : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is ConstantInstruction) {
|
||||
when (instruction.value) {
|
||||
is String -> {
|
||||
invokeStatic("sandbox/java/lang/String", "toDJVM", "(Ljava/lang/String;)Lsandbox/java/lang/String;", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ class StubOutNativeMethods : MemberDefinitionProvider {
|
||||
|
||||
private fun writeExceptionMethodBody(emitter: EmitterModule): Unit = with(emitter) {
|
||||
lineNumber(0)
|
||||
throwException(RuleViolationError::class.java, "Native method has been deleted")
|
||||
throwException<RuleViolationError>("Native method has been deleted")
|
||||
}
|
||||
|
||||
private fun writeStubMethodBody(emitter: EmitterModule): Unit = with(emitter) {
|
||||
|
@ -19,7 +19,7 @@ class StubOutReflectionMethods : MemberDefinitionProvider {
|
||||
|
||||
private fun writeMethodBody(emitter: EmitterModule): Unit = with(emitter) {
|
||||
lineNumber(0)
|
||||
throwException(RuleViolationError::class.java, "Disallowed reference to reflection API")
|
||||
throwException<RuleViolationError>("Disallowed reference to reflection API")
|
||||
}
|
||||
|
||||
// The method must be public and with a Java implementation.
|
||||
|
@ -1,17 +1,20 @@
|
||||
package net.corda.djvm.source
|
||||
|
||||
import net.corda.djvm.code.asResourcePath
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* The source of one or more compiled Java classes.
|
||||
*
|
||||
* @property qualifiedClassName The fully qualified class name.
|
||||
* @property internalClassName The fully qualified internal class name, i.e. with '/' instead of '.'.
|
||||
* @property origin The origin of the class source, if any.
|
||||
*/
|
||||
class ClassSource private constructor(
|
||||
val qualifiedClassName: String = "",
|
||||
val origin: String? = null
|
||||
) {
|
||||
val internalClassName: String = qualifiedClassName.asResourcePath
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -7,7 +7,7 @@ import java.lang.reflect.Modifier
|
||||
* Find and instantiate types that implement a certain interface.
|
||||
*/
|
||||
object Discovery {
|
||||
const val FORBIDDEN_CLASS_MASK = (Modifier.STATIC or Modifier.ABSTRACT)
|
||||
const val FORBIDDEN_CLASS_MASK = (Modifier.STATIC or Modifier.ABSTRACT or Modifier.PRIVATE or Modifier.PROTECTED)
|
||||
|
||||
/**
|
||||
* Get an instance of each concrete class that implements interface or class [T].
|
||||
|
@ -17,6 +17,7 @@ import org.objectweb.asm.ClassVisitor
|
||||
* Helper class for validating a set of rules for a class or set of classes.
|
||||
*
|
||||
* @property rules A set of rules to validate for provided classes.
|
||||
* @param configuration The configuration to use for class analysis.
|
||||
* @param classVisitor Class visitor to use when traversing the structure of classes.
|
||||
*/
|
||||
class RuleValidator(
|
||||
|
24
djvm/src/main/kotlin/sandbox/Task.kt
Normal file
24
djvm/src/main/kotlin/sandbox/Task.kt
Normal file
@ -0,0 +1,24 @@
|
||||
@file:JvmName("TaskTypes")
|
||||
package sandbox
|
||||
|
||||
import sandbox.java.lang.sandbox
|
||||
import sandbox.java.lang.unsandbox
|
||||
|
||||
typealias SandboxFunction<TInput, TOutput> = sandbox.java.util.function.Function<TInput, TOutput>
|
||||
|
||||
@Suppress("unused")
|
||||
class Task(private val function: SandboxFunction<in Any?, out Any?>?) : SandboxFunction<Any?, Any?> {
|
||||
|
||||
/**
|
||||
* This function runs inside the sandbox. It marshalls the input
|
||||
* object to its sandboxed equivalent, executes the user's code
|
||||
* and then marshalls the result out again.
|
||||
*
|
||||
* The marshalling should be effective for Java primitives,
|
||||
* Strings and Enums, as well as for arrays of these types.
|
||||
*/
|
||||
override fun apply(input: Any?): Any? {
|
||||
return function?.apply(input?.sandbox())?.unsandbox()
|
||||
}
|
||||
|
||||
}
|
158
djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt
Normal file
158
djvm/src/main/kotlin/sandbox/java/lang/DJVM.kt
Normal file
@ -0,0 +1,158 @@
|
||||
@file:JvmName("DJVM")
|
||||
@file:Suppress("unused")
|
||||
package sandbox.java.lang
|
||||
|
||||
import org.objectweb.asm.Opcodes.ACC_ENUM
|
||||
|
||||
private const val SANDBOX_PREFIX = "sandbox."
|
||||
|
||||
fun Any.unsandbox(): Any {
|
||||
return when (this) {
|
||||
is Enum<*> -> fromDJVMEnum()
|
||||
is Object -> fromDJVM()
|
||||
is Array<*> -> fromDJVMArray()
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
fun Any.sandbox(): Any {
|
||||
return when (this) {
|
||||
is kotlin.String -> String.toDJVM(this)
|
||||
is kotlin.Char -> Character.toDJVM(this)
|
||||
is kotlin.Long -> Long.toDJVM(this)
|
||||
is kotlin.Int -> Integer.toDJVM(this)
|
||||
is kotlin.Short -> Short.toDJVM(this)
|
||||
is kotlin.Byte -> Byte.toDJVM(this)
|
||||
is kotlin.Float -> Float.toDJVM(this)
|
||||
is kotlin.Double -> Double.toDJVM(this)
|
||||
is kotlin.Boolean -> Boolean.toDJVM(this)
|
||||
is kotlin.Enum<*> -> toDJVMEnum()
|
||||
is Array<*> -> toDJVMArray<Object>()
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
private fun Array<*>.fromDJVMArray(): Array<*> = Object.fromDJVM(this)
|
||||
|
||||
/**
|
||||
* These functions use the "current" classloader, i.e. classloader
|
||||
* that owns this DJVM class.
|
||||
*/
|
||||
private fun Class<*>.toDJVMType(): Class<*> = Class.forName(name.toSandboxPackage())
|
||||
private fun Class<*>.fromDJVMType(): Class<*> = Class.forName(name.fromSandboxPackage())
|
||||
|
||||
private fun kotlin.String.toSandboxPackage(): kotlin.String {
|
||||
return if (startsWith(SANDBOX_PREFIX)) {
|
||||
this
|
||||
} else {
|
||||
SANDBOX_PREFIX + this
|
||||
}
|
||||
}
|
||||
|
||||
private fun kotlin.String.fromSandboxPackage(): kotlin.String {
|
||||
return if (startsWith(SANDBOX_PREFIX)) {
|
||||
drop(SANDBOX_PREFIX.length)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T : Object> Array<*>.toDJVMArray(): Array<out T?> {
|
||||
@Suppress("unchecked_cast")
|
||||
return (java.lang.reflect.Array.newInstance(javaClass.componentType.toDJVMType(), size) as Array<T?>).also {
|
||||
for ((i, item) in withIndex()) {
|
||||
it[i] = item?.sandbox() as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Enum<*>.fromDJVMEnum(): kotlin.Enum<*> {
|
||||
return javaClass.fromDJVMType().enumConstants[ordinal()] as kotlin.Enum<*>
|
||||
}
|
||||
|
||||
private fun kotlin.Enum<*>.toDJVMEnum(): Enum<*> {
|
||||
@Suppress("unchecked_cast")
|
||||
return (getEnumConstants(javaClass.toDJVMType() as Class<Enum<*>>) as Array<Enum<*>>)[ordinal]
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement functions for the members of Class<*> that support Enums.
|
||||
*/
|
||||
fun isEnum(clazz: Class<*>): kotlin.Boolean
|
||||
= (clazz.modifiers and ACC_ENUM != 0) && (clazz.superclass == sandbox.java.lang.Enum::class.java)
|
||||
|
||||
fun getEnumConstants(clazz: Class<out Enum<*>>): Array<*>? {
|
||||
return getEnumConstantsShared(clazz)?.clone()
|
||||
}
|
||||
|
||||
internal fun enumConstantDirectory(clazz: Class<out Enum<*>>): sandbox.java.util.Map<String, out Enum<*>>? {
|
||||
// DO NOT replace get with Kotlin's [] because Kotlin would use java.util.Map.
|
||||
return allEnumDirectories.get(clazz) ?: createEnumDirectory(clazz)
|
||||
}
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
internal fun getEnumConstantsShared(clazz: Class<out Enum<*>>): Array<out Enum<*>>? {
|
||||
return if (isEnum(clazz)) {
|
||||
// DO NOT replace get with Kotlin's [] because Kotlin would use java.util.Map.
|
||||
allEnums.get(clazz) ?: createEnum(clazz)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
private fun createEnum(clazz: Class<out Enum<*>>): Array<out Enum<*>>? {
|
||||
return clazz.getMethod("values").let { method ->
|
||||
method.isAccessible = true
|
||||
method.invoke(null) as? Array<out Enum<*>>
|
||||
// DO NOT replace put with Kotlin's [] because Kotlin would use java.util.Map.
|
||||
}?.apply { allEnums.put(clazz, this) }
|
||||
}
|
||||
|
||||
private fun createEnumDirectory(clazz: Class<out Enum<*>>): sandbox.java.util.Map<String, out Enum<*>> {
|
||||
val universe = getEnumConstantsShared(clazz) ?: throw IllegalArgumentException("${clazz.name} is not an enum type")
|
||||
val directory = sandbox.java.util.LinkedHashMap<String, Enum<*>>(2 * universe.size)
|
||||
for (entry in universe) {
|
||||
// DO NOT replace put with Kotlin's [] because Kotlin would use java.util.Map.
|
||||
directory.put(entry.name(), entry)
|
||||
}
|
||||
// DO NOT replace put with Kotlin's [] because Kotlin would use java.util.Map.
|
||||
allEnumDirectories.put(clazz, directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
private val allEnums: sandbox.java.util.Map<Class<out Enum<*>>, Array<out Enum<*>>> = sandbox.java.util.LinkedHashMap()
|
||||
private val allEnumDirectories: sandbox.java.util.Map<Class<out Enum<*>>, sandbox.java.util.Map<String, out Enum<*>>> = sandbox.java.util.LinkedHashMap()
|
||||
|
||||
/**
|
||||
* Replacement functions for Class<*>.forName(...) which protect
|
||||
* against users loading classes from outside the sandbox.
|
||||
*/
|
||||
@Throws(ClassNotFoundException::class)
|
||||
fun classForName(className: kotlin.String): Class<*> {
|
||||
return Class.forName(toSandbox(className))
|
||||
}
|
||||
|
||||
@Throws(ClassNotFoundException::class)
|
||||
fun classForName(className: kotlin.String, initialize: kotlin.Boolean, classLoader: ClassLoader): Class<*> {
|
||||
return Class.forName(toSandbox(className), initialize, classLoader)
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the qualified class name into the sandbox.* namespace.
|
||||
* Throw [ClassNotFoundException] anyway if we wouldn't want to
|
||||
* return the resulting sandbox class. E.g. for any of our own
|
||||
* internal classes.
|
||||
*/
|
||||
private fun toSandbox(className: kotlin.String): kotlin.String {
|
||||
if (bannedClasses.any { it.matches(className) }) {
|
||||
throw ClassNotFoundException(className)
|
||||
}
|
||||
return SANDBOX_PREFIX + className
|
||||
}
|
||||
|
||||
private val bannedClasses = setOf(
|
||||
"^java\\.lang\\.DJVM(.*)?\$".toRegex(),
|
||||
"^net\\.corda\\.djvm\\..*\$".toRegex(),
|
||||
"^Task\$".toRegex()
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
package sandbox.java.lang
|
||||
|
||||
/**
|
||||
* Sandboxed implementation of `java/lang/Object`.
|
||||
*/
|
||||
@Suppress("EqualsOrHashCode")
|
||||
open class Object {
|
||||
|
||||
/**
|
||||
* Deterministic hash code for objects.
|
||||
*/
|
||||
override fun hashCode(): Int = sandbox.java.lang.System.identityHashCode(this)
|
||||
|
||||
/**
|
||||
* Deterministic string representation of [Object].
|
||||
*/
|
||||
override fun toString(): String = "sandbox.java.lang.Object@${hashCode().toString(16)}"
|
||||
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
@file:Suppress("UNUSED_PARAMETER")
|
||||
|
||||
package sandbox.java.lang
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
object System {
|
||||
|
||||
private var objectCounter = object : ThreadLocal<Int>() {
|
||||
override fun initialValue() = 0
|
||||
}
|
||||
|
||||
private var objectHashCodes = object : ThreadLocal<MutableMap<Int, Int>>() {
|
||||
override fun initialValue() = mutableMapOf<Int, Int>()
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val `in`: java.io.InputStream? = null
|
||||
|
||||
@JvmField
|
||||
val out: java.io.PrintStream? = null
|
||||
|
||||
@JvmField
|
||||
val err: java.io.PrintStream? = null
|
||||
|
||||
fun setIn(stream: java.io.InputStream) {}
|
||||
|
||||
fun setOut(stream: java.io.PrintStream) {}
|
||||
|
||||
fun setErr(stream: java.io.PrintStream) {}
|
||||
|
||||
fun console(): java.io.Console? {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
@Throws(java.io.IOException::class)
|
||||
fun inheritedChannel(): java.nio.channels.Channel? {
|
||||
throw IOException()
|
||||
}
|
||||
|
||||
fun setSecurityManager(manager: java.lang.SecurityManager) {}
|
||||
|
||||
fun getSecurityManager(): java.lang.SecurityManager? = null
|
||||
|
||||
fun currentTimeMillis(): Long = 0L
|
||||
|
||||
fun nanoTime(): Long = 0L
|
||||
|
||||
fun arraycopy(src: Object, srcPos: Int, dest: Object, destPos: Int, length: Int) {
|
||||
java.lang.System.arraycopy(src, srcPos, dest, destPos, length)
|
||||
}
|
||||
|
||||
fun identityHashCode(obj: Object): Int {
|
||||
val nativeHashCode = java.lang.System.identityHashCode(obj)
|
||||
// TODO Instead of using a magic offset below, one could take in a per-context seed
|
||||
return objectHashCodes.get().getOrPut(nativeHashCode) {
|
||||
val newCounter = objectCounter.get() + 1
|
||||
objectCounter.set(newCounter)
|
||||
0xfed_c0de + newCounter
|
||||
}
|
||||
}
|
||||
|
||||
fun getProperties(): java.util.Properties {
|
||||
return Properties()
|
||||
}
|
||||
|
||||
fun lineSeparator() = "\n"
|
||||
|
||||
fun setProperties(properties: java.util.Properties) {}
|
||||
|
||||
fun getProperty(property: String): String? = null
|
||||
|
||||
fun getProperty(property: String, defaultValue: String): String? = defaultValue
|
||||
|
||||
fun setProperty(property: String, value: String): String? = null
|
||||
|
||||
fun clearProperty(property: String): String? = null
|
||||
|
||||
fun getenv(variable: String): String? = null
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
fun getenv(): java.util.Map<String, String>? = null
|
||||
|
||||
fun exit(exitCode: Int) {}
|
||||
|
||||
fun gc() {}
|
||||
|
||||
fun runFinalization() {}
|
||||
|
||||
fun runFinalizersOnExit(flag: Boolean) {}
|
||||
|
||||
fun load(path: String) {}
|
||||
|
||||
fun loadLibrary(path: String) {}
|
||||
|
||||
fun mapLibraryName(path: String): String? = null
|
||||
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
package foo.bar.sandbox
|
||||
|
||||
import java.util.*
|
||||
|
||||
fun testRandom(): Int {
|
||||
val random = Random()
|
||||
return random.nextInt()
|
||||
fun testClock(): Long {
|
||||
return System.nanoTime()
|
||||
}
|
||||
|
||||
fun String.toNumber(): Int {
|
||||
return this.toInt()
|
||||
fun String.toNumber(): Long {
|
||||
return this.toLong()
|
||||
}
|
||||
|
126
djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt
Normal file
126
djvm/src/test/kotlin/net/corda/djvm/DJVMTest.kt
Normal file
@ -0,0 +1,126 @@
|
||||
package net.corda.djvm
|
||||
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import sandbox.java.lang.sandbox
|
||||
import sandbox.java.lang.unsandbox
|
||||
|
||||
class DJVMTest {
|
||||
|
||||
@Test
|
||||
fun testDJVMString() {
|
||||
val djvmString = sandbox.java.lang.String("New Value")
|
||||
assertNotEquals(djvmString, "New Value")
|
||||
assertEquals(djvmString, "New Value".sandbox())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleIntegerFormats() {
|
||||
val result = sandbox.java.lang.String.format("%d-%d-%d-%d".toDJVM(),
|
||||
10.toDJVM(), 999999L.toDJVM(), 1234.toShort().toDJVM(), 108.toByte().toDJVM()).toString()
|
||||
assertEquals("10-999999-1234-108", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHexFormat() {
|
||||
val result = sandbox.java.lang.String.format("%0#6x".toDJVM(), 768.toDJVM()).toString()
|
||||
assertEquals("0x0300", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDoubleFormat() {
|
||||
val result = sandbox.java.lang.String.format("%9.4f".toDJVM(), 1234.5678.toDJVM()).toString()
|
||||
assertEquals("1234.5678", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatFormat() {
|
||||
val result = sandbox.java.lang.String.format("%7.2f".toDJVM(), 1234.5678f.toDJVM()).toString()
|
||||
assertEquals("1234.57", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharFormat() {
|
||||
val result = sandbox.java.lang.String.format("[%c]".toDJVM(), 'A'.toDJVM()).toString()
|
||||
assertEquals("[A]", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testObjectFormat() {
|
||||
val result = sandbox.java.lang.String.format("%s".toDJVM(), object : sandbox.java.lang.Object() {}).toString()
|
||||
assertThat(result).startsWith("sandbox.java.lang.Object@")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStringEquality() {
|
||||
val number = sandbox.java.lang.String.valueOf((Double.MIN_VALUE / 2.0) * 2.0)
|
||||
require(number == "0.0".sandbox())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSandboxingArrays() {
|
||||
val result = arrayOf(1, 10L, "Hello World", '?', false, 1234.56).sandbox()
|
||||
assertThat(result)
|
||||
.isEqualTo(arrayOf(1.toDJVM(), 10L.toDJVM(), "Hello World".toDJVM(), '?'.toDJVM(), false.toDJVM(), 1234.56.toDJVM()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnsandboxingObjectArray() {
|
||||
val result = arrayOf<sandbox.java.lang.Object>(1.toDJVM(), 10L.toDJVM(), "Hello World".toDJVM(), '?'.toDJVM(), false.toDJVM(), 1234.56.toDJVM()).unsandbox()
|
||||
assertThat(result)
|
||||
.isEqualTo(arrayOf(1, 10L, "Hello World", '?', false, 1234.56))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSandboxingPrimitiveArray() {
|
||||
val result = intArrayOf(1, 2, 3, 10).sandbox()
|
||||
assertThat(result).isEqualTo(intArrayOf(1, 2, 3, 10))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSandboxingIntegersAsObjectArray() {
|
||||
val result = arrayOf(1, 2, 3, 10).sandbox()
|
||||
assertThat(result).isEqualTo(arrayOf(1.toDJVM(), 2.toDJVM(), 3.toDJVM(), 10.toDJVM()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnsandboxingArrays() {
|
||||
val arr = arrayOf(
|
||||
Array(1) { "Hello".toDJVM() },
|
||||
Array(1) { 1234000L.toDJVM() },
|
||||
Array(1) { 1234.toDJVM() },
|
||||
Array(1) { 923.toShort().toDJVM() },
|
||||
Array(1) { 27.toByte().toDJVM() },
|
||||
Array(1) { 'X'.toDJVM() },
|
||||
Array(1) { 987.65f.toDJVM() },
|
||||
Array(1) { 343.282.toDJVM() },
|
||||
Array(1) { true.toDJVM() },
|
||||
ByteArray(1) { 127.toByte() },
|
||||
CharArray(1) { '?'}
|
||||
)
|
||||
val result = arr.unsandbox() as Array<*>
|
||||
assertEquals(arr.size, result.size)
|
||||
assertArrayEquals(Array(1) { "Hello" }, result[0] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 1234000L }, result[1] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 1234 }, result[2] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 923.toShort() }, result[3] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 27.toByte() }, result[4] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 'X' }, result[5] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 987.65f }, result[6] as Array<*>)
|
||||
assertArrayEquals(Array(1) { 343.282 }, result[7] as Array<*>)
|
||||
assertArrayEquals(Array(1) { true }, result[8] as Array<*>)
|
||||
assertArrayEquals(ByteArray(1) { 127.toByte() }, result[9] as ByteArray)
|
||||
assertArrayEquals(CharArray(1) { '?' }, result[10] as CharArray)
|
||||
}
|
||||
|
||||
private fun String.toDJVM(): sandbox.java.lang.String = sandbox.java.lang.String.toDJVM(this)
|
||||
private fun Long.toDJVM(): sandbox.java.lang.Long = sandbox.java.lang.Long.toDJVM(this)
|
||||
private fun Int.toDJVM(): sandbox.java.lang.Integer = sandbox.java.lang.Integer.toDJVM(this)
|
||||
private fun Short.toDJVM(): sandbox.java.lang.Short = sandbox.java.lang.Short.toDJVM(this)
|
||||
private fun Byte.toDJVM(): sandbox.java.lang.Byte = sandbox.java.lang.Byte.toDJVM(this)
|
||||
private fun Float.toDJVM(): sandbox.java.lang.Float = sandbox.java.lang.Float.toDJVM(this)
|
||||
private fun Double.toDJVM(): sandbox.java.lang.Double = sandbox.java.lang.Double.toDJVM(this)
|
||||
private fun Char.toDJVM(): sandbox.java.lang.Character = sandbox.java.lang.Character.toDJVM(this)
|
||||
private fun Boolean.toDJVM(): sandbox.java.lang.Boolean = sandbox.java.lang.Boolean.toDJVM(this)
|
||||
}
|
@ -13,6 +13,7 @@ import net.corda.djvm.messages.Severity
|
||||
import net.corda.djvm.references.ClassHierarchy
|
||||
import net.corda.djvm.rewiring.LoadedClass
|
||||
import net.corda.djvm.rules.Rule
|
||||
import net.corda.djvm.rules.implementation.*
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import net.corda.djvm.utilities.Discovery
|
||||
import net.corda.djvm.validation.RuleValidator
|
||||
@ -35,8 +36,19 @@ abstract class TestBase {
|
||||
|
||||
val ALL_EMITTERS = Discovery.find<Emitter>()
|
||||
|
||||
// We need at least these emitters to handle the Java API classes.
|
||||
val BASIC_EMITTERS: List<Emitter> = listOf(
|
||||
ArgumentUnwrapper(),
|
||||
ReturnTypeWrapper(),
|
||||
RewriteClassMethods(),
|
||||
StringConstantWrapper()
|
||||
)
|
||||
|
||||
val ALL_DEFINITION_PROVIDERS = Discovery.find<DefinitionProvider>()
|
||||
|
||||
// We need at least these providers to handle the Java API classes.
|
||||
val BASIC_DEFINITION_PROVIDERS: List<DefinitionProvider> = listOf(StaticConstantRemover())
|
||||
|
||||
val BLANK = emptySet<Any>()
|
||||
|
||||
val DEFAULT = (ALL_RULES + ALL_EMITTERS + ALL_DEFINITION_PROVIDERS).distinctBy(Any::javaClass)
|
||||
@ -86,14 +98,6 @@ abstract class TestBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand for analysing a class.
|
||||
*/
|
||||
inline fun analyze(block: (ClassAndMemberVisitor.(AnalysisContext) -> Unit)) {
|
||||
val validator = RuleValidator(emptyList(), configuration)
|
||||
block(validator, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Run action on a separate thread to ensure that the code is run off a clean slate. The sandbox context is local to
|
||||
* the current thread, so this allows inspection of the cost summary object, etc. from within the provided delegate.
|
||||
@ -106,8 +110,8 @@ abstract class TestBase {
|
||||
action: SandboxRuntimeContext.() -> Unit
|
||||
) {
|
||||
val rules = mutableListOf<Rule>()
|
||||
val emitters = mutableListOf<Emitter>()
|
||||
val definitionProviders = mutableListOf<DefinitionProvider>()
|
||||
val emitters = mutableListOf<Emitter>().apply { addAll(BASIC_EMITTERS) }
|
||||
val definitionProviders = mutableListOf<DefinitionProvider>().apply { addAll(BASIC_DEFINITION_PROVIDERS) }
|
||||
val classSources = mutableListOf<ClassSource>()
|
||||
var executionProfile = ExecutionProfile.UNLIMITED
|
||||
var whitelist = Whitelist.MINIMAL
|
||||
@ -137,7 +141,12 @@ abstract class TestBase {
|
||||
minimumSeverityLevel = minimumSeverityLevel
|
||||
).use { analysisConfiguration ->
|
||||
SandboxRuntimeContext(SandboxConfiguration.of(
|
||||
executionProfile, rules, emitters, definitionProviders, enableTracing, analysisConfiguration
|
||||
executionProfile,
|
||||
rules.distinctBy(Any::javaClass),
|
||||
emitters.distinctBy(Any::javaClass),
|
||||
definitionProviders.distinctBy(Any::javaClass),
|
||||
enableTracing,
|
||||
analysisConfiguration
|
||||
)).use {
|
||||
assertThat(runtimeCosts).areZero()
|
||||
action(this)
|
||||
@ -163,7 +172,7 @@ abstract class TestBase {
|
||||
inline fun <reified T : Any> SandboxRuntimeContext.loadClass(): LoadedClass = loadClass(T::class.jvmName)
|
||||
|
||||
fun SandboxRuntimeContext.loadClass(className: String): LoadedClass =
|
||||
classLoader.loadClassAndBytes(ClassSource.fromClassName(className), context)
|
||||
classLoader.loadForSandbox(className, context)
|
||||
|
||||
/**
|
||||
* Run the entry-point of the loaded [Callable] class.
|
||||
|
22
djvm/src/test/kotlin/net/corda/djvm/Utilities.kt
Normal file
22
djvm/src/test/kotlin/net/corda/djvm/Utilities.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package net.corda.djvm
|
||||
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
import sandbox.net.corda.djvm.rules.RuleViolationError
|
||||
|
||||
object Utilities {
|
||||
fun throwRuleViolationError(): Nothing = throw RuleViolationError("Can't catch this!")
|
||||
|
||||
fun throwThresholdViolationError(): Nothing = throw ThresholdViolationError("Can't catch this!")
|
||||
|
||||
fun throwContractConstraintViolation(): Nothing = throw IllegalArgumentException("Contract constraint violated")
|
||||
|
||||
fun throwError(): Nothing = throw Error()
|
||||
|
||||
fun throwThrowable(): Nothing = throw Throwable()
|
||||
|
||||
fun throwThreadDeath(): Nothing = throw ThreadDeath()
|
||||
|
||||
fun throwStackOverflowError(): Nothing = throw StackOverflowError("FAKE OVERFLOW!")
|
||||
|
||||
fun throwOutOfMemoryError(): Nothing = throw OutOfMemoryError("FAKE OOM!")
|
||||
}
|
@ -5,25 +5,25 @@ import org.junit.Test
|
||||
|
||||
class ClassResolverTest {
|
||||
|
||||
private val resolver = ClassResolver(emptySet(), Whitelist.MINIMAL, "sandbox/")
|
||||
private val resolver = ClassResolver(emptySet(), emptySet(), Whitelist.MINIMAL, "sandbox/")
|
||||
|
||||
@Test
|
||||
fun `can resolve class name`() {
|
||||
assertThat(resolver.resolve("java/lang/Object")).isEqualTo("java/lang/Object")
|
||||
assertThat(resolver.resolve("java/lang/String")).isEqualTo("java/lang/String")
|
||||
assertThat(resolver.resolve("java/lang/String")).isEqualTo("sandbox/java/lang/String")
|
||||
assertThat(resolver.resolve("foo/bar/Test")).isEqualTo("sandbox/foo/bar/Test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can resolve class name for arrays`() {
|
||||
assertThat(resolver.resolve("[Ljava/lang/Object;")).isEqualTo("[Ljava/lang/Object;")
|
||||
assertThat(resolver.resolve("[Ljava/lang/String;")).isEqualTo("[Ljava/lang/String;")
|
||||
assertThat(resolver.resolve("[Ljava/lang/String;")).isEqualTo("[Lsandbox/java/lang/String;")
|
||||
assertThat(resolver.resolve("[Lfoo/bar/Test;")).isEqualTo("[Lsandbox/foo/bar/Test;")
|
||||
assertThat(resolver.resolve("[[Ljava/lang/Object;")).isEqualTo("[[Ljava/lang/Object;")
|
||||
assertThat(resolver.resolve("[[Ljava/lang/String;")).isEqualTo("[[Ljava/lang/String;")
|
||||
assertThat(resolver.resolve("[[Ljava/lang/String;")).isEqualTo("[[Lsandbox/java/lang/String;")
|
||||
assertThat(resolver.resolve("[[Lfoo/bar/Test;")).isEqualTo("[[Lsandbox/foo/bar/Test;")
|
||||
assertThat(resolver.resolve("[[[Ljava/lang/Object;")).isEqualTo("[[[Ljava/lang/Object;")
|
||||
assertThat(resolver.resolve("[[[Ljava/lang/String;")).isEqualTo("[[[Ljava/lang/String;")
|
||||
assertThat(resolver.resolve("[[[Ljava/lang/String;")).isEqualTo("[[[Lsandbox/java/lang/String;")
|
||||
assertThat(resolver.resolve("[[[Lfoo/bar/Test;")).isEqualTo("[[[Lsandbox/foo/bar/Test;")
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,8 @@ class WhitelistTest : TestBase() {
|
||||
val whitelist = Whitelist.MINIMAL
|
||||
assertThat(whitelist.matches("java/lang/Object")).isTrue()
|
||||
assertThat(whitelist.matches("java/lang/Object.<init>:()V")).isTrue()
|
||||
assertThat(whitelist.matches("java/lang/Integer")).isTrue()
|
||||
assertThat(whitelist.matches("java/lang/Integer.<init>:(I)V")).isTrue()
|
||||
assertThat(whitelist.matches("java/lang/reflect/Array")).isTrue()
|
||||
assertThat(whitelist.matches("java/lang/reflect/Array.setInt(Ljava/lang/Object;II)V")).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -28,4 +28,9 @@ class AssertiveClassWithByteCode(private val loadedClass: LoadedClass) {
|
||||
assertThat(loadedClass.type.name).isEqualTo(className)
|
||||
return this
|
||||
}
|
||||
|
||||
fun hasInterface(className: String): AssertiveClassWithByteCode {
|
||||
assertThat(loadedClass.type.interfaces.map(Class<*>::getName)).contains(className)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.Test
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class RuntimeCostTest {
|
||||
|
||||
@ -16,17 +17,13 @@ class RuntimeCostTest {
|
||||
|
||||
@Test
|
||||
fun `cannot increment cost beyond threshold`() {
|
||||
Thread {
|
||||
thread(name = "Foo") {
|
||||
val cost = RuntimeCost(10) { "failed in ${it.name}" }
|
||||
assertThatExceptionOfType(ThresholdViolationError::class.java)
|
||||
.isThrownBy { cost.increment(11) }
|
||||
.withMessage("failed in Foo")
|
||||
assertThat(cost.value).isEqualTo(11)
|
||||
}.apply {
|
||||
name = "Foo"
|
||||
start()
|
||||
join()
|
||||
}
|
||||
}.join()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.TestBase
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
|
||||
class SandboxEnumTest : TestBase() {
|
||||
@Test
|
||||
fun `test enum inside sandbox`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Array<String>>(configuration)
|
||||
contractExecutor.run<TransformEnum>(0).apply {
|
||||
assertThat(result).isEqualTo(arrayOf("ONE", "TWO", "THREE"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return enum from sandbox`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, ExampleEnum>(configuration)
|
||||
contractExecutor.run<FetchEnum>("THREE").apply {
|
||||
assertThat(result).isEqualTo(ExampleEnum.THREE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test we can identify class as Enum`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration)
|
||||
contractExecutor.run<AssertEnum>(ExampleEnum.THREE).apply {
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test we can create EnumMap`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Int>(configuration)
|
||||
contractExecutor.run<UseEnumMap>(ExampleEnum.TWO).apply {
|
||||
assertThat(result).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test we can create EnumSet`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration)
|
||||
contractExecutor.run<UseEnumSet>(ExampleEnum.ONE).apply {
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AssertEnum : Function<ExampleEnum, Boolean> {
|
||||
override fun apply(input: ExampleEnum): Boolean {
|
||||
return input::class.java.isEnum
|
||||
}
|
||||
}
|
||||
|
||||
class TransformEnum : Function<Int, Array<String>> {
|
||||
override fun apply(input: Int): Array<String> {
|
||||
return ExampleEnum.values().map(ExampleEnum::name).toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
class FetchEnum : Function<String, ExampleEnum> {
|
||||
override fun apply(input: String): ExampleEnum {
|
||||
return ExampleEnum.valueOf(input)
|
||||
}
|
||||
}
|
||||
|
||||
class UseEnumMap : Function<ExampleEnum, Int> {
|
||||
override fun apply(input: ExampleEnum): Int {
|
||||
val map = EnumMap<ExampleEnum, String>(ExampleEnum::class.java)
|
||||
map[input] = input.name
|
||||
return map.size
|
||||
}
|
||||
}
|
||||
|
||||
class UseEnumSet : Function<ExampleEnum, Boolean> {
|
||||
override fun apply(input: ExampleEnum): Boolean {
|
||||
return EnumSet.allOf(ExampleEnum::class.java).contains(input)
|
||||
}
|
||||
}
|
||||
|
||||
enum class ExampleEnum {
|
||||
ONE, TWO, THREE
|
||||
}
|
@ -1,10 +1,19 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import foo.bar.sandbox.MyObject
|
||||
import foo.bar.sandbox.testRandom
|
||||
import foo.bar.sandbox.testClock
|
||||
import foo.bar.sandbox.toNumber
|
||||
import net.corda.djvm.TestBase
|
||||
import net.corda.djvm.analysis.Whitelist
|
||||
import net.corda.djvm.Utilities
|
||||
import net.corda.djvm.Utilities.throwContractConstraintViolation
|
||||
import net.corda.djvm.Utilities.throwError
|
||||
import net.corda.djvm.Utilities.throwOutOfMemoryError
|
||||
import net.corda.djvm.Utilities.throwRuleViolationError
|
||||
import net.corda.djvm.Utilities.throwStackOverflowError
|
||||
import net.corda.djvm.Utilities.throwThreadDeath
|
||||
import net.corda.djvm.Utilities.throwThresholdViolationError
|
||||
import net.corda.djvm.Utilities.throwThrowable
|
||||
import net.corda.djvm.assertions.AssertionExtensions.withProblem
|
||||
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -13,8 +22,8 @@ import org.junit.Test
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
import sandbox.net.corda.djvm.rules.RuleViolationError
|
||||
import java.nio.file.Files
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
import java.util.stream.Collectors.*
|
||||
|
||||
class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@ -34,7 +43,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute contract`() = sandbox(
|
||||
pinnedClasses = setOf(Transaction::class.java)
|
||||
pinnedClasses = setOf(Transaction::class.java, Utilities::class.java)
|
||||
) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Transaction, Unit>(configuration)
|
||||
val tx = Transaction(1)
|
||||
@ -44,13 +53,13 @@ class SandboxExecutorTest : TestBase() {
|
||||
.withMessageContaining("Contract constraint violated")
|
||||
}
|
||||
|
||||
class Contract : Function<Transaction?, Unit> {
|
||||
override fun apply(input: Transaction?) {
|
||||
throw IllegalArgumentException("Contract constraint violated")
|
||||
class Contract : Function<Transaction, Unit> {
|
||||
override fun apply(input: Transaction) {
|
||||
throwContractConstraintViolation()
|
||||
}
|
||||
}
|
||||
|
||||
data class Transaction(val id: Int?)
|
||||
data class Transaction(val id: Int)
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that overrides object hash code`() = sandbox(DEFAULT) {
|
||||
@ -65,7 +74,11 @@ class SandboxExecutorTest : TestBase() {
|
||||
val obj = Object()
|
||||
val hash1 = obj.hashCode()
|
||||
val hash2 = obj.hashCode()
|
||||
require(hash1 == hash2)
|
||||
//require(hash1 == hash2)
|
||||
// TODO: Replace require() once we have working exception support.
|
||||
if (hash1 != hash2) {
|
||||
throwError()
|
||||
}
|
||||
return Object().hashCode()
|
||||
}
|
||||
}
|
||||
@ -123,37 +136,37 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can detect illegal references in Kotlin meta-classes`() = sandbox(DEFAULT, ExecutionProfile.DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Long>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestKotlinMetaClasses>(0) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
.withMessageContaining("Disallowed reference to reflection API")
|
||||
.withCauseInstanceOf(NoSuchMethodError::class.java)
|
||||
.withProblem("sandbox.java.lang.System.nanoTime()J")
|
||||
}
|
||||
|
||||
class TestKotlinMetaClasses : Function<Int, Int> {
|
||||
override fun apply(input: Int): Int {
|
||||
val someNumber = testRandom()
|
||||
class TestKotlinMetaClasses : Function<Int, Long> {
|
||||
override fun apply(input: Int): Long {
|
||||
val someNumber = testClock()
|
||||
return "12345".toNumber() * someNumber
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot execute runnable that references non-deterministic code`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Long>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestNonDeterministicCode>(0) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
.withProblem("Disallowed reference to reflection API")
|
||||
.withCauseInstanceOf(NoSuchMethodError::class.java)
|
||||
.withProblem("sandbox.java.lang.System.currentTimeMillis()J")
|
||||
}
|
||||
|
||||
class TestNonDeterministicCode : Function<Int, Int> {
|
||||
override fun apply(input: Int): Int {
|
||||
return Random().nextInt()
|
||||
class TestNonDeterministicCode : Function<Int, Long> {
|
||||
override fun apply(input: Int): Long {
|
||||
return System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot execute runnable that catches ThreadDeath`() = sandbox(DEFAULT) {
|
||||
fun `cannot execute runnable that catches ThreadDeath`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
TestCatchThreadDeath().apply {
|
||||
assertThat(apply(0)).isEqualTo(1)
|
||||
}
|
||||
@ -175,7 +188,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot execute runnable that catches ThresholdViolationError`() = sandbox(DEFAULT) {
|
||||
fun `cannot execute runnable that catches ThresholdViolationError`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
TestCatchThresholdViolationError().apply {
|
||||
assertThat(apply(0)).isEqualTo(1)
|
||||
}
|
||||
@ -190,7 +203,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
class TestCatchThresholdViolationError : Function<Int, Int> {
|
||||
override fun apply(input: Int): Int {
|
||||
return try {
|
||||
throw ThresholdViolationError("Can't catch this!")
|
||||
throwThresholdViolationError()
|
||||
} catch (exception: ThresholdViolationError) {
|
||||
1
|
||||
}
|
||||
@ -198,7 +211,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot execute runnable that catches RuleViolationError`() = sandbox(DEFAULT) {
|
||||
fun `cannot execute runnable that catches RuleViolationError`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
TestCatchRuleViolationError().apply {
|
||||
assertThat(apply(0)).isEqualTo(1)
|
||||
}
|
||||
@ -213,7 +226,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
class TestCatchRuleViolationError : Function<Int, Int> {
|
||||
override fun apply(input: Int): Int {
|
||||
return try {
|
||||
throw RuleViolationError("Can't catch this!")
|
||||
throwRuleViolationError()
|
||||
} catch (exception: RuleViolationError) {
|
||||
1
|
||||
}
|
||||
@ -221,7 +234,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can catch Throwable`() = sandbox(DEFAULT) {
|
||||
fun `can catch Throwable`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
contractExecutor.run<TestCatchThrowableAndError>(1).apply {
|
||||
assertThat(result).isEqualTo(1)
|
||||
@ -229,7 +242,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can catch Error`() = sandbox(DEFAULT) {
|
||||
fun `can catch Error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
contractExecutor.run<TestCatchThrowableAndError>(2).apply {
|
||||
assertThat(result).isEqualTo(2)
|
||||
@ -237,7 +250,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot catch ThreadDeath`() = sandbox(DEFAULT) {
|
||||
fun `cannot catch ThreadDeath`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(3) }
|
||||
@ -272,12 +285,12 @@ class SandboxExecutorTest : TestBase() {
|
||||
3
|
||||
}
|
||||
4 -> try {
|
||||
throw StackOverflowError("FAKE OVERFLOW!")
|
||||
throwStackOverflowError()
|
||||
} catch (ex: StackOverflowError) {
|
||||
4
|
||||
}
|
||||
5 -> try {
|
||||
throw OutOfMemoryError("FAKE OOM!")
|
||||
throwOutOfMemoryError()
|
||||
} catch (ex: OutOfMemoryError) {
|
||||
5
|
||||
}
|
||||
@ -292,7 +305,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot catch stack-overflow error`() = sandbox(DEFAULT) {
|
||||
fun `cannot catch stack-overflow error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(4) }
|
||||
@ -301,7 +314,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cannot catch out-of-memory error`() = sandbox(DEFAULT) {
|
||||
fun `cannot catch out-of-memory error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(5) }
|
||||
@ -371,7 +384,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that uses notify()`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestMonitors>(1) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
@ -381,7 +394,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that uses notifyAll()`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestMonitors>(2) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
@ -391,7 +404,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that uses wait()`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestMonitors>(3) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
@ -401,7 +414,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that uses wait(long)`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestMonitors>(4) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
@ -411,7 +424,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `can load and execute code that uses wait(long,int)`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestMonitors>(5) }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
@ -421,13 +434,13 @@ class SandboxExecutorTest : TestBase() {
|
||||
|
||||
@Test
|
||||
fun `code after forbidden APIs is intact`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String?>(configuration)
|
||||
assertThat(contractExecutor.run<TestMonitors>(0).result)
|
||||
.isEqualTo("unknown")
|
||||
}
|
||||
|
||||
class TestMonitors : Function<Int, String> {
|
||||
override fun apply(input: Int): String {
|
||||
class TestMonitors : Function<Int, String?> {
|
||||
override fun apply(input: Int): String? {
|
||||
return synchronized(this) {
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
val javaObject = this as java.lang.Object
|
||||
@ -493,6 +506,73 @@ class SandboxExecutorTest : TestBase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check building a string`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String?, String?>(configuration)
|
||||
contractExecutor.run<TestStringBuilding>("Hello Sandbox!").apply {
|
||||
assertThat(result)
|
||||
.isEqualTo("SANDBOX: Boolean=true, Char='X', Integer=1234, Long=99999, Short=3200, Byte=101, String='Hello Sandbox!', Float=123.456, Double=987.6543")
|
||||
}
|
||||
}
|
||||
|
||||
class TestStringBuilding : Function<String?, String?> {
|
||||
override fun apply(input: String?): String? {
|
||||
return StringBuilder("SANDBOX")
|
||||
.append(": Boolean=").append(true)
|
||||
.append(", Char='").append('X')
|
||||
.append("', Integer=").append(1234)
|
||||
.append(", Long=").append(99999L)
|
||||
.append(", Short=").append(3200.toShort())
|
||||
.append(", Byte=").append(101.toByte())
|
||||
.append(", String='").append(input)
|
||||
.append("', Float=").append(123.456f)
|
||||
.append(", Double=").append(987.6543)
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check System-arraycopy still works with Objects`() = sandbox(DEFAULT) {
|
||||
val source = arrayOf("one", "two", "three")
|
||||
assertThat(TestArrayCopy().apply(source))
|
||||
.isEqualTo(source)
|
||||
.isNotSameAs(source)
|
||||
|
||||
val contractExecutor = DeterministicSandboxExecutor<Array<String>, Array<String>>(configuration)
|
||||
contractExecutor.run<TestArrayCopy>(source).apply {
|
||||
assertThat(result)
|
||||
.isEqualTo(source)
|
||||
.isNotSameAs(source)
|
||||
}
|
||||
}
|
||||
|
||||
class TestArrayCopy : Function<Array<String>, Array<String>> {
|
||||
override fun apply(input: Array<String>): Array<String> {
|
||||
val newArray = Array(input.size) { "" }
|
||||
System.arraycopy(input, 0, newArray, 0, newArray.size)
|
||||
return newArray
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test System-arraycopy still works with CharArray`() = sandbox(DEFAULT) {
|
||||
val source = CharArray(10) { '?' }
|
||||
val contractExecutor = DeterministicSandboxExecutor<CharArray, CharArray>(configuration)
|
||||
contractExecutor.run<TestCharArrayCopy>(source).apply {
|
||||
assertThat(result)
|
||||
.isEqualTo(source)
|
||||
.isNotSameAs(source)
|
||||
}
|
||||
}
|
||||
|
||||
class TestCharArrayCopy : Function<CharArray, CharArray> {
|
||||
override fun apply(input: CharArray): CharArray {
|
||||
val newArray = CharArray(input.size) { 'X' }
|
||||
System.arraycopy(input, 0, newArray, 0, newArray.size)
|
||||
return newArray
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can load and execute class that has finalize`() = sandbox(DEFAULT) {
|
||||
assertThatExceptionOfType(UnsupportedOperationException::class.java)
|
||||
@ -515,4 +595,152 @@ class SandboxExecutorTest : TestBase() {
|
||||
throw UnsupportedOperationException("Very Bad Thing")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can execute parallel stream`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, String>(configuration)
|
||||
contractExecutor.run<TestParallelStream>("Pebble").apply {
|
||||
assertThat(result).isEqualTo("Five,Four,One,Pebble,Three,Two")
|
||||
}
|
||||
}
|
||||
|
||||
class TestParallelStream : Function<String, String> {
|
||||
override fun apply(input: String): String {
|
||||
return listOf(input, "One", input, "Two", input, "Three", input, "Four", input, "Five")
|
||||
.stream()
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(joining(","))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `users cannot load our sandboxed classes`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<TestClassForName>("java.lang.DJVM") }
|
||||
.withCauseInstanceOf(ClassNotFoundException::class.java)
|
||||
.withMessageContaining("java.lang.DJVM")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `users can load sandboxed classes`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
|
||||
contractExecutor.run<TestClassForName>("java.util.List").apply {
|
||||
assertThat(result?.name).isEqualTo("sandbox.java.util.List")
|
||||
}
|
||||
}
|
||||
|
||||
class TestClassForName : Function<String, Class<*>> {
|
||||
override fun apply(input: String): Class<*> {
|
||||
return Class.forName(input)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test case-insensitive string sorting`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Array<String>, Array<String>>(configuration)
|
||||
contractExecutor.run<CaseInsensitiveSort>(arrayOf("Zelda", "angela", "BOB", "betsy", "ALBERT")).apply {
|
||||
assertThat(result).isEqualTo(arrayOf("ALBERT", "angela", "betsy", "BOB", "Zelda"))
|
||||
}
|
||||
}
|
||||
|
||||
class CaseInsensitiveSort : Function<Array<String>, Array<String>> {
|
||||
override fun apply(input: Array<String>): Array<String> {
|
||||
return listOf(*input).sortedWith(String.CASE_INSENSITIVE_ORDER).toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unicode characters`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
|
||||
contractExecutor.run<ExamineUnicodeBlock>(0x01f600).apply {
|
||||
assertThat(result).isEqualTo("EMOTICONS")
|
||||
}
|
||||
}
|
||||
|
||||
class ExamineUnicodeBlock : Function<Int, String> {
|
||||
override fun apply(codePoint: Int): String {
|
||||
return Character.UnicodeBlock.of(codePoint).toString()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unicode scripts`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Character.UnicodeScript?>(configuration)
|
||||
contractExecutor.run<ExamineUnicodeScript>("COMMON").apply {
|
||||
assertThat(result).isEqualTo(Character.UnicodeScript.COMMON)
|
||||
}
|
||||
}
|
||||
|
||||
class ExamineUnicodeScript : Function<String, Character.UnicodeScript?> {
|
||||
override fun apply(scriptName: String): Character.UnicodeScript? {
|
||||
val script = Character.UnicodeScript.valueOf(scriptName)
|
||||
return if (script::class.java.isEnum) script else null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test users cannot define new classes`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<DefineNewClass>("sandbox.java.lang.DJVM") }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
.withMessageContaining("Disallowed reference to API;")
|
||||
.withMessageContaining("java.lang.ClassLoader.defineClass")
|
||||
}
|
||||
|
||||
class DefineNewClass : Function<String, Class<*>> {
|
||||
override fun apply(input: String): Class<*> {
|
||||
val data = ByteArray(0)
|
||||
val cl = object : ClassLoader(this::class.java.classLoader) {
|
||||
fun define(): Class<*> {
|
||||
return super.defineClass(input, data, 0, data.size)
|
||||
}
|
||||
}
|
||||
return cl.define()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test users cannot load new classes`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<LoadNewClass>("sandbox.java.lang.DJVM") }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
.withMessageContaining("Disallowed reference to API;")
|
||||
.withMessageContaining("java.lang.ClassLoader.loadClass")
|
||||
}
|
||||
|
||||
class LoadNewClass : Function<String, Class<*>> {
|
||||
override fun apply(input: String): Class<*> {
|
||||
val cl = object : ClassLoader(this::class.java.classLoader) {
|
||||
fun load(): Class<*> {
|
||||
return super.loadClass(input)
|
||||
}
|
||||
}
|
||||
return cl.load()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test users cannot lookup classes`() = sandbox(DEFAULT) {
|
||||
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
|
||||
assertThatExceptionOfType(SandboxException::class.java)
|
||||
.isThrownBy { contractExecutor.run<FindClass>("sandbox.java.lang.DJVM") }
|
||||
.withCauseInstanceOf(RuleViolationError::class.java)
|
||||
.withMessageContaining("Disallowed reference to API;")
|
||||
.withMessageContaining("java.lang.ClassLoader.findClass")
|
||||
}
|
||||
|
||||
class FindClass : Function<String, Class<*>> {
|
||||
override fun apply(input: String): Class<*> {
|
||||
val cl = object : ClassLoader(this::class.java.classLoader) {
|
||||
fun find(): Class<*> {
|
||||
return super.findClass(input)
|
||||
}
|
||||
}
|
||||
return cl.find()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,14 @@
|
||||
package net.corda.djvm.rewiring
|
||||
|
||||
import foo.bar.sandbox.A
|
||||
import foo.bar.sandbox.B
|
||||
import foo.bar.sandbox.Empty
|
||||
import foo.bar.sandbox.StrictFloat
|
||||
import foo.bar.sandbox.*
|
||||
import net.corda.djvm.TestBase
|
||||
import net.corda.djvm.assertions.AssertionExtensions.assertThat
|
||||
import net.corda.djvm.execution.ExecutionProfile
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Test
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
class ClassRewriterTest : TestBase() {
|
||||
|
||||
@ -102,4 +100,44 @@ class ClassRewriterTest : TestBase() {
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can load class with constant fields`() = sandbox(DEFAULT) {
|
||||
assertThat(loadClass<ObjectWithConstants>())
|
||||
.hasClassName("sandbox.net.corda.djvm.rewiring.ObjectWithConstants")
|
||||
.hasBeenModified()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test rewrite static method`() = sandbox(DEFAULT) {
|
||||
assertThat(loadClass<Arrays>())
|
||||
.hasClassName("sandbox.java.util.Arrays")
|
||||
.hasBeenModified()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test stitch new super-interface`() = sandbox(DEFAULT) {
|
||||
assertThat(loadClass<CharSequence>())
|
||||
.hasClassName("sandbox.java.lang.CharSequence")
|
||||
.hasInterface("java.lang.CharSequence")
|
||||
.hasBeenModified()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test class with stitched interface`() = sandbox(DEFAULT) {
|
||||
assertThat(loadClass<StringBuilder>())
|
||||
.hasClassName("sandbox.java.lang.StringBuilder")
|
||||
.hasInterface("sandbox.java.lang.CharSequence")
|
||||
.hasBeenModified()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private object ObjectWithConstants {
|
||||
const val MESSAGE = "Hello Sandbox!"
|
||||
const val BIG_NUMBER = 99999L
|
||||
const val NUMBER = 100
|
||||
const val CHAR = '?'
|
||||
const val BYTE = 7f.toByte()
|
||||
val DATA = Array(0) { "" }
|
||||
}
|
@ -10,7 +10,7 @@ import java.nio.file.Path
|
||||
|
||||
class SourceClassLoaderTest {
|
||||
|
||||
private val classResolver = ClassResolver(emptySet(), Whitelist.MINIMAL, "")
|
||||
private val classResolver = ClassResolver(emptySet(), emptySet(), Whitelist.MINIMAL, "")
|
||||
|
||||
@Test
|
||||
fun `can load class from Java's lang package when no files are provided to the class loader`() {
|
||||
|
Loading…
Reference in New Issue
Block a user