Merge remote-tracking branch 'open/master' into anthony-os-merge-2018-10-24

This commit is contained in:
Anthony Keenan 2018-10-25 11:31:15 +01:00
commit 35a2ee2c8f
29 changed files with 819 additions and 320 deletions

View File

@ -57,7 +57,8 @@ shadowJar {
// we will generate better versions from deterministic-rt.jar. // we will generate better versions from deterministic-rt.jar.
exclude 'sandbox/java/lang/Appendable.class' exclude 'sandbox/java/lang/Appendable.class'
exclude 'sandbox/java/lang/CharSequence.class' exclude 'sandbox/java/lang/CharSequence.class'
exclude 'sandbox/java/lang/Character\$*.class' exclude 'sandbox/java/lang/Character\$Subset.class'
exclude 'sandbox/java/lang/Character\$Unicode*.class'
exclude 'sandbox/java/lang/Comparable.class' exclude 'sandbox/java/lang/Comparable.class'
exclude 'sandbox/java/lang/Enum.class' exclude 'sandbox/java/lang/Enum.class'
exclude 'sandbox/java/lang/Iterable.class' exclude 'sandbox/java/lang/Iterable.class'

View File

@ -191,12 +191,14 @@ abstract class ClassCommand : CommandBase() {
emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(), emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(),
definitionProviders = if (ignoreDefinitionProviders) { emptyList() } else { Discovery.find() }, definitionProviders = if (ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
enableTracing = !disableTracing, enableTracing = !disableTracing,
analysisConfiguration = AnalysisConfiguration( analysisConfiguration = AnalysisConfiguration.createRoot(
whitelist = whitelist, whitelist = whitelist,
minimumSeverityLevel = level, minimumSeverityLevel = level,
classPath = getClasspath(),
analyzeAnnotations = analyzeAnnotations, analyzeAnnotations = analyzeAnnotations,
prefixFilters = prefixFilters.toList() prefixFilters = prefixFilters.toList(),
sourceClassLoaderFactory = { classResolver, bootstrapClassLoader ->
SourceClassLoader(getClasspath(), classResolver, bootstrapClassLoader)
}
) )
) )
} }

View File

