mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
ENT-1906: Trivial tweaks to DJVM code. (#3890)
* Trivial tweaks to DJVM code. - Use ASM Type.getInternalName() - Use @JvmDefault annotation - Declare test base class as abstract - Ensure test Log4J configuration has precedence - Replace assert() with require() - Replace simple lambdas with function references * Publish corda-djvm artifact. * Migrate Utilities class into the CLI tool. * Configure unit tests for console logging.
This commit is contained in:
parent
a7f9320985
commit
5b255c81a8
@ -331,6 +331,7 @@ bintrayConfig {
|
||||
'corda-rpc',
|
||||
'corda-core',
|
||||
'corda-core-deterministic',
|
||||
'corda-djvm',
|
||||
'corda',
|
||||
'corda-finance',
|
||||
'corda-node',
|
||||
|
@ -1,6 +1,10 @@
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '2.0.4'
|
||||
}
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
description 'Corda deterministic JVM sandbox'
|
||||
|
||||
ext {
|
||||
// Shaded version of ASM to avoid conflict with root project.
|
||||
@ -30,8 +34,8 @@ dependencies {
|
||||
jar.enabled = false
|
||||
|
||||
shadowJar {
|
||||
baseName = "djvm"
|
||||
classifier = ""
|
||||
baseName 'corda-djvm'
|
||||
classifier ''
|
||||
dependencies {
|
||||
exclude(dependency('com.jcabi:.*:.*'))
|
||||
exclude(dependency('org.apache.*:.*:.*'))
|
||||
@ -40,10 +44,14 @@ shadowJar {
|
||||
exclude(dependency('io.github.lukehutch:.*:.*'))
|
||||
}
|
||||
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
|
||||
artifacts {
|
||||
shadow(tasks.shadowJar.archivePath) {
|
||||
builtBy shadowJar
|
||||
}
|
||||
}
|
||||
}
|
||||
assemble.dependsOn shadowJar
|
||||
|
||||
artifacts {
|
||||
publish shadowJar
|
||||
}
|
||||
|
||||
publish {
|
||||
disableDefaultJar true
|
||||
name shadowJar.baseName
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.tools.Utilities.createCodePath
|
||||
import net.corda.djvm.tools.Utilities.getFileNames
|
||||
import net.corda.djvm.tools.Utilities.jarPath
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
import java.nio.file.Path
|
||||
|
@ -7,9 +7,6 @@ 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.tools.Utilities.find
|
||||
import net.corda.djvm.tools.Utilities.onEmpty
|
||||
import net.corda.djvm.tools.Utilities.userClassPath
|
||||
import net.corda.djvm.utilities.Discovery
|
||||
import djvm.org.objectweb.asm.ClassReader
|
||||
import picocli.CommandLine.Option
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.source.ClassSource
|
||||
import net.corda.djvm.tools.Utilities.createCodePath
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Parameters
|
||||
import java.nio.file.Files
|
||||
|
@ -1,9 +1,5 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.tools.Utilities.baseName
|
||||
import net.corda.djvm.tools.Utilities.createCodePath
|
||||
import net.corda.djvm.tools.Utilities.getFiles
|
||||
import net.corda.djvm.tools.Utilities.openOptions
|
||||
import picocli.CommandLine.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import net.corda.djvm.tools.Utilities.workingDirectory
|
||||
import picocli.CommandLine.Command
|
||||
import java.nio.file.Files
|
||||
|
||||
|
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal file
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal file
@ -0,0 +1,104 @@
|
||||
@file:JvmName("Utilities")
|
||||
package net.corda.djvm.tools.cli
|
||||
|
||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import java.lang.reflect.Modifier
|
||||
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", "djvm").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/djvm"): List<Class<*>> {
|
||||
val references = mutableListOf<Class<*>>()
|
||||
FastClasspathScanner(scanSpec)
|
||||
.matchClassesImplementing(T::class.java) { clazz ->
|
||||
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
|
||||
references.add(clazz)
|
||||
}
|
||||
}
|
||||
.scan()
|
||||
return references
|
||||
}
|
@ -20,6 +20,7 @@ interface Emitter {
|
||||
/**
|
||||
* Indication of whether or not the emitter performs instrumentation for tracing inside the sandbox.
|
||||
*/
|
||||
@JvmDefault
|
||||
val isTracer: Boolean
|
||||
get() = false
|
||||
|
||||
|
@ -12,5 +12,6 @@ interface MemberInformation {
|
||||
val className: String
|
||||
val memberName: String
|
||||
val signature: String
|
||||
@JvmDefault
|
||||
val reference: String get() = "$className.$memberName:$signature"
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.djvm.rewiring
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
/**
|
||||
* A class or interface running in a Java application, together with its raw byte code representation and all references
|
||||
* made from within the type.
|
||||
@ -16,7 +18,7 @@ class LoadedClass(
|
||||
* The name of the loaded type.
|
||||
*/
|
||||
val name: String
|
||||
get() = type.name.replace('.', '/')
|
||||
get() = Type.getInternalName(type)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Class(type=$name, size=${byteCode.bytes.size}, isModified=${byteCode.isModified})"
|
||||
|
@ -4,6 +4,7 @@ import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassWriter
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
|
||||
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
/**
|
||||
* Class writer for sandbox execution, with configurable a [classLoader] to ensure correct deduction of the used class
|
||||
@ -52,7 +53,7 @@ open class SandboxClassWriter(
|
||||
do {
|
||||
clazz = clazz.superclass
|
||||
} while (!clazz.isAssignableFrom(class2))
|
||||
clazz.name.replace('.', '/')
|
||||
Type.getInternalName(clazz)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ open class SourceClassLoader(
|
||||
when {
|
||||
!Files.exists(it) -> throw FileNotFoundException("File not found; $it")
|
||||
Files.isDirectory(it) -> {
|
||||
listOf(it.toURL()) + Files.list(it).filter { isJarFile(it) }.map { it.toURL() }.toList()
|
||||
listOf(it.toURL()) + Files.list(it).filter(::isJarFile).map { it.toURL() }.toList()
|
||||
}
|
||||
Files.isReadable(it) && isJarFile(it) -> listOf(it.toURL())
|
||||
else -> throw IllegalArgumentException("Expected JAR or class file, but found $it")
|
||||
|
@ -1,114 +0,0 @@
|
||||
package net.corda.djvm.tools
|
||||
|
||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import java.lang.reflect.Modifier
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardOpenOption
|
||||
|
||||
/**
|
||||
* Various utility functions.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
object Utilities {
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
val root = Paths.get("tmp")
|
||||
.resolve("net")
|
||||
.resolve("corda")
|
||||
.resolve("djvm")
|
||||
Files.createDirectories(root)
|
||||
return root
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = Utilities::class.java.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/djvm"): List<Class<*>> {
|
||||
val references = mutableListOf<Class<*>>()
|
||||
FastClasspathScanner(scanSpec)
|
||||
.matchClassesImplementing(T::class.java) { clazz ->
|
||||
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
|
||||
references.add(clazz)
|
||||
}
|
||||
}
|
||||
.scan()
|
||||
return references
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import java.lang.reflect.Modifier
|
||||
* Find and instantiate types that implement a certain interface.
|
||||
*/
|
||||
object Discovery {
|
||||
const val FORBIDDEN_CLASS_MASK = (Modifier.STATIC or Modifier.ABSTRACT)
|
||||
|
||||
/**
|
||||
* Get an instance of each concrete class that implements interface or class [T].
|
||||
@ -15,7 +16,7 @@ object Discovery {
|
||||
val instances = mutableListOf<T>()
|
||||
FastClasspathScanner("net/corda/djvm")
|
||||
.matchClassesImplementing(T::class.java) { clazz ->
|
||||
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
|
||||
if (clazz.modifiers and FORBIDDEN_CLASS_MASK == 0) {
|
||||
try {
|
||||
instances.add(clazz.newInstance())
|
||||
} catch (exception: Throwable) {
|
||||
|
@ -4,7 +4,7 @@ class StrictFloat : Callable {
|
||||
override fun call() {
|
||||
val d = java.lang.Double.MIN_VALUE
|
||||
val x = d / 2 * 2
|
||||
assert(x.toString() == "0.0")
|
||||
require(x.toString() == "0.0")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,10 @@ import net.corda.djvm.utilities.Discovery
|
||||
import net.corda.djvm.validation.RuleValidator
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Type
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
open class TestBase {
|
||||
abstract class TestBase {
|
||||
|
||||
companion object {
|
||||
|
||||
@ -38,8 +39,7 @@ open class TestBase {
|
||||
/**
|
||||
* Get the full name of type [T].
|
||||
*/
|
||||
inline fun <reified T> nameOf(prefix: String = "") =
|
||||
"$prefix${T::class.java.name.replace('.', '/')}"
|
||||
inline fun <reified T> nameOf(prefix: String = "") = "$prefix${Type.getInternalName(T::class.java)}"
|
||||
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ open class TestBase {
|
||||
var thrownException: Throwable? = null
|
||||
Thread {
|
||||
try {
|
||||
val pinnedTestClasses = pinnedClasses.map { it.name.replace('.', '/') }.toSet()
|
||||
val pinnedTestClasses = pinnedClasses.map(Type::getInternalName).toSet()
|
||||
val analysisConfiguration = AnalysisConfiguration(
|
||||
whitelist = whitelist,
|
||||
additionalPinnedClasses = pinnedTestClasses,
|
||||
|
@ -63,7 +63,7 @@ class SandboxExecutorTest : TestBase() {
|
||||
val obj = Object()
|
||||
val hash1 = obj.hashCode()
|
||||
val hash2 = obj.hashCode()
|
||||
assert(hash1 == hash2)
|
||||
require(hash1 == hash2)
|
||||
return Object().hashCode()
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import net.corda.djvm.annotations.NonDeterministic
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.junit.Test
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
class MemberModuleTest {
|
||||
|
||||
@ -132,7 +133,7 @@ class MemberModuleTest {
|
||||
}
|
||||
|
||||
private val java.lang.Class<*>.descriptor: String
|
||||
get() = "L${name.replace('.', '/')};"
|
||||
get() = Type.getDescriptor(this)
|
||||
|
||||
private fun member(member: String) =
|
||||
MemberReference("", member, "")
|
||||
|
@ -4,6 +4,7 @@ import foo.bar.sandbox.Callable
|
||||
import net.corda.djvm.TestBase
|
||||
import net.corda.djvm.assertions.AssertionExtensions.assertThat
|
||||
import org.junit.Test
|
||||
import org.objectweb.asm.Type
|
||||
import java.util.*
|
||||
|
||||
class ReferenceExtractorTest : TestBase() {
|
||||
@ -32,7 +33,7 @@ class ReferenceExtractorTest : TestBase() {
|
||||
@Test
|
||||
fun `can find field references`() = validate<B> { context ->
|
||||
assertThat(context.references)
|
||||
.hasMember(B::class.java.name.replace('.', '/'), "foo", "Ljava/lang/String;")
|
||||
.hasMember(Type.getInternalName(B::class.java), "foo", "Ljava/lang/String;")
|
||||
}
|
||||
|
||||
class B {
|
||||
@ -47,7 +48,7 @@ class ReferenceExtractorTest : TestBase() {
|
||||
@Test
|
||||
fun `can find class references`() = validate<C> { context ->
|
||||
assertThat(context.references)
|
||||
.hasClass(A::class.java.name.replace('.', '/'))
|
||||
.hasClass(Type.getInternalName(A::class.java))
|
||||
}
|
||||
|
||||
class C {
|
||||
|
17
djvm/src/test/resources/log4j2-test.xml
Normal file
17
djvm/src/test/resources/log4j2-test.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
|
||||
<ThresholdFilter level="info"/>
|
||||
<Appenders>
|
||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%date %highlight{%level %c{1}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console-Appender"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info">
|
||||
|
||||
<ThresholdFilter level="info"/>
|
||||
<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>
|
Loading…
x
Reference in New Issue
Block a user