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:
Chris Rankin 2018-10-11 13:48:32 +01:00 committed by GitHub
parent 0e68f26c0f
commit 825c544cac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 3986 additions and 336 deletions

View File

@ -52,6 +52,20 @@ shadowJar {
baseName 'corda-djvm' baseName 'corda-djvm'
classifier '' classifier ''
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm' 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 assemble.dependsOn shadowJar

View 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;
}

View 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); }
}

View 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);
}
}

View 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();
}

View 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() {}
}
}

View 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> {
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}
}

View 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();
}

View 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);
}
}
}

View 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());
}
}

View 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());
}
}

View 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() {}
}

View 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);
}
}

View 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();
}
}

View 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);
}

View 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);
}

View 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() {}
}

View 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();
}
}
}

View 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;
}
}

View 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> {
}

View 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() {
}
}

View 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();
}

View 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> {
}

View 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);
}

View 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();
}

View 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);
}

View 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;
}
}

View File

@ -1,12 +1,17 @@
package net.corda.djvm.analysis package net.corda.djvm.analysis
import net.corda.djvm.code.EmitterModule
import net.corda.djvm.code.ruleViolationError import net.corda.djvm.code.ruleViolationError
import net.corda.djvm.code.thresholdViolationError import net.corda.djvm.code.thresholdViolationError
import net.corda.djvm.messages.Severity import net.corda.djvm.messages.Severity
import net.corda.djvm.references.ClassModule import net.corda.djvm.references.ClassModule
import net.corda.djvm.references.Member
import net.corda.djvm.references.MemberModule import net.corda.djvm.references.MemberModule
import net.corda.djvm.references.MethodBody
import net.corda.djvm.source.BootstrapClassLoader import net.corda.djvm.source.BootstrapClassLoader
import net.corda.djvm.source.SourceClassLoader import net.corda.djvm.source.SourceClassLoader
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
import java.io.Closeable import java.io.Closeable
import java.io.IOException 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 * 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( val pinnedClasses: Set<String> = MANDATORY_PINNED_CLASSES + additionalPinnedClasses
SANDBOXED_OBJECT,
RuntimeCostAccounter.TYPE_NAME, /**
ruleViolationError, * These interfaces are modified as they are mapped into the sandbox by
thresholdViolationError * having their unsandboxed version "stitched in" as a super-interface.
) + additionalPinnedClasses * 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. * 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) } private val bootstrapClassLoader = bootstrapJar?.let { BootstrapClassLoader(it, classResolver) }
val supportingClassLoader = SourceClassLoader(classPath, classResolver, bootstrapClassLoader) 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 { companion object {
/** /**
* The package name prefix to use for classes loaded into a sandbox. * The package name prefix to use for classes loaded into a sandbox.
*/ */
private const val SANDBOX_PREFIX: String = "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
)
}
} }

View File