@ -33,7 +33,7 @@ class WhitelistGenerateCommand : CommandBase() {
override fun validateArguments() = paths.isNotEmpty() override fun validateArguments() = paths.isNotEmpty()
override fun handleCommand(): Boolean { override fun handleCommand(): Boolean {
val entries = AnalysisConfiguration().use { configuration -> val entries = AnalysisConfiguration.createRoot().use { configuration ->
val entries = mutableListOf<String>() val entries = mutableListOf<String>()
val visitor = object : ClassAndMemberVisitor(configuration, null) { val visitor = object : ClassAndMemberVisitor(configuration, null) {
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation { override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {

View File

@ -45,8 +45,8 @@ public class Object {
private static java.lang.Object unwrap(java.lang.Object arg) { private static java.lang.Object unwrap(java.lang.Object arg) {
if (arg instanceof Object) { if (arg instanceof Object) {
return ((Object) arg).fromDJVM(); return ((Object) arg).fromDJVM();
} else if (Object[].class.isAssignableFrom(arg.getClass())) { } else if (java.lang.Object[].class.isAssignableFrom(arg.getClass())) {
return fromDJVM((Object[]) arg); return fromDJVM((java.lang.Object[]) arg);
} else { } else {
return arg; return arg;
} }

View File

@ -1,5 +1,6 @@
package sandbox.java.lang; package sandbox.java.lang;
import net.corda.djvm.SandboxRuntimeContext;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import sandbox.java.nio.charset.Charset; import sandbox.java.nio.charset.Charset;
import sandbox.java.util.Comparator; import sandbox.java.util.Comparator;
@ -8,7 +9,6 @@ import sandbox.java.util.Locale;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Map;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class String extends Object implements Comparable<String>, CharSequence, Serializable { public final class String extends Object implements Comparable<String>, CharSequence, Serializable {
@ -24,7 +24,6 @@ public final class String extends Object implements Comparable<String>, CharSequ
private static final String TRUE = new String("true"); private static final String TRUE = new String("true");
private static final String FALSE = new String("false"); private static final String FALSE = new String("false");
private static final Map<java.lang.String, String> INTERNAL = new java.util.HashMap<>();
private static final Constructor SHARED; private static final Constructor SHARED;
static { static {
@ -335,7 +334,7 @@ public final class String extends Object implements Comparable<String>, CharSequ
return toDJVM(value.trim()); return toDJVM(value.trim());
} }
public String intern() { return INTERNAL.computeIfAbsent(value, s -> this); } public String intern() { return (String) SandboxRuntimeContext.getInstance().intern(value, this); }
public char[] toCharArray() { public char[] toCharArray() {
return value.toCharArray(); return value.toCharArray();

View File

@ -1,20 +1,15 @@
package sandbox.java.lang; package sandbox.java.lang;
import net.corda.djvm.SandboxRuntimeContext;
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public final class System extends Object { public final class System extends Object {
private System() {} 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) { public static int identityHashCode(java.lang.Object obj) {
int nativeHashCode = java.lang.System.identityHashCode(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 SandboxRuntimeContext.getInstance().getHashCodeFor(nativeHashCode);
return objectHashCodes.computeIfAbsent(nativeHashCode, i -> ++objectCounter + 0xfed_c0de);
} }
public static final String lineSeparator = String.toDJVM("\n"); public static final String lineSeparator = String.toDJVM("\n");

View File

@ -5,6 +5,7 @@ import net.corda.djvm.code.DefinitionProvider
import net.corda.djvm.code.EMIT_TRACING import net.corda.djvm.code.EMIT_TRACING
import net.corda.djvm.code.Emitter import net.corda.djvm.code.Emitter
import net.corda.djvm.execution.ExecutionProfile import net.corda.djvm.execution.ExecutionProfile
import net.corda.djvm.rewiring.SandboxClassLoader
import net.corda.djvm.rules.Rule import net.corda.djvm.rules.Rule
import net.corda.djvm.utilities.Discovery import net.corda.djvm.utilities.Discovery
@ -16,24 +17,28 @@ import net.corda.djvm.utilities.Discovery
* @property definitionProviders The meta-data providers to apply to class and member definitions. * @property definitionProviders The meta-data providers to apply to class and member definitions.
* @property executionProfile The execution profile to use in the sandbox. * @property executionProfile The execution profile to use in the sandbox.
* @property analysisConfiguration The configuration used in the analysis of classes. * @property analysisConfiguration The configuration used in the analysis of classes.
* @property parentClassLoader The [SandboxClassLoader] that this sandbox will use as a parent.
*/ */
@Suppress("unused")
class SandboxConfiguration private constructor( class SandboxConfiguration private constructor(
val rules: List<Rule>, val rules: List<Rule>,
val emitters: List<Emitter>, val emitters: List<Emitter>,
val definitionProviders: List<DefinitionProvider>, val definitionProviders: List<DefinitionProvider>,
val executionProfile: ExecutionProfile, val executionProfile: ExecutionProfile,
val analysisConfiguration: AnalysisConfiguration val analysisConfiguration: AnalysisConfiguration,
val parentClassLoader: SandboxClassLoader?
) { ) {
@Suppress("unused")
companion object { companion object {
/** /**
* Default configuration for the deterministic sandbox. * Default configuration for the deterministic sandbox.
*/ */
@JvmField
val DEFAULT = SandboxConfiguration.of() val DEFAULT = SandboxConfiguration.of()
/** /**
* Configuration with no emitters, rules, meta-data providers or runtime thresholds. * Configuration with no emitters, rules, meta-data providers or runtime thresholds.
*/ */
@JvmField
val EMPTY = SandboxConfiguration.of( val EMPTY = SandboxConfiguration.of(
ExecutionProfile.UNLIMITED, emptyList(), emptyList(), emptyList() ExecutionProfile.UNLIMITED, emptyList(), emptyList(), emptyList()
) )
@ -47,7 +52,8 @@ class SandboxConfiguration private constructor(
emitters: List<Emitter>? = null, emitters: List<Emitter>? = null,
definitionProviders: List<DefinitionProvider> = Discovery.find(), definitionProviders: List<DefinitionProvider> = Discovery.find(),
enableTracing: Boolean = true, enableTracing: Boolean = true,
analysisConfiguration: AnalysisConfiguration = AnalysisConfiguration() analysisConfiguration: AnalysisConfiguration = AnalysisConfiguration.createRoot(),
parentClassLoader: SandboxClassLoader? = null
) = SandboxConfiguration( ) = SandboxConfiguration(
executionProfile = profile, executionProfile = profile,
rules = rules, rules = rules,
@ -55,7 +61,8 @@ class SandboxConfiguration private constructor(
enableTracing || it.priority > EMIT_TRACING enableTracing || it.priority > EMIT_TRACING
}, },
definitionProviders = definitionProviders, definitionProviders = definitionProviders,
analysisConfiguration = analysisConfiguration analysisConfiguration = analysisConfiguration,
parentClassLoader = parentClassLoader
) )
} }
} }

View File

@ -1,6 +1,5 @@
package net.corda.djvm package net.corda.djvm
import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.costing.RuntimeCostSummary import net.corda.djvm.costing.RuntimeCostSummary
import net.corda.djvm.rewiring.SandboxClassLoader import net.corda.djvm.rewiring.SandboxClassLoader
@ -14,16 +13,27 @@ class SandboxRuntimeContext(val configuration: SandboxConfiguration) {
/** /**
* The class loader to use inside the sandbox. * The class loader to use inside the sandbox.
*/ */
val classLoader: SandboxClassLoader = SandboxClassLoader( val classLoader: SandboxClassLoader = SandboxClassLoader.createFor(configuration)
configuration,
AnalysisContext.fromConfiguration(configuration.analysisConfiguration)
)
/** /**
* A summary of the currently accumulated runtime costs (for, e.g., memory allocations, invocations, etc.). * A summary of the currently accumulated runtime costs (for, e.g., memory allocations, invocations, etc.).
*/ */
val runtimeCosts = RuntimeCostSummary(configuration.executionProfile) val runtimeCosts = RuntimeCostSummary(configuration.executionProfile)
private val hashCodes: MutableMap<Int, Int> = mutableMapOf()
private var objectCounter: Int = 0
// TODO Instead of using a magic offset below, one could take in a per-context seed
fun getHashCodeFor(nativeHashCode: Int): Int {
return hashCodes.computeIfAbsent(nativeHashCode) { ++objectCounter + MAGIC_HASH_OFFSET }
}
private val internStrings: MutableMap<String, Any> = mutableMapOf()
fun intern(key: String, value: Any): Any {
return internStrings.computeIfAbsent(key) { value }
}
/** /**
* Run a set of actions within the provided sandbox context. * Run a set of actions within the provided sandbox context.
*/ */
@ -39,10 +49,12 @@ class SandboxRuntimeContext(val configuration: SandboxConfiguration) {
companion object { companion object {
private val threadLocalContext = ThreadLocal<SandboxRuntimeContext?>() private val threadLocalContext = ThreadLocal<SandboxRuntimeContext?>()
private const val MAGIC_HASH_OFFSET = 0xfed_c0de
/** /**
* When called from within a sandbox, this returns the context for the current sandbox thread. * When called from within a sandbox, this returns the context for the current sandbox thread.
*/ */
@JvmStatic
var instance: SandboxRuntimeContext var instance: SandboxRuntimeContext
get() = threadLocalContext.get() get() = threadLocalContext.get()
?: throw IllegalStateException("SandboxContext has not been initialized before use") ?: throw IllegalStateException("SandboxContext has not been initialized before use")

View File

@ -8,6 +8,7 @@ import net.corda.djvm.references.ClassModule
import net.corda.djvm.references.Member 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.references.MethodBody
import net.corda.djvm.source.AbstractSourceClassLoader
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.Opcodes.*
@ -21,36 +22,36 @@ import java.nio.file.Path
* The configuration to use for an analysis. * The configuration to use for an analysis.
* *
* @property whitelist The whitelist of class names. * @property whitelist The whitelist of class names.
* @param additionalPinnedClasses Classes that have already been declared in the sandbox namespace and that should be * @property pinnedClasses Classes that have already been declared in the sandbox namespace and that should be
* made available inside the sandboxed environment. * made available inside the sandboxed environment. These classes belong to the application
* classloader and so are shared across all sandboxes.
* @property classResolver Functionality used to resolve the qualified name and relevant information about a class.
* @property exceptionResolver Resolves the internal names of synthetic exception classes.
* @property minimumSeverityLevel The minimum severity level to log and report. * @property minimumSeverityLevel The minimum severity level to log and report.
* @param classPath The extended class path to use for the analysis.
* @param bootstrapJar The location of a jar containing the Java APIs.
* @property analyzeAnnotations Analyze annotations despite not being explicitly referenced. * @property analyzeAnnotations Analyze annotations despite not being explicitly referenced.
* @property prefixFilters Only record messages where the originating class name matches one of the provided prefixes. * @property prefixFilters Only record messages where the originating class name matches one of the provided prefixes.
* If none are provided, all messages will be reported. * If none are provided, all messages will be reported.
* @property classModule Module for handling evolution of a class hierarchy during analysis. * @property classModule Module for handling evolution of a class hierarchy during analysis.
* @property memberModule Module for handling the specification and inspection of class members. * @property memberModule Module for handling the specification and inspection of class members.
* @property bootstrapClassLoader Optional provider for the Java API classes.
* @property supportingClassLoader ClassLoader providing the classes to run inside the sandbox.
* @property isRootConfiguration Effectively, whether we are allowed to close [bootstrapClassLoader].
*/ */
class AnalysisConfiguration( class AnalysisConfiguration private constructor(
val whitelist: Whitelist = Whitelist.MINIMAL, val whitelist: Whitelist,
additionalPinnedClasses: Set<String> = emptySet(), val pinnedClasses: Set<String>,
val minimumSeverityLevel: Severity = Severity.WARNING, val classResolver: ClassResolver,
classPath: List<Path> = emptyList(), val exceptionResolver: ExceptionResolver,
bootstrapJar: Path? = null, val minimumSeverityLevel: Severity,
val analyzeAnnotations: Boolean = false, val analyzeAnnotations: Boolean,
val prefixFilters: List<String> = emptyList(), val prefixFilters: List<String>,
val classModule: ClassModule = ClassModule(), val classModule: ClassModule,
val memberModule: MemberModule = MemberModule() val memberModule: MemberModule,
private val bootstrapClassLoader: BootstrapClassLoader?,
val supportingClassLoader: AbstractSourceClassLoader,
private val isRootConfiguration: Boolean
) : Closeable { ) : Closeable {
/**
* Classes that have already been declared in the sandbox namespace and that should be made
* available inside the sandboxed environment. These classes belong to the application
* classloader and so are shared across all sandboxes.
*/
val pinnedClasses: Set<String> = MANDATORY_PINNED_CLASSES + additionalPinnedClasses
/** /**
* These interfaces are modified as they are mapped into the sandbox by * These interfaces are modified as they are mapped into the sandbox by
* having their unsandboxed version "stitched in" as a super-interface. * having their unsandboxed version "stitched in" as a super-interface.
@ -63,26 +64,39 @@ class AnalysisConfiguration(
*/ */
val stitchedClasses: Map<String, List<Member>> get() = STITCHED_CLASSES val stitchedClasses: Map<String, List<Member>> get() = STITCHED_CLASSES
/**
* Functionality used to resolve the qualified name and relevant information about a class.
*/
val classResolver: ClassResolver = ClassResolver(pinnedClasses, TEMPLATE_CLASSES, whitelist, SANDBOX_PREFIX)
/**
* Resolves the internal names of synthetic exception classes.
*/
val exceptionResolver: ExceptionResolver = ExceptionResolver(JVM_EXCEPTIONS, pinnedClasses, SANDBOX_PREFIX)
private val bootstrapClassLoader = bootstrapJar?.let { BootstrapClassLoader(it, classResolver) }
val supportingClassLoader = SourceClassLoader(classPath, classResolver, bootstrapClassLoader)
@Throws(IOException::class) @Throws(IOException::class)
override fun close() { override fun close() {
supportingClassLoader.use { supportingClassLoader.use {
bootstrapClassLoader?.close() if (isRootConfiguration) {
bootstrapClassLoader?.close()
}
} }
} }
/**
* Creates a child [AnalysisConfiguration] with this instance as its parent.
* The child inherits the same [whitelist], [pinnedClasses] and [bootstrapClassLoader].
*/
fun createChild(
classPaths: List<Path> = emptyList(),
newMinimumSeverityLevel: Severity?
): AnalysisConfiguration {
return AnalysisConfiguration(
whitelist = whitelist,
pinnedClasses = pinnedClasses,
classResolver = classResolver,
exceptionResolver = exceptionResolver,
minimumSeverityLevel = newMinimumSeverityLevel ?: minimumSeverityLevel,
analyzeAnnotations = analyzeAnnotations,
prefixFilters = prefixFilters,
classModule = classModule,
memberModule = memberModule,
bootstrapClassLoader = bootstrapClassLoader,
supportingClassLoader = SourceClassLoader(classPaths, classResolver, bootstrapClassLoader),
isRootConfiguration = false
)
}
fun isTemplateClass(className: String): Boolean = className in TEMPLATE_CLASSES fun isTemplateClass(className: String): Boolean = className in TEMPLATE_CLASSES
fun isPinnedClass(className: String): Boolean = className in pinnedClasses fun isPinnedClass(className: String): Boolean = className in pinnedClasses
@ -107,7 +121,7 @@ class AnalysisConfiguration(
/** /**
* These classes will be duplicated into every sandbox's * These classes will be duplicated into every sandbox's
* classloader. * parent classloader.
*/ */
private val TEMPLATE_CLASSES: Set<String> = setOf( private val TEMPLATE_CLASSES: Set<String> = setOf(
java.lang.Boolean::class.java, java.lang.Boolean::class.java,
@ -131,6 +145,7 @@ class AnalysisConfiguration(
).sandboxed() + setOf( ).sandboxed() + setOf(
"sandbox/Task", "sandbox/Task",
"sandbox/TaskTypes", "sandbox/TaskTypes",
"sandbox/java/lang/Character\$Cache",
"sandbox/java/lang/DJVM", "sandbox/java/lang/DJVM",
"sandbox/java/lang/DJVMException", "sandbox/java/lang/DJVMException",
"sandbox/java/lang/DJVMThrowableWrapper", "sandbox/java/lang/DJVMThrowableWrapper",
@ -139,8 +154,8 @@ class AnalysisConfiguration(
) )
/** /**
* These are thrown by the JVM itself, and so * These exceptions are thrown by the JVM itself, and
* we need to handle them without wrapping them. * so we need to handle them without wrapping them.
* *
* Note that this set is closed, i.e. every one * Note that this set is closed, i.e. every one
* of these exceptions' [Throwable] super classes * of these exceptions' [Throwable] super classes
@ -271,6 +286,41 @@ class AnalysisConfiguration(
private fun Set<Class<*>>.sandboxed(): Set<String> = map(Companion::sandboxed).toSet() private fun Set<Class<*>>.sandboxed(): Set<String> = map(Companion::sandboxed).toSet()
private fun Iterable<Member>.mapByClassName(): Map<String, List<Member>> private fun Iterable<Member>.mapByClassName(): Map<String, List<Member>>
= groupBy(Member::className).mapValues(Map.Entry<String, List<Member>>::value) = groupBy(Member::className).mapValues(Map.Entry<String, List<Member>>::value)
/**
* @see [AnalysisConfiguration]
*/
fun createRoot(
whitelist: Whitelist = Whitelist.MINIMAL,
additionalPinnedClasses: Set<String> = emptySet(),
minimumSeverityLevel: Severity = Severity.WARNING,
analyzeAnnotations: Boolean = false,
prefixFilters: List<String> = emptyList(),
classModule: ClassModule = ClassModule(),
memberModule: MemberModule = MemberModule(),
bootstrapClassLoader: BootstrapClassLoader? = null,
sourceClassLoaderFactory: (ClassResolver, BootstrapClassLoader?) -> AbstractSourceClassLoader = { classResolver, bootstrapCL ->
SourceClassLoader(emptyList(), classResolver, bootstrapCL)
}
): AnalysisConfiguration {
val pinnedClasses = MANDATORY_PINNED_CLASSES + additionalPinnedClasses
val classResolver = ClassResolver(pinnedClasses, TEMPLATE_CLASSES, whitelist, SANDBOX_PREFIX)
return AnalysisConfiguration(
whitelist = whitelist,
pinnedClasses = pinnedClasses,
classResolver = classResolver,
exceptionResolver = ExceptionResolver(JVM_EXCEPTIONS, pinnedClasses, SANDBOX_PREFIX),
minimumSeverityLevel = minimumSeverityLevel,
analyzeAnnotations = analyzeAnnotations,
prefixFilters = prefixFilters,
classModule = classModule,
memberModule = memberModule,
bootstrapClassLoader = bootstrapClassLoader,
supportingClassLoader = sourceClassLoaderFactory(classResolver, bootstrapClassLoader),
isRootConfiguration = true
)
}
} }
private open class MethodBuilder( private open class MethodBuilder(

View File

@ -71,14 +71,14 @@ open class SandboxExecutor<in TInput, out TOutput>(
// Load the "entry-point" task class into the sandbox. This task will marshall // Load the "entry-point" task class into the sandbox. This task will marshall
// the input and outputs between Java types and sandbox wrapper types. // the input and outputs between Java types and sandbox wrapper types.
val taskClass = Class.forName("sandbox.Task", false, classLoader) val taskClass = classLoader.loadClass("sandbox.Task")
// Create the user's task object inside the sandbox. // Create the user's task object inside the sandbox.
val runnable = classLoader.loadForSandbox(runnableClass, context).type.newInstance() val runnable = classLoader.loadClassForSandbox(runnableClass).newInstance()
// Fetch this sandbox's instance of Class<Function> so we can retrieve Task(Function) // Fetch this sandbox's instance of Class<Function> so we can retrieve Task(Function)
// and then instantiate the Task. // and then instantiate the Task.
val functionClass = Class.forName("sandbox.java.util.function.Function", false, classLoader) val functionClass = classLoader.loadClass("sandbox.java.util.function.Function")
val task = taskClass.getDeclaredConstructor(functionClass).newInstance(runnable) val task = taskClass.getDeclaredConstructor(functionClass).newInstance(runnable)
// Execute the task... // Execute the task...
@ -114,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.loadForSandbox(classSource, context) classLoader.copyEmpty(context).loadForSandbox(classSource)
} }
return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName) return result.output ?: throw ClassNotFoundException(classSource.qualifiedClassName)
} }
@ -159,11 +159,13 @@ 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.loadForSandbox(classSource, context) classLoader.copyEmpty(context).loadClassForSandbox(classSource)
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]
false false
} finally {
context.messages.acceptProvisional()
} }
if (didLoad) { if (didLoad) {
context.classes[className]?.apply { context.classes[className]?.apply {

View File

@ -22,6 +22,7 @@ class MessageCollection(
private val memberMessages = mutableMapOf<String, MutableList<Message>>() private val memberMessages = mutableMapOf<String, MutableList<Message>>()
private val provisional = mutableListOf<Message>()
private var cachedEntries: List<Message>? = null private var cachedEntries: List<Message>? = null
/** /**
@ -58,6 +59,28 @@ class MessageCollection(
} }
} }
/**
* Hold this message until we've decided whether or not it's real.
*/
fun provisionalAdd(message: Message) {
provisional.add(message)
}
/**
* Discard all provisional messages.
*/
fun clearProvisional() {
provisional.clear()
}
/**
* Accept all provisional messages.
*/
fun acceptProvisional() {
addAll(provisional)
clearProvisional()
}
/** /**
* Get all recorded messages for a given class. * Get all recorded messages for a given class.
*/ */

View File

@ -1,6 +1,7 @@
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 import net.corda.djvm.analysis.ClassAndMemberVisitor
import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMExceptionOwner import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMExceptionOwner
@ -8,30 +9,32 @@ import net.corda.djvm.analysis.ExceptionResolver.Companion.isDJVMException
import net.corda.djvm.code.asPackagePath import net.corda.djvm.code.asPackagePath
import net.corda.djvm.code.asResourcePath import net.corda.djvm.code.asResourcePath
import net.corda.djvm.references.ClassReference import net.corda.djvm.references.ClassReference
import net.corda.djvm.source.AbstractSourceClassLoader
import net.corda.djvm.source.ClassSource import net.corda.djvm.source.ClassSource
import net.corda.djvm.utilities.loggerFor import net.corda.djvm.utilities.loggerFor
import net.corda.djvm.validation.RuleValidator import net.corda.djvm.validation.RuleValidator
import org.objectweb.asm.Type
/** /**
* Class loader that enables registration of rewired classes. * Class loader that enables registration of rewired classes.
* *
* @param configuration The configuration to use for the sandbox. * @property analysisConfiguration The configuration to use for the analysis.
* @property ruleValidator The instance used to validate that any loaded class complies with the specified rules.
* @property supportingClassLoader The class loader used to find classes on the extended class path.
* @property rewriter The re-writer to use for registered classes.
* @property context The context in which analysis and processing is performed. * @property context The context in which analysis and processing is performed.
* @param throwableClass This sandbox's definition of [sandbox.java.lang.Throwable].
* @param parent This classloader's parent classloader.
*/ */
class SandboxClassLoader( class SandboxClassLoader private constructor(
configuration: SandboxConfiguration, private val analysisConfiguration: AnalysisConfiguration,
private val context: AnalysisContext private val ruleValidator: RuleValidator,
) : ClassLoader() { private val supportingClassLoader: AbstractSourceClassLoader,
private val rewriter: ClassRewriter,
private val analysisConfiguration = configuration.analysisConfiguration private val context: AnalysisContext,
throwableClass: Class<*>?,
/** parent: ClassLoader?
* The instance used to validate that any loaded class complies with the specified rules. ) : ClassLoader(parent ?: getSystemClassLoader()) {
*/
private val ruleValidator: RuleValidator = RuleValidator(
rules = configuration.rules,
configuration = analysisConfiguration
)
/** /**
* The analyzer used to traverse the class hierarchy. * The analyzer used to traverse the class hierarchy.
@ -50,36 +53,65 @@ class SandboxClassLoader(
private val loadedClasses = mutableMapOf<String, LoadedClass>() private val loadedClasses = mutableMapOf<String, LoadedClass>()
/** /**
* The class loader used to find classes on the extended class path. * We need to load [sandbox.java.lang.Throwable] up front, so that we can
* identify sandboxed exception classes.
*/ */
private val supportingClassLoader = analysisConfiguration.supportingClassLoader private val throwableClass: Class<*> = throwableClass ?: run {
/**
* The re-writer to use for registered classes.
*/
private val rewriter: ClassRewriter = ClassRewriter(configuration, supportingClassLoader)
/**
* We need to load this class up front, so that we can identify sandboxed exception classes.
*/
private val throwableClass: Class<*>
init {
// Bootstrap the loading of the sandboxed Throwable class.
loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.Object"), context) loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.Object"), context)
loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.StackTraceElement"), context) loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.StackTraceElement"), context)
throwableClass = loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.Throwable"), context).type loadClassAndBytes(ClassSource.fromClassName("sandbox.java.lang.Throwable"), context).type
} }
/**
* Creates an empty [SandboxClassLoader] with exactly the same
* configuration as this one, but with the given [AnalysisContext].
* @param newContext The [AnalysisContext] to use for the child classloader.
*/
fun copyEmpty(newContext: AnalysisContext) = SandboxClassLoader(
analysisConfiguration,
ruleValidator,
supportingClassLoader,
rewriter,
newContext,
throwableClass,
parent
)
/** /**
* Given a class name, provide its corresponding [LoadedClass] for the sandbox. * Given a class name, provide its corresponding [LoadedClass] for the sandbox.
* This class may have been loaded by a parent classloader really.
*/ */
fun loadForSandbox(name: String, context: AnalysisContext): LoadedClass { @Throws(ClassNotFoundException::class)
return loadClassAndBytes(ClassSource.fromClassName(analysisConfiguration.classResolver.resolveNormalized(name)), context) fun loadForSandbox(className: String): LoadedClass {
val sandboxClass = loadClassForSandbox(className)
val sandboxName = Type.getInternalName(sandboxClass)
var loader = this
while(true) {
val loaded = loader.loadedClasses[sandboxName]
if (loaded != null) {
return loaded
}
loader = loader.parent as? SandboxClassLoader ?: return LoadedClass(sandboxClass, UNMODIFIED)
}
} }
fun loadForSandbox(source: ClassSource, context: AnalysisContext): LoadedClass { @Throws(ClassNotFoundException::class)
return loadForSandbox(source.qualifiedClassName, context) fun loadForSandbox(source: ClassSource): LoadedClass {
return loadForSandbox(source.qualifiedClassName)
}
private fun loadClassForSandbox(className: String): Class<*> {
val sandboxName = analysisConfiguration.classResolver.resolveNormalized(className)
return try {
loadClass(sandboxName)
} finally {
context.messages.acceptProvisional()
}
}
@Throws(ClassNotFoundException::class)
fun loadClassForSandbox(source: ClassSource): Class<*> {
return loadClassForSandbox(source.qualifiedClassName)
} }
/** /**
@ -95,10 +127,19 @@ class SandboxClassLoader(
var clazz = findLoadedClass(name) var clazz = findLoadedClass(name)
if (clazz == null) { if (clazz == null) {
val source = ClassSource.fromClassName(name) val source = ClassSource.fromClassName(name)
clazz = if (analysisConfiguration.isSandboxClass(source.internalClassName)) { val isSandboxClass = analysisConfiguration.isSandboxClass(source.internalClassName)
loadSandboxClass(source, context).type
} else { if (!isSandboxClass || parent is SandboxClassLoader) {
super.loadClass(name, resolve) try {
clazz = super.loadClass(name, resolve)
} catch (e: ClassNotFoundException) {
} catch (e: SandboxClassLoadingException) {
e.messages.clearProvisional()
}
}
if (clazz == null && isSandboxClass) {
clazz = loadSandboxClass(source, context).type
} }
} }
if (resolve) { if (resolve) {
@ -107,15 +148,31 @@ class SandboxClassLoader(
return clazz return clazz
} }
/**
* A sandboxed exception class cannot be thrown, and so we may also need to create a
* synthetic throwable wrapper for it. Or perhaps we've just been asked to load the
* synthetic wrapper class belonging to an exception that we haven't loaded yet?
* Either way, we need to load the sandboxed exception first so that we know what
* the synthetic wrapper's super-class needs to be.
*/
private fun loadSandboxClass(source: ClassSource, context: AnalysisContext): LoadedClass { private fun loadSandboxClass(source: ClassSource, context: AnalysisContext): LoadedClass {
return if (isDJVMException(source.internalClassName)) { return if (isDJVMException(source.internalClassName)) {
/** /**
* We need to load a DJVMException's owner class before we can create * We need to load a DJVMException's owner class before we can create
* its wrapper exception. And loading the owner should also create the * its wrapper exception. And loading the owner should then also create
* wrapper class automatically. * the wrapper class automatically.
*/ */
loadedClasses.getOrElse(source.internalClassName) { loadedClasses.getOrElse(source.internalClassName) {
loadSandboxClass(ClassSource.fromClassName(getDJVMExceptionOwner(source.qualifiedClassName)), context) val exceptionOwner = ClassSource.fromClassName(getDJVMExceptionOwner(source.qualifiedClassName))
if (!analysisConfiguration.isJvmException(exceptionOwner.internalClassName)) {
/**
* JVM Exceptions belong to the parent classloader, and so will never
* be found inside a child classloader. Which means we must not try to
* create a duplicate inside any child classloaders either. Hence we
* re-invoke [loadClass] which will delegate back to the parent.
*/
loadClass(exceptionOwner.qualifiedClassName, false)
}
loadedClasses[source.internalClassName] loadedClasses[source.internalClassName]
} ?: throw ClassNotFoundException(source.qualifiedClassName) } ?: throw ClassNotFoundException(source.qualifiedClassName)
} else { } else {
@ -171,6 +228,7 @@ class SandboxClassLoader(
} }
// Check if any errors were found during analysis. // Check if any errors were found during analysis.
context.messages.acceptProvisional()
if (context.messages.errorCount > 0) { if (context.messages.errorCount > 0) {
logger.debug("Errors detected after analyzing class {}", request.qualifiedClassName) logger.debug("Errors detected after analyzing class {}", request.qualifiedClassName)
throw SandboxClassLoadingException(context) throw SandboxClassLoadingException(context)
@ -214,6 +272,10 @@ class SandboxClassLoader(
} }
} }
/**
* Check whether the synthetic throwable wrapper already
* exists for this exception, and create it if it doesn't.
*/
private fun loadWrapperFor(throwable: Class<*>): LoadedClass { private fun loadWrapperFor(throwable: Class<*>): LoadedClass {
val className = analysisConfiguration.exceptionResolver.getThrowableName(throwable) val className = analysisConfiguration.exceptionResolver.getThrowableName(throwable)
return loadedClasses.getOrPut(className) { return loadedClasses.getOrPut(className) {
@ -223,9 +285,30 @@ class SandboxClassLoader(
} }
} }
private companion object { companion object {
private val logger = loggerFor<SandboxClassLoader>() private val logger = loggerFor<SandboxClassLoader>()
private val UNMODIFIED = ByteCode(ByteArray(0), false) private val UNMODIFIED = ByteCode(ByteArray(0), false)
/**
* Factory function to create a [SandboxClassLoader].
* @param configuration The [SandboxConfiguration] containing the classloader's configuration parameters.
*/
fun createFor(configuration: SandboxConfiguration): SandboxClassLoader {
val analysisConfiguration = configuration.analysisConfiguration
val supportingClassLoader = analysisConfiguration.supportingClassLoader
val parentClassLoader = configuration.parentClassLoader
return SandboxClassLoader(
analysisConfiguration = analysisConfiguration,
supportingClassLoader = supportingClassLoader,
ruleValidator = RuleValidator(rules = configuration.rules,
configuration = analysisConfiguration),
rewriter = ClassRewriter(configuration, supportingClassLoader),
context = AnalysisContext.fromConfiguration(analysisConfiguration),
throwableClass = parentClassLoader?.throwableClass,
parent = parentClassLoader
)
}
} }
} }

View File

@ -19,7 +19,7 @@ class SandboxClassLoadingException(
val messages: MessageCollection = context.messages, val messages: MessageCollection = context.messages,
val classes: ClassHierarchy = context.classes, val classes: ClassHierarchy = context.classes,
val classOrigins: Map<String, Set<EntityReference>> = context.classOrigins val classOrigins: Map<String, Set<EntityReference>> = context.classOrigins
) : Exception("Failed to load class") { ) : RuntimeException("Failed to load class") {
/** /**
* The detailed description of the exception. * The detailed description of the exception.
@ -28,7 +28,7 @@ class SandboxClassLoadingException(
get() = StringBuilder().apply { get() = StringBuilder().apply {
appendln(super.message) appendln(super.message)
for (message in messages.sorted().map(Message::toString).distinct()) { for (message in messages.sorted().map(Message::toString).distinct()) {
appendln(" - $message") append(" - ").appendln(message)
} }
}.toString().trimEnd('\r', '\n') }.toString().trimEnd('\r', '\n')

View File

@ -1,5 +1,7 @@
@file:JvmName("SourceClassLoaderTools")
package net.corda.djvm.source package net.corda.djvm.source
import net.corda.djvm.analysis.AnalysisConfiguration.Companion.SANDBOX_PREFIX
import net.corda.djvm.analysis.AnalysisContext import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.analysis.ClassResolver import net.corda.djvm.analysis.ClassResolver
import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMExceptionOwner import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMExceptionOwner
@ -33,19 +35,23 @@ abstract class AbstractSourceClassLoader(
className: String, context: AnalysisContext, origin: String? = null className: String, context: AnalysisContext, origin: String? = null
): ClassReader { ): ClassReader {
val originalName = classResolver.reverse(className.asResourcePath) val originalName = classResolver.reverse(className.asResourcePath)
fun throwClassLoadingError(): Nothing {
context.messages.provisionalAdd(Message(
message ="Class file not found; $originalName.class",
severity = Severity.ERROR,
location = SourceLocation(origin ?: "")
))
throw SandboxClassLoadingException(context)
}
return try { return try {
logger.trace("Opening ClassReader for class {}...", originalName) logger.trace("Opening ClassReader for class {}...", originalName)
getResourceAsStream("$originalName.class").use { getResourceAsStream("$originalName.class")?.use {
ClassReader(it) ClassReader(it)
} } ?: run(::throwClassLoadingError)
} catch (exception: IOException) { } catch (exception: IOException) {
context.messages.add(Message( throwClassLoadingError()
message ="Class file not found; $originalName.class",
severity = Severity.ERROR,
location = SourceLocation(origin ?: "")
))
logger.error("Failed to open ClassReader for class", exception)
throw SandboxClassLoadingException(context)
} }
} }
@ -78,50 +84,16 @@ abstract class AbstractSourceClassLoader(
protected companion object { protected companion object {
@JvmStatic @JvmStatic
protected val logger = loggerFor<SourceClassLoader>() protected val logger = loggerFor<SourceClassLoader>()
private fun resolvePaths(paths: List<Path>): Array<URL> {
return paths.map(this::expandPath).flatMap {
when {
!Files.exists(it) -> throw FileNotFoundException("File not found; $it")
Files.isDirectory(it) -> {
listOf(it.toURL()) + Files.list(it).filter(::isJarFile).map { jar -> jar.toURL() }.toList()
}
Files.isReadable(it) && isJarFile(it) -> listOf(it.toURL())
else -> throw IllegalArgumentException("Expected JAR or class file, but found $it")
}
}.apply {
logger.trace("Resolved paths: {}", this)
}.toTypedArray()
}
private fun expandPath(path: Path): Path {
val pathString = path.toString()
if (pathString.startsWith("~/")) {
return homeDirectory.resolve(pathString.removePrefix("~/"))
}
return path
}
private fun isJarFile(path: Path) = path.toString().endsWith(".jar", true)
private fun Path.toURL(): URL = this.toUri().toURL()
private val homeDirectory: Path
get() = Paths.get(System.getProperty("user.home"))
} }
} }
/** /**
* Class loader to manage an optional JAR of replacement Java APIs. * Class loader to manage an optional JAR of replacement Java APIs.
* @param bootstrapJar The location of the JAR containing the Java APIs. * @param bootstrapJar The location of the JAR containing the Java APIs.
* @param classResolver The resolver to use to derive the original name of a requested class.
*/ */
class BootstrapClassLoader( class BootstrapClassLoader(
bootstrapJar: Path, bootstrapJar: Path
classResolver: ClassResolver ) : URLClassLoader(resolvePaths(listOf(bootstrapJar)), null) {
) : AbstractSourceClassLoader(listOf(bootstrapJar), classResolver, null) {
/** /**
* Only search our own jars for the given resource. * Only search our own jars for the given resource.
@ -129,6 +101,37 @@ class BootstrapClassLoader(
override fun getResource(name: String): URL? = findResource(name) override fun getResource(name: String): URL? = findResource(name)
} }
/**
* Class loader that only provides our built-in sandbox classes.
* @param classResolver The resolver to use to derive the original name of a requested class.
*/
class SandboxSourceClassLoader(
classResolver: ClassResolver,
private val bootstrap: BootstrapClassLoader
) : AbstractSourceClassLoader(emptyList(), classResolver, SandboxSourceClassLoader::class.java.classLoader) {
/**
* Always check the bootstrap classloader first. If we're requesting
* built-in sandbox classes then delegate to our parent classloader,
* otherwise deny the request.
*/
override fun getResource(name: String): URL? {
val resource = bootstrap.findResource(name)
if (resource != null) {
return resource
} else if (isJvmInternal(name)) {
logger.error("Denying request for actual {}", name)
return null
}
return if (name.startsWith(SANDBOX_PREFIX)) {
parent.getResource(name)
} else {
null
}
}
}
/** /**
* Customizable class loader that allows the user to explicitly specify additional JARs and directories to scan. * Customizable class loader that allows the user to explicitly specify additional JARs and directories to scan.
* *
@ -168,12 +171,41 @@ class SourceClassLoader(
return if (name.startsWith("net/corda/djvm/")) null else super.findResource(name) return if (name.startsWith("net/corda/djvm/")) null else super.findResource(name)
} }
/** }
* Does [name] exist within any of the packages reserved for Java itself?
*/ private fun resolvePaths(paths: List<Path>): Array<URL> {
private fun isJvmInternal(name: String): Boolean = name.startsWith("java/") return paths.map(::expandPath).flatMap {
|| name.startsWith("javax/") when {
|| name.startsWith("com/sun/") !Files.exists(it) -> throw FileNotFoundException("File not found; $it")
|| name.startsWith("sun/") Files.isDirectory(it) -> {
|| name.startsWith("jdk/") listOf(it.toURL()) + Files.list(it).filter(::isJarFile).map { jar -> jar.toURL() }.toList()
} }
Files.isReadable(it) && isJarFile(it) -> listOf(it.toURL())
else -> throw IllegalArgumentException("Expected JAR or class file, but found $it")
}
}.toTypedArray()
}
private fun expandPath(path: Path): Path {
val pathString = path.toString()
if (pathString.startsWith("~/")) {
return homeDirectory.resolve(pathString.removePrefix("~/"))
}
return path
}
private fun isJarFile(path: Path) = path.toString().endsWith(".jar", true)
private fun Path.toURL(): URL = this.toUri().toURL()
private val homeDirectory: Path
get() = Paths.get(System.getProperty("user.home"))
/**
* Does [name] exist within any of the packages reserved for Java itself?
*/
private fun isJvmInternal(name: String): Boolean = name.startsWith("java/")
|| name.startsWith("javax/")
|| name.startsWith("com/sun/")
|| name.startsWith("sun/")
|| name.startsWith("jdk/")

View File

@ -2,6 +2,7 @@
@file:Suppress("unused") @file:Suppress("unused")
package sandbox.java.lang package sandbox.java.lang
import net.corda.djvm.SandboxRuntimeContext
import net.corda.djvm.analysis.AnalysisConfiguration.Companion.JVM_EXCEPTIONS import net.corda.djvm.analysis.AnalysisConfiguration.Companion.JVM_EXCEPTIONS
import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMException import net.corda.djvm.analysis.ExceptionResolver.Companion.getDJVMException
import net.corda.djvm.rules.implementation.* import net.corda.djvm.rules.implementation.*
@ -42,14 +43,14 @@ fun Any.sandbox(): Any {
private fun Array<*>.fromDJVMArray(): Array<*> = Object.fromDJVM(this) private fun Array<*>.fromDJVMArray(): Array<*> = Object.fromDJVM(this)
/** /**
* These functions use the "current" classloader, i.e. classloader * Use the sandbox's classloader explicitly, because this class
* that owns this DJVM class. * might belong to the shared parent classloader.
*/ */
@Throws(ClassNotFoundException::class) @Throws(ClassNotFoundException::class)
internal fun Class<*>.toDJVMType(): Class<*> = Class.forName(name.toSandboxPackage()) internal fun Class<*>.toDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.toSandboxPackage())
@Throws(ClassNotFoundException::class) @Throws(ClassNotFoundException::class)
internal fun Class<*>.fromDJVMType(): Class<*> = Class.forName(name.fromSandboxPackage()) internal fun Class<*>.fromDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.fromSandboxPackage())
private fun kotlin.String.toSandboxPackage(): kotlin.String { private fun kotlin.String.toSandboxPackage(): kotlin.String {
return if (startsWith(SANDBOX_PREFIX)) { return if (startsWith(SANDBOX_PREFIX)) {
@ -190,10 +191,11 @@ fun fromDJVM(t: Throwable?): kotlin.Throwable {
val sandboxedName = t!!.javaClass.name val sandboxedName = t!!.javaClass.name
if (Type.getInternalName(t.javaClass) in JVM_EXCEPTIONS) { if (Type.getInternalName(t.javaClass) in JVM_EXCEPTIONS) {
// We map these exceptions to their equivalent JVM classes. // We map these exceptions to their equivalent JVM classes.
Class.forName(sandboxedName.fromSandboxPackage()).createJavaThrowable(t) SandboxRuntimeContext.instance.classLoader.loadClass(sandboxedName.fromSandboxPackage())
.createJavaThrowable(t)
} else { } else {
// Whereas the sandbox creates a synthetic throwable wrapper for these. // Whereas the sandbox creates a synthetic throwable wrapper for these.
Class.forName(getDJVMException(sandboxedName)) SandboxRuntimeContext.instance.classLoader.loadClass(getDJVMException(sandboxedName))
.getDeclaredConstructor(sandboxThrowable) .getDeclaredConstructor(sandboxThrowable)
.newInstance(t) as kotlin.Throwable .newInstance(t) as kotlin.Throwable
} }

View File

@ -12,13 +12,11 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.util.Collections.emptySet;
public class SandboxEnumJavaTest extends TestBase { public class SandboxEnumJavaTest extends TestBase {
@Test @Test
public void testEnumInsideSandbox() { public void testEnumInsideSandbox() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<Integer, String[]> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<Integer, String[]> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<String[]> output = WithJava.run(executor, TransformEnum.class, 0); ExecutionSummaryWithResult<String[]> output = WithJava.run(executor, TransformEnum.class, 0);
assertThat(output.getResult()) assertThat(output.getResult())
@ -29,7 +27,7 @@ public class SandboxEnumJavaTest extends TestBase {
@Test @Test
public void testReturnEnumFromSandbox() { public void testReturnEnumFromSandbox() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<String, ExampleEnum> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<String, ExampleEnum> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<ExampleEnum> output = WithJava.run(executor, FetchEnum.class, "THREE"); ExecutionSummaryWithResult<ExampleEnum> output = WithJava.run(executor, FetchEnum.class, "THREE");
assertThat(output.getResult()) assertThat(output.getResult())
@ -40,7 +38,7 @@ public class SandboxEnumJavaTest extends TestBase {
@Test @Test
public void testWeCanIdentifyClassAsEnum() { public void testWeCanIdentifyClassAsEnum() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<ExampleEnum, Boolean> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<ExampleEnum, Boolean> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<Boolean> output = WithJava.run(executor, AssertEnum.class, ExampleEnum.THREE); ExecutionSummaryWithResult<Boolean> output = WithJava.run(executor, AssertEnum.class, ExampleEnum.THREE);
assertThat(output.getResult()).isTrue(); assertThat(output.getResult()).isTrue();
@ -50,7 +48,7 @@ public class SandboxEnumJavaTest extends TestBase {
@Test @Test
public void testWeCanCreateEnumMap() { public void testWeCanCreateEnumMap() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<ExampleEnum, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<ExampleEnum, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, UseEnumMap.class, ExampleEnum.TWO); ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, UseEnumMap.class, ExampleEnum.TWO);
assertThat(output.getResult()).isEqualTo(1); assertThat(output.getResult()).isEqualTo(1);
@ -60,7 +58,7 @@ public class SandboxEnumJavaTest extends TestBase {
@Test @Test
public void testWeCanCreateEnumSet() { public void testWeCanCreateEnumSet() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<ExampleEnum, Boolean> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<ExampleEnum, Boolean> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<Boolean> output = WithJava.run(executor, UseEnumSet.class, ExampleEnum.ONE); ExecutionSummaryWithResult<Boolean> output = WithJava.run(executor, UseEnumSet.class, ExampleEnum.ONE);
assertThat(output.getResult()).isTrue(); assertThat(output.getResult()).isTrue();

View File

@ -12,14 +12,13 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import static java.util.Collections.emptySet;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class SandboxThrowableJavaTest extends TestBase { public class SandboxThrowableJavaTest extends TestBase {
@Test @Test
public void testUserExceptionHandling() { public void testUserExceptionHandling() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<String, String[]> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<String, String[]> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<String[]> output = WithJava.run(executor, ThrowAndCatchJavaExample.class, "Hello World!"); ExecutionSummaryWithResult<String[]> output = WithJava.run(executor, ThrowAndCatchJavaExample.class, "Hello World!");
assertThat(output.getResult()) assertThat(output.getResult())
@ -30,7 +29,7 @@ public class SandboxThrowableJavaTest extends TestBase {
@Test @Test
public void testCheckedExceptions() { public void testCheckedExceptions() {
sandbox(new Object[]{ DEFAULT }, emptySet(), WARNING, true, ctx -> { parentedSandbox(WARNING, true, ctx -> {
SandboxExecutor<String, String> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration()); SandboxExecutor<String, String> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
ExecutionSummaryWithResult<String> success = WithJava.run(executor, JavaWithCheckedExceptions.class, "http://localhost:8080/hello/world"); ExecutionSummaryWithResult<String> success = WithJava.run(executor, JavaWithCheckedExceptions.class, "http://localhost:8080/hello/world");

View File

@ -1,15 +1,17 @@
package net.corda.djvm package net.corda.djvm
import net.corda.djvm.assertions.AssertionExtensions.assertThatDJVM
import net.corda.djvm.rewiring.SandboxClassLoadingException
import org.assertj.core.api.Assertions.assertThat 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.SandboxFunction import sandbox.SandboxFunction
import sandbox.Task import sandbox.Task
import sandbox.java.lang.sandbox import java.util.*
class DJVMExceptionTest { class DJVMExceptionTest : TestBase() {
@Test @Test
fun testSingleException() { fun testSingleException() = parentedSandbox {
val result = Task(SingleExceptionTask()).apply("Hello World") val result = Task(SingleExceptionTask()).apply("Hello World")
assertThat(result).isInstanceOf(Throwable::class.java) assertThat(result).isInstanceOf(Throwable::class.java)
result as Throwable result as Throwable
@ -22,7 +24,7 @@ class DJVMExceptionTest {
} }
@Test @Test
fun testMultipleExceptions() { fun testMultipleExceptions() = parentedSandbox {
val result = Task(MultipleExceptionsTask()).apply("Hello World") val result = Task(MultipleExceptionsTask()).apply("Hello World")
assertThat(result).isInstanceOf(Throwable::class.java) assertThat(result).isInstanceOf(Throwable::class.java)
result as Throwable result as Throwable
@ -56,24 +58,92 @@ class DJVMExceptionTest {
} }
@Test @Test
fun testJavaThrowableToSandbox() { fun testJavaThrowableToSandbox() = parentedSandbox {
val result = Throwable("Hello World").sandbox() val djvm = DJVM(classLoader)
assertThat(result).isInstanceOf(sandbox.java.lang.Throwable::class.java) val helloWorld = djvm.stringOf("Hello World")
result as sandbox.java.lang.Throwable
assertThat(result.message).isEqualTo("Hello World".toDJVM()) val result = djvm.sandbox(Throwable("Hello World"))
assertThat(result.stackTrace).isNotEmpty() assertThatDJVM(result)
assertThat(result.cause).isNull() .hasClassName("sandbox.java.lang.Throwable")
.isAssignableFrom(djvm.throwableClass)
.hasGetterValue("getMessage", helloWorld)
.hasGetterNullValue("getCause")
assertThat(result.getArray("getStackTrace"))
.hasOnlyElementsOfType(djvm.stackTraceElementClass)
.isNotEmpty()
} }
@Test @Test
fun testWeTryToCreateCorrectSandboxExceptionsAtRuntime() { fun testWeCreateCorrectJVMExceptionAtRuntime() = parentedSandbox {
val djvm = DJVM(classLoader)
val helloWorld = djvm.stringOf("Hello World")
val result = djvm.sandbox(RuntimeException("Hello World"))
assertThatDJVM(result)
.hasClassName("sandbox.java.lang.RuntimeException")
.isAssignableFrom(djvm.throwableClass)
.hasGetterValue("getMessage", helloWorld)
.hasGetterNullValue("getCause")
assertThat(result.getArray("getStackTrace"))
.hasOnlyElementsOfType(djvm.stackTraceElementClass)
.isNotEmpty()
assertThatExceptionOfType(ClassNotFoundException::class.java) assertThatExceptionOfType(ClassNotFoundException::class.java)
.isThrownBy { Exception("Hello World").sandbox() } .isThrownBy { djvm.classFor("sandbox.java.lang.RuntimeException\$1DJVM") }
.withMessage("sandbox.java.lang.Exception") .withMessage("sandbox.java.lang.RuntimeException\$1DJVM")
}
@Test
fun testWeCreateCorrectSyntheticExceptionAtRuntime() = parentedSandbox {
val djvm = DJVM(classLoader)
val result = djvm.sandbox(EmptyStackException())
assertThatDJVM(result)
.hasClassName("sandbox.java.util.EmptyStackException")
.isAssignableFrom(djvm.throwableClass)
.hasGetterNullValue("getMessage")
.hasGetterNullValue("getCause")
assertThat(result.getArray("getStackTrace"))
.hasOnlyElementsOfType(djvm.stackTraceElementClass)
.isNotEmpty()
assertThatDJVM(djvm.classFor("sandbox.java.util.EmptyStackException\$1DJVM"))
.isAssignableFrom(RuntimeException::class.java)
}
@Test
fun testWeCannotCreateSyntheticExceptionForNonException() = parentedSandbox {
val djvm = DJVM(classLoader)
assertThatExceptionOfType(ClassNotFoundException::class.java) assertThatExceptionOfType(ClassNotFoundException::class.java)
.isThrownBy { RuntimeException("Hello World").sandbox() } .isThrownBy { djvm.classFor("sandbox.java.util.LinkedList\$1DJVM") }
.withMessage("sandbox.java.lang.RuntimeException") .withMessage("sandbox.java.util.LinkedList\$1DJVM")
}
/**
* This scenario should never happen in practice. We just need to be sure
* that the classloader can handle it.
*/
@Test
fun testWeCannotCreateSyntheticExceptionForImaginaryJavaClass() = parentedSandbox {
val djvm = DJVM(classLoader)
assertThatExceptionOfType(SandboxClassLoadingException::class.java)
.isThrownBy { djvm.classFor("sandbox.java.util.DoesNotExist\$1DJVM") }
.withMessageContaining("Failed to load class")
}
/**
* This scenario should never happen in practice. We just need to be sure
* that the classloader can handle it.
*/
@Test
fun testWeCannotCreateSyntheticExceptionForImaginaryUserClass() = parentedSandbox {
val djvm = DJVM(classLoader)
assertThatExceptionOfType(SandboxClassLoadingException::class.java)
.isThrownBy { djvm.classFor("sandbox.com.example.DoesNotExist\$1DJVM") }
.withMessageContaining("Failed to load class")
} }
} }
@ -92,7 +162,7 @@ class MultipleExceptionsTask : SandboxFunction<Any?, sandbox.java.lang.Throwable
} }
private infix operator fun sandbox.java.lang.String.plus(s: String): sandbox.java.lang.String { private infix operator fun sandbox.java.lang.String.plus(s: String): sandbox.java.lang.String {
return (toString() + s).toDJVM() return sandbox.java.lang.String.valueOf(toString() + s)
} }
private fun Array<StackTraceElement>.toLineNumbers(): IntArray { private fun Array<StackTraceElement>.toLineNumbers(): IntArray {

View File

@ -4,9 +4,8 @@ import org.assertj.core.api.Assertions.*
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import sandbox.java.lang.sandbox import sandbox.java.lang.sandbox
import sandbox.java.lang.unsandbox
class DJVMTest { class DJVMTest : TestBase() {
@Test @Test
fun testDJVMString() { fun testDJVMString() {
@ -16,39 +15,59 @@ class DJVMTest {
} }
@Test @Test
fun testSimpleIntegerFormats() { fun testSimpleIntegerFormats() = parentedSandbox {
val result = sandbox.java.lang.String.format("%d-%d-%d-%d".toDJVM(), val result = with(DJVM(classLoader)) {
10.toDJVM(), 999999L.toDJVM(), 1234.toShort().toDJVM(), 108.toByte().toDJVM()).toString() stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null,
stringOf("%d-%d-%d-%d"),
arrayOf(intOf(10), longOf(999999L), shortOf(1234), byteOf(108))
).toString()
}
assertEquals("10-999999-1234-108", result) assertEquals("10-999999-1234-108", result)
} }
@Test @Test
fun testHexFormat() { fun testHexFormat() = parentedSandbox {
val result = sandbox.java.lang.String.format("%0#6x".toDJVM(), 768.toDJVM()).toString() val result = with(DJVM(classLoader)) {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%0#6x"), arrayOf(intOf(768))).toString()
}
assertEquals("0x0300", result) assertEquals("0x0300", result)
} }
@Test @Test
fun testDoubleFormat() { fun testDoubleFormat() = parentedSandbox {
val result = sandbox.java.lang.String.format("%9.4f".toDJVM(), 1234.5678.toDJVM()).toString() val result = with(DJVM(classLoader)) {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%9.4f"), arrayOf(doubleOf(1234.5678))).toString()
}
assertEquals("1234.5678", result) assertEquals("1234.5678", result)
} }
@Test @Test
fun testFloatFormat() { fun testFloatFormat() = parentedSandbox {
val result = sandbox.java.lang.String.format("%7.2f".toDJVM(), 1234.5678f.toDJVM()).toString() val result = with(DJVM(classLoader)) {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%7.2f"), arrayOf(floatOf(1234.5678f))).toString()
}
assertEquals("1234.57", result) assertEquals("1234.57", result)
} }
@Test @Test
fun testCharFormat() { fun testCharFormat() = parentedSandbox {
val result = sandbox.java.lang.String.format("[%c]".toDJVM(), 'A'.toDJVM()).toString() val result = with(DJVM(classLoader)) {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("[%c]"), arrayOf(charOf('A'))).toString()
}
assertEquals("[A]", result) assertEquals("[A]", result)
} }
@Test @Test
fun testObjectFormat() { fun testObjectFormat() = parentedSandbox {
val result = sandbox.java.lang.String.format("%s".toDJVM(), object : sandbox.java.lang.Object() {}).toString() val result = with(DJVM(classLoader)) {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%s"), arrayOf(object : sandbox.java.lang.Object() {})).toString()
}
assertThat(result).startsWith("sandbox.java.lang.Object@") assertThat(result).startsWith("sandbox.java.lang.Object@")
} }
@ -59,48 +78,60 @@ class DJVMTest {
} }
@Test @Test
fun testSandboxingArrays() { fun testSandboxingArrays() = parentedSandbox {
val result = arrayOf(1, 10L, "Hello World", '?', false, 1234.56).sandbox() with(DJVM(classLoader)) {
assertThat(result) val result = sandbox(arrayOf(1, 10L, "Hello World", '?', false, 1234.56))
.isEqualTo(arrayOf(1.toDJVM(), 10L.toDJVM(), "Hello World".toDJVM(), '?'.toDJVM(), false.toDJVM(), 1234.56.toDJVM())) assertThat(result).isEqualTo(
arrayOf(intOf(1), longOf(10), stringOf("Hello World"), charOf('?'), booleanOf(false), doubleOf(1234.56)))
}
} }
@Test @Test
fun testUnsandboxingObjectArray() { fun testUnsandboxingObjectArray() = parentedSandbox {
val result = arrayOf<sandbox.java.lang.Object>(1.toDJVM(), 10L.toDJVM(), "Hello World".toDJVM(), '?'.toDJVM(), false.toDJVM(), 1234.56.toDJVM()).unsandbox() val result = with(DJVM(classLoader)) {
unsandbox(arrayOf(intOf(1), longOf(10L), stringOf("Hello World"), charOf('?'), booleanOf(false), doubleOf(1234.56)))
}
assertThat(result) assertThat(result)
.isEqualTo(arrayOf(1, 10L, "Hello World", '?', false, 1234.56)) .isEqualTo(arrayOf(1, 10L, "Hello World", '?', false, 1234.56))
} }
@Test @Test
fun testSandboxingPrimitiveArray() { fun testSandboxingPrimitiveArray() = parentedSandbox {
val result = intArrayOf(1, 2, 3, 10).sandbox() val result = with(DJVM(classLoader)) {
sandbox(intArrayOf(1, 2, 3, 10))
}
assertThat(result).isEqualTo(intArrayOf(1, 2, 3, 10)) assertThat(result).isEqualTo(intArrayOf(1, 2, 3, 10))
} }
@Test @Test
fun testSandboxingIntegersAsObjectArray() { fun testSandboxingIntegersAsObjectArray() = parentedSandbox {
val result = arrayOf(1, 2, 3, 10).sandbox() with(DJVM(classLoader)) {
assertThat(result).isEqualTo(arrayOf(1.toDJVM(), 2.toDJVM(), 3.toDJVM(), 10.toDJVM())) val result = sandbox(arrayOf(1, 2, 3, 10))
assertThat(result).isEqualTo(
arrayOf(intOf(1), intOf(2), intOf(3), intOf(10))
)
}
} }
@Test @Test
fun testUnsandboxingArrays() { fun testUnsandboxingArrays() = parentedSandbox {
val arr = arrayOf( val (array, result) = with(DJVM(classLoader)) {
Array(1) { "Hello".toDJVM() }, val arr = arrayOf(
Array(1) { 1234000L.toDJVM() }, objectArrayOf(stringOf("Hello")),
Array(1) { 1234.toDJVM() }, objectArrayOf(longOf(1234000L)),
Array(1) { 923.toShort().toDJVM() }, objectArrayOf(intOf(1234)),
Array(1) { 27.toByte().toDJVM() }, objectArrayOf(shortOf(923)),
Array(1) { 'X'.toDJVM() }, objectArrayOf(byteOf(27)),
Array(1) { 987.65f.toDJVM() }, objectArrayOf(charOf('X')),
Array(1) { 343.282.toDJVM() }, objectArrayOf(floatOf(987.65f)),
Array(1) { true.toDJVM() }, objectArrayOf(doubleOf(343.282)),
ByteArray(1) { 127.toByte() }, objectArrayOf(booleanOf(true)),
CharArray(1) { '?'} ByteArray(1) { 127.toByte() },
) CharArray(1) { '?' }
val result = arr.unsandbox() as Array<*> )
assertEquals(arr.size, result.size) Pair(arr, unsandbox(arr) as Array<*>)
}
assertEquals(array.size, result.size)
assertArrayEquals(Array(1) { "Hello" }, result[0] as Array<*>) assertArrayEquals(Array(1) { "Hello" }, result[0] as Array<*>)
assertArrayEquals(Array(1) { 1234000L }, result[1] as Array<*>) assertArrayEquals(Array(1) { 1234000L }, result[1] as Array<*>)
assertArrayEquals(Array(1) { 1234 }, result[2] as Array<*>) assertArrayEquals(Array(1) { 1234 }, result[2] as Array<*>)

View File

@ -12,13 +12,18 @@ import net.corda.djvm.execution.ExecutionProfile
import net.corda.djvm.messages.Severity 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.rewiring.SandboxClassLoader
import net.corda.djvm.rules.Rule import net.corda.djvm.rules.Rule
import net.corda.djvm.rules.implementation.* import net.corda.djvm.rules.implementation.*
import net.corda.djvm.source.BootstrapClassLoader
import net.corda.djvm.source.ClassSource import net.corda.djvm.source.ClassSource
import net.corda.djvm.source.SandboxSourceClassLoader
import net.corda.djvm.utilities.Discovery import net.corda.djvm.utilities.Discovery
import net.corda.djvm.validation.RuleValidator import net.corda.djvm.validation.RuleValidator
import org.junit.After import org.junit.After
import org.junit.AfterClass
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.BeforeClass
import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Type import org.objectweb.asm.Type
@ -39,6 +44,7 @@ abstract class TestBase {
// We need at least these emitters to handle the Java API classes. // We need at least these emitters to handle the Java API classes.
@JvmField @JvmField
val BASIC_EMITTERS: List<Emitter> = listOf( val BASIC_EMITTERS: List<Emitter> = listOf(
AlwaysInheritFromSandboxedObject(),
ArgumentUnwrapper(), ArgumentUnwrapper(),
HandleExceptionUnwrapper(), HandleExceptionUnwrapper(),
ReturnTypeWrapper(), ReturnTypeWrapper(),
@ -51,7 +57,10 @@ abstract class TestBase {
// We need at least these providers to handle the Java API classes. // We need at least these providers to handle the Java API classes.
@JvmField @JvmField
val BASIC_DEFINITION_PROVIDERS: List<DefinitionProvider> = listOf(StaticConstantRemover()) val BASIC_DEFINITION_PROVIDERS: List<DefinitionProvider> = listOf(
AlwaysInheritFromSandboxedObject(),
StaticConstantRemover()
)
@JvmField @JvmField
val BLANK = emptySet<Any>() val BLANK = emptySet<Any>()
@ -63,17 +72,52 @@ abstract class TestBase {
val DETERMINISTIC_RT: Path = Paths.get( val DETERMINISTIC_RT: Path = Paths.get(
System.getProperty("deterministic-rt.path") ?: throw AssertionError("deterministic-rt.path property not set")) System.getProperty("deterministic-rt.path") ?: throw AssertionError("deterministic-rt.path property not set"))
private lateinit var parentConfiguration: SandboxConfiguration
lateinit var parentClassLoader: SandboxClassLoader
/** /**
* Get the full name of type [T]. * Get the full name of type [T].
*/ */
inline fun <reified T> nameOf(prefix: String = "") = "$prefix${Type.getInternalName(T::class.java)}" inline fun <reified T> nameOf(prefix: String = "") = "$prefix${Type.getInternalName(T::class.java)}"
@BeforeClass
@JvmStatic
fun setupParentClassLoader() {
val rootConfiguration = AnalysisConfiguration.createRoot(
Whitelist.MINIMAL,
bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT),
sourceClassLoaderFactory = { classResolver, bootstrapClassLoader ->
SandboxSourceClassLoader(classResolver, bootstrapClassLoader!!)
},
additionalPinnedClasses = setOf(
Utilities::class.java
).map(Type::getInternalName).toSet()
)
parentConfiguration = SandboxConfiguration.of(
ExecutionProfile.UNLIMITED,
ALL_RULES,
ALL_EMITTERS,
ALL_DEFINITION_PROVIDERS,
true,
rootConfiguration
)
parentClassLoader = SandboxClassLoader.createFor(parentConfiguration)
}
@AfterClass
@JvmStatic
fun destroyRootContext() {
parentConfiguration.analysisConfiguration.close()
}
} }
/** /**
* Default analysis configuration. * Default analysis configuration.
*/ */
val configuration = AnalysisConfiguration(Whitelist.MINIMAL, bootstrapJar = DETERMINISTIC_RT) val configuration = AnalysisConfiguration.createRoot(
Whitelist.MINIMAL,
bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT)
)
/** /**
* Default analysis context * Default analysis context
@ -94,9 +138,9 @@ abstract class TestBase {
noinline block: (RuleValidator.(AnalysisContext) -> Unit) noinline block: (RuleValidator.(AnalysisContext) -> Unit)
) { ) {
val reader = ClassReader(T::class.java.name) val reader = ClassReader(T::class.java.name)
AnalysisConfiguration( AnalysisConfiguration.createRoot(
minimumSeverityLevel = minimumSeverityLevel, minimumSeverityLevel = minimumSeverityLevel,
bootstrapJar = DETERMINISTIC_RT bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT)
).use { analysisConfiguration -> ).use { analysisConfiguration ->
val validator = RuleValidator(ALL_RULES, analysisConfiguration) val validator = RuleValidator(ALL_RULES, analysisConfiguration)
val context = AnalysisContext.fromConfiguration(analysisConfiguration) val context = AnalysisContext.fromConfiguration(analysisConfiguration)
@ -110,11 +154,11 @@ abstract class TestBase {
* 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.
*/ */
fun sandbox( fun sandbox(
vararg options: Any, vararg options: Any,
pinnedClasses: Set<java.lang.Class<*>> = emptySet(), pinnedClasses: Set<java.lang.Class<*>> = emptySet(),
minimumSeverityLevel: Severity = Severity.WARNING, minimumSeverityLevel: Severity = Severity.WARNING,
enableTracing: Boolean = true, enableTracing: Boolean = true,
action: SandboxRuntimeContext.() -> Unit action: SandboxRuntimeContext.() -> Unit
) { ) {
val rules = mutableListOf<Rule>() val rules = mutableListOf<Rule>()
val emitters = mutableListOf<Emitter>().apply { addAll(BASIC_EMITTERS) } val emitters = mutableListOf<Emitter>().apply { addAll(BASIC_EMITTERS) }
@ -141,11 +185,11 @@ abstract class TestBase {
thread { thread {
try { try {
val pinnedTestClasses = pinnedClasses.map(Type::getInternalName).toSet() val pinnedTestClasses = pinnedClasses.map(Type::getInternalName).toSet()
AnalysisConfiguration( AnalysisConfiguration.createRoot(
whitelist = whitelist, whitelist = whitelist,
bootstrapJar = DETERMINISTIC_RT,
additionalPinnedClasses = pinnedTestClasses, additionalPinnedClasses = pinnedTestClasses,
minimumSeverityLevel = minimumSeverityLevel minimumSeverityLevel = minimumSeverityLevel,
bootstrapClassLoader = BootstrapClassLoader(DETERMINISTIC_RT)
).use { analysisConfiguration -> ).use { analysisConfiguration ->
SandboxRuntimeContext(SandboxConfiguration.of( SandboxRuntimeContext(SandboxConfiguration.of(
executionProfile, executionProfile,
@ -166,6 +210,37 @@ abstract class TestBase {
throw thrownException ?: return throw thrownException ?: return
} }
fun parentedSandbox(
minimumSeverityLevel: Severity = Severity.WARNING,
enableTracing: Boolean = true,
action: SandboxRuntimeContext.() -> Unit
) {
var thrownException: Throwable? = null
thread {
try {
parentConfiguration.analysisConfiguration.createChild(
newMinimumSeverityLevel = minimumSeverityLevel
).use { analysisConfiguration ->
SandboxRuntimeContext(SandboxConfiguration.of(
parentConfiguration.executionProfile,
parentConfiguration.rules,
parentConfiguration.emitters,
parentConfiguration.definitionProviders,
enableTracing,
analysisConfiguration,
parentClassLoader
)).use {
assertThat(runtimeCosts).areZero()
action(this)
}
}
} catch (exception: Throwable) {
thrownException = exception
}
}.join()
throw thrownException ?: return
}
/** /**
* Get a class reference from a class hierarchy based on [T]. * Get a class reference from a class hierarchy based on [T].
*/ */
@ -178,8 +253,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.loadForSandbox(className)
classLoader.loadForSandbox(className, context)
/** /**
* Run the entry-point of the loaded [Callable] class. * Run the entry-point of the loaded [Callable] class.
@ -203,4 +277,77 @@ abstract class TestBase {
} }
} }
@Suppress("MemberVisibilityCanBePrivate")
protected class DJVM(private val classLoader: ClassLoader) {
private val djvm: Class<*> = classFor("sandbox.java.lang.DJVM")
val objectClass: Class<*> by lazy { classFor("sandbox.java.lang.Object") }
val stringClass: Class<*> by lazy { classFor("sandbox.java.lang.String") }
val longClass: Class<*> by lazy { classFor("sandbox.java.lang.Long") }
val integerClass: Class<*> by lazy { classFor("sandbox.java.lang.Integer") }
val shortClass: Class<*> by lazy { classFor("sandbox.java.lang.Short") }
val byteClass: Class<*> by lazy { classFor("sandbox.java.lang.Byte") }
val characterClass: Class<*> by lazy { classFor("sandbox.java.lang.Character") }
val booleanClass: Class<*> by lazy { classFor("sandbox.java.lang.Boolean") }
val doubleClass: Class<*> by lazy { classFor("sandbox.java.lang.Double") }
val floatClass: Class<*> by lazy { classFor("sandbox.java.lang.Float") }
val throwableClass: Class<*> by lazy { classFor("sandbox.java.lang.Throwable") }
val stackTraceElementClass: Class<*> by lazy { classFor("sandbox.java.lang.StackTraceElement") }
fun classFor(className: String): Class<*> = Class.forName(className, false, classLoader)
fun sandbox(obj: Any): Any {
return djvm.getMethod("sandbox", Any::class.java).invoke(null, obj)
}
fun unsandbox(obj: Any): Any {
return djvm.getMethod("unsandbox", Any::class.java).invoke(null, obj)
}
fun stringOf(str: String): Any {
return stringClass.getMethod("toDJVM", String::class.java).invoke(null, str)
}
fun longOf(l: Long): Any {
return longClass.getMethod("toDJVM", Long::class.javaObjectType).invoke(null, l)
}
fun intOf(i: Int): Any {
return integerClass.getMethod("toDJVM", Int::class.javaObjectType).invoke(null, i)
}
fun shortOf(i: Int): Any {
return shortClass.getMethod("toDJVM", Short::class.javaObjectType).invoke(null, i.toShort())
}
fun byteOf(i: Int): Any {
return byteClass.getMethod("toDJVM", Byte::class.javaObjectType).invoke(null, i.toByte())
}
fun charOf(c: Char): Any {
return characterClass.getMethod("toDJVM", Char::class.javaObjectType).invoke(null, c)
}
fun booleanOf(bool: Boolean): Any {
return booleanClass.getMethod("toDJVM", Boolean::class.javaObjectType).invoke(null, bool)
}
fun doubleOf(d: Double): Any {
return doubleClass.getMethod("toDJVM", Double::class.javaObjectType).invoke(null, d)
}
fun floatOf(f: Float): Any {
return floatClass.getMethod("toDJVM", Float::class.javaObjectType).invoke(null, f)
}
fun objectArrayOf(vararg objs: Any): Array<in Any> {
@Suppress("unchecked_cast")
return (java.lang.reflect.Array.newInstance(objectClass, objs.size) as Array<in Any>).also {
for (i in 0 until objs.size) {
it[i] = objectClass.cast(objs[i])
}
}
}
}
fun Any.getArray(methodName: String): Array<*> = javaClass.getMethod(methodName).invoke(this) as Array<*>
} }

View File

@ -12,13 +12,3 @@ object Utilities {
fun throwThresholdViolationError(): Nothing = throw ThresholdViolationError("Can't catch this!") fun throwThresholdViolationError(): Nothing = throw ThresholdViolationError("Can't catch this!")
} }
fun String.toDJVM(): sandbox.java.lang.String = sandbox.java.lang.String.toDJVM(this)
fun Long.toDJVM(): sandbox.java.lang.Long = sandbox.java.lang.Long.toDJVM(this)
fun Int.toDJVM(): sandbox.java.lang.Integer = sandbox.java.lang.Integer.toDJVM(this)
fun Short.toDJVM(): sandbox.java.lang.Short = sandbox.java.lang.Short.toDJVM(this)
fun Byte.toDJVM(): sandbox.java.lang.Byte = sandbox.java.lang.Byte.toDJVM(this)
fun Float.toDJVM(): sandbox.java.lang.Float = sandbox.java.lang.Float.toDJVM(this)
fun Double.toDJVM(): sandbox.java.lang.Double = sandbox.java.lang.Double.toDJVM(this)
fun Char.toDJVM(): sandbox.java.lang.Character = sandbox.java.lang.Character.toDJVM(this)
fun Boolean.toDJVM(): sandbox.java.lang.Boolean = sandbox.java.lang.Boolean.toDJVM(this)

View File

@ -36,6 +36,8 @@ object AssertionExtensions {
fun assertThat(references: ReferenceMap) = fun assertThat(references: ReferenceMap) =
AssertiveReferenceMap(references) AssertiveReferenceMap(references)
fun assertThatDJVM(obj: Any) = AssertiveDJVMObject(obj)
inline fun <reified T> IterableAssert<ClassRepresentation>.hasClass(): IterableAssert<ClassRepresentation> = this inline fun <reified T> IterableAssert<ClassRepresentation>.hasClass(): IterableAssert<ClassRepresentation> = this
.`as`("HasClass(${T::class.java.name})") .`as`("HasClass(${T::class.java.name})")
.anySatisfy { .anySatisfy {

View File

@ -24,6 +24,11 @@ class AssertiveClassWithByteCode(private val loadedClass: LoadedClass) {
return this return this
} }
fun hasClassLoader(classLoader: ClassLoader): AssertiveClassWithByteCode {
assertThat(loadedClass.type.classLoader).isEqualTo(classLoader)
return this
}
fun hasClassName(className: String): AssertiveClassWithByteCode { fun hasClassName(className: String): AssertiveClassWithByteCode {
assertThat(loadedClass.type.name).isEqualTo(className) assertThat(loadedClass.type.name).isEqualTo(className)
return this return this

View File

@ -0,0 +1,26 @@
package net.corda.djvm.assertions
import org.assertj.core.api.Assertions.*
class AssertiveDJVMObject(private val djvmObj: Any) {
fun hasClassName(className: String): AssertiveDJVMObject {
assertThat(djvmObj.javaClass.name).isEqualTo(className)
return this
}
fun isAssignableFrom(clazz: Class<*>): AssertiveDJVMObject {
assertThat(djvmObj.javaClass.isAssignableFrom(clazz))
return this
}
fun hasGetterValue(methodName: String, value: Any): AssertiveDJVMObject {
assertThat(djvmObj.javaClass.getMethod(methodName).invoke(djvmObj)).isEqualTo(value)
return this
}
fun hasGetterNullValue(methodName: String): AssertiveDJVMObject {
assertThat(djvmObj.javaClass.getMethod(methodName).invoke(djvmObj)).isNull()
return this
}
}

View File

@ -8,7 +8,7 @@ import java.util.function.Function
class SandboxEnumTest : TestBase() { class SandboxEnumTest : TestBase() {
@Test @Test
fun `test enum inside sandbox`() = sandbox(DEFAULT) { fun `test enum inside sandbox`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Array<String>>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Array<String>>(configuration)
contractExecutor.run<TransformEnum>(0).apply { contractExecutor.run<TransformEnum>(0).apply {
assertThat(result).isEqualTo(arrayOf("ONE", "TWO", "THREE")) assertThat(result).isEqualTo(arrayOf("ONE", "TWO", "THREE"))
@ -16,7 +16,7 @@ class SandboxEnumTest : TestBase() {
} }
@Test @Test
fun `return enum from sandbox`() = sandbox(DEFAULT) { fun `return enum from sandbox`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, ExampleEnum>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, ExampleEnum>(configuration)
contractExecutor.run<FetchEnum>("THREE").apply { contractExecutor.run<FetchEnum>("THREE").apply {
assertThat(result).isEqualTo(ExampleEnum.THREE) assertThat(result).isEqualTo(ExampleEnum.THREE)
@ -24,7 +24,7 @@ class SandboxEnumTest : TestBase() {
} }
@Test @Test
fun `test we can identify class as Enum`() = sandbox(DEFAULT) { fun `test we can identify class as Enum`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration) val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration)
contractExecutor.run<AssertEnum>(ExampleEnum.THREE).apply { contractExecutor.run<AssertEnum>(ExampleEnum.THREE).apply {
assertThat(result).isTrue() assertThat(result).isTrue()
@ -32,7 +32,7 @@ class SandboxEnumTest : TestBase() {
} }
@Test @Test
fun `test we can create EnumMap`() = sandbox(DEFAULT) { fun `test we can create EnumMap`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Int>(configuration)
contractExecutor.run<UseEnumMap>(ExampleEnum.TWO).apply { contractExecutor.run<UseEnumMap>(ExampleEnum.TWO).apply {
assertThat(result).isEqualTo(1) assertThat(result).isEqualTo(1)
@ -40,7 +40,7 @@ class SandboxEnumTest : TestBase() {
} }
@Test @Test
fun `test we can create EnumSet`() = sandbox(DEFAULT) { fun `test we can create EnumSet`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration) val contractExecutor = DeterministicSandboxExecutor<ExampleEnum, Boolean>(configuration)
contractExecutor.run<UseEnumSet>(ExampleEnum.ONE).apply { contractExecutor.run<UseEnumSet>(ExampleEnum.ONE).apply {
assertThat(result).isTrue() assertThat(result).isTrue()

View File

@ -4,7 +4,7 @@ import foo.bar.sandbox.MyObject
import foo.bar.sandbox.testClock 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.Companion.MINIMAL
import net.corda.djvm.Utilities import net.corda.djvm.Utilities
import net.corda.djvm.Utilities.throwRuleViolationError import net.corda.djvm.Utilities.throwRuleViolationError
import net.corda.djvm.Utilities.throwThresholdViolationError import net.corda.djvm.Utilities.throwThresholdViolationError
@ -22,7 +22,7 @@ import java.util.stream.Collectors.*
class SandboxExecutorTest : TestBase() { class SandboxExecutorTest : TestBase() {
@Test @Test
fun `can load and execute runnable`() = sandbox(Whitelist.MINIMAL) { fun `can load and execute runnable`() = sandbox(MINIMAL) {
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
val summary = contractExecutor.run<TestSandboxedRunnable>(1) val summary = contractExecutor.run<TestSandboxedRunnable>(1)
val result = summary.result val result = summary.result
@ -36,7 +36,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute contract`() = sandbox( fun `can load and execute contract`() = sandbox(DEFAULT,
pinnedClasses = setOf(Transaction::class.java, Utilities::class.java) pinnedClasses = setOf(Transaction::class.java, Utilities::class.java)
) { ) {
val contractExecutor = DeterministicSandboxExecutor<Transaction, Unit>(configuration) val contractExecutor = DeterministicSandboxExecutor<Transaction, Unit>(configuration)
@ -56,7 +56,7 @@ class SandboxExecutorTest : TestBase() {
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`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
val summary = contractExecutor.run<TestObjectHashCode>(0) val summary = contractExecutor.run<TestObjectHashCode>(0)
val result = summary.result val result = summary.result
@ -74,7 +74,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute code that overrides object hash code when derived`() = sandbox(DEFAULT) { fun `can load and execute code that overrides object hash code when derived`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
val summary = contractExecutor.run<TestObjectHashCodeWithHierarchy>(0) val summary = contractExecutor.run<TestObjectHashCodeWithHierarchy>(0)
val result = summary.result val result = summary.result
@ -107,7 +107,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can detect stack overflow`() = sandbox(DEFAULT) { fun `can detect stack overflow`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestStackOverflow>(0) } .isThrownBy { contractExecutor.run<TestStackOverflow>(0) }
@ -141,7 +141,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot execute runnable that references non-deterministic code`() = sandbox(DEFAULT) { fun `cannot execute runnable that references non-deterministic code`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Long>(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) }
@ -156,7 +156,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot execute runnable that catches ThreadDeath`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot execute runnable that catches ThreadDeath`() = parentedSandbox {
TestCatchThreadDeath().apply { TestCatchThreadDeath().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -178,7 +178,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot execute runnable that catches ThresholdViolationError`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot execute runnable that catches ThresholdViolationError`() = parentedSandbox {
TestCatchThresholdViolationError().apply { TestCatchThresholdViolationError().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -201,7 +201,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot execute runnable that catches RuleViolationError`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot execute runnable that catches RuleViolationError`() = parentedSandbox {
TestCatchRuleViolationError().apply { TestCatchRuleViolationError().apply {
assertThat(apply(0)).isEqualTo(1) assertThat(apply(0)).isEqualTo(1)
} }
@ -224,7 +224,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can catch Throwable`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `can catch Throwable`() = parentedSandbox {
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)
@ -232,7 +232,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can catch Error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `can catch Error`() = parentedSandbox {
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)
@ -240,7 +240,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot catch ThreadDeath`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot catch ThreadDeath`() = parentedSandbox {
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) }
@ -295,7 +295,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot catch stack-overflow error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot catch stack-overflow error`() = parentedSandbox {
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) }
@ -304,7 +304,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot catch out-of-memory error`() = sandbox(DEFAULT, pinnedClasses = setOf(Utilities::class.java)) { fun `cannot catch out-of-memory error`() = parentedSandbox {
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) }
@ -313,7 +313,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `cannot persist state across sessions`() = sandbox(DEFAULT) { fun `cannot persist state across sessions`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
val result1 = contractExecutor.run<TestStatePersistence>(0) val result1 = contractExecutor.run<TestStatePersistence>(0)
val result2 = contractExecutor.run<TestStatePersistence>(0) val result2 = contractExecutor.run<TestStatePersistence>(0)
@ -335,7 +335,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute code that uses IO`() = sandbox(DEFAULT) { fun `can load and execute code that uses IO`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestIO>(0) } .isThrownBy { contractExecutor.run<TestIO>(0) }
@ -354,7 +354,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute code that uses reflection`() = sandbox(DEFAULT) { fun `can load and execute code that uses reflection`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Int>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestReflection>(0) } .isThrownBy { contractExecutor.run<TestReflection>(0) }
@ -373,7 +373,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()`() = parentedSandbox {
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) }
@ -383,7 +383,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()`() = parentedSandbox {
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) }
@ -393,7 +393,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()`() = parentedSandbox {
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) }
@ -403,7 +403,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)`() = parentedSandbox {
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) }
@ -413,7 +413,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)`() = parentedSandbox {
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) }
@ -423,7 +423,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `code after forbidden APIs is intact`() = sandbox(DEFAULT) { fun `code after forbidden APIs is intact`() = parentedSandbox {
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")
@ -462,7 +462,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute code that has a native method`() = sandbox(DEFAULT) { fun `can load and execute code that has a native method`() = parentedSandbox {
assertThatExceptionOfType(UnsatisfiedLinkError::class.java) assertThatExceptionOfType(UnsatisfiedLinkError::class.java)
.isThrownBy { TestNativeMethod().apply(0) } .isThrownBy { TestNativeMethod().apply(0) }
.withMessageContaining("TestNativeMethod.evilDeeds()I") .withMessageContaining("TestNativeMethod.evilDeeds()I")
@ -483,7 +483,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `check arrays still work`() = sandbox(DEFAULT) { fun `check arrays still work`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, Array<Int>>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, Array<Int>>(configuration)
contractExecutor.run<TestArray>(100).apply { contractExecutor.run<TestArray>(100).apply {
assertThat(result).isEqualTo(arrayOf(100)) assertThat(result).isEqualTo(arrayOf(100))
@ -497,7 +497,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `check building a string`() = sandbox(DEFAULT) { fun `check building a string`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String?, String?>(configuration) val contractExecutor = DeterministicSandboxExecutor<String?, String?>(configuration)
contractExecutor.run<TestStringBuilding>("Hello Sandbox!").apply { contractExecutor.run<TestStringBuilding>("Hello Sandbox!").apply {
assertThat(result) assertThat(result)
@ -522,7 +522,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `check System-arraycopy still works with Objects`() = sandbox(DEFAULT) { fun `check System-arraycopy still works with Objects`() = parentedSandbox {
val source = arrayOf("one", "two", "three") val source = arrayOf("one", "two", "three")
assertThat(TestArrayCopy().apply(source)) assertThat(TestArrayCopy().apply(source))
.isEqualTo(source) .isEqualTo(source)
@ -545,7 +545,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test System-arraycopy still works with CharArray`() = sandbox(DEFAULT) { fun `test System-arraycopy still works with CharArray`() = parentedSandbox {
val source = CharArray(10) { '?' } val source = CharArray(10) { '?' }
val contractExecutor = DeterministicSandboxExecutor<CharArray, CharArray>(configuration) val contractExecutor = DeterministicSandboxExecutor<CharArray, CharArray>(configuration)
contractExecutor.run<TestCharArrayCopy>(source).apply { contractExecutor.run<TestCharArrayCopy>(source).apply {
@ -564,7 +564,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can load and execute class that has finalize`() = sandbox(DEFAULT) { fun `can load and execute class that has finalize`() = parentedSandbox {
assertThatExceptionOfType(UnsupportedOperationException::class.java) assertThatExceptionOfType(UnsupportedOperationException::class.java)
.isThrownBy { TestFinalizeMethod().apply(100) } .isThrownBy { TestFinalizeMethod().apply(100) }
.withMessageContaining("Very Bad Thing") .withMessageContaining("Very Bad Thing")
@ -587,7 +587,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `can execute parallel stream`() = sandbox(DEFAULT) { fun `can execute parallel stream`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, String>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, String>(configuration)
contractExecutor.run<TestParallelStream>("Pebble").apply { contractExecutor.run<TestParallelStream>("Pebble").apply {
assertThat(result).isEqualTo("Five,Four,One,Pebble,Three,Two") assertThat(result).isEqualTo("Five,Four,One,Pebble,Three,Two")
@ -605,7 +605,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `users cannot load our sandboxed classes`() = sandbox(DEFAULT) { fun `users cannot load our sandboxed classes`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<TestClassForName>("java.lang.DJVM") } .isThrownBy { contractExecutor.run<TestClassForName>("java.lang.DJVM") }
@ -614,7 +614,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `users can load sandboxed classes`() = sandbox(DEFAULT) { fun `users can load sandboxed classes`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
contractExecutor.run<TestClassForName>("java.util.List").apply { contractExecutor.run<TestClassForName>("java.util.List").apply {
assertThat(result?.name).isEqualTo("sandbox.java.util.List") assertThat(result?.name).isEqualTo("sandbox.java.util.List")
@ -628,7 +628,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test case-insensitive string sorting`() = sandbox(DEFAULT) { fun `test case-insensitive string sorting`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Array<String>, Array<String>>(configuration) val contractExecutor = DeterministicSandboxExecutor<Array<String>, Array<String>>(configuration)
contractExecutor.run<CaseInsensitiveSort>(arrayOf("Zelda", "angela", "BOB", "betsy", "ALBERT")).apply { contractExecutor.run<CaseInsensitiveSort>(arrayOf("Zelda", "angela", "BOB", "betsy", "ALBERT")).apply {
assertThat(result).isEqualTo(arrayOf("ALBERT", "angela", "betsy", "BOB", "Zelda")) assertThat(result).isEqualTo(arrayOf("ALBERT", "angela", "betsy", "BOB", "Zelda"))
@ -642,7 +642,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test unicode characters`() = sandbox(DEFAULT) { fun `test unicode characters`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
contractExecutor.run<ExamineUnicodeBlock>(0x01f600).apply { contractExecutor.run<ExamineUnicodeBlock>(0x01f600).apply {
assertThat(result).isEqualTo("EMOTICONS") assertThat(result).isEqualTo("EMOTICONS")
@ -656,7 +656,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test unicode scripts`() = sandbox(DEFAULT) { fun `test unicode scripts`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Character.UnicodeScript?>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Character.UnicodeScript?>(configuration)
contractExecutor.run<ExamineUnicodeScript>("COMMON").apply { contractExecutor.run<ExamineUnicodeScript>("COMMON").apply {
assertThat(result).isEqualTo(Character.UnicodeScript.COMMON) assertThat(result).isEqualTo(Character.UnicodeScript.COMMON)
@ -671,7 +671,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test users cannot define new classes`() = sandbox(DEFAULT) { fun `test users cannot define new classes`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<DefineNewClass>("sandbox.java.lang.DJVM") } .isThrownBy { contractExecutor.run<DefineNewClass>("sandbox.java.lang.DJVM") }
@ -693,7 +693,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test users cannot load new classes`() = sandbox(DEFAULT) { fun `test users cannot load new classes`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<LoadNewClass>("sandbox.java.lang.DJVM") } .isThrownBy { contractExecutor.run<LoadNewClass>("sandbox.java.lang.DJVM") }
@ -714,7 +714,7 @@ class SandboxExecutorTest : TestBase() {
} }
@Test @Test
fun `test users cannot lookup classes`() = sandbox(DEFAULT) { fun `test users cannot lookup classes`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Class<*>>(configuration)
assertThatExceptionOfType(SandboxException::class.java) assertThatExceptionOfType(SandboxException::class.java)
.isThrownBy { contractExecutor.run<FindClass>("sandbox.java.lang.DJVM") } .isThrownBy { contractExecutor.run<FindClass>("sandbox.java.lang.DJVM") }

View File

@ -8,7 +8,7 @@ import java.util.function.Function
class SandboxThrowableTest : TestBase() { class SandboxThrowableTest : TestBase() {
@Test @Test
fun `test user exception handling`() = sandbox(DEFAULT) { fun `test user exception handling`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Array<String>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Array<String>>(configuration)
contractExecutor.run<ThrowAndCatchExample>("Hello World").apply { contractExecutor.run<ThrowAndCatchExample>("Hello World").apply {
assertThat(result) assertThat(result)
@ -17,7 +17,7 @@ class SandboxThrowableTest : TestBase() {
} }
@Test @Test
fun `test rethrowing an exception`() = sandbox(DEFAULT) { fun `test rethrowing an exception`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<String, Array<String>>(configuration) val contractExecutor = DeterministicSandboxExecutor<String, Array<String>>(configuration)
contractExecutor.run<ThrowAndRethrowExample>("Hello World").apply { contractExecutor.run<ThrowAndRethrowExample>("Hello World").apply {
assertThat(result) assertThat(result)
@ -26,7 +26,7 @@ class SandboxThrowableTest : TestBase() {
} }
@Test @Test
fun `test JVM exceptions still propagate`() = sandbox(DEFAULT) { fun `test JVM exceptions still propagate`() = parentedSandbox {
val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration) val contractExecutor = DeterministicSandboxExecutor<Int, String>(configuration)
contractExecutor.run<TriggerJVMException>(-1).apply { contractExecutor.run<TriggerJVMException>(-1).apply {
assertThat(result) assertThat(result)

View File

@ -15,7 +15,7 @@ class ClassRewriterTest : TestBase() {
@Test @Test
fun `empty transformer does nothing`() = sandbox(BLANK) { fun `empty transformer does nothing`() = sandbox(BLANK) {
val callable = newCallable<Empty>() val callable = newCallable<Empty>()
assertThat(callable).hasNotBeenModified() assertThat(callable).isSandboxed()
callable.createAndInvoke() callable.createAndInvoke()
assertThat(runtimeCosts).areZero() assertThat(runtimeCosts).areZero()
} }
@ -130,6 +130,32 @@ class ClassRewriterTest : TestBase() {
.hasInterface("sandbox.java.lang.CharSequence") .hasInterface("sandbox.java.lang.CharSequence")
.hasBeenModified() .hasBeenModified()
} }
@Test
fun `test Java class is owned by parent classloader`() = parentedSandbox {
val stringBuilderClass = loadClass<StringBuilder>().type
assertThat(stringBuilderClass.classLoader).isEqualTo(parentClassLoader)
}
@Test
fun `test user class is owned by new classloader`() = parentedSandbox {
assertThat(loadClass<Empty>())
.hasClassLoader(classLoader)
.hasBeenModified()
}
@Test
fun `test template class is owned by parent classloader`() = parentedSandbox {
assertThat(classLoader.loadForSandbox("sandbox.java.lang.DJVM"))
.hasClassLoader(parentClassLoader)
.hasNotBeenModified()
}
@Test
fun `test pinned class is owned by application classloader`() = parentedSandbox {
val violationClass = loadClass<ThresholdViolationError>().type
assertThat(violationClass).isEqualTo(ThresholdViolationError::class.java)
}
} }
@Suppress("unused") @Suppress("unused")

View File

@ -62,9 +62,6 @@ The node can optionally be started with the following command-line options:
Sub-commands Sub-commands
^^^^^^^^^^^^ ^^^^^^^^^^^^
``bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer
addresses), acting as a seed for other nodes to join the cluster.
``clear-network-cache``: Clears local copy of network map, on node startup it will be restored from server or file system. ``clear-network-cache``: Clears local copy of network map, on node startup it will be restored from server or file system.
``initial-registration``: Starts initial node registration with the compatibility zone to obtain a certificate from the Doorman. ``initial-registration``: Starts initial node registration with the compatibility zone to obtain a certificate from the Doorman.