mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
CORDA-2961: Migrate the DJVM into its own repository. (#5166)
* Migrate the DJVM into its own repository. * Update the documentation for DJVM.
This commit is contained in:
parent
5edd732615
commit
aa75157273
13
djvm/.gitignore
vendored
13
djvm/.gitignore
vendored
@ -1,13 +0,0 @@
|
||||
# DJVM-specific files
|
||||
**/tmp/
|
||||
*.log
|
||||
*.log.gz
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
**/out/
|
||||
|
@ -1,122 +0,0 @@
|
||||
buildscript {
|
||||
ext {
|
||||
corda_djvm_version = '5.0-SNAPSHOT'
|
||||
artifactory_contextUrl = 'https://software.r3.com/artifactory'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'net.corda.plugins.publish-utils' version '4.0.44' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '5.0.0' apply false
|
||||
id 'com.jfrog.artifactory' version '4.7.3' apply false
|
||||
id 'com.jfrog.bintray' version '1.4' apply false
|
||||
id 'com.gradle.build-scan' version '2.2.1'
|
||||
}
|
||||
|
||||
import static org.gradle.api.JavaVersion.*
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
subprojects {
|
||||
group 'net.corda'
|
||||
version corda_djvm_version
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = VERSION_1_8
|
||||
targetCompatibility = VERSION_1_8
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile) {
|
||||
kotlinOptions {
|
||||
languageVersion = '1.2'
|
||||
apiVersion = '1.2'
|
||||
jvmTarget = VERSION_1_8
|
||||
javaParameters = true // Useful for reflection.
|
||||
freeCompilerArgs = ['-Xjvm-default=enable']
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Jar) { task ->
|
||||
manifest {
|
||||
attributes('Corda-Vendor': 'Corda Open Source')
|
||||
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
useJUnitPlatform()
|
||||
|
||||
// Prevent the project from creating temporary files outside of the build directory.
|
||||
systemProperty 'java.io.tmpdir', buildDir.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
bintrayConfig {
|
||||
user = System.getenv('CORDA_BINTRAY_USER')
|
||||
key = System.getenv('CORDA_BINTRAY_KEY')
|
||||
repo = 'corda'
|
||||
org = 'r3'
|
||||
licenses = ['Apache-2.0']
|
||||
vcsUrl = 'https://github.com/corda/corda'
|
||||
projectUrl = 'https://github.com/corda/corda'
|
||||
gpgSign = true
|
||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||
publications = [
|
||||
'corda-djvm',
|
||||
'corda-djvm-cli'
|
||||
]
|
||||
license {
|
||||
name = 'Apache-2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
distribution = 'repo'
|
||||
}
|
||||
developer {
|
||||
id = 'R3'
|
||||
name = 'R3'
|
||||
email = 'dev@corda.net'
|
||||
}
|
||||
}
|
||||
|
||||
artifactory {
|
||||
publish {
|
||||
contextUrl = artifactory_contextUrl
|
||||
repository {
|
||||
repoKey = 'corda-dev'
|
||||
username = System.getenv('CORDA_ARTIFACTORY_USERNAME')
|
||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||
}
|
||||
|
||||
defaults {
|
||||
// The root project has applied 'publish-utils' but has nothing to publish.
|
||||
if (project != rootProject) {
|
||||
publications(project.extensions.publish.name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "5.4.1"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
||||
buildScan {
|
||||
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
|
||||
termsOfServiceAgree = 'yes'
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'com.github.johnrengelman.shadow'
|
||||
id 'net.corda.plugins.publish-utils'
|
||||
id 'com.jfrog.artifactory'
|
||||
id 'idea'
|
||||
}
|
||||
|
||||
description 'Corda deterministic JVM sandbox'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "$artifactory_contextUrl/corda-dev"
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
testImplementation.extendsFrom shadow
|
||||
jdkRt.resolutionStrategy {
|
||||
// Always check the repository for a newer SNAPSHOT.
|
||||
cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
shadow "org.slf4j:slf4j-api:$slf4j_version"
|
||||
|
||||
// ASM: byte code manipulation library
|
||||
implementation "org.ow2.asm:asm:$asm_version"
|
||||
implementation "org.ow2.asm:asm-commons:$asm_version"
|
||||
|
||||
// ClassGraph: classpath scanning
|
||||
shadow "io.github.classgraph:classgraph:$class_graph_version"
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version"
|
||||
testRuntimeOnly "org.junit.platform:junit-platform-launcher:$junit_platform_version"
|
||||
|
||||
// Test utilities
|
||||
testImplementation "org.assertj:assertj-core:$assertj_version"
|
||||
testImplementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
jdkRt "net.corda:deterministic-rt:latest.integration"
|
||||
}
|
||||
|
||||
jar.enabled = false
|
||||
|
||||
shadowJar {
|
||||
baseName 'corda-djvm'
|
||||
classifier ''
|
||||
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
|
||||
|
||||
// These particular classes are only needed to "bootstrap"
|
||||
// the compilation of the other sandbox classes. At runtime,
|
||||
// we will generate better versions from deterministic-rt.jar.
|
||||
exclude 'sandbox/java/lang/Appendable.class'
|
||||
exclude 'sandbox/java/lang/CharSequence.class'
|
||||
exclude 'sandbox/java/lang/Character\$Subset.class'
|
||||
exclude 'sandbox/java/lang/Character\$Unicode*.class'
|
||||
exclude 'sandbox/java/lang/Comparable.class'
|
||||
exclude 'sandbox/java/lang/Enum.class'
|
||||
exclude 'sandbox/java/lang/Iterable.class'
|
||||
exclude 'sandbox/java/lang/StackTraceElement.class'
|
||||
exclude 'sandbox/java/lang/StringBuffer.class'
|
||||
exclude 'sandbox/java/lang/StringBuilder.class'
|
||||
exclude 'sandbox/java/nio/**'
|
||||
exclude 'sandbox/java/util/**'
|
||||
}
|
||||
assemble.dependsOn shadowJar
|
||||
|
||||
tasks.withType(Test) {
|
||||
systemProperty 'deterministic-rt.path', configurations.jdkRt.asPath
|
||||
}
|
||||
|
||||
artifacts {
|
||||
publish shadowJar
|
||||
}
|
||||
|
||||
publish {
|
||||
dependenciesFrom configurations.shadow
|
||||
name shadowJar.baseName
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'com.github.johnrengelman.shadow'
|
||||
id 'net.corda.plugins.publish-utils'
|
||||
id 'com.jfrog.artifactory'
|
||||
}
|
||||
|
||||
description 'Corda deterministic JVM sandbox command-line tool'
|
||||
|
||||
ext {
|
||||
djvmName = 'corda-djvm-cli'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
implementation "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
implementation "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||
|
||||
implementation "info.picocli:picocli:$picocli_version"
|
||||
implementation project(path: ':djvm', configuration: 'shadow')
|
||||
}
|
||||
|
||||
jar.enabled = false
|
||||
|
||||
shadowJar {
|
||||
baseName djvmName
|
||||
classifier ''
|
||||
manifest {
|
||||
attributes(
|
||||
'Automatic-Module-Name': 'net.corda.djvm.cli',
|
||||
'Main-Class': 'net.corda.djvm.tools.cli.Program',
|
||||
'Build-Date': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Class-Path': 'tmp/'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
task shadowZip(type: Zip) {
|
||||
archiveBaseName = djvmName
|
||||
archiveClassifier = ''
|
||||
|
||||
from(shadowJar) {
|
||||
rename "$djvmName-(.*).jar", "${djvmName}.jar"
|
||||
}
|
||||
from('src/shell/') {
|
||||
fileMode = 0755
|
||||
}
|
||||
zip64 true
|
||||
}
|
||||
|
||||
assemble.dependsOn shadowZip
|
||||
|
||||
artifacts {
|
||||
publish shadowZip
|
||||
}
|
||||
|
||||
publish {
|
||||
dependenciesFrom configurations.shadow
|
||||
publishSources = false
|
||||
publishJavadoc = false
|
||||
name shadowZip.baseName
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
import java.nio.file.Path
|
||||
|
||||
@Command(
|
||||
name = "build",
|
||||
description = ["Build one or more Java source files, each implementing the sandbox runnable interface " +
|
||||
"required for execution in the deterministic sandbox."]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class BuildCommand : CommandBase() {
|
||||
|
||||
@Parameters
|
||||
var files: Array<Path> = emptyArray()
|
||||
|
||||
override fun validateArguments() = files.isNotEmpty()
|
||||
|
||||
override fun handleCommand(): Boolean {
|
||||
val codePath = createCodePath()
|
||||
val files = files.getFileNames { codePath.resolve(it) }
|
||||
printVerbose("Compiling ${files.joinToString(", ")}...")
|
||||
ProcessBuilder("javac", "-cp", "tmp:$jarPath", *files).apply {
|
||||
inheritIO()
|
||||
environment().putAll(System.getenv())
|
||||
start().apply {
|
||||
waitFor()
|
||||
return (exitValue() == 0).apply {
|
||||
if (this) {
|
||||
printInfo("Build succeeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
|
||||
@Command(
|
||||
name = "check",
|
||||
description = ["Statically validate that a class or set of classes (and their dependencies) do not violate any " +
|
||||
"constraints posed by the deterministic sandbox environment."]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class CheckCommand : ClassCommand() {
|
||||
|
||||
override val filters: Array<String>
|
||||
get() = classes
|
||||
|
||||
@Parameters(description = ["The partial or fully qualified names of the Java classes to analyse and validate."])
|
||||
var classes: Array<String> = emptyArray()
|
||||
|
||||
override fun printSuccess(classes: List<Class<*>>) {
|
||||
for (clazz in classes.sortedBy { it.name }) {
|
||||
printVerbose("Class ${clazz.name} validated")
|
||||
}
|
||||
printVerbose()
|
||||
}
|
||||
|
||||
override fun processClasses(classes: List<Class<*>>) {
|
||||
val sources = classes.map { ClassSource.fromClassName(it.name) }
|
||||
val summary = executor.validate(*sources.toTypedArray())
|
||||
printMessages(summary.messages, summary.classOrigins)
|
||||
}
|
||||
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.Whitelist
|
||||
import net.corda.djvm.execution.*
|
||||
import net.corda.djvm.references.ClassModule
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import net.corda.djvm.source.SourceClassLoader
|
||||
import net.corda.djvm.utilities.Discovery
|
||||
import djvm.org.objectweb.asm.ClassReader
|
||||
import picocli.CommandLine.Option
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
@Suppress("KDocMissingDocumentation", "MemberVisibilityCanBePrivate")
|
||||
abstract class ClassCommand : CommandBase() {
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--profile"],
|
||||
description = ["The execution profile to use (DEFAULT, UNLIMITED, DISABLE_BRANCHING or DISABLE_THROWS)."]
|
||||
)
|
||||
var profile: ExecutionProfile = ExecutionProfile.DEFAULT
|
||||
|
||||
@Option(names = ["--ignore-rules"], description = ["Disable all rules pertaining to the sandbox."])
|
||||
var ignoreRules: Boolean = false
|
||||
|
||||
@Option(names = ["--ignore-emitters"], description = ["Disable all emitters defined for the sandbox."])
|
||||
var ignoreEmitters: Boolean = false
|
||||
|
||||
@Option(names = ["--ignore-definition-providers"], description = ["Disable all definition providers."])
|
||||
var ignoreDefinitionProviders: Boolean = false
|
||||
|
||||
@Option(names = ["-c", "--classpath"], description = ["Additions to the default class path."], split = ":")
|
||||
var classPath: Array<Path> = emptyArray()
|
||||
|
||||
@Option(names = ["--disable-tracing"], description = ["Disable tracing in the sandbox."])
|
||||
var disableTracing: Boolean = false
|
||||
|
||||
@Option(names = ["--analyze-annotations"], description = ["Analyze all annotations even if they are not " +
|
||||
"explicitly referenced."])
|
||||
var analyzeAnnotations: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--prefix-filters"],
|
||||
description = ["Only record messages matching one of the provided prefixes."],
|
||||
split = ":"
|
||||
)
|
||||
var prefixFilters: Array<String> = emptyArray()
|
||||
|
||||
abstract val filters: Array<String>
|
||||
|
||||
private val classModule = ClassModule()
|
||||
|
||||
private lateinit var classLoader: ClassLoader
|
||||
|
||||
protected var executor = SandboxExecutor<Any, Any>(SandboxConfiguration.DEFAULT)
|
||||
|
||||
abstract fun processClasses(classes: List<Class<*>>)
|
||||
|
||||
open fun printSuccess(classes: List<Class<*>>) {}
|
||||
|
||||
override fun validateArguments() = filters.isNotEmpty()
|
||||
|
||||
override fun handleCommand(): Boolean {
|
||||
val configuration = getConfiguration(Whitelist.MINIMAL)
|
||||
classLoader = SourceClassLoader(getClasspath(), configuration.analysisConfiguration.classResolver)
|
||||
createExecutor(configuration)
|
||||
|
||||
val classes = discoverClasses(filters).onEmpty {
|
||||
throw Exception("Could not find any classes matching ${filters.joinToString(" ")} on the " +
|
||||
"system class path")
|
||||
}
|
||||
|
||||
return try {
|
||||
processClasses(classes)
|
||||
printSuccess(classes)
|
||||
true
|
||||
} catch (exception: Throwable) {
|
||||
printException(exception)
|
||||
if (exception is SandboxException) {
|
||||
printCosts(exception.executionSummary.costs)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
protected fun printCosts(costs: CostSummary) {
|
||||
if (disableTracing) {
|
||||
return
|
||||
}
|
||||
printInfo("Runtime Cost Summary:")
|
||||
printInfo(" - allocations = @|yellow ${costs.allocations}|@")
|
||||
printInfo(" - invocations = @|yellow ${costs.invocations}|@")
|
||||
printInfo(" - jumps = @|yellow ${costs.jumps}|@")
|
||||
printInfo(" - throws = @|yellow ${costs.throws}|@")
|
||||
printInfo()
|
||||
}
|
||||
|
||||
private fun discoverClasses(filters: Array<String>): List<Class<*>> {
|
||||
return findDiscoverableRunnables(filters) + findReferencedClasses(filters) + findClassesInJars(filters)
|
||||
}
|
||||
|
||||
private fun findDiscoverableRunnables(filters: Array<String>): List<Class<*>> {
|
||||
val classes = find<java.util.function.Function<*,*>>()
|
||||
val applicableFilters = filters
|
||||
.filter { !isJarFile(it) && !isFullClassName(it) }
|
||||
val filteredClasses = applicableFilters
|
||||
.flatMap { filter ->
|
||||
classes.filter { clazz ->
|
||||
clazz.name.contains(filter, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (applicableFilters.isNotEmpty() && filteredClasses.isEmpty()) {
|
||||
throw Exception("Could not find any classes implementing ${java.util.function.Function::class.java.simpleName} " +
|
||||
"whose name matches '${applicableFilters.joinToString(" ")}'")
|
||||
}
|
||||
|
||||
if (applicableFilters.isNotEmpty()) {
|
||||
printVerbose("Class path: $userClassPath")
|
||||
printVerbose("Discovered runnables on the class path:")
|
||||
for (clazz in classes) {
|
||||
printVerbose(" - ${clazz.name}")
|
||||
}
|
||||
printVerbose()
|
||||
}
|
||||
return filteredClasses
|
||||
}
|
||||
|
||||
private fun findReferencedClasses(filters: Array<String>): List<Class<*>> {
|
||||
return filters.filter { !isJarFile(it) && isFullClassName(it) }.map {
|
||||
val className = classModule.getFormattedClassName(it)
|
||||
printVerbose("Looking up class $className...")
|
||||
lookUpClass(className)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findClassesInJars(filters: Array<String>): List<Class<*>> {
|
||||
return filters.filter { isJarFile(it) }.flatMap { jarFile ->
|
||||
mutableListOf<Class<*>>().apply {
|
||||
ClassSource.fromPath(Paths.get(jarFile)).getStreamIterator().forEach {
|
||||
val reader = ClassReader(it)
|
||||
val className = classModule.getFormattedClassName(reader.className)
|
||||
printVerbose("Looking up class $className in $jarFile...")
|
||||
this.add(lookUpClass(className))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun lookUpClass(className: String): Class<*> {
|
||||
return try {
|
||||
classLoader.loadClass(className)
|
||||
} catch (exception: NoClassDefFoundError) {
|
||||
val reference = exception.message?.let {
|
||||
"referenced class ${classModule.getFormattedClassName(it)} in "
|
||||
} ?: ""
|
||||
throw Exception("Unable to load ${reference}type $className (is it present on the class path?)")
|
||||
} catch (exception: TypeNotPresentException) {
|
||||
val reference = exception.typeName() ?: ""
|
||||
throw Exception("Type $reference not present in class $className")
|
||||
} catch (exception: Throwable) {
|
||||
throw Exception("Unable to load type $className (is it present on the class path?)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isJarFile(filter: String) = Files.exists(Paths.get(filter)) && filter.endsWith(".jar", true)
|
||||
|
||||
private fun isFullClassName(filter: String) = filter.count { it == '.' } > 0
|
||||
|
||||
private fun getClasspath() =
|
||||
classPath.toList() + filters.filter { it.endsWith(".jar", true) }.map { Paths.get(it) }
|
||||
|
||||
private fun getConfiguration(whitelist: Whitelist): SandboxConfiguration {
|
||||
return SandboxConfiguration.of(
|
||||
profile = profile,
|
||||
rules = if (ignoreRules) { emptyList() } else { Discovery.find() },
|
||||
emitters = ignoreEmitters.emptyListIfTrueOtherwiseNull(),
|
||||
definitionProviders = if (ignoreDefinitionProviders) { emptyList() } else { Discovery.find() },
|
||||
enableTracing = !disableTracing,
|
||||
analysisConfiguration = AnalysisConfiguration.createRoot(
|
||||
whitelist = whitelist,
|
||||
minimumSeverityLevel = level,
|
||||
analyzeAnnotations = analyzeAnnotations,
|
||||
prefixFilters = prefixFilters.toList(),
|
||||
sourceClassLoaderFactory = { classResolver, bootstrapClassLoader ->
|
||||
SourceClassLoader(getClasspath(), classResolver, bootstrapClassLoader)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createExecutor(configuration: SandboxConfiguration) {
|
||||
executor = SandboxExecutor(configuration)
|
||||
}
|
||||
|
||||
private fun <T> Boolean.emptyListIfTrueOtherwiseNull(): List<T>? = when (this) {
|
||||
true -> emptyList()
|
||||
false -> null
|
||||
}
|
||||
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.analysis.Whitelist
|
||||
import net.corda.djvm.execution.SandboxException
|
||||
import net.corda.djvm.messages.MessageCollection
|
||||
import net.corda.djvm.messages.Severity
|
||||
import net.corda.djvm.references.ClassReference
|
||||
import net.corda.djvm.references.EntityReference
|
||||
import net.corda.djvm.references.MemberReference
|
||||
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.core.config.Configurator
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Help.Ansi
|
||||
import picocli.CommandLine.Option
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
abstract class CommandBase : Callable<Boolean> {
|
||||
|
||||
@Option(
|
||||
names = ["-l", "--level"],
|
||||
description = ["The minimum severity level to log (TRACE, DEBUG, INFO, WARNING or ERROR."],
|
||||
converter = [SeverityConverter::class]
|
||||
)
|
||||
protected var level: Severity = Severity.WARNING
|
||||
|
||||
@Option(
|
||||
names = ["-q", "--quiet"],
|
||||
description = ["Only print important messages to standard output."]
|
||||
)
|
||||
private var quiet: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-v", "--verbose"],
|
||||
description = ["Enable verbose logging."]
|
||||
)
|
||||
private var verbose: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--debug"],
|
||||
description = ["Print full stack traces upon error."]
|
||||
)
|
||||
private var debug: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--colors"],
|
||||
description = ["Use colors when printing to terminal."]
|
||||
)
|
||||
private var useColors: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--no-colors"],
|
||||
description = ["Do not use colors when printing to terminal."]
|
||||
)
|
||||
private var useNoColors: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--compact"],
|
||||
description = ["Print compact errors and warnings."]
|
||||
)
|
||||
private var compact: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--print-origins"],
|
||||
description = ["Print origins for errors and warnings."]
|
||||
)
|
||||
private var printOrigins: Boolean = false
|
||||
|
||||
private val ansi: Ansi
|
||||
get() = when {
|
||||
useNoColors -> Ansi.OFF
|
||||
useColors -> Ansi.ON
|
||||
else -> Ansi.AUTO
|
||||
}
|
||||
|
||||
|
||||
class SeverityConverter : CommandLine.ITypeConverter<Severity> {
|
||||
override fun convert(value: String): Severity {
|
||||
return try {
|
||||
when (value.toUpperCase()) {
|
||||
"INFO" -> Severity.INFORMATIONAL
|
||||
else -> Severity.valueOf(value.toUpperCase())
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
val candidates = Severity.values().filter { it.name.startsWith(value, true) }
|
||||
if (candidates.size == 1) {
|
||||
candidates.first()
|
||||
} else {
|
||||
println("ERROR: Must be one of ${Severity.values().joinToString(", ") { it.name }}")
|
||||
Severity.INFORMATIONAL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun call(): Boolean {
|
||||
if (!validateArguments()) {
|
||||
CommandLine.usage(this, System.err)
|
||||
return false
|
||||
}
|
||||
if (verbose && quiet) {
|
||||
printError("Error: Cannot set verbose and quiet modes at the same time")
|
||||
return false
|
||||
}
|
||||
configureLogging()
|
||||
return try {
|
||||
handleCommand()
|
||||
} catch (exception: Throwable) {
|
||||
printException(exception)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureLogging() {
|
||||
val logLevel = when(level) {
|
||||
Severity.ERROR -> Level.ERROR
|
||||
Severity.WARNING -> Level.WARN
|
||||
Severity.INFORMATIONAL -> Level.INFO
|
||||
Severity.DEBUG -> Level.DEBUG
|
||||
Severity.TRACE -> Level.TRACE
|
||||
}
|
||||
Configurator.setRootLevel(logLevel)
|
||||
}
|
||||
|
||||
protected fun printException(exception: Throwable) = when (exception) {
|
||||
is SandboxClassLoadingException -> {
|
||||
printMessages(exception.messages, exception.classOrigins)
|
||||
printError()
|
||||
}
|
||||
is SandboxException -> {
|
||||
val cause = exception.cause
|
||||
when (cause) {
|
||||
is SandboxClassLoadingException -> {
|
||||
printMessages(cause.messages, cause.classOrigins)
|
||||
printError()
|
||||
}
|
||||
else -> {
|
||||
if (debug) {
|
||||
exception.exception.printStackTrace(System.err)
|
||||
} else {
|
||||
printError("Error: ${errorMessage(exception.exception)}")
|
||||
}
|
||||
printError()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (debug) {
|
||||
exception.printStackTrace(System.err)
|
||||
} else {
|
||||
printError("Error: ${errorMessage(exception)}")
|
||||
printError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun errorMessage(exception: Throwable): String {
|
||||
return when (exception) {
|
||||
is StackOverflowError -> "Stack overflow"
|
||||
is OutOfMemoryError -> "Out of memory"
|
||||
is ThreadDeath -> "Thread death"
|
||||
else -> {
|
||||
val message = exception.message
|
||||
when {
|
||||
message.isNullOrBlank() -> exception.javaClass.simpleName
|
||||
else -> message!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun printMessages(messages: MessageCollection, origins: Map<String, Set<EntityReference>> = emptyMap()) {
|
||||
val sortedMessages = messages.sorted()
|
||||
val errorCount = messages.errorCount.countOf("error")
|
||||
val warningCount = messages.warningCount.countOf("warning")
|
||||
printInfo("Found $errorCount and $warningCount")
|
||||
if (!compact) {
|
||||
printInfo()
|
||||
}
|
||||
|
||||
var first = true
|
||||
for (message in sortedMessages) {
|
||||
val severityColor = message.severity.color ?: "blue"
|
||||
val location = message.location.format().let {
|
||||
when {
|
||||
it.isNotBlank() -> "in $it: "
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
if (compact) {
|
||||
printError(" - @|$severityColor ${message.severity}|@ $location${message.message}.")
|
||||
} else {
|
||||
if (!first) {
|
||||
printError()
|
||||
}
|
||||
printError(" - @|$severityColor ${message.severity}|@ $location\n ${message.message}.")
|
||||
}
|
||||
if (printOrigins) {
|
||||
val classOrigins = origins[message.location.className.replace("/", ".")] ?: emptySet()
|
||||
for (classOrigin in classOrigins.groupBy({ it.className }, { it })) {
|
||||
val count = classOrigin.value.count()
|
||||
val reference = when (count) {
|
||||
1 -> classOrigin.value.first()
|
||||
else -> ClassReference(classOrigin.value.first().className)
|
||||
}
|
||||
when (reference) {
|
||||
is ClassReference ->
|
||||
printError(" - Reference from ${reference.className}")
|
||||
is MemberReference ->
|
||||
printError(" - Reference from ${reference.className}.${reference.memberName}()")
|
||||
}
|
||||
}
|
||||
printError()
|
||||
}
|
||||
first = false
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun handleCommand(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun validateArguments(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
protected fun printInfo(message: String = "") {
|
||||
if (!quiet) {
|
||||
println(ansi.Text(message).toString())
|
||||
}
|
||||
}
|
||||
|
||||
protected fun printVerbose(message: String = "") {
|
||||
if (verbose) {
|
||||
println(ansi.Text(message).toString())
|
||||
}
|
||||
}
|
||||
|
||||
protected fun printError(message: String = "") {
|
||||
System.err.println(ansi.Text(message).toString())
|
||||
}
|
||||
|
||||
protected fun printResult(result: Any?) {
|
||||
printInfo("Execution successful")
|
||||
printInfo(" - result = $result")
|
||||
printInfo()
|
||||
}
|
||||
|
||||
protected fun whitelistFromPath(whitelist: Path?): Whitelist {
|
||||
return whitelist?.let {
|
||||
if ("$it" == "NONE") {
|
||||
Whitelist.EMPTY
|
||||
} else if ("$it" == "ALL") {
|
||||
Whitelist.EVERYTHING
|
||||
} else if ("$it" == "LANG") {
|
||||
Whitelist.MINIMAL
|
||||
} else {
|
||||
try {
|
||||
Whitelist.fromFile(file = it)
|
||||
} catch (exception: Throwable) {
|
||||
throw Exception("Failed to load whitelist '$it'", exception)
|
||||
}
|
||||
}
|
||||
} ?: Whitelist.MINIMAL
|
||||
}
|
||||
|
||||
private fun Int.countOf(suffix: String): String {
|
||||
return this.let {
|
||||
when (it) {
|
||||
0 -> "no ${suffix}s"
|
||||
1 -> "@|yellow 1|@ $suffix"
|
||||
else -> "@|yellow $it|@ ${suffix}s"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
|
||||
@Command(
|
||||
name = "djvm",
|
||||
versionProvider = VersionProvider::class,
|
||||
description = ["JVM for running programs in a deterministic sandbox."],
|
||||
mixinStandardHelpOptions = true,
|
||||
subcommands = [
|
||||
BuildCommand::class,
|
||||
CheckCommand::class,
|
||||
InspectionCommand::class,
|
||||
NewCommand::class,
|
||||
RunCommand::class,
|
||||
ShowCommand::class,
|
||||
TreeCommand::class
|
||||
]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class Commands : CommandBase() {
|
||||
|
||||
fun run(args: Array<String>) = when (CommandLine.call(this, System.err, *args)) {
|
||||
true -> 0
|
||||
else -> 1
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
import java.nio.file.Files
|
||||
|
||||
@Command(
|
||||
name = "inspect",
|
||||
description = ["Inspect the transformations that are being applied to classes before they get loaded into " +
|
||||
"the sandbox."]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class InspectionCommand : ClassCommand() {
|
||||
|
||||
override val filters: Array<String>
|
||||
get() = classes
|
||||
|
||||
@Parameters(description = ["The partial or fully qualified names of the Java classes to inspect."])
|
||||
var classes: Array<String> = emptyArray()
|
||||
|
||||
override fun processClasses(classes: List<Class<*>>) {
|
||||
val sources = classes.map { ClassSource.fromClassName(it.name) }
|
||||
val (_, messages) = executor.validate(*sources.toTypedArray())
|
||||
|
||||
if (messages.isNotEmpty()) {
|
||||
for (message in messages.sorted()) {
|
||||
printInfo(" - $message")
|
||||
}
|
||||
printInfo()
|
||||
}
|
||||
|
||||
for (classSource in sources) {
|
||||
val loadedClass = executor.load(classSource)
|
||||
val sourceClass = createCodePath().resolve("${loadedClass.type.simpleName}.class")
|
||||
val originalClass = Files.createTempFile("sandbox-", ".java")
|
||||
val transformedClass = Files.createTempFile("sandbox-", ".java")
|
||||
|
||||
printInfo("Class: ${loadedClass.name}")
|
||||
printVerbose(" - Size of the original byte code: ${Files.size(sourceClass)}")
|
||||
printVerbose(" - Size of the transformed byte code: ${loadedClass.byteCode.bytes.size}")
|
||||
printVerbose(" - Original class: $originalClass")
|
||||
printVerbose(" - Transformed class: $transformedClass")
|
||||
printInfo()
|
||||
|
||||
// Generate byte code dump of the original class
|
||||
ProcessBuilder("javap", "-c", sourceClass.toString()).apply {
|
||||
redirectOutput(originalClass.toFile())
|
||||
environment().putAll(System.getenv())
|
||||
start().apply {
|
||||
waitFor()
|
||||
exitValue()
|
||||
}
|
||||
}
|
||||
|
||||
// Generate byte code dump of the transformed class
|
||||
Files.createTempFile("sandbox-", ".class").apply {
|
||||
Files.write(this, loadedClass.byteCode.bytes)
|
||||
ProcessBuilder("javap", "-c", this.toString()).apply {
|
||||
redirectOutput(transformedClass.toFile())
|
||||
environment().putAll(System.getenv())
|
||||
start().apply {
|
||||
waitFor()
|
||||
exitValue()
|
||||
}
|
||||
}
|
||||
Files.delete(this)
|
||||
}
|
||||
|
||||
// Generate and display the difference between the original and the transformed class
|
||||
ProcessBuilder(
|
||||
"git", "diff", originalClass.toString(), transformedClass.toString()
|
||||
).apply {
|
||||
inheritIO()
|
||||
environment().putAll(System.getenv())
|
||||
start().apply {
|
||||
waitFor()
|
||||
exitValue()
|
||||
}
|
||||
}
|
||||
printInfo()
|
||||
|
||||
Files.deleteIfExists(originalClass)
|
||||
Files.deleteIfExists(transformedClass)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import picocli.CommandLine.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
@Command(
|
||||
name = "new",
|
||||
description = ["Create one or more new Java classes implementing the sandbox runnable interface that is " +
|
||||
"required for execution in the deterministic sandbox. Each Java file is created using a template, " +
|
||||
"with class name derived from the provided file name."
|
||||
],
|
||||
showDefaultValues = true
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class NewCommand : CommandBase() {
|
||||
|
||||
@Parameters(description = ["The names of the Java source files that will be created."])
|
||||
var files: Array<Path> = emptyArray()
|
||||
|
||||
@Option(names = ["-f", "--force"], description = ["Forcefully overwrite files if they already exist."])
|
||||
var force: Boolean = false
|
||||
|
||||
@Option(names = ["--from"], description = ["The input type to use for the constructed runnable."])
|
||||
var fromType: String = "Object"
|
||||
|
||||
@Option(names = ["--to"], description = ["The output type to use for the constructed runnable."])
|
||||
var toType: String = "Object"
|
||||
|
||||
@Option(names = ["--return"], description = ["The default return value for the constructed runnable."])
|
||||
var returnValue: String = "null"
|
||||
|
||||
override fun validateArguments() = files.isNotEmpty()
|
||||
|
||||
override fun handleCommand(): Boolean {
|
||||
val codePath = createCodePath()
|
||||
val files = files.getFiles { codePath.resolve(it) }
|
||||
for (file in files) {
|
||||
try {
|
||||
printVerbose("Creating file '$file'...")
|
||||
Files.newBufferedWriter(file, *openOptions(force)).use {
|
||||
it.append(TEMPLATE
|
||||
.replace("[NAME]", file.baseName)
|
||||
.replace("[FROM]", fromType)
|
||||
.replace("[TO]", toType)
|
||||
.replace("[RETURN]", returnValue))
|
||||
}
|
||||
} catch (exception: Throwable) {
|
||||
throw Exception("Failed to create file '$file'", exception)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val TEMPLATE = """
|
||||
|package net.corda.sandbox;
|
||||
|
|
||||
|import java.util.function.Function;
|
||||
|
|
||||
|public class [NAME] implements Function<[FROM], [TO]> {
|
||||
| @Override
|
||||
| public [TO] apply([FROM] input) {
|
||||
| return [RETURN];
|
||||
| }
|
||||
|}
|
||||
""".trimMargin()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@file:JvmName("Program")
|
||||
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* The entry point of the deterministic sandbox tool.
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
exitProcess(Commands().run(args))
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
|
||||
@Command(
|
||||
name = "run",
|
||||
description = ["Execute runnable in sandbox."],
|
||||
showDefaultValues = true
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class RunCommand : ClassCommand() {
|
||||
|
||||
override val filters: Array<String>
|
||||
get() = classes
|
||||
|
||||
@Parameters(description = ["The partial or fully qualified names of the Java classes to run."])
|
||||
var classes: Array<String> = emptyArray()
|
||||
|
||||
override fun processClasses(classes: List<Class<*>>) {
|
||||
val interfaceName = java.util.function.Function::class.java.simpleName
|
||||
for (clazz in classes) {
|
||||
if (!clazz.interfaces.any { it.simpleName == interfaceName }) {
|
||||
printError("Class is not an instance of $interfaceName; ${clazz.name}")
|
||||
return
|
||||
}
|
||||
printInfo("Running class ${clazz.name}...")
|
||||
executor.run(ClassSource.fromClassName(clazz.name), Any()).apply {
|
||||
printResult(result)
|
||||
printCosts(costs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
import java.nio.file.Files
|
||||
|
||||
@Command(
|
||||
name = "show",
|
||||
description = ["Show the transformed version of a class as it is prepared for execution in the deterministic " +
|
||||
"sandbox."]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class ShowCommand : ClassCommand() {
|
||||
|
||||
override val filters: Array<String>
|
||||
get() = classes
|
||||
|
||||
@Parameters(description = ["The partial or fully qualified names of the Java classes to inspect."])
|
||||
var classes: Array<String> = emptyArray()
|
||||
|
||||
override fun processClasses(classes: List<Class<*>>) {
|
||||
val sources = classes.map { ClassSource.fromClassName(it.name) }
|
||||
val (_, messages) = executor.validate(*sources.toTypedArray())
|
||||
|
||||
if (messages.isNotEmpty()) {
|
||||
for (message in messages.sorted()) {
|
||||
printInfo(" - $message")
|
||||
}
|
||||
printInfo()
|
||||
}
|
||||
|
||||
for (classSource in sources) {
|
||||
val loadedClass = executor.load(classSource)
|
||||
printInfo("Class: ${loadedClass.name}")
|
||||
printVerbose(" - Byte code size: ${loadedClass.byteCode.bytes.size}")
|
||||
printVerbose(" - Has been modified: ${loadedClass.byteCode.isModified}")
|
||||
printInfo()
|
||||
|
||||
Files.createTempFile("sandbox-", ".class").apply {
|
||||
Files.write(this, loadedClass.byteCode.bytes)
|
||||
ProcessBuilder("javap", "-c", this.toString()).apply {
|
||||
inheritIO()
|
||||
environment().putAll(System.getenv())
|
||||
start().apply {
|
||||
waitFor()
|
||||
exitValue()
|
||||
}
|
||||
}
|
||||
Files.delete(this)
|
||||
}
|
||||
printInfo()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import picocli.CommandLine.Command
|
||||
import java.nio.file.Files
|
||||
|
||||
@Command(
|
||||
name = "tree",
|
||||
description = ["Show the hierarchy of the classes that have been created with the 'new' command."]
|
||||
)
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class TreeCommand : CommandBase() {
|
||||
|
||||
override fun validateArguments() = true
|
||||
|
||||
override fun handleCommand(): Boolean {
|
||||
val path = workingDirectory.resolve("tmp")
|
||||
if (!Files.exists(path)) {
|
||||
printError("No classes have been created so far. Run `djvm new` to get started.")
|
||||
return false
|
||||
}
|
||||
ProcessBuilder("find", ".", "-type", "f").apply {
|
||||
inheritIO()
|
||||
environment().putAll(System.getenv())
|
||||
directory(path.toFile())
|
||||
start().apply {
|
||||
waitFor()
|
||||
exitValue()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
@file:JvmName("Utilities")
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import io.github.classgraph.ClassGraph
|
||||
import java.lang.reflect.Modifier.isAbstract
|
||||
import java.lang.reflect.Modifier.isStatic
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardOpenOption
|
||||
|
||||
/**
|
||||
* Get the expanded file name of each path in the provided array.
|
||||
*/
|
||||
fun Array<Path>?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map {
|
||||
val pathString = it.toString()
|
||||
val path = map(it)
|
||||
when {
|
||||
'/' in pathString || '\\' in pathString ->
|
||||
throw Exception("Please provide a pathless file name")
|
||||
pathString.endsWith(".java", true) -> path
|
||||
else -> Paths.get("$path.java")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string representation of each expanded file name in the provided array.
|
||||
*/
|
||||
fun Array<Path>?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map {
|
||||
it.toString()
|
||||
}.toTypedArray()
|
||||
|
||||
/**
|
||||
* Execute inlined action if the collection is empty.
|
||||
*/
|
||||
inline fun <T> List<T>.onEmpty(action: () -> Unit): List<T> {
|
||||
if (!this.any()) {
|
||||
action()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute inlined action if the array is empty.
|
||||
*/
|
||||
inline fun <reified T> Array<T>?.onEmpty(action: () -> Unit): Array<T> {
|
||||
return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the set of [StandardOpenOption]'s to use for a file operation.
|
||||
*/
|
||||
fun openOptions(force: Boolean) = if (force) {
|
||||
arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
|
||||
} else {
|
||||
arrayOf(StandardOpenOption.CREATE_NEW)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of where any generated code will be placed. Create the directory if it does not exist.
|
||||
*/
|
||||
fun createCodePath(): Path {
|
||||
return Paths.get("tmp", "net", "corda", "sandbox").let {
|
||||
Files.createDirectories(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base name of a file (i.e., its name without extension)
|
||||
*/
|
||||
val Path.baseName: String
|
||||
get() = this.fileName.toString()
|
||||
.replaceAfterLast('.', "")
|
||||
.removeSuffix(".")
|
||||
|
||||
/**
|
||||
* The path of the executing JAR.
|
||||
*/
|
||||
val jarPath: String = object {}.javaClass.protectionDomain.codeSource.location.toURI().path
|
||||
|
||||
|
||||
/**
|
||||
* The path of the current working directory.
|
||||
*/
|
||||
val workingDirectory: Path = Paths.get(System.getProperty("user.dir"))
|
||||
|
||||
/**
|
||||
* The class path for the current execution context.
|
||||
*/
|
||||
val userClassPath: String = System.getProperty("java.class.path")
|
||||
|
||||
/**
|
||||
* Get a reference of each concrete class that implements interface or class [T].
|
||||
*/
|
||||
inline fun <reified T> find(scanSpec: String = "net/corda/sandbox"): List<Class<*>> {
|
||||
return ClassGraph()
|
||||
.whitelistPaths(scanSpec)
|
||||
.enableAllInfo()
|
||||
.scan()
|
||||
.use { it.getClassesImplementing(T::class.java.name).loadClasses(T::class.java) }
|
||||
.filter { !isAbstract(it.modifiers) && !isStatic(it.modifiers) }
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import com.jcabi.manifests.Manifests
|
||||
import picocli.CommandLine.IVersionProvider
|
||||
|
||||
/**
|
||||
* Get the version number to use for the tool.
|
||||
*/
|
||||
@Suppress("KDocMissingDocumentation")
|
||||
class VersionProvider : IVersionProvider {
|
||||
override fun getVersion(): Array<String> = arrayOf(
|
||||
Manifests.read("Corda-Release-Version")
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
|
||||
<ThresholdFilter level="trace"/>
|
||||
<Appenders>
|
||||
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
|
||||
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||
<RollingFile name="RollingFile-Appender"
|
||||
fileName="djvm.log"
|
||||
filePattern="djvm.%date{yyyy-MM-dd}-%i.log.gz">
|
||||
|
||||
<PatternLayout pattern="%date{ISO8601}{UTC}Z [%-5level] %c - %msg%n"/>
|
||||
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
<SizeBasedTriggeringPolicy size="10MB"/>
|
||||
</Policies>
|
||||
|
||||
<DefaultRolloverStrategy min="1" max="10">
|
||||
<Delete basePath="" maxDepth="1">
|
||||
<IfFileName glob="djvm*.log.gz"/>
|
||||
<IfLastModified age="60d">
|
||||
<IfAny>
|
||||
<IfAccumulatedFileSize exceeds="10 GB"/>
|
||||
</IfAny>
|
||||
</IfLastModified>
|
||||
</Delete>
|
||||
</DefaultRolloverStrategy>
|
||||
|
||||
</RollingFile>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="RollingFile-Appender"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
|
||||
|
||||
CLASSPATH="${CLASSPATH:-}"
|
||||
|
||||
DEBUG=`echo "${DEBUG:-0}" | sed 's/^[Nn][Oo]*$/0/g'`
|
||||
DEBUG_PORT=5005
|
||||
DEBUG_AGENT=""
|
||||
|
||||
if [ "$DEBUG" != 0 ]; then
|
||||
echo "Opening remote debugging session on port $DEBUG_PORT"
|
||||
DEBUG_AGENT="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=$DEBUG_PORT"
|
||||
fi
|
||||
|
||||
exec java $DEBUG_AGENT -cp "$CLASSPATH:.:tmp:$SCRIPT_DIR/corda-djvm-cli.jar" net.corda.djvm.tools.cli.Program "$@"
|
@ -1,15 +0,0 @@
|
||||
@ECHO off
|
||||
|
||||
SETLOCAL ENABLEEXTENSIONS
|
||||
|
||||
IF NOT DEFINED CLASSPATH (SET CLASSPATH=)
|
||||
|
||||
IF DEFINED DEBUG (
|
||||
SET DEBUG_PORT=5005
|
||||
SET DEBUG_AGENT=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=%DEBUG_PORT%
|
||||
ECHO Opening remote debugging session on port %DEBUG_PORT%
|
||||
) ELSE (
|
||||
SET DEBUG_AGENT=
|
||||
)
|
||||
|
||||
CALL java %DEBUG_AGENT% -cp "%CLASSPATH%;.;tmp;%~dp0\corda-djvm-cli.jar" net.corda.djvm.tools.cli.Program %*
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
|
||||
|
||||
# Generate auto-completion file for Bash and ZSH
|
||||
java -cp ${SCRIPT_DIR}/corda-djvm-cli.jar \
|
||||
picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f
|
1
djvm/djvm/shell/.gitignore
vendored
1
djvm/djvm/shell/.gitignore
vendored
@ -1 +0,0 @@
|
||||
djvm_completion
|
@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
file="${BASH_SOURCE[0]}"
|
||||
linked_file="$(test -L "$file" && readlink "$file" || echo "$file")"
|
||||
base_dir="$(cd "$(dirname "$linked_file")/../" && pwd)"
|
||||
djvm_cli_jar=$(ls -1 $base_dir/cli/build/libs/corda-djvm-cli-*.jar)
|
||||
|
||||
CLASSPATH="${CLASSPATH:-}"
|
||||
|
||||
DEBUG=`echo "${DEBUG:-0}" | sed 's/^[Nn][Oo]*$/0/g'`
|
||||
DEBUG_PORT=5005
|
||||
DEBUG_AGENT=""
|
||||
|
||||
if [ "$DEBUG" != 0 ]; then
|
||||
echo "Opening remote debugging session on port $DEBUG_PORT"
|
||||
DEBUG_AGENT="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=$DEBUG_PORT"
|
||||
fi
|
||||
|
||||
java $DEBUG_AGENT -cp "$CLASSPATH:.:tmp:$djvm_cli_jar" net.corda.djvm.tools.cli.Program "$@"
|
@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
file="${BASH_SOURCE[0]}"
|
||||
base_dir="$(cd "$(dirname "$file")/" && pwd)"
|
||||
|
||||
# Build DJVM module and CLI
|
||||
cd "$base_dir/.."
|
||||
if !(../gradlew shadowJar); then
|
||||
echo "Failed to build DJVM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
djvm_cli_jar=$(ls -1 $base_dir/../cli/build/libs/corda-djvm-cli-*.jar)
|
||||
|
||||
# Generate auto-completion file for Bash and ZSH
|
||||
cd "$base_dir"
|
||||
if !(java -cp $djvm_cli_jar \
|
||||
picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f); then
|
||||
echo "Failed to generate auto-completion file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a symbolic link to the `djvm` utility
|
||||
sudo ln -sf "$base_dir/djvm" /usr/local/bin/djvm
|
@ -1,19 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.Appendable}
|
||||
* to keep {@link sandbox.java.lang.StringBuilder}, {@link sandbox.java.lang.StringBuffer}
|
||||
* and {@link sandbox.java.lang.String} honest.
|
||||
* Note that it does not extend {@link java.lang.Appendable}.
|
||||
*/
|
||||
public interface Appendable {
|
||||
|
||||
Appendable append(CharSequence csq, int start, int end) throws IOException;
|
||||
|
||||
Appendable append(CharSequence csq) throws IOException;
|
||||
|
||||
Appendable append(char c) throws IOException;
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Boolean extends Object implements Comparable<Boolean>, Serializable {
|
||||
|
||||
public static final Boolean TRUE = new Boolean(true);
|
||||
public static final Boolean FALSE = new Boolean(false);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Boolean> TYPE = (Class) java.lang.Boolean.TYPE;
|
||||
|
||||
private final boolean value;
|
||||
|
||||
public Boolean(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Boolean(String s) {
|
||||
this(parseBoolean(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Boolean) && ((Boolean) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(boolean value) {
|
||||
return java.lang.Boolean.hashCode(value);
|
||||
}
|
||||
|
||||
public boolean booleanValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Boolean.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return toString(value);
|
||||
}
|
||||
|
||||
public static String toString(boolean b) {
|
||||
return String.valueOf(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Boolean fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Boolean other) {
|
||||
return compare(value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(boolean x, boolean y) {
|
||||
return java.lang.Boolean.compare(x, y);
|
||||
}
|
||||
|
||||
public static boolean parseBoolean(String s) {
|
||||
return java.lang.Boolean.parseBoolean(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Boolean valueOf(boolean b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public static Boolean valueOf(String s) {
|
||||
return valueOf(parseBoolean(s));
|
||||
}
|
||||
|
||||
public static boolean logicalAnd(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalAnd(a, b);
|
||||
}
|
||||
|
||||
public static boolean logicalOr(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalOr(a, b);
|
||||
}
|
||||
|
||||
public static boolean logicalXor(boolean a, boolean b) {
|
||||
return java.lang.Boolean.logicalXor(a, b);
|
||||
}
|
||||
|
||||
public static Boolean toDJVM(java.lang.Boolean b) { return (b == null) ? null : new Boolean(b); }
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Byte extends Number implements Comparable<Byte> {
|
||||
public static final byte MIN_VALUE = java.lang.Byte.MIN_VALUE;
|
||||
public static final byte MAX_VALUE = java.lang.Byte.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Byte.BYTES;
|
||||
public static final int SIZE = java.lang.Byte.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Byte> TYPE = (Class) java.lang.Byte.TYPE;
|
||||
|
||||
private final byte value;
|
||||
|
||||
public Byte(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Byte(String s) throws NumberFormatException {
|
||||
this.value = parseByte(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(byte b) {
|
||||
return java.lang.Byte.hashCode(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Byte) && ((Byte) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Byte.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Byte fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Byte other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(byte x, byte y) {
|
||||
return java.lang.Byte.compare(x, y);
|
||||
}
|
||||
|
||||
public static String toString(byte b) {
|
||||
return Integer.toString(b);
|
||||
}
|
||||
|
||||
public static Byte valueOf(byte b) {
|
||||
return new Byte(b);
|
||||
}
|
||||
|
||||
public static byte parseByte(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Byte.parseByte(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static byte parseByte(String s) throws NumberFormatException {
|
||||
return java.lang.Byte.parseByte(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Byte valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Byte valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Byte decode(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Byte.decode(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static int toUnsignedInt(byte b) {
|
||||
return java.lang.Byte.toUnsignedInt(b);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(byte b) {
|
||||
return java.lang.Byte.toUnsignedLong(b);
|
||||
}
|
||||
|
||||
public static Byte toDJVM(java.lang.Byte b) {
|
||||
return (b == null) ? null : valueOf(b);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.CharSequence}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public interface CharSequence extends java.lang.CharSequence {
|
||||
|
||||
@Override
|
||||
CharSequence subSequence(int start, int end);
|
||||
|
||||
@NotNull
|
||||
String toDJVMString();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.String toString();
|
||||
|
||||
}
|
@ -1,481 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Character extends Object implements Comparable<Character>, Serializable {
|
||||
public static final int MIN_RADIX = java.lang.Character.MIN_RADIX;
|
||||
public static final int MAX_RADIX = java.lang.Character.MAX_RADIX;
|
||||
public static final char MIN_VALUE = java.lang.Character.MIN_VALUE;
|
||||
public static final char MAX_VALUE = java.lang.Character.MAX_VALUE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Character> TYPE = (Class) java.lang.Character.TYPE;
|
||||
|
||||
public static final byte UNASSIGNED = java.lang.Character.UNASSIGNED;
|
||||
public static final byte UPPERCASE_LETTER = java.lang.Character.UPPERCASE_LETTER;
|
||||
public static final byte LOWERCASE_LETTER = java.lang.Character.LOWERCASE_LETTER;
|
||||
public static final byte TITLECASE_LETTER = java.lang.Character.TITLECASE_LETTER;
|
||||
public static final byte MODIFIER_LETTER = java.lang.Character.MODIFIER_LETTER;
|
||||
public static final byte OTHER_LETTER = java.lang.Character.OTHER_LETTER;
|
||||
public static final byte NON_SPACING_MARK = java.lang.Character.NON_SPACING_MARK;
|
||||
public static final byte ENCLOSING_MARK = java.lang.Character.ENCLOSING_MARK;
|
||||
public static final byte COMBINING_SPACING_MARK = java.lang.Character.COMBINING_SPACING_MARK;
|
||||
public static final byte DECIMAL_DIGIT_NUMBER = java.lang.Character.DECIMAL_DIGIT_NUMBER;
|
||||
public static final byte LETTER_NUMBER = java.lang.Character.LETTER_NUMBER;
|
||||
public static final byte OTHER_NUMBER = java.lang.Character.OTHER_NUMBER;
|
||||
public static final byte SPACE_SEPARATOR = java.lang.Character.SPACE_SEPARATOR;
|
||||
public static final byte LINE_SEPARATOR = java.lang.Character.LINE_SEPARATOR;
|
||||
public static final byte PARAGRAPH_SEPARATOR = java.lang.Character.PARAGRAPH_SEPARATOR;
|
||||
public static final byte CONTROL = java.lang.Character.CONTROL;
|
||||
public static final byte FORMAT = java.lang.Character.FORMAT;
|
||||
public static final byte PRIVATE_USE = java.lang.Character.PRIVATE_USE;
|
||||
public static final byte SURROGATE = java.lang.Character.SURROGATE;
|
||||
public static final byte DASH_PUNCTUATION = java.lang.Character.DASH_PUNCTUATION;
|
||||
public static final byte START_PUNCTUATION = java.lang.Character.START_PUNCTUATION;
|
||||
public static final byte END_PUNCTUATION = java.lang.Character.END_PUNCTUATION;
|
||||
public static final byte CONNECTOR_PUNCTUATION = java.lang.Character.CONNECTOR_PUNCTUATION;
|
||||
public static final byte OTHER_PUNCTUATION = java.lang.Character.OTHER_PUNCTUATION;
|
||||
public static final byte MATH_SYMBOL = java.lang.Character.MATH_SYMBOL;
|
||||
public static final byte CURRENCY_SYMBOL = java.lang.Character.CURRENCY_SYMBOL;
|
||||
public static final byte MODIFIER_SYMBOL = java.lang.Character.MODIFIER_SYMBOL;
|
||||
public static final byte OTHER_SYMBOL = java.lang.Character.OTHER_SYMBOL;
|
||||
public static final byte INITIAL_QUOTE_PUNCTUATION = java.lang.Character.INITIAL_QUOTE_PUNCTUATION;
|
||||
public static final byte FINAL_QUOTE_PUNCTUATION = java.lang.Character.FINAL_QUOTE_PUNCTUATION;
|
||||
public static final byte DIRECTIONALITY_UNDEFINED = java.lang.Character.DIRECTIONALITY_UNDEFINED;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR = java.lang.Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR;
|
||||
public static final byte DIRECTIONALITY_ARABIC_NUMBER = java.lang.Character.DIRECTIONALITY_ARABIC_NUMBER;
|
||||
public static final byte DIRECTIONALITY_COMMON_NUMBER_SEPARATOR = java.lang.Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_NONSPACING_MARK = java.lang.Character.DIRECTIONALITY_NONSPACING_MARK;
|
||||
public static final byte DIRECTIONALITY_BOUNDARY_NEUTRAL = java.lang.Character.DIRECTIONALITY_BOUNDARY_NEUTRAL;
|
||||
public static final byte DIRECTIONALITY_PARAGRAPH_SEPARATOR = java.lang.Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_SEGMENT_SEPARATOR = java.lang.Character.DIRECTIONALITY_SEGMENT_SEPARATOR;
|
||||
public static final byte DIRECTIONALITY_WHITESPACE = java.lang.Character.DIRECTIONALITY_WHITESPACE;
|
||||
public static final byte DIRECTIONALITY_OTHER_NEUTRALS = java.lang.Character.DIRECTIONALITY_OTHER_NEUTRALS;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING;
|
||||
public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE = java.lang.Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING;
|
||||
public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE = java.lang.Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE;
|
||||
public static final byte DIRECTIONALITY_POP_DIRECTIONAL_FORMAT = java.lang.Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT;
|
||||
public static final char MIN_HIGH_SURROGATE = java.lang.Character.MIN_HIGH_SURROGATE;
|
||||
public static final char MAX_HIGH_SURROGATE = java.lang.Character.MAX_HIGH_SURROGATE;
|
||||
public static final char MIN_LOW_SURROGATE = java.lang.Character.MIN_LOW_SURROGATE;
|
||||
public static final char MAX_LOW_SURROGATE = java.lang.Character.MAX_LOW_SURROGATE;
|
||||
public static final char MIN_SURROGATE = java.lang.Character.MIN_SURROGATE;
|
||||
public static final char MAX_SURROGATE = java.lang.Character.MAX_SURROGATE;
|
||||
public static final int MIN_SUPPLEMENTARY_CODE_POINT = java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
|
||||
public static final int MIN_CODE_POINT = java.lang.Character.MIN_CODE_POINT;
|
||||
public static final int MAX_CODE_POINT = java.lang.Character.MAX_CODE_POINT;
|
||||
public static final int BYTES = java.lang.Character.BYTES;
|
||||
public static final int SIZE = java.lang.Character.SIZE;
|
||||
|
||||
private final char value;
|
||||
|
||||
public Character(char c) {
|
||||
this.value = c;
|
||||
}
|
||||
|
||||
public char charValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(this.value);
|
||||
}
|
||||
|
||||
public static int hashCode(char value) {
|
||||
return java.lang.Character.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Character) && ((Character) other).value == value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Character.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Character fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Character var1) {
|
||||
return compare(this.value, var1.value);
|
||||
}
|
||||
|
||||
public static int compare(char x, char y) {
|
||||
return java.lang.Character.compare(x, y);
|
||||
}
|
||||
|
||||
public static String toString(char c) {
|
||||
return String.toDJVM(java.lang.Character.toString(c));
|
||||
}
|
||||
|
||||
public static Character valueOf(char c) {
|
||||
return (c <= 127) ? Cache.cache[(int)c] : new Character(c);
|
||||
}
|
||||
|
||||
public static boolean isValidCodePoint(int codePoint) {
|
||||
return java.lang.Character.isValidCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isBmpCodePoint(int codePoint) {
|
||||
return java.lang.Character.isBmpCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isSupplementaryCodePoint(int codePoint) {
|
||||
return java.lang.Character.isSupplementaryCodePoint(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isHighSurrogate(char ch) {
|
||||
return java.lang.Character.isHighSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isLowSurrogate(char ch) {
|
||||
return java.lang.Character.isLowSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isSurrogate(char ch) {
|
||||
return java.lang.Character.isSurrogate(ch);
|
||||
}
|
||||
|
||||
public static boolean isSurrogatePair(char high, char low) {
|
||||
return java.lang.Character.isSurrogatePair(high, low);
|
||||
}
|
||||
|
||||
public static int charCount(int codePoint) {
|
||||
return java.lang.Character.charCount(codePoint);
|
||||
}
|
||||
|
||||
public static int toCodePoint(char high, char low) {
|
||||
return java.lang.Character.toCodePoint(high, low);
|
||||
}
|
||||
|
||||
public static int codePointAt(CharSequence seq, int index) {
|
||||
return java.lang.Character.codePointAt(seq, index);
|
||||
}
|
||||
|
||||
public static int codePointAt(char[] a, int index) {
|
||||
return java.lang.Character.codePointAt(a, index);
|
||||
}
|
||||
|
||||
public static int codePointAt(char[] a, int index, int limit) {
|
||||
return java.lang.Character.codePointAt(a, index, limit);
|
||||
}
|
||||
|
||||
public static int codePointBefore(CharSequence seq, int index) {
|
||||
return java.lang.Character.codePointBefore(seq, index);
|
||||
}
|
||||
|
||||
public static int codePointBefore(char[] a, int index) {
|
||||
return java.lang.Character.codePointBefore(a, index);
|
||||
}
|
||||
|
||||
public static int codePointBefore(char[] a, int index, int limit) {
|
||||
return java.lang.Character.codePointBefore(a, index, limit);
|
||||
}
|
||||
|
||||
public static char highSurrogate(int codePoint) {
|
||||
return java.lang.Character.highSurrogate(codePoint);
|
||||
}
|
||||
|
||||
public static char lowSurrogate(int codePoint) {
|
||||
return java.lang.Character.lowSurrogate(codePoint);
|
||||
}
|
||||
|
||||
public static int toChars(int codePoint, char[] dst, int dstIndex) {
|
||||
return java.lang.Character.toChars(codePoint, dst, dstIndex);
|
||||
}
|
||||
|
||||
public static char[] toChars(int codePoint) {
|
||||
return java.lang.Character.toChars(codePoint);
|
||||
}
|
||||
|
||||
public static int codePointCount(CharSequence seq, int beginIndex, int endIndex) {
|
||||
return java.lang.Character.codePointCount(seq, beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public static int codePointCount(char[] a, int offset, int count) {
|
||||
return java.lang.Character.codePointCount(a, offset, count);
|
||||
}
|
||||
|
||||
public static int offsetByCodePoints(CharSequence seq, int index, int codePointOffset) {
|
||||
return java.lang.Character.offsetByCodePoints(seq, index, codePointOffset);
|
||||
}
|
||||
|
||||
public static int offsetByCodePoints(char[] a, int start, int count, int index, int codePointOffset) {
|
||||
return java.lang.Character.offsetByCodePoints(a, start, count, index, codePointOffset);
|
||||
}
|
||||
|
||||
public static boolean isLowerCase(char ch) {
|
||||
return java.lang.Character.isLowerCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isLowerCase(int codePoint) {
|
||||
return java.lang.Character.isLowerCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUpperCase(char ch) {
|
||||
return java.lang.Character.isUpperCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isUpperCase(int codePoint) {
|
||||
return java.lang.Character.isUpperCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isTitleCase(char ch) {
|
||||
return java.lang.Character.isTitleCase(ch);
|
||||
}
|
||||
|
||||
public static boolean isTitleCase(int codePoint) {
|
||||
return java.lang.Character.isTitleCase(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isDigit(char ch) {
|
||||
return java.lang.Character.isDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isDigit(int codePoint) {
|
||||
return java.lang.Character.isDigit(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isDefined(char ch) {
|
||||
return java.lang.Character.isDefined(ch);
|
||||
}
|
||||
|
||||
public static boolean isDefined(int codePoint) {
|
||||
return java.lang.Character.isDefined(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isLetter(char ch) {
|
||||
return java.lang.Character.isLetter(ch);
|
||||
}
|
||||
|
||||
public static boolean isLetter(int codePoint) {
|
||||
return java.lang.Character.isLetter(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isLetterOrDigit(char ch) {
|
||||
return java.lang.Character.isLetterOrDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isLetterOrDigit(int codePoint) {
|
||||
return java.lang.Character.isLetterOrDigit(codePoint);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isJavaLetter(char ch) {
|
||||
return java.lang.Character.isJavaLetter(ch);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isJavaLetterOrDigit(char ch) {
|
||||
return java.lang.Character.isJavaLetterOrDigit(ch);
|
||||
}
|
||||
|
||||
public static boolean isAlphabetic(int codePoint) {
|
||||
return java.lang.Character.isAlphabetic(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isIdeographic(int codePoint) {
|
||||
return java.lang.Character.isIdeographic(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierStart(char ch) {
|
||||
return java.lang.Character.isJavaIdentifierStart(ch);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierStart(int codePoint) {
|
||||
return java.lang.Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierPart(char ch) {
|
||||
return java.lang.Character.isJavaIdentifierPart(ch);
|
||||
}
|
||||
|
||||
public static boolean isJavaIdentifierPart(int codePoint) {
|
||||
return java.lang.Character.isJavaIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierStart(char ch) {
|
||||
return java.lang.Character.isUnicodeIdentifierStart(ch);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierStart(int codePoint) {
|
||||
return java.lang.Character.isUnicodeIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierPart(char ch) {
|
||||
return java.lang.Character.isUnicodeIdentifierPart(ch);
|
||||
}
|
||||
|
||||
public static boolean isUnicodeIdentifierPart(int codePoint) {
|
||||
return java.lang.Character.isUnicodeIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isIdentifierIgnorable(char ch) {
|
||||
return java.lang.Character.isIdentifierIgnorable(ch);
|
||||
}
|
||||
|
||||
public static boolean isIdentifierIgnorable(int codePoint) {
|
||||
return java.lang.Character.isIdentifierIgnorable(codePoint);
|
||||
}
|
||||
|
||||
public static char toLowerCase(char ch) {
|
||||
return java.lang.Character.toLowerCase(ch);
|
||||
}
|
||||
|
||||
public static int toLowerCase(int codePoint) {
|
||||
return java.lang.Character.toLowerCase(codePoint);
|
||||
}
|
||||
|
||||
public static char toUpperCase(char ch) {
|
||||
return java.lang.Character.toUpperCase(ch);
|
||||
}
|
||||
|
||||
public static int toUpperCase(int codePoint) {
|
||||
return java.lang.Character.toUpperCase(codePoint);
|
||||
}
|
||||
|
||||
public static char toTitleCase(char ch) {
|
||||
return java.lang.Character.toTitleCase(ch);
|
||||
}
|
||||
|
||||
public static int toTitleCase(int codePoint) {
|
||||
return java.lang.Character.toTitleCase(codePoint);
|
||||
}
|
||||
|
||||
public static int digit(char ch, int radix) {
|
||||
return java.lang.Character.digit(ch, radix);
|
||||
}
|
||||
|
||||
public static int digit(int codePoint, int radix) {
|
||||
return java.lang.Character.digit(codePoint, radix);
|
||||
}
|
||||
|
||||
public static int getNumericValue(char ch) {
|
||||
return java.lang.Character.getNumericValue(ch);
|
||||
}
|
||||
|
||||
public static int getNumericValue(int codePoint) {
|
||||
return java.lang.Character.getNumericValue(codePoint);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static boolean isSpace(char ch) {
|
||||
return java.lang.Character.isSpace(ch);
|
||||
}
|
||||
|
||||
public static boolean isSpaceChar(char ch) {
|
||||
return java.lang.Character.isSpaceChar(ch);
|
||||
}
|
||||
|
||||
public static boolean isSpaceChar(int codePoint) {
|
||||
return java.lang.Character.isSpaceChar(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isWhitespace(char ch) {
|
||||
return java.lang.Character.isWhitespace(ch);
|
||||
}
|
||||
|
||||
public static boolean isWhitespace(int codePoint) {
|
||||
return java.lang.Character.isWhitespace(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isISOControl(char ch) {
|
||||
return java.lang.Character.isISOControl(ch);
|
||||
}
|
||||
|
||||
public static boolean isISOControl(int codePoint) {
|
||||
return java.lang.Character.isISOControl(codePoint);
|
||||
}
|
||||
|
||||
public static int getType(char ch) {
|
||||
return java.lang.Character.getType(ch);
|
||||
}
|
||||
|
||||
public static int getType(int codePoint) {
|
||||
return java.lang.Character.getType(codePoint);
|
||||
}
|
||||
|
||||
public static char forDigit(int digit, int radix) {
|
||||
return java.lang.Character.forDigit(digit, radix);
|
||||
}
|
||||
|
||||
public static byte getDirectionality(char ch) {
|
||||
return java.lang.Character.getDirectionality(ch);
|
||||
}
|
||||
|
||||
public static byte getDirectionality(int codePoint) {
|
||||
return java.lang.Character.getDirectionality(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isMirrored(char ch) {
|
||||
return java.lang.Character.isMirrored(ch);
|
||||
}
|
||||
|
||||
public static boolean isMirrored(int codePoint) {
|
||||
return java.lang.Character.isMirrored(codePoint);
|
||||
}
|
||||
|
||||
public static String getName(int codePoint) {
|
||||
return String.toDJVM(java.lang.Character.getName(codePoint));
|
||||
}
|
||||
|
||||
public static Character toDJVM(java.lang.Character c) {
|
||||
return (c == null) ? null : valueOf(c);
|
||||
}
|
||||
|
||||
// These three nested classes are placeholders to ensure that
|
||||
// the Character class bytecode is generated correctly. The
|
||||
// real classes will be loaded from the from the bootstrap jar
|
||||
// and then mapped into the sandbox.* namespace.
|
||||
public static final class UnicodeScript extends Enum<UnicodeScript> {
|
||||
private UnicodeScript(String name, int index) {
|
||||
super(name, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull UnicodeScript other) {
|
||||
throw new UnsupportedOperationException("Bootstrap implementation");
|
||||
}
|
||||
}
|
||||
public static final class UnicodeBlock extends Subset {}
|
||||
public static class Subset extends Object {}
|
||||
|
||||
/**
|
||||
* Keep pre-allocated instances of the first 128 characters
|
||||
* on the basis that these will be used most frequently.
|
||||
*/
|
||||
private static class Cache {
|
||||
private static final Character[] cache = new Character[128];
|
||||
|
||||
static {
|
||||
for (int c = 0; c < cache.length; ++c) {
|
||||
cache[c] = new Character((char) c);
|
||||
}
|
||||
}
|
||||
|
||||
private Cache() {}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.Comparable}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public interface Comparable<T> extends java.lang.Comparable<T> {
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pinned exceptions inherit from {@link java.lang.Throwable}, but we
|
||||
* still need to be able to pass them through the sandbox's
|
||||
* exception handlers. In which case we will wrap them inside
|
||||
* one of these.
|
||||
*
|
||||
* Exceptions wrapped inside one of these cannot be caught.
|
||||
*
|
||||
* Also used for passing exceptions through finally blocks without
|
||||
* any expensive unwrapping to {@link sandbox.java.lang.Throwable}
|
||||
* based types.
|
||||
*/
|
||||
final class DJVMThrowableWrapper extends Throwable {
|
||||
private final java.lang.Throwable throwable;
|
||||
|
||||
DJVMThrowableWrapper(java.lang.Throwable t) {
|
||||
throwable = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent this wrapper from creating its own stack trace.
|
||||
*/
|
||||
@Override
|
||||
public final Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
final java.lang.Throwable fromDJVM() {
|
||||
return throwable;
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Double extends Number implements Comparable<Double> {
|
||||
public static final double POSITIVE_INFINITY = java.lang.Double.POSITIVE_INFINITY;
|
||||
public static final double NEGATIVE_INFINITY = java.lang.Double.NEGATIVE_INFINITY;
|
||||
public static final double NaN = java.lang.Double.NaN;
|
||||
public static final double MAX_VALUE = java.lang.Double.MAX_VALUE;
|
||||
public static final double MIN_NORMAL = java.lang.Double.MIN_NORMAL;
|
||||
public static final double MIN_VALUE = java.lang.Double.MIN_VALUE;
|
||||
public static final int MAX_EXPONENT = java.lang.Double.MAX_EXPONENT;
|
||||
public static final int MIN_EXPONENT = java.lang.Double.MIN_EXPONENT;
|
||||
public static final int BYTES = java.lang.Double.BYTES;
|
||||
public static final int SIZE = java.lang.Double.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Double> TYPE = (Class) java.lang.Double.TYPE;
|
||||
|
||||
private final double value;
|
||||
|
||||
public Double(double value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Double(String s) throws NumberFormatException {
|
||||
this.value = parseDouble(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return java.lang.Double.isNaN(value);
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return isInfinite(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Double) && doubleToLongBits(((Double)other).value) == doubleToLongBits(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(double d) {
|
||||
return java.lang.Double.hashCode(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Double.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Double fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Double other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static String toString(double d) {
|
||||
return String.toDJVM(java.lang.Double.toString(d));
|
||||
}
|
||||
|
||||
public static String toHexString(double d) {
|
||||
return String.toDJVM(java.lang.Double.toHexString(d));
|
||||
}
|
||||
|
||||
public static Double valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Double.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Double valueOf(double d) {
|
||||
return new Double(d);
|
||||
}
|
||||
|
||||
public static double parseDouble(String s) throws NumberFormatException {
|
||||
return java.lang.Double.parseDouble(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static boolean isNaN(double d) {
|
||||
return java.lang.Double.isNaN(d);
|
||||
}
|
||||
|
||||
public static boolean isInfinite(double d) {
|
||||
return java.lang.Double.isInfinite(d);
|
||||
}
|
||||
|
||||
public static boolean isFinite(double d) {
|
||||
return java.lang.Double.isFinite(d);
|
||||
}
|
||||
|
||||
public static long doubleToLongBits(double d) {
|
||||
return java.lang.Double.doubleToLongBits(d);
|
||||
}
|
||||
|
||||
public static long doubleToRawLongBits(double d) {
|
||||
return java.lang.Double.doubleToRawLongBits(d);
|
||||
}
|
||||
|
||||
public static double longBitsToDouble(long bits) {
|
||||
return java.lang.Double.longBitsToDouble(bits);
|
||||
}
|
||||
|
||||
public static int compare(double d1, double d2) {
|
||||
return java.lang.Double.compare(d1, d2);
|
||||
}
|
||||
|
||||
public static double sum(double a, double b) {
|
||||
return java.lang.Double.sum(a, b);
|
||||
}
|
||||
|
||||
public static double max(double a, double b) {
|
||||
return java.lang.Double.max(a, b);
|
||||
}
|
||||
|
||||
public static double min(double a, double b) {
|
||||
return java.lang.Double.min(a, b);
|
||||
}
|
||||
|
||||
public static Double toDJVM(java.lang.Double d) {
|
||||
return (d == null) ? null : valueOf(d);
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class. We will load the actual Enum class at run-time.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable {
|
||||
|
||||
private final String name;
|
||||
private final int ordinal;
|
||||
|
||||
protected Enum(String name, int ordinal) {
|
||||
this.name = name;
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int ordinal() {
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
final java.lang.Enum<?> fromDJVM() {
|
||||
throw new UnsupportedOperationException("Dummy implementation");
|
||||
}
|
||||
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Float extends Number implements Comparable<Float> {
|
||||
public static final float POSITIVE_INFINITY = java.lang.Float.POSITIVE_INFINITY;
|
||||
public static final float NEGATIVE_INFINITY = java.lang.Float.NEGATIVE_INFINITY;
|
||||
public static final float NaN = java.lang.Float.NaN;
|
||||
public static final float MAX_VALUE = java.lang.Float.MAX_VALUE;
|
||||
public static final float MIN_NORMAL = java.lang.Float.MIN_NORMAL;
|
||||
public static final float MIN_VALUE = java.lang.Float.MIN_VALUE;
|
||||
public static final int MAX_EXPONENT = java.lang.Float.MAX_EXPONENT;
|
||||
public static final int MIN_EXPONENT = java.lang.Float.MIN_EXPONENT;
|
||||
public static final int BYTES = java.lang.Float.BYTES;
|
||||
public static final int SIZE = java.lang.Float.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Float> TYPE = (Class) java.lang.Float.TYPE;
|
||||
|
||||
private final float value;
|
||||
|
||||
public Float(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Float(String s) throws NumberFormatException {
|
||||
this.value = parseFloat(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(float f) {
|
||||
return java.lang.Float.hashCode(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return other instanceof Float && floatToIntBits(((Float)other).value) == floatToIntBits(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Float.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Float fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Float other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public boolean isNaN() {
|
||||
return isNaN(value);
|
||||
}
|
||||
|
||||
public boolean isInfinite() {
|
||||
return isInfinite(value);
|
||||
}
|
||||
|
||||
public static String toString(float f) {
|
||||
return String.valueOf(f);
|
||||
}
|
||||
|
||||
public static String toHexString(float f) {
|
||||
return String.toDJVM(java.lang.Float.toHexString(f));
|
||||
}
|
||||
|
||||
public static Float valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Float.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Float valueOf(float f) {
|
||||
return new Float(f);
|
||||
}
|
||||
|
||||
public static float parseFloat(String s) throws NumberFormatException {
|
||||
return java.lang.Float.parseFloat(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static boolean isNaN(float f) {
|
||||
return java.lang.Float.isNaN(f);
|
||||
}
|
||||
|
||||
public static boolean isInfinite(float f) {
|
||||
return java.lang.Float.isInfinite(f);
|
||||
}
|
||||
|
||||
public static boolean isFinite(float f) {
|
||||
return java.lang.Float.isFinite(f);
|
||||
}
|
||||
|
||||
public static int floatToIntBits(float f) {
|
||||
return java.lang.Float.floatToIntBits(f);
|
||||
}
|
||||
|
||||
public static int floatToRawIntBits(float f) {
|
||||
return java.lang.Float.floatToIntBits(f);
|
||||
}
|
||||
|
||||
public static float intBitsToFloat(int bits) {
|
||||
return java.lang.Float.intBitsToFloat(bits);
|
||||
}
|
||||
|
||||
public static int compare(float f1, float f2) {
|
||||
return java.lang.Float.compare(f1, f2);
|
||||
}
|
||||
|
||||
public static float sum(float a, float b) {
|
||||
return java.lang.Float.sum(a, b);
|
||||
}
|
||||
|
||||
public static float max(float a, float b) {
|
||||
return java.lang.Float.max(a, b);
|
||||
}
|
||||
|
||||
public static float min(float a, float b) {
|
||||
return java.lang.Float.min(a, b);
|
||||
}
|
||||
|
||||
public static Float toDJVM(java.lang.Float f) {
|
||||
return (f == null) ? null : valueOf(f);
|
||||
}
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Integer extends Number implements Comparable<Integer> {
|
||||
|
||||
public static final int MIN_VALUE = java.lang.Integer.MIN_VALUE;
|
||||
public static final int MAX_VALUE = java.lang.Integer.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Integer.BYTES;
|
||||
public static final int SIZE = java.lang.Integer.SIZE;
|
||||
|
||||
static final int[] SIZE_TABLE = new int[] { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, MAX_VALUE };
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Integer> TYPE = (Class) java.lang.Integer.TYPE;
|
||||
|
||||
private final int value;
|
||||
|
||||
public Integer(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Integer(String s) throws NumberFormatException {
|
||||
this.value = parseInt(s, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(int i) {
|
||||
return java.lang.Integer.hashCode(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Integer) && (value == ((Integer) other).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Integer other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Integer fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String toString(int i, int radix) {
|
||||
return String.toDJVM(java.lang.Integer.toString(i, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(int i, int radix) {
|
||||
return String.toDJVM(java.lang.Integer.toUnsignedString(i, radix));
|
||||
}
|
||||
|
||||
public static String toHexString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toHexString(i));
|
||||
}
|
||||
|
||||
public static String toOctalString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toOctalString(i));
|
||||
}
|
||||
|
||||
public static String toBinaryString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toBinaryString(i));
|
||||
}
|
||||
|
||||
public static String toString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toString(i));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(int i) {
|
||||
return String.toDJVM(java.lang.Integer.toUnsignedString(i));
|
||||
}
|
||||
|
||||
public static int parseInt(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Integer.parseInt(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static int parseInt(String s) throws NumberFormatException {
|
||||
return java.lang.Integer.parseInt(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static int parseUnsignedInt(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Integer.parseUnsignedInt(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static int parseUnsignedInt(String s) throws NumberFormatException {
|
||||
return java.lang.Integer.parseUnsignedInt(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Integer valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Integer.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Integer valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Integer.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Integer valueOf(int i) {
|
||||
return new Integer(i);
|
||||
}
|
||||
|
||||
public static Integer decode(String nm) throws NumberFormatException {
|
||||
return new Integer(java.lang.Integer.decode(String.fromDJVM(nm)));
|
||||
}
|
||||
|
||||
public static int compare(int x, int y) {
|
||||
return java.lang.Integer.compare(x, y);
|
||||
}
|
||||
|
||||
public static int compareUnsigned(int x, int y) {
|
||||
return java.lang.Integer.compareUnsigned(x, y);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(int x) {
|
||||
return java.lang.Integer.toUnsignedLong(x);
|
||||
}
|
||||
|
||||
public static int divideUnsigned(int dividend, int divisor) {
|
||||
return java.lang.Integer.divideUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static int remainderUnsigned(int dividend, int divisor) {
|
||||
return java.lang.Integer.remainderUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static int highestOneBit(int i) {
|
||||
return java.lang.Integer.highestOneBit(i);
|
||||
}
|
||||
|
||||
public static int lowestOneBit(int i) {
|
||||
return java.lang.Integer.lowestOneBit(i);
|
||||
}
|
||||
|
||||
public static int numberOfLeadingZeros(int i) {
|
||||
return java.lang.Integer.numberOfLeadingZeros(i);
|
||||
}
|
||||
|
||||
public static int numberOfTrailingZeros(int i) {
|
||||
return java.lang.Integer.numberOfTrailingZeros(i);
|
||||
}
|
||||
|
||||
public static int bitCount(int i) {
|
||||
return java.lang.Integer.bitCount(i);
|
||||
}
|
||||
|
||||
public static int rotateLeft(int i, int distance) {
|
||||
return java.lang.Integer.rotateLeft(i, distance);
|
||||
}
|
||||
|
||||
public static int rotateRight(int i, int distance) {
|
||||
return java.lang.Integer.rotateRight(i, distance);
|
||||
}
|
||||
|
||||
public static int reverse(int i) {
|
||||
return java.lang.Integer.reverse(i);
|
||||
}
|
||||
|
||||
public static int signum(int i) {
|
||||
return java.lang.Integer.signum(i);
|
||||
}
|
||||
|
||||
public static int reverseBytes(int i) {
|
||||
return java.lang.Integer.reverseBytes(i);
|
||||
}
|
||||
|
||||
public static int sum(int a, int b) {
|
||||
return java.lang.Integer.sum(a, b);
|
||||
}
|
||||
|
||||
public static int max(int a, int b) {
|
||||
return java.lang.Integer.max(a, b);
|
||||
}
|
||||
|
||||
public static int min(int a, int b) {
|
||||
return java.lang.Integer.min(a, b);
|
||||
}
|
||||
|
||||
public static Integer toDJVM(java.lang.Integer i) {
|
||||
return (i == null) ? null : valueOf(i);
|
||||
}
|
||||
|
||||
static int stringSize(final int number) {
|
||||
int i = 0;
|
||||
while (number > SIZE_TABLE[i]) {
|
||||
++i;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
static void getChars(final int number, int index, char[] buffer) {
|
||||
java.lang.String s = java.lang.Integer.toString(number);
|
||||
int length = s.length();
|
||||
|
||||
while (length > 0) {
|
||||
buffer[--index] = s.charAt(--length);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.Iterable}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public interface Iterable<T> extends java.lang.Iterable<T> {
|
||||
@Override
|
||||
@NotNull
|
||||
Iterator<T> iterator();
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Long extends Number implements Comparable<Long> {
|
||||
|
||||
public static final long MIN_VALUE = java.lang.Long.MIN_VALUE;
|
||||
public static final long MAX_VALUE = java.lang.Long.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Long.BYTES;
|
||||
public static final int SIZE = java.lang.Long.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Long> TYPE = (Class) java.lang.Long.TYPE;
|
||||
|
||||
private final long value;
|
||||
|
||||
public Long(long value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Long(String s) throws NumberFormatException {
|
||||
this.value = parseLong(s, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Long) && ((Long) other).longValue() == value;
|
||||
}
|
||||
|
||||
public static int hashCode(long l) {
|
||||
return java.lang.Long.hashCode(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return (short) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Long other) {
|
||||
return compare(value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(long x, long y) {
|
||||
return java.lang.Long.compare(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Long fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Long.toString(value);
|
||||
}
|
||||
|
||||
public static String toString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toString(l));
|
||||
}
|
||||
|
||||
public static String toString(long l, int radix) {
|
||||
return String.toDJVM(java.lang.Long.toString(l, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(long l, int radix) {
|
||||
return String.toDJVM(java.lang.Long.toUnsignedString(l, radix));
|
||||
}
|
||||
|
||||
public static String toUnsignedString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toUnsignedString(l));
|
||||
}
|
||||
|
||||
public static String toHexString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toHexString(l));
|
||||
}
|
||||
|
||||
public static String toOctalString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toOctalString(l));
|
||||
}
|
||||
|
||||
public static String toBinaryString(long l) {
|
||||
return String.toDJVM(java.lang.Long.toBinaryString(l));
|
||||
}
|
||||
|
||||
public static long parseLong(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Long.parseLong(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static long parseLong(String s) throws NumberFormatException {
|
||||
return java.lang.Long.parseLong(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static long parseUnsignedLong(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Long.parseUnsignedLong(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static long parseUnsignedLong(String s) throws NumberFormatException {
|
||||
return java.lang.Long.parseUnsignedLong(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Long valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Long valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Long valueOf(long l) {
|
||||
return new Long(l);
|
||||
}
|
||||
|
||||
public static Long decode(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Long.decode(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static int compareUnsigned(long x, long y) {
|
||||
return java.lang.Long.compareUnsigned(x, y);
|
||||
}
|
||||
|
||||
public static long divideUnsigned(long dividend, long divisor) {
|
||||
return java.lang.Long.divideUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static long remainderUnsigned(long dividend, long divisor) {
|
||||
return java.lang.Long.remainderUnsigned(dividend, divisor);
|
||||
}
|
||||
|
||||
public static long highestOneBit(long l) {
|
||||
return java.lang.Long.highestOneBit(l);
|
||||
}
|
||||
|
||||
public static long lowestOneBit(long l) {
|
||||
return java.lang.Long.lowestOneBit(l);
|
||||
}
|
||||
|
||||
public static int numberOfLeadingZeros(long l) {
|
||||
return java.lang.Long.numberOfLeadingZeros(l);
|
||||
}
|
||||
|
||||
public static int numberOfTrailingZeros(long l) {
|
||||
return java.lang.Long.numberOfTrailingZeros(l);
|
||||
}
|
||||
|
||||
public static int bitCount(long l) {
|
||||
return java.lang.Long.bitCount(l);
|
||||
}
|
||||
|
||||
public static long rotateLeft(long i, int distance) {
|
||||
return java.lang.Long.rotateLeft(i, distance);
|
||||
}
|
||||
|
||||
public static long rotateRight(long i, int distance) {
|
||||
return java.lang.Long.rotateRight(i, distance);
|
||||
}
|
||||
|
||||
public static long reverse(long l) {
|
||||
return java.lang.Long.reverse(l);
|
||||
}
|
||||
|
||||
public static int signum(long l) {
|
||||
return java.lang.Long.signum(l);
|
||||
}
|
||||
|
||||
public static long reverseBytes(long l) {
|
||||
return java.lang.Long.reverseBytes(l);
|
||||
}
|
||||
|
||||
public static long sum(long a, long b) {
|
||||
return java.lang.Long.sum(a, b);
|
||||
}
|
||||
|
||||
public static long max(long a, long b) {
|
||||
return java.lang.Long.max(a, b);
|
||||
}
|
||||
|
||||
public static long min(long a, long b) {
|
||||
return java.lang.Long.min(a, b);
|
||||
}
|
||||
|
||||
public static Long toDJVM(java.lang.Long l) {
|
||||
return (l == null) ? null : valueOf(l);
|
||||
}
|
||||
|
||||
static int stringSize(final long number) {
|
||||
long l = 10;
|
||||
int i = 1;
|
||||
|
||||
while ((i < 19) && (number >= l)) {
|
||||
l *= 10;
|
||||
++i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void getChars(final long number, int index, char[] buffer) {
|
||||
java.lang.String s = java.lang.Long.toString(number);
|
||||
int length = s.length();
|
||||
|
||||
while (length > 0) {
|
||||
buffer[--index] = s.charAt(--length);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Number extends Object implements Serializable {
|
||||
|
||||
public abstract double doubleValue();
|
||||
public abstract float floatValue();
|
||||
public abstract long longValue();
|
||||
public abstract int intValue();
|
||||
public abstract short shortValue();
|
||||
public abstract byte byteValue();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return String.toDJVM(toString());
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sandbox.net.corda.djvm.rules.RuleViolationError;
|
||||
|
||||
public class Object {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return sandbox.java.lang.System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return toDJVMString().toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return String.toDJVM("sandbox.java.lang.Object@" + java.lang.Integer.toString(hashCode(), 16));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
java.lang.Object fromDJVM() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static java.lang.Object[] fromDJVM(java.lang.Object[] args) {
|
||||
if (args == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
java.lang.Object[] unwrapped = (java.lang.Object[]) java.lang.reflect.Array.newInstance(
|
||||
fromDJVM(args.getClass().getComponentType()), args.length
|
||||
);
|
||||
int i = 0;
|
||||
for (java.lang.Object arg : args) {
|
||||
unwrapped[i] = unwrap(arg);
|
||||
++i;
|
||||
}
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
private static java.lang.Object unwrap(java.lang.Object arg) {
|
||||
if (arg instanceof Object) {
|
||||
return ((Object) arg).fromDJVM();
|
||||
} else if (java.lang.Object[].class.isAssignableFrom(arg.getClass())) {
|
||||
return fromDJVM((java.lang.Object[]) arg);
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> fromDJVM(Class<?> type) {
|
||||
try {
|
||||
return DJVM.fromDJVMType(type);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuleViolationError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static java.util.Locale fromDJVM(sandbox.java.util.Locale locale) {
|
||||
return java.util.Locale.forLanguageTag(locale.toLanguageTag().fromDJVM());
|
||||
}
|
||||
|
||||
static java.nio.charset.Charset fromDJVM(sandbox.java.nio.charset.Charset charset) {
|
||||
return java.nio.charset.Charset.forName(charset.name().fromDJVM());
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class Runtime extends Object {
|
||||
private static final Runtime RUNTIME = new Runtime();
|
||||
|
||||
private Runtime() {}
|
||||
|
||||
public static Runtime getRuntime() {
|
||||
return RUNTIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything inside the sandbox is single-threaded.
|
||||
* @return 1
|
||||
*/
|
||||
public int availableProcessors() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void loadLibrary(String libraryName) {}
|
||||
|
||||
public void load(String fileName) {}
|
||||
|
||||
public void runFinalization() {}
|
||||
public void gc() {}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public final class Short extends Number implements Comparable<Short> {
|
||||
public static final short MIN_VALUE = java.lang.Short.MIN_VALUE;
|
||||
public static final short MAX_VALUE = java.lang.Short.MAX_VALUE;
|
||||
public static final int BYTES = java.lang.Short.BYTES;
|
||||
public static final int SIZE = java.lang.Short.SIZE;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Class<Short> TYPE = (Class) java.lang.Short.TYPE;
|
||||
|
||||
private final short value;
|
||||
|
||||
public Short(short value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Short(String s) throws NumberFormatException {
|
||||
this.value = parseShort(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return (double)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return java.lang.Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Short fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode(value);
|
||||
}
|
||||
|
||||
public static int hashCode(short value) {
|
||||
return java.lang.Short.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof Short) && ((Short) other).value == value;
|
||||
}
|
||||
|
||||
public int compareTo(@NotNull Short other) {
|
||||
return compare(this.value, other.value);
|
||||
}
|
||||
|
||||
public static int compare(short x, short y) {
|
||||
return java.lang.Short.compare(x, y);
|
||||
}
|
||||
|
||||
public static short reverseBytes(short value) {
|
||||
return java.lang.Short.reverseBytes(value);
|
||||
}
|
||||
|
||||
public static int toUnsignedInt(short x) {
|
||||
return java.lang.Short.toUnsignedInt(x);
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(short x) {
|
||||
return java.lang.Short.toUnsignedLong(x);
|
||||
}
|
||||
|
||||
public static short parseShort(String s, int radix) throws NumberFormatException {
|
||||
return java.lang.Short.parseShort(String.fromDJVM(s), radix);
|
||||
}
|
||||
|
||||
public static short parseShort(String s) throws NumberFormatException {
|
||||
return java.lang.Short.parseShort(String.fromDJVM(s));
|
||||
}
|
||||
|
||||
public static Short valueOf(String s, int radix) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.valueOf(String.fromDJVM(s), radix));
|
||||
}
|
||||
|
||||
public static Short valueOf(String s) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.valueOf(String.fromDJVM(s)));
|
||||
}
|
||||
|
||||
public static Short valueOf(short s) {
|
||||
return new Short(s);
|
||||
}
|
||||
|
||||
public static Short decode(String nm) throws NumberFormatException {
|
||||
return toDJVM(java.lang.Short.decode(String.fromDJVM(nm)));
|
||||
}
|
||||
|
||||
public static Short toDJVM(java.lang.Short i) {
|
||||
return (i == null) ? null : valueOf(i);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This is a dummy class. We will load the genuine class at runtime.
|
||||
*/
|
||||
public final class StackTraceElement extends Object implements java.io.Serializable {
|
||||
|
||||
private final String className;
|
||||
private final String methodName;
|
||||
private final String fileName;
|
||||
private final int lineNumber;
|
||||
|
||||
public StackTraceElement(String className, String methodName, String fileName, int lineNumber) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.fileName = fileName;
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return String.toDJVM(
|
||||
className.toString() + ':' + methodName.toString()
|
||||
+ (fileName != null ? '(' + fileName.toString() + ':' + lineNumber + ')' : "")
|
||||
);
|
||||
}
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import net.corda.djvm.SandboxRuntimeContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sandbox.java.nio.charset.Charset;
|
||||
import sandbox.java.util.Comparator;
|
||||
import sandbox.java.util.Locale;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class String extends Object implements Comparable<String>, CharSequence, Serializable {
|
||||
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
|
||||
|
||||
private static class CaseInsensitiveComparator extends Object implements Comparator<String>, Serializable {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
return java.lang.String.CASE_INSENSITIVE_ORDER.compare(String.fromDJVM(s1), String.fromDJVM(s2));
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TRUE = new String("true");
|
||||
private static final String FALSE = new String("false");
|
||||
|
||||
private static final Constructor SHARED;
|
||||
|
||||
static {
|
||||
try {
|
||||
SHARED = java.lang.String.class.getDeclaredConstructor(char[].class, java.lang.Boolean.TYPE);
|
||||
SHARED.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new NoSuchMethodError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private final java.lang.String value;
|
||||
|
||||
public String() {
|
||||
this.value = "";
|
||||
}
|
||||
|
||||
public String(java.lang.String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String(char value[]) {
|
||||
this.value = new java.lang.String(value);
|
||||
}
|
||||
|
||||
public String(char value[], int offset, int count) {
|
||||
this.value = new java.lang.String(value, offset, count);
|
||||
}
|
||||
|
||||
public String(int[] codePoints, int offset, int count) {
|
||||
this.value = new java.lang.String(codePoints, offset, count);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String(byte ascii[], int hibyte, int offset, int count) {
|
||||
this.value = new java.lang.String(ascii, hibyte, offset, count);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String(byte ascii[], int hibyte) {
|
||||
this.value = new java.lang.String(ascii, hibyte);
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length, String charsetName)
|
||||
throws UnsupportedEncodingException {
|
||||
this.value = new java.lang.String(bytes, offset, length, fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length, Charset charset) {
|
||||
this.value = new java.lang.String(bytes, offset, length, fromDJVM(charset));
|
||||
}
|
||||
|
||||
public String(byte bytes[], String charsetName)
|
||||
throws UnsupportedEncodingException {
|
||||
this.value = new java.lang.String(bytes, fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public String(byte bytes[], Charset charset) {
|
||||
this.value = new java.lang.String(bytes, fromDJVM(charset));
|
||||
}
|
||||
|
||||
public String(byte bytes[], int offset, int length) {
|
||||
this.value = new java.lang.String(bytes, offset, length);
|
||||
}
|
||||
|
||||
public String(byte bytes[]) {
|
||||
this.value = new java.lang.String(bytes);
|
||||
}
|
||||
|
||||
public String(StringBuffer buffer) {
|
||||
this.value = buffer.toString();
|
||||
}
|
||||
|
||||
public String(StringBuilder builder) {
|
||||
this.value = builder.toString();
|
||||
}
|
||||
|
||||
String(char[] value, boolean share) {
|
||||
java.lang.String newValue;
|
||||
try {
|
||||
// This is (presumably) an optimisation for memory usage.
|
||||
newValue = (java.lang.String) SHARED.newInstance(value, share);
|
||||
} catch (Exception e) {
|
||||
newValue = new java.lang.String(value);
|
||||
}
|
||||
this.value = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return value.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return value.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return value.isEmpty();
|
||||
}
|
||||
|
||||
public int codePointAt(int index) {
|
||||
return value.codePointAt(index);
|
||||
}
|
||||
|
||||
public int codePointBefore(int index) {
|
||||
return value.codePointBefore(index);
|
||||
}
|
||||
|
||||
public int codePointCount(int beginIndex, int endIndex) {
|
||||
return value.codePointCount(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
public int offsetByCodePoints(int index, int codePointOffset) {
|
||||
return value.offsetByCodePoints(index, codePointOffset);
|
||||
}
|
||||
|
||||
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
|
||||
value.getChars(srcBegin, srcEnd, dst, dstBegin);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
|
||||
value.getBytes(srcBegin, srcEnd, dst, dstBegin);
|
||||
}
|
||||
|
||||
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
|
||||
return value.getBytes(fromDJVM(charsetName));
|
||||
}
|
||||
|
||||
public byte[] getBytes(Charset charset) {
|
||||
return value.getBytes(fromDJVM(charset));
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return value.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(java.lang.Object other) {
|
||||
return (other instanceof String) && ((String) other).value.equals(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public java.lang.String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.String fromDJVM() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean contentEquals(StringBuffer sb) {
|
||||
return value.contentEquals((CharSequence) sb);
|
||||
}
|
||||
|
||||
public boolean contentEquals(CharSequence cs) {
|
||||
return value.contentEquals(cs);
|
||||
}
|
||||
|
||||
public boolean equalsIgnoreCase(String anotherString) {
|
||||
return value.equalsIgnoreCase(fromDJVM(anotherString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return toDJVM((java.lang.String) value.subSequence(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull String other) {
|
||||
return value.compareTo(other.toString());
|
||||
}
|
||||
|
||||
public int compareToIgnoreCase(String str) {
|
||||
return value.compareToIgnoreCase(fromDJVM(str));
|
||||
}
|
||||
|
||||
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
|
||||
return value.regionMatches(toffset, fromDJVM(other), ooffset, len);
|
||||
}
|
||||
|
||||
public boolean regionMatches(boolean ignoreCase, int toffset,
|
||||
String other, int ooffset, int len) {
|
||||
return value.regionMatches(ignoreCase, toffset, fromDJVM(other), ooffset, len);
|
||||
}
|
||||
|
||||
public boolean startsWith(String prefix, int toffset) {
|
||||
return value.startsWith(fromDJVM(prefix), toffset);
|
||||
}
|
||||
|
||||
public boolean startsWith(String prefix) {
|
||||
return value.startsWith(fromDJVM(prefix));
|
||||
}
|
||||
|
||||
public boolean endsWith(String suffix) {
|
||||
return value.endsWith(fromDJVM(suffix));
|
||||
}
|
||||
|
||||
public int indexOf(int ch) {
|
||||
return value.indexOf(ch);
|
||||
}
|
||||
|
||||
public int indexOf(int ch, int fromIndex) {
|
||||
return value.indexOf(ch, fromIndex);
|
||||
}
|
||||
|
||||
public int lastIndexOf(int ch) {
|
||||
return value.lastIndexOf(ch);
|
||||
}
|
||||
|
||||
public int lastIndexOf(int ch, int fromIndex) {
|
||||
return value.lastIndexOf(ch, fromIndex);
|
||||
}
|
||||
|
||||
public int indexOf(String str) {
|
||||
return value.indexOf(fromDJVM(str));
|
||||
}
|
||||
|
||||
public int indexOf(String str, int fromIndex) {
|
||||
return value.indexOf(fromDJVM(str), fromIndex);
|
||||
}
|
||||
|
||||
public int lastIndexOf(String str) {
|
||||
return value.lastIndexOf(fromDJVM(str));
|
||||
}
|
||||
|
||||
public int lastIndexOf(String str, int fromIndex) {
|
||||
return value.lastIndexOf(fromDJVM(str), fromIndex);
|
||||
}
|
||||
|
||||
public String substring(int beginIndex) {
|
||||
return toDJVM(value.substring(beginIndex));
|
||||
}
|
||||
|
||||
public String substring(int beginIndex, int endIndex) {
|
||||
return toDJVM(value.substring(beginIndex, endIndex));
|
||||
}
|
||||
|
||||
public String concat(String str) {
|
||||
return toDJVM(value.concat(fromDJVM(str)));
|
||||
}
|
||||
|
||||
public String replace(char oldChar, char newChar) {
|
||||
return toDJVM(value.replace(oldChar, newChar));
|
||||
}
|
||||
|
||||
public boolean matches(String regex) {
|
||||
return value.matches(fromDJVM(regex));
|
||||
}
|
||||
|
||||
public boolean contains(CharSequence s) {
|
||||
return value.contains(s);
|
||||
}
|
||||
|
||||
public String replaceFirst(String regex, String replacement) {
|
||||
return toDJVM(value.replaceFirst(fromDJVM(regex), fromDJVM(replacement)));
|
||||
}
|
||||
|
||||
public String replaceAll(String regex, String replacement) {
|
||||
return toDJVM(value.replaceAll(fromDJVM(regex), fromDJVM(replacement)));
|
||||
}
|
||||
|
||||
public String replace(CharSequence target, CharSequence replacement) {
|
||||
return toDJVM(value.replace(target, replacement));
|
||||
}
|
||||
|
||||
public String[] split(String regex, int limit) {
|
||||
return toDJVM(value.split(fromDJVM(regex), limit));
|
||||
}
|
||||
|
||||
public String[] split(String regex) {
|
||||
return toDJVM(value.split(fromDJVM(regex)));
|
||||
}
|
||||
|
||||
public String toLowerCase(Locale locale) {
|
||||
return toDJVM(value.toLowerCase(fromDJVM(locale)));
|
||||
}
|
||||
|
||||
public String toLowerCase() {
|
||||
return toDJVM(value.toLowerCase());
|
||||
}
|
||||
|
||||
public String toUpperCase(Locale locale) {
|
||||
return toDJVM(value.toUpperCase(fromDJVM(locale)));
|
||||
}
|
||||
|
||||
public String toUpperCase() {
|
||||
return toDJVM(value.toUpperCase());
|
||||
}
|
||||
|
||||
public String trim() {
|
||||
return toDJVM(value.trim());
|
||||
}
|
||||
|
||||
public String intern() { return (String) SandboxRuntimeContext.getInstance().intern(value, this); }
|
||||
|
||||
public char[] toCharArray() {
|
||||
return value.toCharArray();
|
||||
}
|
||||
|
||||
public static String format(String format, java.lang.Object... args) {
|
||||
return toDJVM(java.lang.String.format(fromDJVM(format), fromDJVM(args)));
|
||||
}
|
||||
|
||||
public static String format(Locale locale, String format, java.lang.Object... args) {
|
||||
return toDJVM(java.lang.String.format(fromDJVM(locale), fromDJVM(format), fromDJVM(args)));
|
||||
}
|
||||
|
||||
public static String join(CharSequence delimiter, CharSequence... elements) {
|
||||
return toDJVM(java.lang.String.join(delimiter, elements));
|
||||
}
|
||||
|
||||
public static String join(CharSequence delimiter,
|
||||
Iterable<? extends CharSequence> elements) {
|
||||
return toDJVM(java.lang.String.join(delimiter, elements));
|
||||
}
|
||||
|
||||
public static String valueOf(java.lang.Object obj) {
|
||||
return (obj instanceof Object) ? ((Object) obj).toDJVMString() : toDJVM(java.lang.String.valueOf(obj));
|
||||
}
|
||||
|
||||
public static String valueOf(char data[]) {
|
||||
return toDJVM(java.lang.String.valueOf(data));
|
||||
}
|
||||
|
||||
public static String valueOf(char data[], int offset, int count) {
|
||||
return toDJVM(java.lang.String.valueOf(data, offset, count));
|
||||
}
|
||||
|
||||
public static String copyValueOf(char data[], int offset, int count) {
|
||||
return toDJVM(java.lang.String.copyValueOf(data, offset, count));
|
||||
}
|
||||
|
||||
public static String copyValueOf(char data[]) {
|
||||
return toDJVM(java.lang.String.copyValueOf(data));
|
||||
}
|
||||
|
||||
public static String valueOf(boolean b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
public static String valueOf(char c) {
|
||||
return toDJVM(java.lang.String.valueOf(c));
|
||||
}
|
||||
|
||||
public static String valueOf(int i) {
|
||||
return toDJVM(java.lang.String.valueOf(i));
|
||||
}
|
||||
|
||||
public static String valueOf(long l) {
|
||||
return toDJVM(java.lang.String.valueOf(l));
|
||||
}
|
||||
|
||||
public static String valueOf(float f) {
|
||||
return toDJVM(java.lang.String.valueOf(f));
|
||||
}
|
||||
|
||||
public static String valueOf(double d) {
|
||||
return toDJVM(java.lang.String.valueOf(d));
|
||||
}
|
||||
|
||||
static String[] toDJVM(java.lang.String[] value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String[] result = new String[value.length];
|
||||
int i = 0;
|
||||
for (java.lang.String v : value) {
|
||||
result[i] = toDJVM(v);
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String toDJVM(java.lang.String value) {
|
||||
return (value == null) ? null : new String(value);
|
||||
}
|
||||
|
||||
public static java.lang.String fromDJVM(String value) {
|
||||
return (value == null) ? null : value.fromDJVM();
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.StringBuffer}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public abstract class StringBuffer extends Object implements CharSequence, Appendable, Serializable {
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(CharSequence seq);
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(CharSequence seq, int start, int end);
|
||||
|
||||
@Override
|
||||
public abstract StringBuffer append(char c);
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.lang.StringBuilder}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public abstract class StringBuilder extends Object implements Appendable, CharSequence, Serializable {
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(CharSequence seq);
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(CharSequence seq, int start, int end);
|
||||
|
||||
@Override
|
||||
public abstract StringBuilder append(char c);
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import net.corda.djvm.SandboxRuntimeContext;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public final class System extends Object {
|
||||
|
||||
private System() {}
|
||||
|
||||
public static int identityHashCode(java.lang.Object obj) {
|
||||
int nativeHashCode = java.lang.System.identityHashCode(obj);
|
||||
return SandboxRuntimeContext.getInstance().getHashCodeFor(nativeHashCode);
|
||||
}
|
||||
|
||||
public static final String lineSeparator = String.toDJVM("\n");
|
||||
|
||||
public static void arraycopy(java.lang.Object src, int srcPos, java.lang.Object dest, int destPos, int length) {
|
||||
java.lang.System.arraycopy(src, srcPos, dest, destPos, length);
|
||||
}
|
||||
|
||||
public static void runFinalization() {}
|
||||
public static void gc() {}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import sandbox.java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Everything inside the sandbox is single-threaded, so this
|
||||
* implementation of ThreadLocal is sufficient.
|
||||
* @param <T> Underlying type of this thread-local variable.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class ThreadLocal<T> extends Object {
|
||||
|
||||
private T value;
|
||||
private boolean isSet;
|
||||
|
||||
public ThreadLocal() {
|
||||
}
|
||||
|
||||
protected T initialValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
if (!isSet) {
|
||||
set(initialValue());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
this.value = value;
|
||||
this.isSet = true;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
value = null;
|
||||
isSet = false;
|
||||
}
|
||||
|
||||
public static <V> ThreadLocal<V> withInitial(Supplier<? extends V> supplier) {
|
||||
return new SuppliedThreadLocal<>(supplier);
|
||||
}
|
||||
|
||||
// Stub class for compiling ThreadLocal. The sandbox will import the
|
||||
// actual SuppliedThreadLocal class at run-time. Having said that, we
|
||||
// still need a working implementation here for the sake of our tests.
|
||||
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
|
||||
private final Supplier<? extends T> supplier;
|
||||
|
||||
SuppliedThreadLocal(Supplier<? extends T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T initialValue() {
|
||||
return supplier.get();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package sandbox.java.lang;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sandbox.TaskTypes;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class Throwable extends Object implements Serializable {
|
||||
private static final StackTraceElement[] NO_STACK_TRACE = new StackTraceElement[0];
|
||||
|
||||
private String message;
|
||||
private Throwable cause;
|
||||
private StackTraceElement[] stackTrace;
|
||||
|
||||
public Throwable() {
|
||||
this.cause = this;
|
||||
fillInStackTrace();
|
||||
}
|
||||
|
||||
public Throwable(String message) {
|
||||
this();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Throwable(Throwable cause) {
|
||||
this.cause = cause;
|
||||
this.message = (cause == null) ? null : cause.toDJVMString();
|
||||
fillInStackTrace();
|
||||
}
|
||||
|
||||
public Throwable(String message, Throwable cause) {
|
||||
this.message = message;
|
||||
this.cause = cause;
|
||||
fillInStackTrace();
|
||||
}
|
||||
|
||||
protected Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
if (writableStackTrace) {
|
||||
fillInStackTrace();
|
||||
} else {
|
||||
stackTrace = NO_STACK_TRACE;
|
||||
}
|
||||
this.message = message;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return getMessage();
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return (cause == this) ? null : cause;
|
||||
}
|
||||
|
||||
public Throwable initCause(Throwable cause) {
|
||||
if (this.cause != this) {
|
||||
throw new java.lang.IllegalStateException(
|
||||
"Can't overwrite cause with " + java.util.Objects.toString(cause, "a null"), fromDJVM());
|
||||
}
|
||||
if (cause == this) {
|
||||
throw new java.lang.IllegalArgumentException("Self-causation not permitted", fromDJVM());
|
||||
}
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toDJVMString() {
|
||||
java.lang.String s = getClass().getName();
|
||||
String localized = getLocalizedMessage();
|
||||
return String.valueOf((localized != null) ? (s + ": " + localized.toString()) : s);
|
||||
}
|
||||
|
||||
public StackTraceElement[] getStackTrace() {
|
||||
return (stackTrace == NO_STACK_TRACE) ? stackTrace : stackTrace.clone();
|
||||
}
|
||||
|
||||
public void setStackTrace(StackTraceElement[] stackTrace) {
|
||||
StackTraceElement[] traceCopy = stackTrace.clone();
|
||||
|
||||
for (int i = 0; i < traceCopy.length; ++i) {
|
||||
if (traceCopy[i] == null) {
|
||||
throw new java.lang.NullPointerException("stackTrace[" + i + ']');
|
||||
}
|
||||
}
|
||||
|
||||
this.stackTrace = traceCopy;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ThrowableNotThrown", "UnusedReturnValue"})
|
||||
public Throwable fillInStackTrace() {
|
||||
if (stackTrace == null) {
|
||||
/*
|
||||
* We have been invoked from within this exception's constructor.
|
||||
* Work our way up the stack trace until we find this constructor,
|
||||
* and then find out who actually invoked it. This is where our
|
||||
* sandboxed stack trace will start from.
|
||||
*
|
||||
* Our stack trace will end at the point where we entered the sandbox.
|
||||
*/
|
||||
final java.lang.StackTraceElement[] elements = new java.lang.Throwable().getStackTrace();
|
||||
final java.lang.String exceptionName = getClass().getName();
|
||||
int startIdx = 1;
|
||||
while (startIdx < elements.length && !isConstructorFor(elements[startIdx], exceptionName)) {
|
||||
++startIdx;
|
||||
}
|
||||
while (startIdx < elements.length && isConstructorFor(elements[startIdx], exceptionName)) {
|
||||
++startIdx;
|
||||
}
|
||||
|
||||
int endIdx = startIdx;
|
||||
while (endIdx < elements.length && !TaskTypes.isEntryPoint(elements[endIdx])) {
|
||||
++endIdx;
|
||||
}
|
||||
stackTrace = (startIdx == elements.length) ? NO_STACK_TRACE : DJVM.copyToDJVM(elements, startIdx, endIdx);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean isConstructorFor(java.lang.StackTraceElement elt, java.lang.String className) {
|
||||
return elt.getClassName().equals(className) && elt.getMethodName().equals("<init>");
|
||||
}
|
||||
|
||||
public void printStackTrace() {}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
java.lang.Throwable fromDJVM() {
|
||||
return DJVM.fromDJVM(this);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package sandbox.java.nio.charset;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.nio.charset.Charset}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class Charset extends sandbox.java.lang.Object {
|
||||
private final sandbox.java.lang.String canonicalName;
|
||||
|
||||
protected Charset(sandbox.java.lang.String canonicalName, sandbox.java.lang.String[] aliases) {
|
||||
this.canonicalName = canonicalName;
|
||||
}
|
||||
|
||||
public final sandbox.java.lang.String name() {
|
||||
return canonicalName;
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.util.Comparator}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Comparator<T> extends java.util.Comparator<T> {
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class to bootstrap us into the sandbox.
|
||||
*/
|
||||
public class LinkedHashMap<K, V> extends java.util.LinkedHashMap<K, V> implements Map<K, V> {
|
||||
public LinkedHashMap(int initialSize) {
|
||||
super(initialSize);
|
||||
}
|
||||
|
||||
public LinkedHashMap() {
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.util.Locale}
|
||||
* to allow us to compile {@link sandbox.java.lang.String}.
|
||||
*/
|
||||
public abstract class Locale extends sandbox.java.lang.Object {
|
||||
public abstract sandbox.java.lang.String toLanguageTag();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package sandbox.java.util;
|
||||
|
||||
/**
|
||||
* This is a dummy class to bootstrap us into the sandbox.
|
||||
*/
|
||||
public interface Map<K, V> extends java.util.Map<K, V> {
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package sandbox.java.util.function;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of {@link java.util.function.Function}
|
||||
* to allow us to compile {@link sandbox.Task}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Function<T, R> {
|
||||
R apply(T item);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package sandbox.java.util.function;
|
||||
|
||||
/**
|
||||
* This is a dummy class that implements just enough of @{link java.util.function.Supplier}
|
||||
* to allow us to compile {@link sandbox.java.lang.ThreadLocal}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Supplier<T> {
|
||||
T get();
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package sandbox.sun.misc;
|
||||
|
||||
import sandbox.java.lang.Enum;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public interface JavaLangAccess {
|
||||
|
||||
<E extends Enum<E>> E[] getEnumConstantsShared(Class<E> enumClass);
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package sandbox.sun.misc;
|
||||
|
||||
import sandbox.java.lang.Enum;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SharedSecrets extends sandbox.java.lang.Object {
|
||||
private static final JavaLangAccess javaLangAccess = new JavaLangAccessImpl();
|
||||
|
||||
private static class JavaLangAccessImpl implements JavaLangAccess {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <E extends Enum<E>> E[] getEnumConstantsShared(Class<E> enumClass) {
|
||||
return (E[]) sandbox.java.lang.DJVM.getEnumConstantsShared(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static JavaLangAccess getJavaLangAccess() {
|
||||
return javaLangAccess;
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
package net.corda.djvm
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.code.DefinitionProvider
|
||||
import net.corda.djvm.code.EMIT_TRACING
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.execution.ExecutionProfile
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.djvm.rules.Rule
|
||||
import net.corda.djvm.utilities.Discovery
|
||||
|
||||
/**
|
||||
* Configuration to use for the deterministic sandbox.
|
||||
*
|
||||
* @property rules The rules to apply during the analysis phase.
|
||||
* @property emitters The code emitters / re-writers to apply to all loaded classes.
|
||||
* @property definitionProviders The meta-data providers to apply to class and member definitions.
|
||||
* @property executionProfile The execution profile to use in the sandbox.
|
||||
* @property analysisConfiguration The configuration used in the analysis of classes.
|
||||
* @property parentClassLoader The [SandboxClassLoader] that this sandbox will use as a parent.
|
||||
*/
|
||||
class SandboxConfiguration private constructor(
|
||||
val rules: List<Rule>,
|
||||
val emitters: List<Emitter>,
|
||||
val definitionProviders: List<DefinitionProvider>,
|
||||
val executionProfile: ExecutionProfile,
|
||||
val analysisConfiguration: AnalysisConfiguration,
|
||||
val parentClassLoader: SandboxClassLoader?
|
||||
) {
|
||||
@Suppress("unused")
|
||||
companion object {
|
||||
/**
|
||||
* Default configuration for the deterministic sandbox.
|
||||
*/
|
||||
@JvmField
|
||||
val DEFAULT = SandboxConfiguration.of()
|
||||
|
||||
/**
|
||||
* Configuration with no emitters, rules, meta-data providers or runtime thresholds.
|
||||
*/
|
||||
@JvmField
|
||||
val EMPTY = SandboxConfiguration.of(
|
||||
ExecutionProfile.UNLIMITED, emptyList(), emptyList(), emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a sandbox configuration where one or more properties deviates from the default.
|
||||
*/
|
||||
fun of(
|
||||
profile: ExecutionProfile = ExecutionProfile.DEFAULT,
|
||||
rules: List<Rule> = Discovery.find(),
|
||||
emitters: List<Emitter>? = null,
|
||||
definitionProviders: List<DefinitionProvider> = Discovery.find(),
|
||||
enableTracing: Boolean = true,
|
||||
analysisConfiguration: AnalysisConfiguration = AnalysisConfiguration.createRoot(),
|
||||
parentClassLoader: SandboxClassLoader? = null
|
||||
) = SandboxConfiguration(
|
||||
executionProfile = profile,
|
||||
rules = rules,
|
||||
emitters = (emitters ?: Discovery.find()).filter {
|
||||
enableTracing || it.priority > EMIT_TRACING
|
||||
},
|
||||
definitionProviders = definitionProviders,
|
||||
analysisConfiguration = analysisConfiguration,
|
||||
parentClassLoader = parentClassLoader
|
||||
)
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package net.corda.djvm
|
||||
|
||||
import net.corda.djvm.costing.RuntimeCostSummary
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
|
||||
/**
|
||||
* The context in which a sandboxed operation is run.
|
||||
*
|
||||
* @property configuration The configuration of the sandbox.
|
||||
*/
|
||||
class SandboxRuntimeContext(val configuration: SandboxConfiguration) {
|
||||
|
||||
/**
|
||||
* The class loader to use inside the sandbox.
|
||||
*/
|
||||
val classLoader: SandboxClassLoader = SandboxClassLoader.createFor(configuration)
|
||||
|
||||
/**
|
||||
* A summary of the currently accumulated runtime costs (for, e.g., memory allocations, invocations, etc.).
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
fun use(action: SandboxRuntimeContext.() -> Unit) {
|
||||
SandboxRuntimeContext.instance = this
|
||||
try {
|
||||
action(this)
|
||||
} finally {
|
||||
threadLocalContext.remove()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
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.
|
||||
*/
|
||||
@JvmStatic
|
||||
var instance: SandboxRuntimeContext
|
||||
get() = threadLocalContext.get()
|
||||
?: throw IllegalStateException("SandboxContext has not been initialized before use")
|
||||
private set(value) {
|
||||
threadLocalContext.set(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.code.EmitterModule
|
||||
import net.corda.djvm.code.ruleViolationError
|
||||
import net.corda.djvm.code.thresholdViolationError
|
||||
import net.corda.djvm.messages.Severity
|
||||
import net.corda.djvm.references.ClassModule
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.references.MemberModule
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import net.corda.djvm.source.BootstrapClassLoader
|
||||
import net.corda.djvm.source.SourceClassLoader
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import org.objectweb.asm.Type
|
||||
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* The configuration to use for an analysis.
|
||||
*
|
||||
* @property whitelist The whitelist of class names.
|
||||
* @property pinnedClasses 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.
|
||||
* @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 analyzeAnnotations Analyze annotations despite not being explicitly referenced.
|
||||
* @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.
|
||||
* @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 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 private constructor(
|
||||
val whitelist: Whitelist,
|
||||
val pinnedClasses: Set<String>,
|
||||
val classResolver: ClassResolver,
|
||||
val exceptionResolver: ExceptionResolver,
|
||||
val minimumSeverityLevel: Severity,
|
||||
val analyzeAnnotations: Boolean,
|
||||
val prefixFilters: List<String>,
|
||||
val classModule: ClassModule,
|
||||
val memberModule: MemberModule,
|
||||
private val bootstrapClassLoader: BootstrapClassLoader?,
|
||||
val supportingClassLoader: SourceClassLoader,
|
||||
private val isRootConfiguration: Boolean
|
||||
) : Closeable {
|
||||
|
||||
/**
|
||||
* These interfaces are modified as they are mapped into the sandbox by
|
||||
* having their unsandboxed version "stitched in" as a super-interface.
|
||||
* And in some cases, we need to add some synthetic bridge methods as well.
|
||||
*/
|
||||
val stitchedInterfaces: Map<String, List<Member>> get() = STITCHED_INTERFACES
|
||||
|
||||
/**
|
||||
* These classes have extra methods added as they are mapped into the sandbox.
|
||||
*/
|
||||
val stitchedClasses: Map<String, List<Member>> get() = STITCHED_CLASSES
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
supportingClassLoader.use {
|
||||
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 isPinnedClass(className: String): Boolean = className in pinnedClasses
|
||||
|
||||
fun isJvmException(className: String): Boolean = className in JVM_EXCEPTIONS
|
||||
fun isSandboxClass(className: String): Boolean = className.startsWith(SANDBOX_PREFIX) && !isPinnedClass(className)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The package name prefix to use for classes loaded into a sandbox.
|
||||
*/
|
||||
const val SANDBOX_PREFIX: String = "sandbox/"
|
||||
|
||||
/**
|
||||
* These class must belong to the application class loader.
|
||||
* They should already exist within the sandbox namespace.
|
||||
*/
|
||||
private val MANDATORY_PINNED_CLASSES: Set<String> = setOf(
|
||||
RuntimeCostAccounter.TYPE_NAME,
|
||||
ruleViolationError,
|
||||
thresholdViolationError
|
||||
)
|
||||
|
||||
/**
|
||||
* These classes will be duplicated into every sandbox's
|
||||
* parent classloader.
|
||||
*/
|
||||
private val TEMPLATE_CLASSES: Set<String> = setOf(
|
||||
java.lang.Boolean::class.java,
|
||||
java.lang.Byte::class.java,
|
||||
java.lang.Character::class.java,
|
||||
java.lang.Double::class.java,
|
||||
java.lang.Float::class.java,
|
||||
java.lang.Integer::class.java,
|
||||
java.lang.Long::class.java,
|
||||
java.lang.Number::class.java,
|
||||
java.lang.Runtime::class.java,
|
||||
java.lang.Short::class.java,
|
||||
java.lang.String::class.java,
|
||||
java.lang.String.CASE_INSENSITIVE_ORDER::class.java,
|
||||
java.lang.System::class.java,
|
||||
java.lang.ThreadLocal::class.java,
|
||||
java.lang.Throwable::class.java,
|
||||
kotlin.Any::class.java,
|
||||
sun.misc.JavaLangAccess::class.java,
|
||||
sun.misc.SharedSecrets::class.java
|
||||
).sandboxed() + setOf(
|
||||
"sandbox/Task",
|
||||
"sandbox/TaskTypes",
|
||||
"sandbox/java/lang/Character\$Cache",
|
||||
"sandbox/java/lang/DJVM",
|
||||
"sandbox/java/lang/DJVMException",
|
||||
"sandbox/java/lang/DJVMThrowableWrapper",
|
||||
"sandbox/sun/misc/SharedSecrets\$1",
|
||||
"sandbox/sun/misc/SharedSecrets\$JavaLangAccessImpl"
|
||||
)
|
||||
|
||||
/**
|
||||
* These exceptions are thrown by the JVM itself, and
|
||||
* so we need to handle them without wrapping them.
|
||||
*
|
||||
* Note that this set is closed, i.e. every one
|
||||
* of these exceptions' [Throwable] super classes
|
||||
* is also within this set.
|
||||
*
|
||||
* The full list of exceptions is determined by:
|
||||
* hotspot/src/share/vm/classfile/vmSymbols.hpp
|
||||
*/
|
||||
val JVM_EXCEPTIONS: Set<String> = setOf(
|
||||
java.io.IOException::class.java,
|
||||
java.lang.AbstractMethodError::class.java,
|
||||
java.lang.ArithmeticException::class.java,
|
||||
java.lang.ArrayIndexOutOfBoundsException::class.java,
|
||||
java.lang.ArrayStoreException::class.java,
|
||||
java.lang.ClassCastException::class.java,
|
||||
java.lang.ClassCircularityError::class.java,
|
||||
java.lang.ClassFormatError::class.java,
|
||||
java.lang.ClassNotFoundException::class.java,
|
||||
java.lang.CloneNotSupportedException::class.java,
|
||||
java.lang.Error::class.java,
|
||||
java.lang.Exception::class.java,
|
||||
java.lang.ExceptionInInitializerError::class.java,
|
||||
java.lang.IllegalAccessError::class.java,
|
||||
java.lang.IllegalAccessException::class.java,
|
||||
java.lang.IllegalArgumentException::class.java,
|
||||
java.lang.IllegalStateException::class.java,
|
||||
java.lang.IncompatibleClassChangeError::class.java,
|
||||
java.lang.IndexOutOfBoundsException::class.java,
|
||||
java.lang.InstantiationError::class.java,
|
||||
java.lang.InstantiationException::class.java,
|
||||
java.lang.InternalError::class.java,
|
||||
java.lang.LinkageError::class.java,
|
||||
java.lang.NegativeArraySizeException::class.java,
|
||||
java.lang.NoClassDefFoundError::class.java,
|
||||
java.lang.NoSuchFieldError::class.java,
|
||||
java.lang.NoSuchFieldException::class.java,
|
||||
java.lang.NoSuchMethodError::class.java,
|
||||
java.lang.NoSuchMethodException::class.java,
|
||||
java.lang.NullPointerException::class.java,
|
||||
java.lang.OutOfMemoryError::class.java,
|
||||
java.lang.ReflectiveOperationException::class.java,
|
||||
java.lang.RuntimeException::class.java,
|
||||
java.lang.StackOverflowError::class.java,
|
||||
java.lang.StringIndexOutOfBoundsException::class.java,
|
||||
java.lang.ThreadDeath::class.java,
|
||||
java.lang.Throwable::class.java,
|
||||
java.lang.UnknownError::class.java,
|
||||
java.lang.UnsatisfiedLinkError::class.java,
|
||||
java.lang.UnsupportedClassVersionError::class.java,
|
||||
java.lang.UnsupportedOperationException::class.java,
|
||||
java.lang.VerifyError::class.java,
|
||||
java.lang.VirtualMachineError::class.java
|
||||
).sandboxed() + setOf(
|
||||
// Mentioned here to prevent the DJVM from generating a synthetic wrapper.
|
||||
"sandbox/java/lang/DJVMThrowableWrapper"
|
||||
)
|
||||
|
||||
/**
|
||||
* These interfaces will be modified as follows when
|
||||
* added to the sandbox:
|
||||
*
|
||||
* <code>interface sandbox.A extends A</code>
|
||||
*/
|
||||
private val STITCHED_INTERFACES: Map<String, List<Member>> = listOf(
|
||||
object : MethodBuilder(
|
||||
access = ACC_PUBLIC or ACC_SYNTHETIC or ACC_BRIDGE,
|
||||
className = sandboxed(CharSequence::class.java),
|
||||
memberName = "subSequence",
|
||||
descriptor = "(II)Ljava/lang/CharSequence;"
|
||||
) {
|
||||
override fun writeBody(emitter: EmitterModule) = with(emitter) {
|
||||
pushObject(0)
|
||||
pushInteger(1)
|
||||
pushInteger(2)
|
||||
invokeInterface(className, memberName, "(II)L$className;")
|
||||
returnObject()
|
||||
}
|
||||
}.withBody()
|
||||
.build(),
|
||||
|
||||
MethodBuilder(
|
||||
access = ACC_PUBLIC or ACC_ABSTRACT,
|
||||
className = sandboxed(CharSequence::class.java),
|
||||
memberName = "toString",
|
||||
descriptor = "()Ljava/lang/String;"
|
||||
).build()
|
||||
).mapByClassName() + mapOf(
|
||||
sandboxed(Comparable::class.java) to emptyList(),
|
||||
sandboxed(Comparator::class.java) to emptyList(),
|
||||
sandboxed(Iterable::class.java) to emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* These classes have extra methods added when mapped into the sandbox.
|
||||
*/
|
||||
private val STITCHED_CLASSES: Map<String, List<Member>> = listOf(
|
||||
object : MethodBuilder(
|
||||
access = ACC_FINAL,
|
||||
className = sandboxed(Enum::class.java),
|
||||
memberName = "fromDJVM",
|
||||
descriptor = "()Ljava/lang/Enum;",
|
||||
signature = "()Ljava/lang/Enum<*>;"
|
||||
) {
|
||||
override fun writeBody(emitter: EmitterModule) = with(emitter) {
|
||||
pushObject(0)
|
||||
invokeStatic("sandbox/java/lang/DJVM", "fromDJVMEnum", "(Lsandbox/java/lang/Enum;)Ljava/lang/Enum;")
|
||||
returnObject()
|
||||
}
|
||||
}.withBody()
|
||||
.build(),
|
||||
|
||||
object : MethodBuilder(
|
||||
access = ACC_BRIDGE or ACC_SYNTHETIC,
|
||||
className = sandboxed(Enum::class.java),
|
||||
memberName = "fromDJVM",
|
||||
descriptor = "()Ljava/lang/Object;"
|
||||
) {
|
||||
override fun writeBody(emitter: EmitterModule) = with(emitter) {
|
||||
pushObject(0)
|
||||
invokeVirtual(className, memberName, "()Ljava/lang/Enum;")
|
||||
returnObject()
|
||||
}
|
||||
}.withBody()
|
||||
.build()
|
||||
).mapByClassName()
|
||||
|
||||
private fun sandboxed(clazz: Class<*>): String = (SANDBOX_PREFIX + Type.getInternalName(clazz)).intern()
|
||||
private fun Set<Class<*>>.sandboxed(): Set<String> = map(Companion::sandboxed).toSet()
|
||||
private fun Iterable<Member>.mapByClassName(): Map<String, List<Member>>
|
||||
= 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?) -> SourceClassLoader = { 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(
|
||||
protected val access: Int,
|
||||
protected val className: String,
|
||||
protected val memberName: String,
|
||||
protected val descriptor: String,
|
||||
protected val signature: String = ""
|
||||
) {
|
||||
private val bodies = mutableListOf<MethodBody>()
|
||||
|
||||
protected open fun writeBody(emitter: EmitterModule) {}
|
||||
|
||||
fun withBody(): MethodBuilder {
|
||||
bodies.add(::writeBody)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build() = Member(
|
||||
access = access,
|
||||
className = className,
|
||||
memberName = memberName,
|
||||
signature = descriptor,
|
||||
genericsDetails = signature,
|
||||
body = bodies
|
||||
)
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.code.asPackagePath
|
||||
import net.corda.djvm.messages.MessageCollection
|
||||
import net.corda.djvm.references.ClassHierarchy
|
||||
import net.corda.djvm.references.EntityReference
|
||||
import net.corda.djvm.references.ReferenceMap
|
||||
|
||||
/**
|
||||
* The context in which one or more classes are analysed.
|
||||
*
|
||||
* @property messages Collection of messages gathered as part of the analysis.
|
||||
* @property classes List of class definitions that have been analyzed.
|
||||
* @property references A collection of all referenced members found during analysis together with the locations from
|
||||
* where each member has been accessed or invoked.
|
||||
*/
|
||||
class AnalysisContext private constructor(
|
||||
val messages: MessageCollection,
|
||||
val classes: ClassHierarchy,
|
||||
val references: ReferenceMap
|
||||
) {
|
||||
|
||||
private val origins = mutableMapOf<String, MutableSet<EntityReference>>()
|
||||
|
||||
/**
|
||||
* Record a class origin in the current analysis context.
|
||||
*/
|
||||
fun recordClassOrigin(name: String, origin: EntityReference) {
|
||||
origins.getOrPut(name.asPackagePath) { mutableSetOf() }.add(origin)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of class origins. The resulting set represents the types referencing the class in question.
|
||||
*/
|
||||
val classOrigins: Map<String, Set<EntityReference>>
|
||||
get() = origins
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create a new analysis context from provided configuration.
|
||||
*/
|
||||
fun fromConfiguration(configuration: AnalysisConfiguration): AnalysisContext {
|
||||
return AnalysisContext(
|
||||
MessageCollection(configuration.minimumSeverityLevel, configuration.prefixFilters),
|
||||
ClassHierarchy(configuration.classModule, configuration.memberModule),
|
||||
ReferenceMap(configuration.classModule)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.messages.MessageCollection
|
||||
import net.corda.djvm.references.ClassRepresentation
|
||||
import net.corda.djvm.references.Member
|
||||
|
||||
/**
|
||||
* The context of a class analysis.
|
||||
*
|
||||
* @property clazz The class currently being analyzed.
|
||||
* @property member The member currently being analyzed.
|
||||
* @property location The current source location.
|
||||
* @property messages Collection of messages gathered as part of the analysis.
|
||||
* @property configuration The configuration used in the analysis.
|
||||
*/
|
||||
data class AnalysisRuntimeContext(
|
||||
val clazz: ClassRepresentation,
|
||||
val member: Member?,
|
||||
val location: SourceLocation,
|
||||
val messages: MessageCollection,
|
||||
val configuration: AnalysisConfiguration
|
||||
)
|
@ -1,598 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.code.EmitterModule
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.emptyAsNull
|
||||
import net.corda.djvm.code.instructions.*
|
||||
import net.corda.djvm.messages.Message
|
||||
import net.corda.djvm.references.*
|
||||
import org.objectweb.asm.*
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Functionality for traversing a class and its members.
|
||||
*
|
||||
* @property configuration The configuration to use for the analysis
|
||||
* @property classVisitor Class visitor to use when traversing the structure of classes.
|
||||
*/
|
||||
open class ClassAndMemberVisitor(
|
||||
private val configuration: AnalysisConfiguration,
|
||||
private val classVisitor: ClassVisitor?
|
||||
) {
|
||||
|
||||
/**
|
||||
* Holds a reference to the currently used analysis context.
|
||||
*/
|
||||
protected var analysisContext: AnalysisContext = AnalysisContext.fromConfiguration(configuration)
|
||||
|
||||
/**
|
||||
* Holds a link to the class currently being traversed.
|
||||
*/
|
||||
private var currentClass: ClassRepresentation? = null
|
||||
|
||||
/**
|
||||
* Holds a link to the member currently being traversed.
|
||||
*/
|
||||
private var currentMember: Member? = null
|
||||
|
||||
/**
|
||||
* The current source location.
|
||||
*/
|
||||
private var sourceLocation = SourceLocation()
|
||||
|
||||
/**
|
||||
* Analyze class by using the provided qualified name of the class.
|
||||
*/
|
||||
inline fun <reified T> analyze(context: AnalysisContext) = analyze(T::class.java.name, context)
|
||||
|
||||
/**
|
||||
* Analyze class by using the provided qualified name of the class.
|
||||
*
|
||||
* @param className The full, qualified name of the class.
|
||||
* @param context The context in which the analysis is conducted.
|
||||
* @param origin The originating class for the analysis.
|
||||
*/
|
||||
fun analyze(className: String, context: AnalysisContext, origin: String? = null) {
|
||||
configuration.supportingClassLoader.classReader(className, context, origin).apply {
|
||||
analyze(this, context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze class by using the provided stream of its byte code.
|
||||
*
|
||||
* @param classStream A stream of the class' byte code.
|
||||
* @param context The context in which the analysis is conducted.
|
||||
*/
|
||||
fun analyze(classStream: InputStream, context: AnalysisContext) =
|
||||
analyze(ClassReader(classStream), context)
|
||||
|
||||
/**
|
||||
* Analyze class by using the provided class reader.
|
||||
*
|
||||
* @param classReader An instance of the class reader to use to access the byte code.
|
||||
* @param context The context in which to analyse the provided class.
|
||||
* @param options Options for how to parse and process the class.
|
||||
*/
|
||||
fun analyze(classReader: ClassReader, context: AnalysisContext, options: Int = 0) {
|
||||
analysisContext = context
|
||||
classReader.accept(ClassVisitorImpl(classVisitor), options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed class.
|
||||
*/
|
||||
open fun visitClass(clazz: ClassRepresentation): ClassRepresentation = clazz
|
||||
|
||||
/**
|
||||
* Process class after it has been fully traversed and analyzed.
|
||||
* The [classVisitor] has finished visiting all of the class's
|
||||
* existing elements (i.e. methods, fields, inner classes etc)
|
||||
* and is about to complete. However, it can still add new
|
||||
* elements to the class, if required.
|
||||
*/
|
||||
open fun visitClassEnd(classVisitor: ClassVisitor, clazz: ClassRepresentation) {}
|
||||
|
||||
/**
|
||||
* Extract the meta-data indicating the source file of the traversed class (i.e., where it is compiled from).
|
||||
*/
|
||||
open fun visitSource(clazz: ClassRepresentation, source: String) {}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed class annotation.
|
||||
*/
|
||||
open fun visitClassAnnotation(clazz: ClassRepresentation, descriptor: String) {}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed member annotation.
|
||||
*/
|
||||
open fun visitMemberAnnotation(clazz: ClassRepresentation, member: Member, descriptor: String) {}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed method.
|
||||
*/
|
||||
open fun visitMethod(clazz: ClassRepresentation, method: Member): Member = method
|
||||
|
||||
/**
|
||||
* Extract information about the traversed field.
|
||||
*/
|
||||
open fun visitField(clazz: ClassRepresentation, field: Member): Member = field
|
||||
|
||||
/**
|
||||
* Extract information about the traversed instruction.
|
||||
*/
|
||||
open fun visitInstruction(method: Member, emitter: EmitterModule, instruction: Instruction) {}
|
||||
|
||||
/**
|
||||
* Get the analysis context to pass on to method and field visitors.
|
||||
*/
|
||||
protected fun currentAnalysisContext(): AnalysisRuntimeContext {
|
||||
return AnalysisRuntimeContext(
|
||||
currentClass!!,
|
||||
currentMember,
|
||||
sourceLocation,
|
||||
analysisContext.messages,
|
||||
configuration
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a class should be processed or not.
|
||||
*/
|
||||
protected fun shouldBeProcessed(className: String): Boolean {
|
||||
return !configuration.whitelist.inNamespace(className) &&
|
||||
!configuration.isPinnedClass(className)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed member annotation.
|
||||
*/
|
||||
private fun visitMemberAnnotation(
|
||||
descriptor: String,
|
||||
referencedClass: ClassRepresentation? = null,
|
||||
referencedMember: Member? = null
|
||||
) {
|
||||
val clazz = (referencedClass ?: currentClass) ?: return
|
||||
val member = (referencedMember ?: currentMember) ?: return
|
||||
member.annotations.add(descriptor)
|
||||
captureExceptions {
|
||||
visitMemberAnnotation(clazz, member, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run action with a guard that populates [AnalysisRuntimeContext.messages]
|
||||
* based on the output.
|
||||
*/
|
||||
private inline fun captureExceptions(action: () -> Unit): Boolean {
|
||||
return try {
|
||||
action()
|
||||
true
|
||||
} catch (exception: Throwable) {
|
||||
recordMessage(exception, currentAnalysisContext())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a message derived from a [Throwable].
|
||||
*/
|
||||
private fun recordMessage(exception: Throwable, context: AnalysisRuntimeContext) {
|
||||
context.messages.add(Message.fromThrowable(exception, context.location))
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a reference to a class.
|
||||
*/
|
||||
private fun recordTypeReference(type: String) {
|
||||
val typeName = configuration.classModule
|
||||
.normalizeClassName(type)
|
||||
.replace("[]", "")
|
||||
if (shouldBeProcessed(currentClass!!.name)) {
|
||||
val classReference = ClassReference(typeName)
|
||||
analysisContext.references.add(classReference, sourceLocation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a reference to a class member.
|
||||
*/
|
||||
private fun recordMemberReference(owner: String, name: String, desc: String) {
|
||||
if (shouldBeProcessed(currentClass!!.name)) {
|
||||
recordTypeReference(owner)
|
||||
val memberReference = MemberReference(owner, name, desc)
|
||||
analysisContext.references.add(memberReference, sourceLocation)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor used to traverse and analyze a class.
|
||||
*/
|
||||
private inner class ClassVisitorImpl(
|
||||
targetVisitor: ClassVisitor?
|
||||
) : ClassVisitor(API_VERSION, targetVisitor) {
|
||||
|
||||
/**
|
||||
* Extract information about the traversed class.
|
||||
*/
|
||||
override fun visit(
|
||||
version: Int, access: Int, name: String, signature: String?, superName: String?,
|
||||
interfaces: Array<String>?
|
||||
) {
|
||||
val superClassName = superName ?: ""
|
||||
val interfaceNames = interfaces?.toMutableList() ?: mutableListOf()
|
||||
ClassRepresentation(version, access, name, superClassName, interfaceNames, genericsDetails = signature ?: "").also {
|
||||
currentClass = it
|
||||
currentMember = null
|
||||
sourceLocation = SourceLocation(className = name)
|
||||
}
|
||||
captureExceptions {
|
||||
currentClass = visitClass(currentClass!!)
|
||||
}
|
||||
val visitedClass = currentClass!!
|
||||
analysisContext.classes.add(visitedClass)
|
||||
super.visit(
|
||||
version, access, visitedClass.name, signature,
|
||||
visitedClass.superClass.emptyAsNull,
|
||||
visitedClass.interfaces.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processing of the traversed class.
|
||||
*/
|
||||
override fun visitEnd() {
|
||||
configuration.classModule
|
||||
.getClassReferencesFromClass(currentClass!!, configuration.analyzeAnnotations)
|
||||
.forEach(::recordTypeReference)
|
||||
captureExceptions {
|
||||
visitClassEnd(this, currentClass!!)
|
||||
}
|
||||
super.visitEnd()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the meta-data indicating the source file of the traversed class (i.e., where it is compiled from).
|
||||
*/
|
||||
override fun visitSource(source: String?, debug: String?) {
|
||||
currentClass!!.apply {
|
||||
sourceFile = configuration.classModule.getFullSourceLocation(this, source)
|
||||
sourceLocation = sourceLocation.copy(sourceFile = sourceFile)
|
||||
captureExceptions {
|
||||
visitSource(this, sourceFile)
|
||||
}
|
||||
}
|
||||
super.visitSource(source, debug)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided annotations.
|
||||
*/
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
currentClass!!.apply {
|
||||
annotations.add(desc)
|
||||
captureExceptions {
|
||||
visitClassAnnotation(this, desc)
|
||||
}
|
||||
}
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed method.
|
||||
*/
|
||||
override fun visitMethod(
|
||||
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
|
||||
): MethodVisitor? {
|
||||
var visitedMember: Member? = null
|
||||
val clazz = currentClass!!
|
||||
val member = Member(
|
||||
access = access,
|
||||
className = clazz.name,
|
||||
memberName = name,
|
||||
signature = desc,
|
||||
genericsDetails = signature ?: "",
|
||||
exceptions = exceptions?.toMutableSet() ?: mutableSetOf()
|
||||
)
|
||||
currentMember = member
|
||||
sourceLocation = sourceLocation.copy(
|
||||
memberName = name,
|
||||
signature = desc,
|
||||
lineNumber = 0
|
||||
)
|
||||
val processMember = captureExceptions {
|
||||
visitedMember = visitMethod(clazz, member)
|
||||
}
|
||||
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
||||
return if (processMember) {
|
||||
val derivedMember = visitedMember ?: member
|
||||
super.visitMethod(
|
||||
derivedMember.access,
|
||||
derivedMember.memberName,
|
||||
derivedMember.signature,
|
||||
signature,
|
||||
derivedMember.exceptions.toTypedArray()
|
||||
)?.let { targetVisitor ->
|
||||
MethodVisitorImpl(targetVisitor, derivedMember)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about the traversed field.
|
||||
*/
|
||||
override fun visitField(
|
||||
access: Int, name: String, desc: String, signature: String?, value: Any?
|
||||
): FieldVisitor? {
|
||||
var visitedMember: Member? = null
|
||||
val clazz = currentClass!!
|
||||
val member = Member(
|
||||
access = access,
|
||||
className = clazz.name,
|
||||
memberName = name,
|
||||
signature = desc,
|
||||
genericsDetails = "",
|
||||
value = value
|
||||
)
|
||||
currentMember = member
|
||||
sourceLocation = sourceLocation.copy(
|
||||
memberName = name,
|
||||
signature = desc,
|
||||
lineNumber = 0
|
||||
)
|
||||
val processMember = captureExceptions {
|
||||
visitedMember = visitField(clazz, member)
|
||||
}
|
||||
configuration.memberModule.addToClass(clazz, visitedMember ?: member)
|
||||
return if (processMember) {
|
||||
val derivedMember = visitedMember ?: member
|
||||
super.visitField(
|
||||
derivedMember.access,
|
||||
derivedMember.memberName,
|
||||
derivedMember.signature,
|
||||
signature,
|
||||
derivedMember.value
|
||||
)?.let { targetVisitor ->
|
||||
FieldVisitorImpl(targetVisitor)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor used to traverse and analyze a method.
|
||||
*/
|
||||
private inner class MethodVisitorImpl(
|
||||
targetVisitor: MethodVisitor,
|
||||
private val method: Member
|
||||
) : MethodVisitor(API_VERSION, targetVisitor) {
|
||||
|
||||
/**
|
||||
* Record line number of current instruction.
|
||||
*/
|
||||
override fun visitLineNumber(line: Int, start: Label?) {
|
||||
sourceLocation = sourceLocation.copy(lineNumber = line)
|
||||
super.visitLineNumber(line, start)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided label.
|
||||
*/
|
||||
override fun visitLabel(label: Label) {
|
||||
visit(CodeLabel(label), defaultFirst = true) {
|
||||
super.visitLabel(label)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided annotations.
|
||||
*/
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
visitMemberAnnotation(desc)
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any new method body code, assuming the definition providers
|
||||
* have provided any. This handler will not be visited if this method
|
||||
* has no existing code.
|
||||
*/
|
||||
override fun visitCode() {
|
||||
tryReplaceMethodBody()
|
||||
visit(MethodEntry(method)) {
|
||||
super.visitCode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided field access instruction.
|
||||
*/
|
||||
override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
|
||||
recordMemberReference(owner, name, desc)
|
||||
visit(MemberAccessInstruction(opcode, owner, name, desc, isMethod = false)) {
|
||||
super.visitFieldInsn(opcode, owner, name, desc)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided method invocation instruction.
|
||||
*/
|
||||
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
|
||||
recordMemberReference(owner, name, desc)
|
||||
visit(MemberAccessInstruction(opcode, owner, name, desc, itf, isMethod = true)) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc, itf)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided dynamic invocation instruction.
|
||||
*/
|
||||
override fun visitInvokeDynamicInsn(name: String, desc: String, bsm: Handle?, vararg bsmArgs: Any?) {
|
||||
val module = configuration.memberModule
|
||||
visit(DynamicInvocationInstruction(
|
||||
name, desc, module.numberOfArguments(desc), module.returnsValueOrReference(desc)
|
||||
)) {
|
||||
super.visitInvokeDynamicInsn(name, desc, bsm, *bsmArgs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided jump instruction.
|
||||
*/
|
||||
override fun visitJumpInsn(opcode: Int, label: Label) {
|
||||
visit(BranchInstruction(opcode, label)) {
|
||||
super.visitJumpInsn(opcode, label)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided instruction (general instruction with no operands).
|
||||
*/
|
||||
override fun visitInsn(opcode: Int) {
|
||||
visit(Instruction(opcode)) {
|
||||
super.visitInsn(opcode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided instruction (general instruction with one operand).
|
||||
*/
|
||||
override fun visitIntInsn(opcode: Int, operand: Int) {
|
||||
visit(IntegerInstruction(opcode, operand)) {
|
||||
super.visitIntInsn(opcode, operand)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided type instruction (e.g., [Opcodes.NEW], [Opcodes.ANEWARRAY],
|
||||
* [Opcodes.INSTANCEOF] and [Opcodes.CHECKCAST]).
|
||||
*/
|
||||
override fun visitTypeInsn(opcode: Int, type: String) {
|
||||
recordTypeReference(type)
|
||||
visit(TypeInstruction(opcode, type)) {
|
||||
try {
|
||||
super.visitTypeInsn(opcode, type)
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
throw IllegalArgumentException("Invalid name used in type instruction; $type", exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided try-catch/finally block.
|
||||
*/
|
||||
override fun visitTryCatchBlock(start: Label, end: Label, handler: Label, type: String?) {
|
||||
val block = if (type != null) {
|
||||
TryCatchBlock(type, handler)
|
||||
} else {
|
||||
TryFinallyBlock(handler)
|
||||
}
|
||||
visit(block) {
|
||||
super.visitTryCatchBlock(start, end, handler, type)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided table switch instruction.
|
||||
*/
|
||||
override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label, vararg labels: Label) {
|
||||
visit(TableSwitchInstruction(min, max, dflt, labels.toList())) {
|
||||
super.visitTableSwitchInsn(min, max, dflt, *labels)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract information about provided increment instruction.
|
||||
*/
|
||||
override fun visitIincInsn(`var`: Int, increment: Int) {
|
||||
visit(IntegerInstruction(Opcodes.IINC, increment)) {
|
||||
super.visitIincInsn(`var`, increment)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform values loaded from the constants pool.
|
||||
*/
|
||||
override fun visitLdcInsn(value: Any) {
|
||||
visit(ConstantInstruction(value), defaultFirst = true) {
|
||||
super.visitLdcInsn(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish visiting this method, writing any new method body byte-code
|
||||
* if we haven't written it already. This would (presumably) only happen
|
||||
* for methods that previously had no body, e.g. native methods.
|
||||
*/
|
||||
override fun visitEnd() {
|
||||
tryReplaceMethodBody()
|
||||
super.visitEnd()
|
||||
}
|
||||
|
||||
private fun tryReplaceMethodBody() {
|
||||
if (method.body.isNotEmpty() && (mv != null)) {
|
||||
EmitterModule(mv).apply {
|
||||
for (body in method.body) {
|
||||
body(this)
|
||||
}
|
||||
}
|
||||
mv.visitMaxs(-1, -1)
|
||||
mv.visitEnd()
|
||||
mv = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to streamline the access to an instruction and to catch any related processing errors.
|
||||
*/
|
||||
private inline fun visit(instruction: Instruction, defaultFirst: Boolean = false, defaultAction: () -> Unit) {
|
||||
val emitterModule = EmitterModule(mv ?: StubMethodVisitor())
|
||||
if (defaultFirst) {
|
||||
defaultAction()
|
||||
}
|
||||
val success = captureExceptions {
|
||||
visitInstruction(currentMember!!, emitterModule, instruction)
|
||||
}
|
||||
if (!defaultFirst) {
|
||||
if (success && emitterModule.emitDefaultInstruction) {
|
||||
defaultAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor used to traverse and analyze a field.
|
||||
*/
|
||||
private inner class FieldVisitorImpl(
|
||||
targetVisitor: FieldVisitor
|
||||
) : FieldVisitor(API_VERSION, targetVisitor) {
|
||||
|
||||
/**
|
||||
* Extract information about provided annotations.
|
||||
*/
|
||||
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
|
||||
visitMemberAnnotation(desc)
|
||||
return super.visitAnnotation(desc, visible)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class StubMethodVisitor : MethodVisitor(API_VERSION, null)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The API version of ASM.
|
||||
*/
|
||||
const val API_VERSION: Int = Opcodes.ASM6
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.code.asPackagePath
|
||||
import net.corda.djvm.code.asResourcePath
|
||||
|
||||
/**
|
||||
* Functionality for resolving the class name of a sandboxable class.
|
||||
*
|
||||
* The resolution of a class name entails determining whether the class can be instrumented or not. This means that the
|
||||
* following criteria need to be satisfied:
|
||||
* - The class do not reside in the "java/lang" package.
|
||||
* - The class has not been explicitly pinned.
|
||||
* - The class does not already reside in the top-level package named [sandboxPrefix].
|
||||
*
|
||||
* If these criteria have been satisfied, the fully-qualified class name will be derived by prepending [sandboxPrefix]
|
||||
* to it. Note that [ClassLoader] will not allow defining a class in a package whose fully-qualified class name starts
|
||||
* with "java/". That will result in the class loader throwing [SecurityException]. Also, some values map onto types
|
||||
* defined in "java/lang/", e.g., [Integer] and [String]. These cannot be trivially moved into a different package due
|
||||
* to the internal mechanisms of the JVM.
|
||||
*
|
||||
* @property pinnedClasses Classes that have already been declared in the sandbox namespace and that should be made
|
||||
* available inside the sandboxed environment.
|
||||
* @property whitelist The set of classes in the Java runtime libraries that have been whitelisted and that should be
|
||||
* left alone.
|
||||
* @property sandboxPrefix The package name prefix to use for classes loaded into a sandbox.
|
||||
*/
|
||||
class ClassResolver(
|
||||
private val pinnedClasses: Set<String>,
|
||||
private val templateClasses: Set<String>,
|
||||
private val whitelist: Whitelist,
|
||||
private val sandboxPrefix: String
|
||||
) {
|
||||
|
||||
/**
|
||||
* Resolve the class name from a fully qualified name.
|
||||
*/
|
||||
fun resolve(name: String): String {
|
||||
return when {
|
||||
name.startsWith('[') && name.endsWith(';') -> {
|
||||
complexArrayTypeRegex.replace(name) {
|
||||
"${it.groupValues[1]}L${resolveName(it.groupValues[2])};"
|
||||
}
|
||||
}
|
||||
name.startsWith('[') && !name.endsWith(';') -> name
|
||||
else -> resolveName(name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the class name from a fully qualified normalized name.
|
||||
*/
|
||||
fun resolveNormalized(name: String): String {
|
||||
return resolve(name.asResourcePath).asPackagePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive descriptor by resolving all referenced class names.
|
||||
*/
|
||||
fun resolveDescriptor(descriptor: String): String {
|
||||
val outputDescriptor = StringBuilder()
|
||||
var longName = StringBuilder()
|
||||
var isProcessingLongName = false
|
||||
loop@ for (char in descriptor) {
|
||||
when {
|
||||
char != ';' && isProcessingLongName -> {
|
||||
longName.append(char)
|
||||
continue@loop
|
||||
}
|
||||
char == 'L' -> {
|
||||
isProcessingLongName = true
|
||||
longName = StringBuilder()
|
||||
}
|
||||
char == ';' -> {
|
||||
outputDescriptor.append(resolve(longName.toString()))
|
||||
isProcessingLongName = false
|
||||
}
|
||||
}
|
||||
outputDescriptor.append(char)
|
||||
}
|
||||
return outputDescriptor.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the resolution of a class name.
|
||||
*/
|
||||
fun reverse(resolvedClassName: String): String {
|
||||
return if (resolvedClassName in pinnedClasses || resolvedClassName in templateClasses) {
|
||||
resolvedClassName
|
||||
} else {
|
||||
removeSandboxPrefix(resolvedClassName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the resolution of a class name from a fully qualified normalized name.
|
||||
*/
|
||||
fun reverseNormalized(className: String): String {
|
||||
return reverse(className.asResourcePath).asPackagePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the equivalent class name outside the sandbox from a fully qualified normalized name.
|
||||
*/
|
||||
fun toSourceNormalized(className: String): String {
|
||||
return toSource(className.asResourcePath).asPackagePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve sandboxed class name from a fully qualified name.
|
||||
*/
|
||||
private fun resolveName(name: String): String {
|
||||
return if (isPinnedOrWhitelistedClass(name) || name in templateClasses) {
|
||||
name
|
||||
} else {
|
||||
"$sandboxPrefix$name"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a class name to its equivalent class outside the sandbox.
|
||||
* Needed by [net.corda.djvm.source.SourceClassLoader].
|
||||
*/
|
||||
private fun toSource(className: String): String {
|
||||
return if (className in pinnedClasses) {
|
||||
className
|
||||
} else {
|
||||
removeSandboxPrefix(className)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeSandboxPrefix(className: String): String {
|
||||
if (className.startsWith(sandboxPrefix)) {
|
||||
val nameWithoutPrefix = className.drop(sandboxPrefix.length)
|
||||
if (resolve(nameWithoutPrefix) == className) {
|
||||
return nameWithoutPrefix
|
||||
}
|
||||
}
|
||||
return className
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if class is whitelisted or pinned.
|
||||
*/
|
||||
private fun isPinnedOrWhitelistedClass(name: String): Boolean {
|
||||
return whitelist.matches(name) ||
|
||||
name in pinnedClasses ||
|
||||
sandboxRegex.matches(name)
|
||||
}
|
||||
|
||||
private val sandboxRegex = "^$sandboxPrefix.*\$".toRegex()
|
||||
|
||||
companion object {
|
||||
private val complexArrayTypeRegex = "^(\\[+)L(.*);\$".toRegex()
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
class ExceptionResolver(
|
||||
private val jvmExceptionClasses: Set<String>,
|
||||
private val pinnedClasses: Set<String>,
|
||||
private val sandboxPrefix: String
|
||||
) {
|
||||
companion object {
|
||||
private const val DJVM_EXCEPTION_NAME = "\$1DJVM"
|
||||
|
||||
fun isDJVMException(className: String): Boolean = className.endsWith(DJVM_EXCEPTION_NAME)
|
||||
fun getDJVMException(className: String): String = className + DJVM_EXCEPTION_NAME
|
||||
fun getDJVMExceptionOwner(className: String): String = className.dropLast(DJVM_EXCEPTION_NAME.length)
|
||||
}
|
||||
|
||||
fun getThrowableName(clazz: Class<*>): String {
|
||||
return getDJVMException(Type.getInternalName(clazz))
|
||||
}
|
||||
|
||||
fun getThrowableSuperName(clazz: Class<*>): String {
|
||||
return getThrowableOwnerName(Type.getInternalName(clazz.superclass))
|
||||
}
|
||||
|
||||
fun getThrowableOwnerName(className: String): String {
|
||||
return if (className in jvmExceptionClasses) {
|
||||
className.unsandboxed
|
||||
} else if (className in pinnedClasses) {
|
||||
className
|
||||
} else {
|
||||
getDJVMException(className)
|
||||
}
|
||||
}
|
||||
|
||||
private val String.unsandboxed: String get() = if (startsWith(sandboxPrefix)) {
|
||||
drop(sandboxPrefix.length)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import net.corda.djvm.formatting.MemberFormatter
|
||||
import net.corda.djvm.references.MemberInformation
|
||||
|
||||
/**
|
||||
* Representation of the source location of a class, member or instruction.
|
||||
*
|
||||
* @property className The name of the class.
|
||||
* @property sourceFile The file containing the source of the compiled class.
|
||||
* @property memberName The name of the field or method.
|
||||
* @property signature The signature of the field or method.
|
||||
* @property lineNumber The index of the line from which the instruction was compiled.
|
||||
*/
|
||||
data class SourceLocation(
|
||||
override val className: String = "",
|
||||
val sourceFile: String = "",
|
||||
override val memberName: String = "",
|
||||
override val signature: String = "",
|
||||
val lineNumber: Int = 0
|
||||
) : MemberInformation {
|
||||
|
||||
/**
|
||||
* Check whether or not information about the source file is available.
|
||||
*/
|
||||
val hasSourceFile: Boolean
|
||||
get() = sourceFile.isNotBlank()
|
||||
|
||||
/**
|
||||
* Check whether or not information about the line number is available.
|
||||
*/
|
||||
val hasLineNumber: Boolean
|
||||
get() = lineNumber != 0
|
||||
|
||||
/**
|
||||
* Get a string representation of the source location.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return StringBuilder().apply {
|
||||
append(className.removePrefix("sandbox/"))
|
||||
if (memberName.isNotBlank()) {
|
||||
append(".$memberName")
|
||||
if (memberFormatter.isMethod(signature)) {
|
||||
append("(${memberFormatter.format(signature)})")
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted string representation of the source location.
|
||||
*/
|
||||
fun format(): String {
|
||||
if (className.isBlank()) {
|
||||
return ""
|
||||
}
|
||||
return StringBuilder().apply {
|
||||
append("@|blue ")
|
||||
append(if (hasSourceFile) {
|
||||
sourceFile
|
||||
} else {
|
||||
className
|
||||
}.removePrefix("sandbox/"))
|
||||
append("|@")
|
||||
if (hasLineNumber) {
|
||||
append(" on @|yellow line $lineNumber|@")
|
||||
}
|
||||
if (memberName.isNotBlank()) {
|
||||
append(" in @|green ")
|
||||
if (hasSourceFile) {
|
||||
append("${memberFormatter.getShortClassName(className)}.$memberName")
|
||||
} else {
|
||||
append(memberName)
|
||||
}
|
||||
if (memberFormatter.isMethod(signature)) {
|
||||
append("(${memberFormatter.format(signature)})")
|
||||
}
|
||||
append("|@")
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private val memberFormatter = MemberFormatter()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
package net.corda.djvm.analysis
|
||||
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.io.PushbackInputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
/**
|
||||
* Representation of a whitelist deciding what classes, interfaces and members are permissible and consequently can be
|
||||
* referenced from sandboxed code.
|
||||
*
|
||||
* @property namespace If provided, this parameter bounds the namespace of the whitelist.
|
||||
* @property entries A set of regular expressions used to determine whether a name is covered by the whitelist or not.
|
||||
* @property textEntries A set of textual entries used to determine whether a name is covered by the whitelist or not.
|
||||
*/
|
||||
open class Whitelist private constructor(
|
||||
private val namespace: Whitelist? = null,
|
||||
private val entries: Set<Regex>,
|
||||
private val textEntries: Set<String>
|
||||
) {
|
||||
|
||||
/**
|
||||
* Set of seen names that matched with the whitelist.
|
||||
*/
|
||||
private val seenNames = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* Check if name falls within the namespace of the whitelist.
|
||||
*/
|
||||
fun inNamespace(name: String): Boolean {
|
||||
return namespace != null && namespace.matches(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a name is covered by the whitelist.
|
||||
*/
|
||||
fun matches(name: String): Boolean {
|
||||
if (name in seenNames) {
|
||||
return true
|
||||
}
|
||||
return when {
|
||||
name in textEntries -> {
|
||||
seenNames.add(name)
|
||||
true
|
||||
}
|
||||
entries.any { it.matches(name) } -> {
|
||||
seenNames.add(name)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine two whitelists into one.
|
||||
*/
|
||||
operator fun plus(whitelist: Whitelist): Whitelist {
|
||||
val entries = entries + whitelist.entries
|
||||
val textEntries = textEntries + whitelist.textEntries
|
||||
return when {
|
||||
namespace != null && whitelist.namespace != null ->
|
||||
Whitelist(namespace + whitelist.namespace, entries, textEntries)
|
||||
namespace != null ->
|
||||
Whitelist(namespace, entries, textEntries)
|
||||
whitelist.namespace != null ->
|
||||
Whitelist(whitelist.namespace, entries, textEntries)
|
||||
else ->
|
||||
Whitelist(null, entries, textEntries)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a derived whitelist by adding a set of additional entries.
|
||||
*/
|
||||
operator fun plus(additionalEntries: Set<Regex>): Whitelist {
|
||||
return plus(Whitelist(null, additionalEntries, emptySet()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a derived whitelist by adding an additional entry.
|
||||
*/
|
||||
operator fun plus(additionalEntry: Regex): Whitelist {
|
||||
return plus(setOf(additionalEntry))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate all the entries of the whitelist.
|
||||
*/
|
||||
val items: Set<String>
|
||||
get() = textEntries + entries.map(Regex::pattern)
|
||||
|
||||
companion object {
|
||||
private val everythingRegex = setOf(".*".toRegex())
|
||||
|
||||
private val minimumSet = setOf(
|
||||
"^java/lang/Class(\\..*)?\$".toRegex(),
|
||||
"^java/lang/ClassLoader(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Cloneable(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Object(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Override(\\..*)?\$".toRegex(),
|
||||
"^java/lang/Void\$".toRegex(),
|
||||
"^java/lang/invoke/LambdaMetafactory\$".toRegex(),
|
||||
"^java/lang/invoke/MethodHandles(\\\$.*)?\$".toRegex(),
|
||||
"^java/lang/reflect/Array(\\..*)?\$".toRegex(),
|
||||
"^java/io/Serializable\$".toRegex()
|
||||
)
|
||||
|
||||
/**
|
||||
* Empty whitelist.
|
||||
*/
|
||||
val EMPTY: Whitelist = Whitelist(null, emptySet(), emptySet())
|
||||
|
||||
/**
|
||||
* The minimum set of classes that needs to be pinned from standard Java libraries.
|
||||
*/
|
||||
val MINIMAL: Whitelist = Whitelist(Whitelist(null, minimumSet, emptySet()), minimumSet, emptySet())
|
||||
|
||||
/**
|
||||
* Whitelist everything.
|
||||
*/
|
||||
val EVERYTHING: Whitelist = Whitelist(
|
||||
Whitelist(null, everythingRegex, emptySet()),
|
||||
everythingRegex,
|
||||
emptySet()
|
||||
)
|
||||
|
||||
/**
|
||||
* Load a whitelist from a resource stream.
|
||||
*/
|
||||
fun fromResource(resourceName: String): Whitelist {
|
||||
val inputStream = Whitelist::class.java.getResourceAsStream("/$resourceName")
|
||||
?: throw FileNotFoundException("Cannot find resource \"$resourceName\"")
|
||||
return fromStream(inputStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a whitelist from a file.
|
||||
*/
|
||||
fun fromFile(file: Path): Whitelist {
|
||||
return Files.newInputStream(file).use(this::fromStream)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a whitelist from a GZIP'ed or raw input stream.
|
||||
*/
|
||||
fun fromStream(inputStream: InputStream): Whitelist {
|
||||
val namespaceEntries = mutableSetOf<Regex>()
|
||||
val entries = mutableSetOf<String>()
|
||||
decompressStream(inputStream).bufferedReader().use {
|
||||
var isCollectingFilterEntries = false
|
||||
for (line in it.lines().filter(String::isNotBlank)) {
|
||||
when {
|
||||
line.trim() == SECTION_SEPARATOR -> {
|
||||
isCollectingFilterEntries = true
|
||||
}
|
||||
isCollectingFilterEntries -> entries.add(line)
|
||||
else -> namespaceEntries.add(Regex(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
val namespace = if (namespaceEntries.isNotEmpty()) {
|
||||
Whitelist(null, namespaceEntries, emptySet())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return Whitelist(namespace = namespace, entries = emptySet(), textEntries = entries)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress stream if GZIP'ed, otherwise, use the raw stream.
|
||||
*/
|
||||
private fun decompressStream(inputStream: InputStream): InputStream {
|
||||
val rawStream = PushbackInputStream(inputStream, 2)
|
||||
val signature = ByteArray(2)
|
||||
val length = rawStream.read(signature)
|
||||
rawStream.unread(signature, 0, length)
|
||||
return if (signature[0] == GZIP_MAGIC_FIRST_BYTE && signature[1] == GZIP_MAGIC_SECOND_BYTE) {
|
||||
GZIPInputStream(rawStream)
|
||||
} else {
|
||||
rawStream
|
||||
}
|
||||
}
|
||||
|
||||
private const val SECTION_SEPARATOR = "---"
|
||||
private const val GZIP_MAGIC_FIRST_BYTE = GZIPInputStream.GZIP_MAGIC.toByte()
|
||||
private const val GZIP_MAGIC_SECOND_BYTE = (GZIPInputStream.GZIP_MAGIC shr 8).toByte()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisRuntimeContext
|
||||
import net.corda.djvm.references.ClassRepresentation
|
||||
|
||||
/**
|
||||
* A class definition provider is a hook for [ClassMutator], from where one can modify the name and meta-data of
|
||||
* processed classes.
|
||||
*/
|
||||
interface ClassDefinitionProvider : DefinitionProvider {
|
||||
|
||||
/**
|
||||
* Hook for providing modifications to a class definition.
|
||||
*
|
||||
* @param context The context in which the hook is called.
|
||||
* @param clazz The original class definition.
|
||||
*
|
||||
* @return The updated class definition, or [clazz] if no changes are desired.
|
||||
*/
|
||||
fun define(context: AnalysisRuntimeContext, clazz: ClassRepresentation): ClassRepresentation
|
||||
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.ClassAndMemberVisitor
|
||||
import net.corda.djvm.code.instructions.MethodEntry
|
||||
import net.corda.djvm.references.ClassRepresentation
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import net.corda.djvm.utilities.Processor
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* Helper class for applying a set of definition providers and emitters to a class or set of classes.
|
||||
*
|
||||
* @param classVisitor Class visitor to use when traversing the structure of classes.
|
||||
* @property configuration The configuration to use for class analysis.
|
||||
* @property definitionProviders A set of providers used to update the name or meta-data of classes and members.
|
||||
* @param emitters A set of code emitters used to modify and instrument method bodies.
|
||||
*/
|
||||
class ClassMutator(
|
||||
classVisitor: ClassVisitor,
|
||||
private val configuration: AnalysisConfiguration,
|
||||
private val definitionProviders: List<DefinitionProvider> = emptyList(),
|
||||
emitters: List<Emitter> = emptyList()
|
||||
) : ClassAndMemberVisitor(configuration, classVisitor) {
|
||||
|
||||
/**
|
||||
* Internal [Emitter] to add static field initializers to
|
||||
* any class constructor method.
|
||||
*/
|
||||
private inner class PrependClassInitializer : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MethodEntry
|
||||
&& instruction.method.memberName == "<clinit>" && instruction.method.signature == "()V"
|
||||
&& initializers.isNotEmpty()) {
|
||||
writeByteCode(initializers)
|
||||
initializers.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Some emitters must be executed before others. E.g. we need to apply
|
||||
* the tracing emitters before the non-tracing ones.
|
||||
*/
|
||||
private val emitters: List<Emitter> = (emitters + PrependClassInitializer()).sortedBy(Emitter::priority)
|
||||
private val initializers = mutableListOf<MethodBody>()
|
||||
|
||||
/**
|
||||
* Tracks whether any modifications have been applied to any of the processed class(es) and pertinent members.
|
||||
*/
|
||||
var hasBeenModified: Boolean = false
|
||||
private set
|
||||
|
||||
/**
|
||||
* Apply definition providers to a class. This can be used to update the name or definition (pertinent meta-data)
|
||||
* of the class itself.
|
||||
*/
|
||||
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
|
||||
var resultingClass = clazz
|
||||
Processor.processEntriesOfType<ClassDefinitionProvider>(definitionProviders, analysisContext.messages) {
|
||||
resultingClass = it.define(currentAnalysisContext(), resultingClass)
|
||||
}
|
||||
if (clazz != resultingClass) {
|
||||
logger.trace("Type has been mutated {}", clazz)
|
||||
hasBeenModified = true
|
||||
}
|
||||
return super.visitClass(resultingClass)
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have some static fields to initialise, and haven't already added them
|
||||
* to an existing class initialiser block then we need to create one.
|
||||
*/
|
||||
override fun visitClassEnd(classVisitor: ClassVisitor, clazz: ClassRepresentation) {
|
||||
tryWriteClassInitializer(classVisitor)
|
||||
super.visitClassEnd(classVisitor, clazz)
|
||||
}
|
||||
|
||||
private fun tryWriteClassInitializer(classVisitor: ClassVisitor) {
|
||||
if (initializers.isNotEmpty()) {
|
||||
classVisitor.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null)?.also { mv ->
|
||||
mv.visitCode()
|
||||
EmitterModule(mv).writeByteCode(initializers)
|
||||
mv.visitInsn(RETURN)
|
||||
mv.visitMaxs(-1, -1)
|
||||
mv.visitEnd()
|
||||
}
|
||||
initializers.clear()
|
||||
hasBeenModified = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply definition providers to a method. This can be used to update the name or definition (pertinent meta-data)
|
||||
* of a class member.
|
||||
*/
|
||||
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
|
||||
var resultingMethod = method
|
||||
Processor.processEntriesOfType<MemberDefinitionProvider>(definitionProviders, analysisContext.messages) {
|
||||
resultingMethod = it.define(currentAnalysisContext(), resultingMethod)
|
||||
}
|
||||
if (method != resultingMethod) {
|
||||
logger.trace("Method has been mutated {}", method)
|
||||
hasBeenModified = true
|
||||
}
|
||||
return super.visitMethod(clazz, resultingMethod)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply definition providers to a field. This can be used to update the name or definition (pertinent meta-data)
|
||||
* of a class member.
|
||||
*/
|
||||
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
|
||||
var resultingField = field
|
||||
Processor.processEntriesOfType<MemberDefinitionProvider>(definitionProviders, analysisContext.messages) {
|
||||
resultingField = it.define(currentAnalysisContext(), resultingField)
|
||||
}
|
||||
if (field != resultingField) {
|
||||
logger.trace("Field has been mutated {}", field)
|
||||
initializers += resultingField.body
|
||||
hasBeenModified = true
|
||||
}
|
||||
return super.visitField(clazz, resultingField)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply emitters to an instruction. This can be used to instrument a part of the code block, change behaviour of
|
||||
* an existing instruction, or strip it out completely.
|
||||
*/
|
||||
override fun visitInstruction(method: Member, emitter: EmitterModule, instruction: Instruction) {
|
||||
val context = EmitterContext(currentAnalysisContext(), configuration, emitter)
|
||||
Processor.processEntriesOfType<Emitter>(emitters, analysisContext.messages) {
|
||||
it.emit(context, instruction)
|
||||
}
|
||||
if (!emitter.emitDefaultInstruction || emitter.hasEmittedCustomCode) {
|
||||
hasBeenModified = true
|
||||
}
|
||||
super.visitInstruction(method, emitter, instruction)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val logger = loggerFor<ClassMutator>()
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
/**
|
||||
* A definition provider is a hook for [ClassMutator], from where one can modify the name and meta-data of processed
|
||||
* classes and class members.
|
||||
*/
|
||||
interface DefinitionProvider
|
@ -1,27 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
/**
|
||||
* An emitter is a hook for [ClassMutator], from where one can modify the byte code of a class method.
|
||||
*/
|
||||
interface Emitter {
|
||||
|
||||
/**
|
||||
* Hook for providing modifications to an instruction in a method body. One can also prepend and append instructions
|
||||
* by using the [EmitterContext], and skip the default instruction altogether by invoking
|
||||
* [EmitterModule.preventDefault] from within [EmitterContext.emit].
|
||||
*
|
||||
* @param context The context from which the emitter is invoked. By calling [EmitterContext.emit], one gets access
|
||||
* to an instance of [EmitterModule] from within the supplied closure. From there, one can emit new instructions and
|
||||
* intercept the original instruction (for instance, modify or delete the instruction).
|
||||
* @param instruction The instruction currently being processed.
|
||||
*/
|
||||
fun emit(context: EmitterContext, instruction: Instruction)
|
||||
|
||||
/**
|
||||
* Determines the order in which emitters are executed within the sandbox.
|
||||
*/
|
||||
@JvmDefault
|
||||
val priority: Int
|
||||
get() = EMIT_DEFAULT
|
||||
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisConfiguration
|
||||
import net.corda.djvm.analysis.AnalysisRuntimeContext
|
||||
import net.corda.djvm.analysis.SourceLocation
|
||||
import net.corda.djvm.analysis.Whitelist
|
||||
import net.corda.djvm.references.ClassRepresentation
|
||||
import net.corda.djvm.references.ClassModule
|
||||
import net.corda.djvm.references.Member
|
||||
import net.corda.djvm.references.MemberModule
|
||||
|
||||
/**
|
||||
* The context in which an emitter is invoked.
|
||||
*
|
||||
* @property analysisContext The context in which a class and its members are processed.
|
||||
* @property configuration The configuration to used for the analysis.
|
||||
* @property emitterModule A module providing code generation functionality that can be used from within an emitter.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
open class EmitterContext(
|
||||
private val analysisContext: AnalysisRuntimeContext,
|
||||
private val configuration: AnalysisConfiguration,
|
||||
val emitterModule: EmitterModule
|
||||
) {
|
||||
|
||||
/**
|
||||
* The class currently being analysed.
|
||||
*/
|
||||
val clazz: ClassRepresentation
|
||||
get() = analysisContext.clazz
|
||||
|
||||
/**
|
||||
* The member currently being analysed, if any.
|
||||
*/
|
||||
val member: Member?
|
||||
get() = analysisContext.member
|
||||
|
||||
/**
|
||||
* The current source location.
|
||||
*/
|
||||
val location: SourceLocation
|
||||
get() = analysisContext.location
|
||||
|
||||
/**
|
||||
* The configured whitelist.
|
||||
*/
|
||||
val whitelist: Whitelist
|
||||
get() = analysisContext.configuration.whitelist
|
||||
|
||||
/**
|
||||
* Utilities for dealing with classes.
|
||||
*/
|
||||
val classModule: ClassModule
|
||||
get() = analysisContext.configuration.classModule
|
||||
|
||||
/**
|
||||
* Utilities for dealing with members.
|
||||
*/
|
||||
val memberModule: MemberModule
|
||||
get() = analysisContext.configuration.memberModule
|
||||
|
||||
/**
|
||||
* Resolve the sandboxed name of a class or interface.
|
||||
*/
|
||||
fun resolve(typeName: String): String {
|
||||
return configuration.classResolver.resolve(typeName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up and execute an emitter block for a particular member.
|
||||
*/
|
||||
inline fun emit(action: EmitterModule.() -> Unit) {
|
||||
action(emitterModule)
|
||||
}
|
||||
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.references.MethodBody
|
||||
import org.objectweb.asm.Label
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
import org.objectweb.asm.Type
|
||||
import sandbox.net.corda.djvm.costing.RuntimeCostAccounter
|
||||
|
||||
/**
|
||||
* Helper functions for emitting code to a method body.
|
||||
*
|
||||
* @property methodVisitor The underlying visitor which controls all the byte code for the current method.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class EmitterModule(
|
||||
private val methodVisitor: MethodVisitor
|
||||
) {
|
||||
|
||||
/**
|
||||
* Indicates whether the default instruction in the currently processed block is to be emitted or not.
|
||||
*/
|
||||
var emitDefaultInstruction: Boolean = true
|
||||
private set
|
||||
|
||||
/**
|
||||
* Indicates whether any custom code has been emitted in the applicable context.
|
||||
*/
|
||||
var hasEmittedCustomCode: Boolean = false
|
||||
private set
|
||||
|
||||
/**
|
||||
* Emit instruction for creating a new object of type [typeName].
|
||||
*/
|
||||
fun new(typeName: String, opcode: Int = NEW) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitTypeInsn(opcode, typeName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for creating a new object of type [T].
|
||||
*/
|
||||
inline fun <reified T> new() {
|
||||
new(Type.getInternalName(T::class.java))
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for loading a constant onto the stack.
|
||||
*/
|
||||
fun loadConstant(constant: Any) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitLdcInsn(constant)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a static method.
|
||||
*/
|
||||
fun invokeStatic(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitMethodInsn(INVOKESTATIC, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a virtual method.
|
||||
*/
|
||||
fun invokeVirtual(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a special method, e.g. a constructor or a method on a super-type.
|
||||
*/
|
||||
fun invokeSpecial(owner: String, name: String, descriptor: String, isInterface: Boolean = false) {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a special method on class [T], e.g. a constructor or a method on a super-type.
|
||||
*/
|
||||
inline fun <reified T> invokeSpecial(name: String, descriptor: String, isInterface: Boolean = false) {
|
||||
invokeSpecial(Type.getInternalName(T::class.java), name, descriptor, isInterface)
|
||||
}
|
||||
|
||||
fun invokeInterface(owner: String, name: String, descriptor: String) {
|
||||
methodVisitor.visitMethodInsn(INVOKEINTERFACE, owner, name, descriptor, true)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for storing a value into a static field.
|
||||
*/
|
||||
fun putStatic(owner: String, name: String, descriptor: String) {
|
||||
methodVisitor.visitFieldInsn(PUTSTATIC, owner, name, descriptor)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for popping one element off the stack.
|
||||
*/
|
||||
fun pop() {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitInsn(POP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for duplicating the top of the stack.
|
||||
*/
|
||||
fun duplicate() {
|
||||
hasEmittedCustomCode = true
|
||||
methodVisitor.visitInsn(DUP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for pushing an object reference
|
||||
* from a register onto the stack.
|
||||
*/
|
||||
fun pushObject(regNum: Int) {
|
||||
methodVisitor.visitVarInsn(ALOAD, regNum)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for pushing an integer value
|
||||
* from a register onto the stack.
|
||||
*/
|
||||
fun pushInteger(regNum: Int) {
|
||||
methodVisitor.visitVarInsn(ILOAD, regNum)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions to rearrange the stack as follows:
|
||||
* [W1] [W3]
|
||||
* [W2] -> [W1]
|
||||
* [w3] [W2]
|
||||
*/
|
||||
fun raiseThirdWordToTop() {
|
||||
methodVisitor.visitInsn(DUP2_X1)
|
||||
methodVisitor.visitInsn(POP2)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions to rearrange the stack as follows:
|
||||
* [W1] [W2]
|
||||
* [W2] -> [W3]
|
||||
* [W3] [W1]
|
||||
*/
|
||||
fun sinkTopToThirdWord() {
|
||||
methodVisitor.visitInsn(DUP_X2)
|
||||
methodVisitor.visitInsn(POP)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a sequence of instructions for instantiating and throwing an exception based on the provided message.
|
||||
*/
|
||||
fun <T : Throwable> throwException(exceptionType: Class<T>, message: String) {
|
||||
val exceptionName = Type.getInternalName(exceptionType)
|
||||
new(exceptionName)
|
||||
methodVisitor.visitInsn(DUP)
|
||||
methodVisitor.visitLdcInsn(message)
|
||||
invokeSpecial(exceptionName, "<init>", "(Ljava/lang/String;)V")
|
||||
methodVisitor.visitInsn(ATHROW)
|
||||
}
|
||||
|
||||
inline fun <reified T : Throwable> throwException(message: String) = throwException(T::class.java, message)
|
||||
|
||||
/**
|
||||
* Attempt to cast the object on the top of the stack to the given class.
|
||||
*/
|
||||
fun castObjectTo(className: String) {
|
||||
methodVisitor.visitTypeInsn(CHECKCAST, className)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for returning from "void" method.
|
||||
*/
|
||||
fun returnVoid() {
|
||||
methodVisitor.visitInsn(RETURN)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for a function that returns an object reference.
|
||||
*/
|
||||
fun returnObject() {
|
||||
methodVisitor.visitInsn(ARETURN)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instructions for a new line number.
|
||||
*/
|
||||
fun lineNumber(line: Int) {
|
||||
val label = Label()
|
||||
methodVisitor.visitLabel(label)
|
||||
methodVisitor.visitLineNumber(line, label)
|
||||
hasEmittedCustomCode = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bytecode from these [MethodBody] objects as provided.
|
||||
*/
|
||||
fun writeByteCode(bodies: Iterable<MethodBody>) {
|
||||
for (body in bodies) {
|
||||
body(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the code writer not to emit the default instruction.
|
||||
*/
|
||||
fun preventDefault() {
|
||||
emitDefaultInstruction = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit instruction for invoking a method on the static runtime cost accounting and instrumentation object.
|
||||
*/
|
||||
fun invokeInstrumenter(methodName: String, methodSignature: String) {
|
||||
invokeStatic(RuntimeCostAccounter.TYPE_NAME, methodName, methodSignature)
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
/**
|
||||
* Byte code instruction.
|
||||
*
|
||||
* @property operation The operation code, enumerated in [Opcodes].
|
||||
*/
|
||||
open class Instruction(
|
||||
val operation: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Byte code for the breakpoint operation.
|
||||
*/
|
||||
const val OP_BREAKPOINT: Int = 202
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package net.corda.djvm.code
|
||||
|
||||
import net.corda.djvm.analysis.AnalysisRuntimeContext
|
||||
import net.corda.djvm.references.Member
|
||||
|
||||
/**
|
||||
* A member definition provider is a hook for [ClassMutator], from where one can modify the name and meta-data of
|
||||
* processed class members.
|
||||
*/
|
||||
interface MemberDefinitionProvider : DefinitionProvider {
|
||||
|
||||
/**
|
||||
* Hook for providing modifications to a member definition.
|
||||
*
|
||||
* @param context The context in which the hook is called.
|
||||
* @param member The original member definition.
|
||||
*
|
||||
* @return The updated member definition, or [member] if no changes are desired.
|
||||
*/
|
||||
fun define(context: AnalysisRuntimeContext, member: Member): Member
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
@file:JvmName("Types")
|
||||
package net.corda.djvm.code
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
import sandbox.java.lang.DJVMException
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
import sandbox.net.corda.djvm.rules.RuleViolationError
|
||||
|
||||
/**
|
||||
* These are the priorities for executing [Emitter] instances.
|
||||
* Tracing emitters are executed first.
|
||||
*/
|
||||
const val EMIT_TRACING: Int = 0
|
||||
const val EMIT_TRAPPING_EXCEPTIONS: Int = EMIT_TRACING + 1
|
||||
const val EMIT_HANDLING_EXCEPTIONS: Int = EMIT_TRAPPING_EXCEPTIONS + 1
|
||||
const val EMIT_DEFAULT: Int = 10
|
||||
|
||||
val ruleViolationError: String = Type.getInternalName(RuleViolationError::class.java)
|
||||
val thresholdViolationError: String = Type.getInternalName(ThresholdViolationError::class.java)
|
||||
val djvmException: String = Type.getInternalName(DJVMException::class.java)
|
||||
|
||||
/**
|
||||
* Local extension method for normalizing a class name.
|
||||
*/
|
||||
val String.asPackagePath: String get() = this.replace('/', '.')
|
||||
val String.asResourcePath: String get() = this.replace('.', '/')
|
||||
|
||||
val String.emptyAsNull: String? get() = if (isEmpty()) null else this
|
@ -1,15 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Label
|
||||
|
||||
/**
|
||||
* Branch instruction.
|
||||
*
|
||||
* @property label The label of the target.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class BranchInstruction(
|
||||
operation: Int,
|
||||
val label: Label
|
||||
) : Instruction(operation)
|
@ -1,12 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import org.objectweb.asm.Label
|
||||
|
||||
/**
|
||||
* Label of a code block.
|
||||
*
|
||||
* @property label The label for the given code block.
|
||||
*/
|
||||
class CodeLabel(
|
||||
val label: Label
|
||||
) : NoOperationInstruction()
|
@ -1,6 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
class ConstantInstruction(val value: Any) : Instruction(Opcodes.LDC)
|
@ -1,20 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
/**
|
||||
* Dynamic invocation instruction.
|
||||
*
|
||||
* @property memberName The name of the method to invoke.
|
||||
* @property signature The function signature of the method being invoked.
|
||||
* @property numberOfArguments The number of arguments to pass to the target.
|
||||
* @property returnsValueOrReference False if the target returns `void`, or true if it returns a value or a reference.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class DynamicInvocationInstruction(
|
||||
val memberName: String,
|
||||
val signature: String,
|
||||
val numberOfArguments: Int,
|
||||
val returnsValueOrReference: Boolean
|
||||
) : Instruction(Opcodes.INVOKEDYNAMIC)
|
@ -1,13 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
|
||||
/**
|
||||
* Instruction with a single, constant integer operand.
|
||||
*
|
||||
* @property operand The integer operand.
|
||||
*/
|
||||
class IntegerInstruction(
|
||||
operation: Int,
|
||||
val operand: Int
|
||||
) : Instruction(operation)
|
@ -1,35 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.references.MemberReference
|
||||
|
||||
/**
|
||||
* Field access and method invocation instruction.
|
||||
*
|
||||
* @property owner The class owning the field or method.
|
||||
* @property memberName The name of the field or the method being accessed.
|
||||
* @property signature The return type of a field or function signature for a method.
|
||||
* @property ownerIsInterface If the member is a method, this is true if the owner is an interface.
|
||||
* @property isMethod Indicates whether the member is a method or a field.
|
||||
*/
|
||||
class MemberAccessInstruction(
|
||||
operation: Int,
|
||||
val owner: String,
|
||||
val memberName: String,
|
||||
val signature: String,
|
||||
val ownerIsInterface: Boolean = false,
|
||||
val isMethod: Boolean = false
|
||||
) : Instruction(operation) {
|
||||
|
||||
/**
|
||||
* The absolute name of the referenced member.
|
||||
*/
|
||||
val reference = "$owner.$memberName:$signature"
|
||||
|
||||
/**
|
||||
* Get a member reference representation of the target of the instruction.
|
||||
*/
|
||||
val member: MemberReference
|
||||
get() = MemberReference(owner, memberName, signature)
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.references.Member
|
||||
|
||||
/**
|
||||
* Pseudo-instruction marking the beginning of a method.
|
||||
* @property method [Member] describing this method.
|
||||
*/
|
||||
class MethodEntry(val method: Member): NoOperationInstruction()
|
@ -1,9 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
/**
|
||||
* Instruction that, surprise surprise (!), does nothing!
|
||||
*/
|
||||
open class NoOperationInstruction : Instruction(Opcodes.NOP)
|
@ -1,22 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
import org.objectweb.asm.Label
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
/**
|
||||
* Table switch instruction.
|
||||
*
|
||||
* @property min The minimum key value.
|
||||
* @property max The maximum key value.
|
||||
* @property defaultHandler The label of the default handler block.
|
||||
* @property handlers The labels of each of the handler blocks, where the label of the handler block for key
|
||||
* `min + i` is at index `i` in `handlers`.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class TableSwitchInstruction(
|
||||
val min: Int,
|
||||
val max: Int,
|
||||
val defaultHandler: Label,
|
||||
val handlers: List<Label>
|
||||
) : Instruction(Opcodes.TABLESWITCH)
|
@ -1,8 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import org.objectweb.asm.Label
|
||||
|
||||
open class TryBlock(
|
||||
val handler: Label,
|
||||
val typeName: String
|
||||
) : NoOperationInstruction()
|
@ -1,14 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import org.objectweb.asm.Label
|
||||
|
||||
/**
|
||||
* Try-catch block.
|
||||
*
|
||||
* @property typeName The type of the exception being caught.
|
||||
* @property handler The label of the exception handler.
|
||||
*/
|
||||
class TryCatchBlock(
|
||||
typeName: String,
|
||||
handler: Label
|
||||
) : TryBlock(handler, typeName)
|
@ -1,12 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import org.objectweb.asm.Label
|
||||
|
||||
/**
|
||||
* Try-finally block.
|
||||
*
|
||||
* @property handler The handler for the finally-block.
|
||||
*/
|
||||
class TryFinallyBlock(
|
||||
handler: Label
|
||||
) : TryBlock(handler, "")
|
@ -1,13 +0,0 @@
|
||||
package net.corda.djvm.code.instructions
|
||||
|
||||
import net.corda.djvm.code.Instruction
|
||||
|
||||
/**
|
||||
* Object instantiation instruction.
|
||||
*
|
||||
* @property typeName The class name of the object being instantiated.
|
||||
*/
|
||||
class TypeInstruction(
|
||||
operation: Int,
|
||||
val typeName: String
|
||||
) : Instruction(operation)
|
@ -1,30 +0,0 @@
|
||||
package net.corda.djvm.costing
|
||||
|
||||
/**
|
||||
* Cost metric to be used in a sandbox environment. The metric has a threshold and a mechanism for reporting violations.
|
||||
* The implementation assumes that each metric is tracked on a per-thread basis, i.e., that each sandbox runs on its own
|
||||
* thread.
|
||||
*
|
||||
* @param threshold The threshold for this metric.
|
||||
* @param errorMessage A delegate for generating an error message based on the thread it was reported from.
|
||||
*/
|
||||
class RuntimeCost(
|
||||
threshold: Long,
|
||||
errorMessage: (Thread) -> String
|
||||
) : TypedRuntimeCost<Long>(
|
||||
0,
|
||||
{ value: Long -> value > threshold },
|
||||
errorMessage
|
||||
) {
|
||||
|
||||
/**
|
||||
* Increment the accumulated cost by an integer.
|
||||
*/
|
||||
fun increment(incrementBy: Int) = increment(incrementBy.toLong())
|
||||
|
||||
/**
|
||||
* Increment the accumulated cost by a long integer.
|
||||
*/
|
||||
fun increment(incrementBy: Long = 1) = incrementAndCheck { value -> Math.addExact(value, incrementBy) }
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.corda.djvm.costing
|
||||
|
||||
import net.corda.djvm.execution.ExecutionProfile
|
||||
|
||||
/**
|
||||
* This class provides a summary of the accumulated costs for the runtime metrics that are being tracked. It also keeps
|
||||
* track of applicable thresholds and will terminate sandbox execution if any of them are breached.
|
||||
*
|
||||
* The costs are tracked on a per-thread basis, and thus, are isolated for each sandbox. Each sandbox live on its own
|
||||
* thread.
|
||||
*/
|
||||
class RuntimeCostSummary private constructor(
|
||||
allocationCostThreshold: Long,
|
||||
jumpCostThreshold: Long,
|
||||
invocationCostThreshold: Long,
|
||||
throwCostThreshold: Long
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a new runtime cost tracker based on an execution profile.
|
||||
*/
|
||||
constructor(profile: ExecutionProfile) : this(
|
||||
allocationCostThreshold = profile.allocationCostThreshold,
|
||||
jumpCostThreshold = profile.jumpCostThreshold,
|
||||
invocationCostThreshold = profile.invocationCostThreshold,
|
||||
throwCostThreshold = profile.throwCostThreshold
|
||||
)
|
||||
|
||||
/**
|
||||
* Accumulated cost of memory allocations.
|
||||
*/
|
||||
val allocationCost = RuntimeCost(allocationCostThreshold) {
|
||||
"Sandbox [${it.name}] terminated due to over-allocation"
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulated cost of jump operations.
|
||||
*/
|
||||
val jumpCost = RuntimeCost(jumpCostThreshold) {
|
||||
"Sandbox [${it.name}] terminated due to excessive use of looping"
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulated cost of method invocations.
|
||||
*/
|
||||
val invocationCost = RuntimeCost(invocationCostThreshold) {
|
||||
"Sandbox [${it.name}] terminated due to excessive method calling"
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulated cost of throw operations.
|
||||
*/
|
||||
val throwCost = RuntimeCost(throwCostThreshold) {
|
||||
"Sandbox [${it.name}] terminated due to excessive exception throwing"
|
||||
}
|
||||
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package net.corda.djvm.costing
|
||||
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import sandbox.net.corda.djvm.costing.ThresholdViolationError
|
||||
|
||||
/**
|
||||
* Cost metric to be used in a sandbox environment. The metric has a threshold and a mechanism for reporting violations.
|
||||
* The implementation assumes that each metric is tracked on a per-thread basis, i.e., that each sandbox runs on its own
|
||||
* thread.
|
||||
*
|
||||
* @param initialValue The initial value of this metric.
|
||||
* @property thresholdPredicate A delegate for determining whether a threshold has been reached or not.
|
||||
* @property errorMessage A delegate for generating an error message based on the thread it was reported from.
|
||||
*/
|
||||
open class TypedRuntimeCost<T>(
|
||||
initialValue: T,
|
||||
private val thresholdPredicate: (T) -> Boolean,
|
||||
private val errorMessage: (Thread) -> String
|
||||
) {
|
||||
|
||||
/**
|
||||
* The thread-local container for the cost accumulator.
|
||||
*/
|
||||
private val costValue = object : ThreadLocal<T>() {
|
||||
override fun initialValue() = initialValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Property getter for accessing the current accumulated cost.
|
||||
*/
|
||||
val value: T
|
||||
get() = costValue.get()
|
||||
|
||||
/**
|
||||
* Helper function for doing a guarded increment of the cost value, with a mechanism for consistent error reporting
|
||||
* and nuking of the current thread environment if threshold breaches are encountered.
|
||||
*/
|
||||
protected fun incrementAndCheck(increment: (T) -> T) {
|
||||
val currentThread = getAndCheckThread() ?: return
|
||||
val newValue = increment(costValue.get())
|
||||
costValue.set(newValue)
|
||||
if (thresholdPredicate(newValue)) {
|
||||
val message = errorMessage(currentThread)
|
||||
logger.error("Threshold breached; {}", message)
|
||||
throw ThresholdViolationError(message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If [filteredThreads] is specified, this method will filter out those threads whenever threshold constraints are
|
||||
* being tested. This can be used to disable cost accounting on a primary thread, for instance.
|
||||
*/
|
||||
private fun getAndCheckThread(): Thread? {
|
||||
val currentThread = Thread.currentThread()
|
||||
if (filteredThreads.contains(currentThread)) {
|
||||
logger.trace("Thread will not be affected by runtime costing")
|
||||
return null
|
||||
}
|
||||
return currentThread
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
/**
|
||||
* A set of threads to which cost accounting will be disabled.
|
||||
*/
|
||||
private val filteredThreads: List<Thread> = emptyList()
|
||||
|
||||
private val logger = loggerFor<RuntimeCost>()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.costing.RuntimeCostSummary
|
||||
|
||||
/**
|
||||
* A read-only copy of a the costs accumulated in an [IsolatedTask].
|
||||
*
|
||||
* @property allocations Number of bytes allocated.
|
||||
* @property invocations Number of invocations made.
|
||||
* @property jumps Number of jumps made (includes conditional branches that might not have been taken).
|
||||
* @property throws Number of throws made.
|
||||
*/
|
||||
data class CostSummary(
|
||||
val allocations: Long,
|
||||
val invocations: Long,
|
||||
val jumps: Long,
|
||||
val throws: Long
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a read-only cost summary object from an instance of [RuntimeCostSummary].
|
||||
*/
|
||||
constructor(costs: RuntimeCostSummary) : this(
|
||||
costs.allocationCost.value,
|
||||
costs.invocationCost.value,
|
||||
costs.jumpCost.value,
|
||||
costs.throwCost.value
|
||||
)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* A blank summary of costs.
|
||||
*/
|
||||
val empty = CostSummary(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import java.util.function.Function
|
||||
|
||||
/**
|
||||
* The executor is responsible for spinning up a deterministic, sandboxed environment and launching the referenced code
|
||||
* block inside it. The code will run on a separate thread for complete isolation, and to enable context-based costing
|
||||
* of said code. Any exceptions should be forwarded to the caller of [SandboxExecutor.run]. Similarly, the returned
|
||||
* output from the referenced code block should be returned to the caller.
|
||||
*
|
||||
* @param configuration The configuration of the sandbox.
|
||||
*/
|
||||
class DeterministicSandboxExecutor<TInput, TOutput>(
|
||||
configuration: SandboxConfiguration
|
||||
) : SandboxExecutor<TInput, TOutput>(configuration) {
|
||||
|
||||
/**
|
||||
* Short-hand for running a [Function] in a sandbox by its type reference.
|
||||
*/
|
||||
inline fun <reified TRunnable : Function<in TInput, out TOutput>> run(input: TInput):
|
||||
ExecutionSummaryWithResult<TOutput> {
|
||||
return run(ClassSource.fromClassName(TRunnable::class.java.name), input)
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
/**
|
||||
* The execution profile of a [java.util.function.Function] when run in a sandbox.
|
||||
*
|
||||
* @property allocationCostThreshold The threshold placed on allocations.
|
||||
* @property invocationCostThreshold The threshold placed on invocations.
|
||||
* @property jumpCostThreshold The threshold placed on jumps.
|
||||
* @property throwCostThreshold The threshold placed on throw statements.
|
||||
*/
|
||||
enum class ExecutionProfile(
|
||||
val allocationCostThreshold: Long = Long.MAX_VALUE,
|
||||
val invocationCostThreshold: Long = Long.MAX_VALUE,
|
||||
val jumpCostThreshold: Long = Long.MAX_VALUE,
|
||||
val throwCostThreshold: Long = Long.MAX_VALUE
|
||||
) {
|
||||
|
||||
// TODO Define sensible runtime thresholds and make further improvements to instrumentation.
|
||||
|
||||
/**
|
||||
* Profile with a set of default thresholds.
|
||||
*/
|
||||
DEFAULT(
|
||||
allocationCostThreshold = 1024 * 1024 * 1024,
|
||||
invocationCostThreshold = 1_000_000,
|
||||
jumpCostThreshold = 1_000_000,
|
||||
throwCostThreshold = 1_000_000
|
||||
),
|
||||
|
||||
/**
|
||||
* Profile where no limitations have been imposed on the sandbox.
|
||||
*/
|
||||
UNLIMITED(),
|
||||
|
||||
/**
|
||||
* Profile where throw statements have been disallowed.
|
||||
*/
|
||||
DISABLE_THROWS(throwCostThreshold = 0),
|
||||
|
||||
/**
|
||||
* Profile where branching statements have been disallowed.
|
||||
*/
|
||||
DISABLE_BRANCHING(jumpCostThreshold = 0)
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
/**
|
||||
* The summary of the execution of a [java.util.function.Function] in a sandbox. This class has no representation of the
|
||||
* outcome, and is typically used when there has been a pre-mature exit from the sandbox, for instance, if an exception
|
||||
* was thrown.
|
||||
*
|
||||
* @property costs The costs accumulated when running the sandboxed code.
|
||||
*/
|
||||
open class ExecutionSummary(
|
||||
val costs: CostSummary = CostSummary.empty
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
/**
|
||||
* The summary of the execution of a [java.util.function.Function] in a sandbox.
|
||||
*
|
||||
* @property result The outcome of the sandboxed operation.
|
||||
* @see ExecutionSummary
|
||||
*/
|
||||
class ExecutionSummaryWithResult<out TResult>(
|
||||
val result: TResult? = null,
|
||||
costs: CostSummary = CostSummary.empty
|
||||
) : ExecutionSummary(costs)
|
@ -1,98 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.SandboxConfiguration
|
||||
import net.corda.djvm.SandboxRuntimeContext
|
||||
import net.corda.djvm.messages.MessageCollection
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.djvm.rewiring.SandboxClassLoadingException
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* Container for running a task in an isolated environment.
|
||||
*/
|
||||
class IsolatedTask(
|
||||
private val identifier: String,
|
||||
private val configuration: SandboxConfiguration
|
||||
) {
|
||||
|
||||
/**
|
||||
* Run an action in an isolated environment.
|
||||
*/
|
||||
fun <T> run(action: IsolatedTask.() -> T?): Result<T> {
|
||||
val runnable = this
|
||||
val threadName = "DJVM-$identifier-${uniqueIdentifier.getAndIncrement()}"
|
||||
val completionLatch = CountDownLatch(1)
|
||||
var output: T? = null
|
||||
var costs = CostSummary.empty
|
||||
var exception: Throwable? = null
|
||||
thread(name = threadName, isDaemon = true) {
|
||||
logger.trace("Entering isolated runtime environment...")
|
||||
SandboxRuntimeContext(configuration).use {
|
||||
output = try {
|
||||
action(runnable)
|
||||
} catch (ex: Throwable) {
|
||||
logger.error("Exception caught in isolated runtime environment", ex)
|
||||
exception = (ex as? LinkageError)?.cause ?: ex
|
||||
null
|
||||
}
|
||||
costs = CostSummary(runtimeCosts)
|
||||
}
|
||||
logger.trace("Exiting isolated runtime environment...")
|
||||
completionLatch.countDown()
|
||||
}
|
||||
completionLatch.await()
|
||||
val messages = exception.let {
|
||||
when (it) {
|
||||
is SandboxClassLoadingException -> it.messages
|
||||
is SandboxException -> {
|
||||
when (it.exception) {
|
||||
is SandboxClassLoadingException -> it.exception.messages
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} ?: MessageCollection()
|
||||
return Result(threadName, output, costs, messages, exception)
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a run of an [IsolatedTask].
|
||||
*
|
||||
* @property identifier The identifier of the [IsolatedTask].
|
||||
* @property output The result of the run, if successful.
|
||||
* @property costs Captured runtime costs as reported at the end of the run.
|
||||
* @property messages The messages collated during the run.
|
||||
* @property exception This holds any exceptions that might get thrown during execution.
|
||||
*/
|
||||
data class Result<T>(
|
||||
val identifier: String,
|
||||
val output: T?,
|
||||
val costs: CostSummary,
|
||||
val messages: MessageCollection,
|
||||
val exception: Throwable?
|
||||
)
|
||||
|
||||
/**
|
||||
* The class loader to use for loading the [java.util.function.Function] and any referenced code in [SandboxExecutor.run].
|
||||
*/
|
||||
val classLoader: SandboxClassLoader
|
||||
get() = SandboxRuntimeContext.instance.classLoader
|
||||
|
||||
// TODO Caching can transcend thread-local contexts by taking the sandbox configuration into account in the key derivation
|
||||
|
||||
private companion object {
|
||||
|
||||
/**
|
||||
* An atomically incrementing identifier used to uniquely identify each runnable.
|
||||
*/
|
||||
private val uniqueIdentifier = AtomicLong(0)
|
||||
|
||||
private val logger = loggerFor<IsolatedTask>()
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.utilities.loggerFor
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
* Helper class for processing queued entities.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class QueueProcessor<T>(
|
||||
private val deduplicationKeyExtractor: (T) -> String,
|
||||
vararg elements: T
|
||||
) {
|
||||
|
||||
private val queue = ConcurrentLinkedQueue<T>(elements.toMutableList())
|
||||
|
||||
private val seenElements = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* Add an element to the queue.
|
||||
*/
|
||||
fun enqueue(element: T) {
|
||||
logger.trace("Enqueuing {}...", element)
|
||||
val key = deduplicationKeyExtractor(element)
|
||||
if (key !in seenElements) {
|
||||
queue.add(element)
|
||||
seenElements.add(key)
|
||||
} else {
|
||||
logger.trace("Skipped {} as it has already been processed", element)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one element from the queue.
|
||||
*/
|
||||
fun dequeue(): T = queue.remove().apply {
|
||||
logger.trace("Popping {} from the queue...", this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if queue is empty.
|
||||
*/
|
||||
fun isNotEmpty() = queue.isNotEmpty()
|
||||
|
||||
/**
|
||||
* Process the current queue with provided action per element.
|
||||
*/
|
||||
inline fun process(action: QueueProcessor<T>.(T) -> Unit) {
|
||||
while (isNotEmpty()) {
|
||||
val element = dequeue()
|
||||
action(this, element)
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = loggerFor<QueueProcessor<T>>()
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package net.corda.djvm.execution
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import java.io.PrintWriter
|
||||
|
||||
/**
|
||||
* An exception raised due to a runtime error inside a sandbox.
|
||||
*
|
||||
* @param message The detailed message describing the problem.
|
||||
* @property sandboxName The name of the sandbox in which the error occurred.
|
||||
* @property sandboxClass The class used as the sandbox entry point.
|
||||
* @property executionSummary A snapshot of the execution summary for the sandbox.
|
||||
* @property exception The inner exception, as it was raised within the sandbox.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class SandboxException(
|
||||
message: String,
|
||||
val sandboxName: String,
|
||||
val sandboxClass: ClassSource,
|
||||
val executionSummary: ExecutionSummary,
|
||||
val exception: Throwable
|
||||
) : Exception(message, exception) {
|
||||
|
||||
/**
|
||||
* Provide programmatic access to the stack trace information captured at the time the exception was thrown.
|
||||
*/
|
||||
override fun getStackTrace(): Array<out StackTraceElement>? = exception.stackTrace
|
||||
|
||||
/**
|
||||
* Print the stack trace information of the exception using a [PrintWriter].
|
||||
*/
|
||||
override fun printStackTrace(writer: PrintWriter) = exception.printStackTrace(writer)
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user