@ -85,8 +85,12 @@ open class ClassAndMemberVisitor(
/** /**
* Process class after it has been fully traversed and analyzed. * 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). * 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 { protected fun shouldBeProcessed(className: String): Boolean {
return !configuration.whitelist.inNamespace(className) && return !configuration.whitelist.inNamespace(className) &&
className !in configuration.pinnedClasses !configuration.isPinnedClass(className)
} }
/** /**
@ -241,7 +245,7 @@ open class ClassAndMemberVisitor(
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations) .getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
.forEach(::recordTypeReference) .forEach(::recordTypeReference)
captureExceptions { captureExceptions {
visitClassEnd(currentClass!!) visitClassEnd(this, currentClass!!)
} }
super.visitEnd() super.visitEnd()
} }
@ -385,7 +389,9 @@ open class ClassAndMemberVisitor(
*/ */
override fun visitCode() { override fun visitCode() {
tryReplaceMethodBody() tryReplaceMethodBody()
super.visitCode() visit(MethodEntry(method)) {
super.visitCode()
}
} }
/** /**
@ -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 * Finish visiting this method, writing any new method body byte-code
* if we haven't written it already. This would (presumably) only happen * if we haven't written it already. This would (presumably) only happen

View File

@ -26,6 +26,7 @@ import net.corda.djvm.code.asResourcePath
*/ */
class ClassResolver( class ClassResolver(
private val pinnedClasses: Set<String>, private val pinnedClasses: Set<String>,
private val templateClasses: Set<String>,
private val whitelist: Whitelist, private val whitelist: Whitelist,
private val sandboxPrefix: String private val sandboxPrefix: String
) { ) {
@ -83,7 +84,7 @@ class ClassResolver(
* Reverse the resolution of a class name. * Reverse the resolution of a class name.
*/ */
fun reverse(resolvedClassName: String): String { fun reverse(resolvedClassName: String): String {
if (resolvedClassName in pinnedClasses) { if (resolvedClassName in pinnedClasses || resolvedClassName in templateClasses) {
return resolvedClassName return resolvedClassName
} }
if (resolvedClassName.startsWith(sandboxPrefix)) { 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 { private fun resolveName(name: String): String {
return if (isPinnedOrWhitelistedClass(name)) { return if (isPinnedOrWhitelistedClass(name) || name in templateClasses) {
name name
} else { } else {
"$sandboxPrefix$name" "$sandboxPrefix$name"
@ -122,10 +123,10 @@ class ClassResolver(
sandboxRegex.matches(name) sandboxRegex.matches(name)
} }
private val sandboxRegex = "^$sandboxPrefix.*$".toRegex() private val sandboxRegex = "^$sandboxPrefix.*\$".toRegex()
companion object { companion object {
private val complexArrayTypeRegex = "^(\\[+)L(.*);$".toRegex() private val complexArrayTypeRegex = "^(\\[+)L(.*);\$".toRegex()
} }
} }

View File

@ -89,36 +89,25 @@ open class Whitelist private constructor(
* Enumerate all the entries of the whitelist. * Enumerate all the entries of the whitelist.
*/ */
val items: Set<String> val items: Set<String>
get() = textEntries + entries.map { it.pattern } get() = textEntries + entries.map(Regex::pattern)
companion object { companion object {
private val everythingRegex = setOf(".*".toRegex()) private val everythingRegex = setOf(".*".toRegex())
private val minimumSet = setOf( private val minimumSet = setOf(
"^java/lang/Boolean(\\..*)?$".toRegex(), "^java/lang/Class(\\..*)?\$".toRegex(),
"^java/lang/Byte(\\..*)?$".toRegex(), "^java/lang/ClassLoader(\\..*)?\$".toRegex(),
"^java/lang/Character(\\..*)?$".toRegex(), "^java/lang/Cloneable(\\..*)?\$".toRegex(),
"^java/lang/Class(\\..*)?$".toRegex(), "^java/lang/Object(\\..*)?\$".toRegex(),
"^java/lang/ClassLoader(\\..*)?$".toRegex(), "^java/lang/Override(\\..*)?\$".toRegex(),
"^java/lang/Cloneable(\\..*)?$".toRegex(), // TODO: sandbox exception handling!
"^java/lang/Comparable(\\..*)?$".toRegex(), "^java/lang/StackTraceElement\$".toRegex(),
"^java/lang/Double(\\..*)?$".toRegex(), "^java/lang/Throwable\$".toRegex(),
"^java/lang/Enum(\\..*)?$".toRegex(), "^java/lang/Void\$".toRegex(),
"^java/lang/Float(\\..*)?$".toRegex(), "^java/lang/invoke/LambdaMetafactory\$".toRegex(),
"^java/lang/Integer(\\..*)?$".toRegex(), "^java/lang/invoke/MethodHandles(\\\$.*)?\$".toRegex(),
"^java/lang/Iterable(\\..*)?$".toRegex(), "^java/lang/reflect/Array(\\..*)?\$".toRegex(),
"^java/lang/Long(\\..*)?$".toRegex(), "^java/io/Serializable\$".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()
) )
/** /**

View File

@ -2,26 +2,48 @@ package net.corda.djvm.code
import net.corda.djvm.analysis.AnalysisConfiguration import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.ClassAndMemberVisitor import net.corda.djvm.analysis.ClassAndMemberVisitor
import net.corda.djvm.code.instructions.MethodEntry
import net.corda.djvm.references.ClassRepresentation import net.corda.djvm.references.ClassRepresentation
import net.corda.djvm.references.Member import net.corda.djvm.references.Member
import net.corda.djvm.references.MethodBody
import net.corda.djvm.utilities.Processor import net.corda.djvm.utilities.Processor
import net.corda.djvm.utilities.loggerFor import net.corda.djvm.utilities.loggerFor
import org.objectweb.asm.ClassVisitor 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. * 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. * @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 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( class ClassMutator(
classVisitor: ClassVisitor, classVisitor: ClassVisitor,
private val configuration: AnalysisConfiguration, private val configuration: AnalysisConfiguration,
private val definitionProviders: List<DefinitionProvider> = emptyList(), private val definitionProviders: List<DefinitionProvider> = emptyList(),
private val emitters: List<Emitter> = emptyList() emitters: List<Emitter> = emptyList()
) : ClassAndMemberVisitor(configuration, classVisitor) { ) : 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. * 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) 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) * Apply definition providers to a method. This can be used to update the name or definition (pertinent meta-data)
* of a class member. * of a class member.
@ -71,6 +116,7 @@ class ClassMutator(
} }
if (field != resultingField) { if (field != resultingField) {
logger.trace("Field has been mutated {}", field) logger.trace("Field has been mutated {}", field)
initializers += resultingField.body
hasBeenModified = true hasBeenModified = true
} }
return super.visitField(clazz, resultingField) return super.visitField(clazz, resultingField)

View File

@ -1,5 +1,6 @@
package net.corda.djvm.code package net.corda.djvm.code
import net.corda.djvm.references.MethodBody
import org.objectweb.asm.Label import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.* 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) { fun loadConstant(constant: Any) {
hasEmittedCustomCode = true
methodVisitor.visitLdcInsn(constant)
}
/**
* Emit instruction for loading a string constant onto the stack.
*/
fun loadConstant(constant: String) {
hasEmittedCustomCode = true hasEmittedCustomCode = true
methodVisitor.visitLdcInsn(constant) methodVisitor.visitLdcInsn(constant)
} }
@ -67,6 +60,14 @@ class EmitterModule(
methodVisitor.visitMethodInsn(INVOKESTATIC, owner, name, descriptor, isInterface) 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. * 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) 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. * Emit instruction for popping one element off the stack.
*/ */
@ -98,11 +112,52 @@ class EmitterModule(
methodVisitor.visitInsn(DUP) 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. * 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) { fun <T : Throwable> throwException(exceptionType: Class<T>, message: String) {
hasEmittedCustomCode = true
val exceptionName = Type.getInternalName(exceptionType) val exceptionName = Type.getInternalName(exceptionType)
new(exceptionName) new(exceptionName)
methodVisitor.visitInsn(DUP) methodVisitor.visitInsn(DUP)
@ -121,6 +176,14 @@ class EmitterModule(
hasEmittedCustomCode = true 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. * Emit instructions for a new line number.
*/ */
@ -131,6 +194,15 @@ class EmitterModule(
hasEmittedCustomCode = true 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. * Tell the code writer not to emit the default instruction.
*/ */

View File

@ -12,4 +12,6 @@ val thresholdViolationError: String = Type.getInternalName(ThresholdViolationErr
* Local extension method for normalizing a class name. * Local extension method for normalizing a class name.
*/ */
val String.asPackagePath: String get() = this.replace('/', '.') val String.asPackagePath: String get() = this.replace('/', '.')
val String.asResourcePath: String get() = this.replace('.', '/') val String.asResourcePath: String get() = this.replace('.', '/')
val String.emptyAsNull: String? get() = if (isEmpty()) null else this

View File

@ -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)

View File

@ -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()

View File

@ -5,6 +5,7 @@ import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.messages.Message import net.corda.djvm.messages.Message
import net.corda.djvm.references.ClassReference import net.corda.djvm.references.ClassReference
import net.corda.djvm.references.MemberReference import net.corda.djvm.references.MemberReference
import net.corda.djvm.references.ReferenceWithLocation
import net.corda.djvm.rewiring.LoadedClass import net.corda.djvm.rewiring.LoadedClass
import net.corda.djvm.rewiring.SandboxClassLoader import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.rewiring.SandboxClassLoadingException import net.corda.djvm.rewiring.SandboxClassLoadingException
@ -67,12 +68,24 @@ open class SandboxExecutor<in TInput, out TOutput>(
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration) val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
val result = IsolatedTask(runnableClass.qualifiedClassName, configuration).run { val result = IsolatedTask(runnableClass.qualifiedClassName, configuration).run {
validate(context, classLoader, classSources) validate(context, classLoader, classSources)
val loadedClass = classLoader.loadClassAndBytes(runnableClass, context)
val instance = loadedClass.type.newInstance() // Load the "entry-point" task class into the sandbox. This task will marshall
val method = loadedClass.type.getMethod("apply", Any::class.java) // 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 { try {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
method.invoke(instance, input) as? TOutput method.invoke(task, input) as? TOutput
} catch (ex: InvocationTargetException) { } catch (ex: InvocationTargetException) {
throw ex.targetException throw ex.targetException
} }
@ -101,7 +114,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
fun load(classSource: ClassSource): LoadedClass { fun load(classSource: ClassSource): LoadedClass {
val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration) val context = AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
val result = IsolatedTask("LoadClass", configuration).run { val result = IsolatedTask("LoadClass", configuration).run {
classLoader.loadClassAndBytes(classSource, context) classLoader.loadForSandbox(classSource, context)
} }
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName) return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
} }
@ -146,7 +159,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
): ReferenceValidationSummary { ): ReferenceValidationSummary {
processClassQueue(*classSources.toTypedArray()) { classSource, className -> processClassQueue(*classSources.toTypedArray()) { classSource, className ->
val didLoad = try { val didLoad = try {
classLoader.loadClassAndBytes(classSource, context) classLoader.loadForSandbox(classSource, context)
true true
} catch (exception: SandboxClassLoadingException) { } catch (exception: SandboxClassLoadingException) {
// Continue; all warnings and errors are captured in [context.messages] // Continue; all warnings and errors are captured in [context.messages]
@ -155,7 +168,7 @@ open class SandboxExecutor<in TInput, out TOutput>(
if (didLoad) { if (didLoad) {
context.classes[className]?.apply { context.classes[className]?.apply {
context.references.referencesFromLocation(className) context.references.referencesFromLocation(className)
.map { it.reference } .map(ReferenceWithLocation::reference)
.filterIsInstance<ClassReference>() .filterIsInstance<ClassReference>()
.filter { it.className != className } .filter { it.className != className }
.distinct() .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<*, *>>()
}
} }

View File

@ -1,23 +1,26 @@
package net.corda.djvm.rewiring package net.corda.djvm.rewiring
import net.corda.djvm.SandboxConfiguration import net.corda.djvm.SandboxConfiguration
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.AnalysisContext 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.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 net.corda.djvm.utilities.loggerFor
import org.objectweb.asm.ClassReader 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 configuration The configuration of the sandbox.
* @property classLoader The class loader used to load the classes that are to be rewritten. * @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( open class ClassRewriter(
private val configuration: SandboxConfiguration, private val configuration: SandboxConfiguration,
private val classLoader: ClassLoader, private val classLoader: ClassLoader
private val remapper: SandboxRemapper = SandboxRemapper(configuration.analysisConfiguration.classResolver)
) { ) {
/** /**
@ -29,20 +32,53 @@ open class ClassRewriter(
fun rewrite(reader: ClassReader, context: AnalysisContext): ByteCode { fun rewrite(reader: ClassReader, context: AnalysisContext): ByteCode {
logger.debug("Rewriting class {}...", reader.className) logger.debug("Rewriting class {}...", reader.className)
val writer = SandboxClassWriter(reader, classLoader) val writer = SandboxClassWriter(reader, classLoader)
val classRemapper = ClassRemapper(writer, remapper) val analysisConfiguration = configuration.analysisConfiguration
val classRemapper = SandboxClassRemapper(InterfaceStitcher(writer, analysisConfiguration), analysisConfiguration)
val visitor = ClassMutator( val visitor = ClassMutator(
classRemapper, classRemapper,
configuration.analysisConfiguration, analysisConfiguration,
configuration.definitionProviders, configuration.definitionProviders,
configuration.emitters configuration.emitters
) )
visitor.analyze(reader, context, options = ClassReader.EXPAND_FRAMES) visitor.analyze(reader, context, options = ClassReader.EXPAND_FRAMES)
val hasBeenModified = visitor.hasBeenModified return ByteCode(writer.toByteArray(), visitor.hasBeenModified)
return ByteCode(writer.toByteArray(), hasBeenModified)
} }
private companion object { private companion object {
private val logger = loggerFor<ClassRewriter>() 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()
}
}
} }

View File

@ -18,7 +18,7 @@ import net.corda.djvm.validation.RuleValidator
class SandboxClassLoader( class SandboxClassLoader(
configuration: SandboxConfiguration, configuration: SandboxConfiguration,
private val context: AnalysisContext private val context: AnalysisContext
) : ClassLoader(null) { ) : ClassLoader() {
private val analysisConfiguration = configuration.analysisConfiguration private val analysisConfiguration = configuration.analysisConfiguration
@ -36,11 +36,6 @@ class SandboxClassLoader(
val analyzer: ClassAndMemberVisitor val analyzer: ClassAndMemberVisitor
get() = ruleValidator 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. * Set of classes that should be left untouched due to whitelisting.
*/ */
@ -61,6 +56,17 @@ class SandboxClassLoader(
*/ */
private val rewriter: ClassRewriter = ClassRewriter(configuration, supportingClassLoader) 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. * Load the class with the specified binary name.
* *
@ -69,69 +75,68 @@ class SandboxClassLoader(
* *
* @return The resulting <tt>Class</tt> object. * @return The resulting <tt>Class</tt> object.
*/ */
@Throws(ClassNotFoundException::class)
override fun loadClass(name: String, resolve: Boolean): 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. * 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. * @param context The context in which the analysis is conducted.
* *
* @return The resulting <tt>Class</tt> object and its byte code representation. * @return The resulting <tt>Class</tt> object and its byte code representation.
*/ */
fun loadClassAndBytes(source: ClassSource, context: AnalysisContext): LoadedClass { private fun loadClassAndBytes(request: ClassSource, context: AnalysisContext): LoadedClass {
logger.debug("Loading class {}, origin={}...", source.qualifiedClassName, source.origin) logger.debug("Loading class {}, origin={}...", request.qualifiedClassName, request.origin)
val name = analysisConfiguration.classResolver.reverseNormalized(source.qualifiedClassName) val requestedPath = request.internalClassName
val resolvedName = analysisConfiguration.classResolver.resolveNormalized(name) val sourceName = analysisConfiguration.classResolver.reverseNormalized(request.qualifiedClassName)
val resolvedName = analysisConfiguration.classResolver.resolveNormalized(sourceName)
// Check if the class has already been loaded. // Check if the class has already been loaded.
val loadedClass = loadedClasses[name] val loadedClass = loadedClasses[requestedPath]
if (loadedClass != null) { if (loadedClass != null) {
logger.trace("Class {} already loaded", source.qualifiedClassName) logger.trace("Class {} already loaded", request.qualifiedClassName)
return loadedClass return loadedClass
} else if (analysisConfiguration.isPinnedClass(requestedPath)) {
logger.debug("Class {} is loaded unmodified", request.qualifiedClassName)
return loadUnmodifiedClass(requestedPath)
} }
// Load the byte code for the specified class. val byteCode = if (analysisConfiguration.isTemplateClass(requestedPath)) {
val reader = supportingClassLoader.classReader(name, context, source.origin) loadUnmodifiedByteCode(requestedPath)
} else {
// Load the byte code for the specified class.
val reader = supportingClassLoader.classReader(sourceName, context, request.origin)
// Analyse the class if not matching the whitelist. // Analyse the class if not matching the whitelist.
val readClassName = reader.className val readClassName = reader.className
if (!analysisConfiguration.whitelist.matches(readClassName)) { if (!analysisConfiguration.whitelist.matches(readClassName)) {
logger.trace("Class {} does not match with the whitelist", source.qualifiedClassName) logger.trace("Class {} does not match with the whitelist", request.qualifiedClassName)
logger.trace("Analyzing class {}...", source.qualifiedClassName) logger.trace("Analyzing class {}...", request.qualifiedClassName)
analyzer.analyze(reader, context) 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. // Check if any errors were found during analysis.
if (context.messages.errorCount > 0) { 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) throw SandboxClassLoadingException(context)
} }
// Transform the class definition and byte code in accordance with provided rules. // 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. // Try to define the transformed class.
val clazz = try { val clazz = try {
when { when {
whitelistedClasses.matches(qualifiedName) -> supportingClassLoader.loadClass(name) whitelistedClasses.matches(sourceName.asResourcePath) -> supportingClassLoader.loadClass(sourceName)
else -> defineClass(resolvedName, byteCode.bytes, 0, byteCode.bytes.size) else -> defineClass(resolvedName, byteCode.bytes, 0, byteCode.bytes.size)
} }
} catch (exception: SecurityException) { } catch (exception: SecurityException) {
@ -140,19 +145,31 @@ class SandboxClassLoader(
// Cache transformed class. // Cache transformed class.
val classWithByteCode = LoadedClass(clazz, byteCode) val classWithByteCode = LoadedClass(clazz, byteCode)
loadedClasses[name] = classWithByteCode loadedClasses[requestedPath] = classWithByteCode
if (source.origin != null) { if (request.origin != null) {
context.recordClassOrigin(name, ClassReference(source.origin)) context.recordClassOrigin(sourceName, ClassReference(request.origin))
} }
logger.debug("Loaded class {}, bytes={}, isModified={}", logger.debug("Loaded class {}, bytes={}, isModified={}",
source.qualifiedClassName, byteCode.bytes.size, byteCode.isModified) request.qualifiedClassName, byteCode.bytes.size, byteCode.isModified)
return classWithByteCode 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 companion object {
private val logger = loggerFor<SandboxClassLoader>() private val logger = loggerFor<SandboxClassLoader>()
private val UNMODIFIED = ByteCode(ByteArray(0), false)
} }
} }

View File

@ -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)
}

View File

@ -8,13 +8,13 @@ import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Type 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. * hierarchy.
* *
* @param classReader The [ClassReader] used to read the original class. It will be used to copy the entire constant * @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 * pool and bootstrap methods from the original class and also to copy other fragments of original byte code where
* applicable. * 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 * @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 * 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 * 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" private const val OBJECT_NAME = "java/lang/Object"

View File

@ -1,15 +1,19 @@
package net.corda.djvm.rewiring package net.corda.djvm.rewiring
import net.corda.djvm.analysis.ClassResolver import net.corda.djvm.analysis.ClassResolver
import net.corda.djvm.analysis.Whitelist
import org.objectweb.asm.*
import org.objectweb.asm.commons.Remapper import org.objectweb.asm.commons.Remapper
/** /**
* Class name and descriptor re-mapper for use in a sandbox. * 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 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( open class SandboxRemapper(
private val classResolver: ClassResolver private val classResolver: ClassResolver,
private val whitelist: Whitelist
) : Remapper() { ) : Remapper() {
/** /**
@ -26,6 +30,32 @@ open class SandboxRemapper(
return rewriteTypeName(super.map(typename)) 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. * Function for rewriting a descriptor.
*/ */

View File

@ -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;)")
}

View File

@ -17,7 +17,7 @@ class DisallowNonDeterministicMethods : Emitter {
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit { override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
if (instruction is MemberAccessInstruction && isForbidden(instruction)) { if (instruction is MemberAccessInstruction && isForbidden(instruction)) {
when (instruction.operation) { when (instruction.operation) {
INVOKEVIRTUAL -> { INVOKEVIRTUAL, INVOKESPECIAL -> {
throwException<RuleViolationError>("Disallowed reference to API; ${memberFormatter.format(instruction.member)}") throwException<RuleViolationError>("Disallowed reference to API; ${memberFormatter.format(instruction.member)}")
preventDefault() preventDefault()
} }
@ -31,12 +31,20 @@ class DisallowNonDeterministicMethods : Emitter {
|| instruction.signature.contains("Ljava/lang/reflect/")) || 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 = 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")) || (instruction.memberName == "wait" && (instruction.signature == "(J)V" || instruction.signature == "(JI)V"))
private fun isForbidden(instruction: MemberAccessInstruction): Boolean 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 val memberFormatter = MemberFormatter()
private companion object {
private val MONITOR_METHODS = setOf("notify", "notifyAll", "wait")
private val CLASSLOADING_METHODS = setOf("defineClass", "loadClass", "findClass")
}
} }

View File

@ -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;")
}

View File

@ -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;")
}

View File

@ -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;")
}
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -23,7 +23,7 @@ class StubOutNativeMethods : MemberDefinitionProvider {
private fun writeExceptionMethodBody(emitter: EmitterModule): Unit = with(emitter) { private fun writeExceptionMethodBody(emitter: EmitterModule): Unit = with(emitter) {
lineNumber(0) 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) { private fun writeStubMethodBody(emitter: EmitterModule): Unit = with(emitter) {

View File

@ -19,7 +19,7 @@ class StubOutReflectionMethods : MemberDefinitionProvider {
private fun writeMethodBody(emitter: EmitterModule): Unit = with(emitter) { private fun writeMethodBody(emitter: EmitterModule): Unit = with(emitter) {
lineNumber(0) 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. // The method must be public and with a Java implementation.

View File

@ -1,17 +1,20 @@
package net.corda.djvm.source package net.corda.djvm.source
import net.corda.djvm.code.asResourcePath
import java.nio.file.Path import java.nio.file.Path
/** /**
* The source of one or more compiled Java classes. * The source of one or more compiled Java classes.
* *
* @property qualifiedClassName The fully qualified class name. * @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. * @property origin The origin of the class source, if any.
*/ */
class ClassSource private constructor( class ClassSource private constructor(
val qualifiedClassName: String = "", val qualifiedClassName: String = "",
val origin: String? = null val origin: String? = null
) { ) {
val internalClassName: String = qualifiedClassName.asResourcePath
companion object { companion object {

View File

@ -7,7 +7,7 @@ import java.lang.reflect.Modifier
* Find and instantiate types that implement a certain interface. * Find and instantiate types that implement a certain interface.
*/ */
object Discovery { 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]. * Get an instance of each concrete class that implements interface or class [T].

View File

@ -17,6 +17,7 @@ import org.objectweb.asm.ClassVisitor
* Helper class for validating a set of rules for a class or set of classes. * 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. * @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. * @param classVisitor Class visitor to use when traversing the structure of classes.
*/ */
class RuleValidator( class RuleValidator(

View 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()
}
}

View 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()
)

View File

@ -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)}"
}

View File

@ -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
}

View File

@ -1,12 +1,9 @@
package foo.bar.sandbox package foo.bar.sandbox
import java.util.* fun testClock(): Long {
return System.nanoTime()
fun testRandom(): Int {
val random = Random()
return random.nextInt()
} }
fun String.toNumber(): Int { fun String.toNumber(): Long {
return this.toInt() return this.toLong()
} }

View 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)
}

View File

@ -13,6 +13,7 @@ import net.corda.djvm.messages.Severity
import net.corda.djvm.references.ClassHierarchy import net.corda.djvm.references.ClassHierarchy
import net.corda.djvm.rewiring.LoadedClass import net.corda.djvm.rewiring.LoadedClass
import net.corda.djvm.rules.Rule import net.corda.djvm.rules.Rule
import net.corda.djvm.rules.implementation.*
import net.corda.djvm.source.ClassSource import net.corda.djvm.source.ClassSource
import net.corda.djvm.utilities.Discovery import net.corda.djvm.utilities.Discovery
import net.corda.djvm.validation.RuleValidator import net.corda.djvm.validation.RuleValidator
@ -35,8 +36,19 @@ abstract class TestBase {
val ALL_EMITTERS = Discovery.find<Emitter>() 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>() 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 BLANK = emptySet<Any>()
val DEFAULT = (ALL_RULES + ALL_EMITTERS + ALL_DEFINITION_PROVIDERS).distinctBy(Any::javaClass) 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 * 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. * 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 action: SandboxRuntimeContext.() -> Unit
) { ) {
val rules = mutableListOf<Rule>() val rules = mutableListOf<Rule>()
val emitters = mutableListOf<Emitter>() val emitters = mutableListOf<Emitter>().apply { addAll(BASIC_EMITTERS) }
val definitionProviders = mutableListOf<DefinitionProvider>() val definitionProviders = mutableListOf<DefinitionProvider>().apply { addAll(BASIC_DEFINITION_PROVIDERS) }
val classSources = mutableListOf<ClassSource>() val classSources = mutableListOf<ClassSource>()
var executionProfile = ExecutionProfile.UNLIMITED var executionProfile = ExecutionProfile.UNLIMITED
var whitelist = Whitelist.MINIMAL var whitelist = Whitelist.MINIMAL
@ -137,7 +141,12 @@ abstract class TestBase {
minimumSeverityLevel = minimumSeverityLevel minimumSeverityLevel = minimumSeverityLevel
).use { analysisConfiguration -> ).use { analysisConfiguration ->
SandboxRuntimeContext(SandboxConfiguration.of( 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 { )).use {
assertThat(runtimeCosts).areZero() assertThat(runtimeCosts).areZero()
action(this) action(this)
@ -163,7 +172,7 @@ abstract class TestBase {
inline fun <reified T : Any> SandboxRuntimeContext.loadClass(): LoadedClass = loadClass(T::class.jvmName) inline fun <reified T : Any> SandboxRuntimeContext.loadClass(): LoadedClass = loadClass(T::class.jvmName)
fun SandboxRuntimeContext.loadClass(className: String): LoadedClass = 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. * Run the entry-point of the loaded [Callable] class.

View 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!")
}

View File

@ -5,25 +5,25 @@ import org.junit.Test
class ClassResolverTest { class ClassResolverTest {
private val resolver = ClassResolver(emptySet(), Whitelist.MINIMAL, "sandbox/") private val resolver = ClassResolver(emptySet(), emptySet(), Whitelist.MINIMAL, "sandbox/")
@Test @Test
fun `can resolve class name`() { fun `can resolve class name`() {
assertThat(resolver.resolve("java/lang/Object")).isEqualTo("java/lang/Object") 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") assertThat(resolver.resolve("foo/bar/Test")).isEqualTo("sandbox/foo/bar/Test")
} }
@Test @Test
fun `can resolve class name for arrays`() { fun `can resolve class name for arrays`() {
assertThat(resolver.resolve("[Ljava/lang/Object;")).isEqualTo("[Ljava/lang/Object;") 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("[Lfoo/bar/Test;")).isEqualTo("[Lsandbox/foo/bar/Test;")
assertThat(resolver.resolve("[[Ljava/lang/Object;")).isEqualTo("[[Ljava/lang/Object;") 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("[[Lfoo/bar/Test;")).isEqualTo("[[Lsandbox/foo/bar/Test;")
assertThat(resolver.resolve("[[[Ljava/lang/Object;")).isEqualTo("[[[Ljava/lang/Object;") 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("[[[Lfoo/bar/Test;")).isEqualTo("[[[Lsandbox/foo/bar/Test;")
} }

View File

@ -11,8 +11,8 @@ class WhitelistTest : TestBase() {
val whitelist = Whitelist.MINIMAL val whitelist = Whitelist.MINIMAL
assertThat(whitelist.matches("java/lang/Object")).isTrue() assertThat(whitelist.matches("java/lang/Object")).isTrue()
assertThat(whitelist.matches("java/lang/Object.<init>:()V")).isTrue() assertThat(whitelist.matches("java/lang/Object.<init>:()V")).isTrue()
assertThat(whitelist.matches("java/lang/Integer")).isTrue() assertThat(whitelist.matches("java/lang/reflect/Array")).isTrue()
assertThat(whitelist.matches("java/lang/Integer.<init>:(I)V")).isTrue() assertThat(whitelist.matches("java/lang/reflect/Array.setInt(Ljava/lang/Object;II)V")).isTrue()
} }
@Test @Test

View File

@ -28,4 +28,9 @@ class AssertiveClassWithByteCode(private val loadedClass: LoadedClass) {
assertThat(loadedClass.type.name).isEqualTo(className) assertThat(loadedClass.type.name).isEqualTo(className)
return this return this
} }
fun hasInterface(className: String): AssertiveClassWithByteCode {
assertThat(loadedClass.type.interfaces.map(Class<*>::getName)).contains(className)
return this
}
} }

View File

@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test import org.junit.Test
import sandbox.net.corda.djvm.costing.ThresholdViolationError import sandbox.net.corda.djvm.costing.ThresholdViolationError
import kotlin.concurrent.thread
class RuntimeCostTest { class RuntimeCostTest {
@ -16,17 +17,13 @@ class RuntimeCostTest {
@Test @Test
fun `cannot increment cost beyond threshold`() { fun `cannot increment cost beyond threshold`() {
Thread { thread(name = "Foo") {
val cost = RuntimeCost(10) { "failed in ${it.name}" } val cost = RuntimeCost(10) { "failed in ${it.name}" }
assertThatExceptionOfType(ThresholdViolationError::class.java) assertThatExceptionOfType(ThresholdViolationError::class.java)
.isThrownBy { cost.increment(11) } .isThrownBy { cost.increment(11) }
.withMessage("failed in Foo") .withMessage("failed in Foo")
assertThat(cost.value).isEqualTo(11) assertThat(cost.value).isEqualTo(11)
}.apply { }.join()
name = "Foo"
start()
join()
}
} }
} }

View File

@ -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
}

View File

@ -1,10 +1,19 @@
package net.corda.djvm.execution package net.corda.djvm.execution
import foo.bar.sandbox.MyObject import foo.bar.sandbox.MyObject
import foo.bar.sandbox.testRandom import foo.bar.sandbox.testClock
import foo.bar.sandbox.toNumber import foo.bar.sandbox.toNumber
import net.corda.djvm.TestBase import net.corda.djvm.TestBase
import net.corda.djvm.analysis.Whitelist 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.assertions.AssertionExtensions.withProblem
import net.corda.djvm.rewiring.SandboxClassLoadingException import net.corda.djvm.rewiring.SandboxClassLoadingException
import org.assertj.core.api.Assertions.assertThat 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.costing.ThresholdViolationError
import sandbox.net.corda.djvm.rules.RuleViolationError import sandbox.net.corda.djvm.rules.RuleViolationError
import java.nio.file.Files import java.nio.file.Files
import java.util.*
import java.util.function.Function import java.util.function.Function
import java.util.stream.Collectors.*
class SandboxExecutorTest : TestBase() { class SandboxExecutorTest : TestBase() {
@ -34,7 +43,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute contract`() = sandbox( 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 contractExecutor = DeterministicSandboxExecutor<Transaction, Unit>(configuration)
val tx = Transaction(1) val tx = Transaction(1)
@ -44,13 +53,13 @@ class SandboxExecutorTest : TestBase() {
.withMessageContaining("Contract constraint violated") .withMessageContaining("Contract constraint violated")
} }
class Contract : Function<Transaction?, Unit> { class Contract : Function<Transaction, Unit> {
override fun apply(input: Transaction?) { override fun apply(input: Transaction) {
throw IllegalArgumentException("Contract constraint violated") throwContractConstraintViolation()
} }
} }
data class Transaction(val id: Int?) data class Transaction(val id: Int)
@Test @Test
fun `can load and execute code that overrides object hash code`() = sandbox(DEFAULT) { fun `can load and execute code that overrides object hash code`() = sandbox(DEFAULT) {
@ -65,7 +74,11 @@ class SandboxExecutorTest : TestBase() {
val obj = Object() val obj = Object()
val hash1 = obj.hashCode() val hash1 = obj.hashCode()
val hash2 = 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() return Object().hashCode()
} }
} }
@ -123,37 +136,37 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can detect illegal references in Kotlin meta-classes`() = sandbox(DEFAULT, ExecutionProfile.DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestKotlinMetaClasses>(0) } .isThrownBy { contractExecutor.run<TestKotlinMetaClasses>(0) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(NoSuchMethodError::class.java)
.withMessageContaining("Disallowed reference to reflection API") .withProblem("sandbox.java.lang.System.nanoTime()J")
} }
class TestKotlinMetaClasses : Function<Int, Int> { class TestKotlinMetaClasses : Function<Int, Long> {
override fun apply(input: Int): Int { override fun apply(input: Int): Long {
val someNumber = testRandom() val someNumber = testClock()
return "12345".toNumber() * someNumber return "12345".toNumber() * someNumber
} }
} }
@Test @Test
fun `cannot execute runnable that references non-deterministic code`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestNonDeterministicCode>(0) } .isThrownBy { contractExecutor.run<TestNonDeterministicCode>(0) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(NoSuchMethodError::class.java)
.withProblem("Disallowed reference to reflection API") .withProblem("sandbox.java.lang.System.currentTimeMillis()J")
} }
class TestNonDeterministicCode : Function<Int, Int> { class TestNonDeterministicCode : Function<Int, Long> {
override fun apply(input: Int): Int { override fun apply(input: Int): Long {
return Random().nextInt() return System.currentTimeMillis()
} }
} }
@Test @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 { TestCatchThreadDeath().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -167,7 +180,7 @@ class SandboxExecutorTest : TestBase() {
class TestCatchThreadDeath : Function<Int, Int> { class TestCatchThreadDeath : Function<Int, Int> {
override fun apply(input: Int): Int { override fun apply(input: Int): Int {
return try { return try {
throw ThreadDeath() throwThreadDeath()
} catch (exception: ThreadDeath) { } catch (exception: ThreadDeath) {
1 1
} }
@ -175,7 +188,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @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 { TestCatchThresholdViolationError().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -190,7 +203,7 @@ class SandboxExecutorTest : TestBase() {
class TestCatchThresholdViolationError : Function<Int, Int> { class TestCatchThresholdViolationError : Function<Int, Int> {
override fun apply(input: Int): Int { override fun apply(input: Int): Int {
return try { return try {
throw ThresholdViolationError("Can't catch this!") throwThresholdViolationError()
} catch (exception: ThresholdViolationError) { } catch (exception: ThresholdViolationError) {
1 1
} }
@ -198,7 +211,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @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 { TestCatchRuleViolationError().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -213,7 +226,7 @@ class SandboxExecutorTest : TestBase() {
class TestCatchRuleViolationError : Function<Int, Int> { class TestCatchRuleViolationError : Function<Int, Int> {
override fun apply(input: Int): Int { override fun apply(input: Int): Int {
return try { return try {
throw RuleViolationError("Can't catch this!") throwRuleViolationError()
} catch (exception: RuleViolationError) { } catch (exception: RuleViolationError) {
1 1
} }
@ -221,7 +234,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can catch Throwable`() = sandbox(DEFAULT) { fun `can catch Throwable`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
contractExecutor.run<TestCatchThrowableAndError>(1).apply { contractExecutor.run<TestCatchThrowableAndError>(1).apply {
assertThat(result).isEqualTo(1) assertThat(result).isEqualTo(1)
@ -229,7 +242,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can catch Error`() = sandbox(DEFAULT) { fun `can catch Error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
contractExecutor.run<TestCatchThrowableAndError>(2).apply { contractExecutor.run<TestCatchThrowableAndError>(2).apply {
assertThat(result).isEqualTo(2) assertThat(result).isEqualTo(2)
@ -237,7 +250,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot catch ThreadDeath`() = sandbox(DEFAULT) { fun `cannot catch ThreadDeath`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(3) } .isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(3) }
@ -248,8 +261,8 @@ class SandboxExecutorTest : TestBase() {
override fun apply(input: Int): Int { override fun apply(input: Int): Int {
return try { return try {
when (input) { when (input) {
1 -> throw Throwable() 1 -> throwThrowable()
2 -> throw Error() 2 -> throwError()
else -> 0 else -> 0
} }
} catch (exception: Error) { } catch (exception: Error) {
@ -264,20 +277,20 @@ class SandboxExecutorTest : TestBase() {
override fun apply(input: Int): Int { override fun apply(input: Int): Int {
return try { return try {
when (input) { when (input) {
1 -> throw Throwable() 1 -> throwThrowable()
2 -> throw Error() 2 -> throwError()
3 -> try { 3 -> try {
throw ThreadDeath() throwThreadDeath()
} catch (ex: ThreadDeath) { } catch (ex: ThreadDeath) {
3 3
} }
4 -> try { 4 -> try {
throw StackOverflowError("FAKE OVERFLOW!") throwStackOverflowError()
} catch (ex: StackOverflowError) { } catch (ex: StackOverflowError) {
4 4
} }
5 -> try { 5 -> try {
throw OutOfMemoryError("FAKE OOM!") throwOutOfMemoryError()
} catch (ex: OutOfMemoryError) { } catch (ex: OutOfMemoryError) {
5 5
} }
@ -292,7 +305,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @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) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(4) } .isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(4) }
@ -301,7 +314,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @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) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(5) } .isThrownBy { contractExecutor.run<TestCatchThrowableErrorsAndThreadDeath>(5) }
@ -371,7 +384,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute code that uses notify()`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestMonitors>(1) } .isThrownBy { contractExecutor.run<TestMonitors>(1) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(RuleViolationError::class.java)
@ -381,7 +394,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute code that uses notifyAll()`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestMonitors>(2) } .isThrownBy { contractExecutor.run<TestMonitors>(2) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(RuleViolationError::class.java)
@ -391,7 +404,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute code that uses wait()`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestMonitors>(3) } .isThrownBy { contractExecutor.run<TestMonitors>(3) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(RuleViolationError::class.java)
@ -401,7 +414,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute code that uses wait(long)`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestMonitors>(4) } .isThrownBy { contractExecutor.run<TestMonitors>(4) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(RuleViolationError::class.java)
@ -411,7 +424,7 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute code that uses wait(long,int)`() = sandbox(DEFAULT) { 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) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestMonitors>(5) } .isThrownBy { contractExecutor.run<TestMonitors>(5) }
.withCauseInstanceOf(RuleViolationError::class.java) .withCauseInstanceOf(RuleViolationError::class.java)
@ -421,13 +434,13 @@ class SandboxExecutorTest : TestBase() {
@Test @Test
fun `code after forbidden APIs is intact`() = sandbox(DEFAULT) { 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) assertThat(contractExecutor.run<TestMonitors>(0).result)
.isEqualTo("unknown") .isEqualTo("unknown")
} }
class TestMonitors : Function<Int, String> { class TestMonitors : Function<Int, String?> {
override fun apply(input: Int): String { override fun apply(input: Int): String? {
return synchronized(this) { return synchronized(this) {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
val javaObject = this as java.lang.Object 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 @Test
fun `can load and execute class that has finalize`() = sandbox(DEFAULT) { fun `can load and execute class that has finalize`() = sandbox(DEFAULT) {
assertThatExceptionOfType(UnsupportedOperationException::class.java) assertThatExceptionOfType(UnsupportedOperationException::class.java)
@ -515,4 +595,152 @@ class SandboxExecutorTest : TestBase() {
throw UnsupportedOperationException("Very Bad Thing") 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()
}
}
} }

View File

@ -1,16 +1,14 @@
package net.corda.djvm.rewiring package net.corda.djvm.rewiring
import foo.bar.sandbox.A import foo.bar.sandbox.*
import foo.bar.sandbox.B
import foo.bar.sandbox.Empty
import foo.bar.sandbox.StrictFloat
import net.corda.djvm.TestBase import net.corda.djvm.TestBase
import net.corda.djvm.assertions.AssertionExtensions.assertThat import net.corda.djvm.assertions.AssertionExtensions.assertThat
import net.corda.djvm.execution.ExecutionProfile import net.corda.djvm.execution.ExecutionProfile
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.*
import org.junit.Test import org.junit.Test
import sandbox.net.corda.djvm.costing.ThresholdViolationError import sandbox.net.corda.djvm.costing.ThresholdViolationError
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
class ClassRewriterTest : TestBase() { class ClassRewriterTest : TestBase() {
@ -102,4 +100,44 @@ class ClassRewriterTest : TestBase() {
return input 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) { "" }
}

View File

@ -10,7 +10,7 @@ import java.nio.file.Path
class SourceClassLoaderTest { class SourceClassLoaderTest {
private val classResolver = ClassResolver(emptySet(), Whitelist.MINIMAL, "") private val classResolver = ClassResolver(emptySet(), emptySet(), Whitelist.MINIMAL, "")
@Test @Test
fun `can load class from Java's lang package when no files are provided to the class loader`() { fun `can load class from Java's lang package when no files are provided to the class loader`() {