From 17b5bbbb09064c26b5fc6111a289f12f64704f71 Mon Sep 17 00:00:00 2001 From: seqradev Date: Wed, 21 Jan 2026 11:52:32 +0000 Subject: [PATCH] feat: Add JRT module support for newer JDKs, fix unknown type super --- .../kotlin/org/seqra/ir/api/jvm/Symbols.kt | 2 - .../org/seqra/ir/impl/JIRDatabaseImpl.kt | 13 +- .../features/classpaths/JIRUnknownType.kt | 5 +- .../ir/impl/fs/AbstractByteCodeLocation.kt | 25 +-- .../seqra/ir/impl/fs/BuildFolderLocation.kt | 5 +- .../org/seqra/ir/impl/fs/ByteCodeLocations.kt | 24 +-- .../org/seqra/ir/impl/fs/JarLocationImpl.kt | 10 +- .../org/seqra/ir/impl/fs/JavaRuntime.kt | 23 ++- .../ir/impl/fs/JavaRuntimeModuleLocation.kt | 144 ++++++++++++++++++ .../impl/storage/AbstractJIRDbPersistence.kt | 12 +- .../storage/PersistentByteCodeLocation.kt | 46 +++--- .../ir/testing/tree/DummyCodeLocation.kt | 3 - 12 files changed, 235 insertions(+), 77 deletions(-) create mode 100644 seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntimeModuleLocation.kt diff --git a/seqra-ir-api-jvm/src/main/kotlin/org/seqra/ir/api/jvm/Symbols.kt b/seqra-ir-api-jvm/src/main/kotlin/org/seqra/ir/api/jvm/Symbols.kt index fa7e4c5..2daa8a2 100644 --- a/seqra-ir-api-jvm/src/main/kotlin/org/seqra/ir/api/jvm/Symbols.kt +++ b/seqra-ir-api-jvm/src/main/kotlin/org/seqra/ir/api/jvm/Symbols.kt @@ -15,8 +15,6 @@ import java.math.BigInteger * */ interface JIRByteCodeLocation { - val jarOrFolder: File - /** * Is being calculated each time when it is invoked. */ diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/JIRDatabaseImpl.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/JIRDatabaseImpl.kt index cf99ea7..14e80a1 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/JIRDatabaseImpl.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/JIRDatabaseImpl.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.JIRByteCodeLocation import org.seqra.ir.api.jvm.JIRClasspath import org.seqra.ir.api.jvm.JIRClasspathFeature @@ -21,6 +20,7 @@ import org.seqra.ir.api.jvm.JIRDatabase import org.seqra.ir.api.jvm.JIRDatabasePersistence import org.seqra.ir.api.jvm.JIRFeature import org.seqra.ir.api.jvm.JIRSettings +import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.RegisteredLocation import org.seqra.ir.impl.features.classpaths.ClasspathCache import org.seqra.ir.impl.features.classpaths.KotlinMetadata @@ -28,8 +28,7 @@ import org.seqra.ir.impl.features.classpaths.MethodInstructionsFeature import org.seqra.ir.impl.features.classpaths.UnknownClassMethodsAndFields import org.seqra.ir.impl.features.classpaths.UnknownClasses import org.seqra.ir.impl.fs.JavaRuntime -import org.seqra.ir.impl.fs.asByteCodeLocation -import org.seqra.ir.impl.fs.filterExisting +import org.seqra.ir.impl.fs.createNonRuntimeByteCodeLocations import org.seqra.ir.impl.fs.lazySources import org.seqra.ir.impl.fs.sources import org.seqra.ir.impl.storage.ers.ERS_DATABASE_PERSISTENCE_SPI @@ -89,8 +88,7 @@ class JIRDatabaseImpl( val runtime = JavaRuntime(settings.jre).allLocations.takeIf { settings.buildModelForJRE } val runtimeNew = runtime?.let { locationsRegistry.setup(it).new } val registeredNew = locationsRegistry.registerIfNeeded( - settings.predefinedDirOrJars.filter { it.exists() } - .flatMap { it.asByteCodeLocation(javaRuntime.version, isRuntime = false) }.distinct() + settings.predefinedDirOrJars.createNonRuntimeByteCodeLocations(javaRuntime.version) ).new if (canBeDumped() && persistence.tryLoad(id)) { isImmutable = true @@ -118,8 +116,7 @@ class JIRDatabaseImpl( override suspend fun classpath(dirOrJars: List, features: List?): JIRClasspath { assertNotClosed() - val existingLocations = - dirOrJars.filterExisting().flatMap { it.asByteCodeLocation(javaRuntime.version) }.distinct() + val existingLocations = dirOrJars.createNonRuntimeByteCodeLocations(javaRuntime.version) val processed = locationsRegistry.registerIfNeeded(existingLocations) .also { it.new.process(true) }.registered + locationsRegistry.runtimeLocations return JIRClasspathImpl( @@ -150,7 +147,7 @@ class JIRDatabaseImpl( override suspend fun load(dirOrJars: List) = apply { assertNotClosed() - loadLocations(dirOrJars.filterExisting().flatMap { it.asByteCodeLocation(javaRuntime.version) }.distinct()) + loadLocations(dirOrJars.createNonRuntimeByteCodeLocations(javaRuntime.version)) } override suspend fun loadLocations(locations: List) = apply { diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/features/classpaths/JIRUnknownType.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/features/classpaths/JIRUnknownType.kt index fdd71ff..58df879 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/features/classpaths/JIRUnknownType.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/features/classpaths/JIRUnknownType.kt @@ -1,10 +1,10 @@ package org.seqra.ir.impl.features.classpaths import org.seqra.ir.api.jvm.* -import org.seqra.ir.api.jvm.ext.objectType import org.seqra.ir.impl.cfg.util.OBJECT_CLASS import org.seqra.ir.impl.cfg.util.typeNameFromJvmName import org.objectweb.asm.Opcodes +import org.seqra.ir.api.jvm.ext.toType class JIRUnknownType( @@ -27,10 +27,11 @@ class JIRUnknownType( override val fields: List = emptyList() override val typeParameters: List = emptyList() override val typeArguments: List = emptyList() - override val superType: JIRClassType get() = classpath.objectType override val interfaces: List = emptyList() override val innerTypes: List = emptyList() + override val superType: JIRClassType? get() = jIRClass.superClass?.toType() + override val typeName: String get() = name diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/AbstractByteCodeLocation.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/AbstractByteCodeLocation.kt index c8fb692..f0d87f8 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/AbstractByteCodeLocation.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/AbstractByteCodeLocation.kt @@ -1,33 +1,10 @@ package org.seqra.ir.impl.fs -import com.google.common.hash.Hashing import org.seqra.ir.api.jvm.JIRByteCodeLocation -import java.io.File import java.math.BigInteger -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets - - -abstract class AbstractByteCodeLocation(override val jarOrFolder: File) : JIRByteCodeLocation { - - override val path: String - get() = jarOrFolder.absolutePath +abstract class AbstractByteCodeLocation : JIRByteCodeLocation { override val fileSystemIdHash: BigInteger by lazy { currentHash } override fun isChanged() = fileSystemIdHash != currentHash - - protected val String.shaHash: ByteArray - get() { - return Hashing.sha256() - .hashString(this, StandardCharsets.UTF_8) - .asBytes() - } - - protected val ByteBuffer.shaHash: ByteArray - get() { - return Hashing.sha256() - .hashBytes(this) - .asBytes() - } } diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/BuildFolderLocation.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/BuildFolderLocation.kt index cefd64a..bff0ac1 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/BuildFolderLocation.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/BuildFolderLocation.kt @@ -11,10 +11,13 @@ import java.nio.file.Paths import kotlin.streams.asSequence import kotlin.text.Charsets.UTF_8 -class BuildFolderLocation(folder: File) : AbstractByteCodeLocation(folder) { +class BuildFolderLocation(val jarOrFolder: File) : AbstractByteCodeLocation() { companion object : KLogging() + override val path: String + get() = jarOrFolder.absolutePath + @Suppress("UnstableApiUsage") override val currentHash: BigInteger get() { diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/ByteCodeLocations.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/ByteCodeLocations.kt index 971e3c7..6a4d6df 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/ByteCodeLocations.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/ByteCodeLocations.kt @@ -1,8 +1,8 @@ package org.seqra.ir.impl.fs import mu.KLogging -import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.JIRByteCodeLocation +import org.seqra.ir.api.jvm.JavaVersion import java.io.File import java.nio.file.Paths import java.util.jar.JarFile @@ -15,19 +15,23 @@ val logger = object : KLogging() {}.logger * The method called of different files can have same locations in the result, so use `distinct()` to * filter duplicates out. */ -fun File.asByteCodeLocation(runtimeVersion: JavaVersion, isRuntime: Boolean = false): Collection { - if (!exists()) { - throw IllegalArgumentException("file $absolutePath doesn't exist") +fun File.dirOrJarAsBytecodeLocation(runtimeVersion: JavaVersion, isRuntime: Boolean): Collection { + if (isJar()) { + return mutableSetOf().also { classPath(it) }.map { JarLocation(it, isRuntime, runtimeVersion) } } - return if (isJar()) { - mutableSetOf().also { classPath(it) }.map { JarLocation(it, isRuntime, runtimeVersion) } - } else if (isDirectory) { - listOf(BuildFolderLocation(this)) - } else { - error("$absolutePath is nether a jar file nor a build directory") + + if (isDirectory) { + return listOf(BuildFolderLocation(this)) } + + error("$absolutePath is invalid bytecode location") } +fun Collection.createNonRuntimeByteCodeLocations(runtimeVersion: JavaVersion): List = + filterExisting() + .flatMap { it.dirOrJarAsBytecodeLocation(runtimeVersion, isRuntime = false) } + .distinct() + fun Collection.filterExisting(): List = filter { file -> file.exists().also { if (!it) { diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JarLocationImpl.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JarLocationImpl.kt index efc5fb9..e0096f5 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JarLocationImpl.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JarLocationImpl.kt @@ -11,20 +11,22 @@ import java.util.jar.JarFile import kotlin.text.Charsets.UTF_8 open class JarLocation( - file: File, + val jarOrFolder: File, private val isRuntime: Boolean, private val runtimeVersion: JavaVersion -) : AbstractByteCodeLocation(file) { +) : AbstractByteCodeLocation() { companion object : KLogging() + override val path: String get() = jarOrFolder.absolutePath + @Suppress("UnstableApiUsage") override val currentHash: BigInteger get() { val jarFile = jarFile() ?: return BigInteger.ZERO return Hashing.sha256().newHasher().let { h -> - jarFile.use { - it.entries().asSequence().filter { !it.isDirectory }.sortedBy { it.name }.forEach { entry -> + jarFile.use { jf -> + jf.entries().asSequence().filter { !it.isDirectory }.sortedBy { it.name }.forEach { entry -> h.putString(entry.name, UTF_8) h.putLong(entry.crc) h.putLong(entry.size) diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntime.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntime.kt index 64f98b0..bc99620 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntime.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntime.kt @@ -1,7 +1,7 @@ package org.seqra.ir.impl.fs -import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.JIRByteCodeLocation +import org.seqra.ir.api.jvm.JavaVersion import java.io.File import java.nio.file.Paths @@ -21,7 +21,24 @@ class JavaRuntime(private val javaHome: File) { parseRuntimeVersion("1.8.0") } - val allLocations: List = modules.takeIf { it.isNotEmpty() } ?: (bootstrapJars + extJars) + val allLocations: List = findRuntimeLocations() + + private fun findRuntimeLocations(): List { + val modulesPath = javaHome.resolve("lib/modules") + if (modulesPath.exists()) { + val modulesLocation = JavaRuntimeModuleLocation.loadModules(javaHome) + if (modulesLocation.isNotEmpty()) return modulesLocation + + val modules = locations("jmods") + if (modules.isEmpty()) { + logger.warn("Can't load JDK modules") + } + } + + modules.takeIf { it.isNotEmpty() }?.let { return it } + + return bootstrapJars + extJars + } private val modules: List get() = locations("jmods") @@ -48,7 +65,7 @@ class JavaRuntime(private val javaHome: File) { .listFiles { file -> file.name.endsWith(".jar") || file.name.endsWith(".jmod") } .orEmpty() .toList() - .flatMap { it.asByteCodeLocation(version, true) } + .flatMap { it.dirOrJarAsBytecodeLocation(version, true) } .distinct() } } diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntimeModuleLocation.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntimeModuleLocation.kt new file mode 100644 index 0000000..ffac37c --- /dev/null +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/fs/JavaRuntimeModuleLocation.kt @@ -0,0 +1,144 @@ +package org.seqra.ir.impl.fs + +import com.google.common.hash.Hashing +import org.seqra.ir.api.jvm.JIRByteCodeLocation +import org.seqra.ir.api.jvm.LocationType +import java.io.File +import java.math.BigInteger +import java.net.URI +import java.nio.file.FileSystem +import java.nio.file.FileSystems +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.fileSize +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.name +import kotlin.io.path.readBytes +import kotlin.io.path.walk +import kotlin.text.Charsets.UTF_8 + +open class JavaRuntimeModuleLocation( + val javaHome: File, + val module: String, +) : AbstractByteCodeLocation() { + + override val path: String get() = createPath(javaHome.absolutePath, module) + + override val type: LocationType get() = LocationType.RUNTIME + + @Suppress("UnstableApiUsage") + override val currentHash: BigInteger + get() { + val hasher = Hashing.sha256().newHasher() + useModule { moduleBase, moduleBasePath -> + moduleBase.walk() + .filter { it.isValidClassFile() } + .sortedBy { it.toString() } + .forEach { classFile -> + val classFileName = classFile.classFileName(moduleBasePath) + hasher.putString(classFileName, UTF_8) + hasher.putLong(classFile.fileSize()) + } + } + return BigInteger(hasher.hash().asBytes()) + } + + override val classNames: Set? + get() = useModule { moduleBase, moduleBasePath -> + moduleBase.walk() + .filter { it.isValidClassFile() } + .mapTo(hashSetOf()) { it.className(moduleBasePath) } + } + + override val classes: Map + get() = useModule { moduleBase, moduleBasePath -> + moduleBase.walk() + .filter { it.isValidClassFile() } + .associateTo(hashMapOf()) { classFile -> + val className = classFile.className(moduleBasePath) + val classBytes = classFile.readBytes() + className to classBytes + } + } + + override fun resolve(classFullName: String): ByteArray? { + val classFilePath = classFullName.replace('.', '/') + ".class" + useModule { moduleBase, _ -> + val classFile = moduleBase.resolve(classFilePath) + if (!classFile.exists()) return null + return classFile.readBytes() + } + } + + override fun createRefreshed(): JIRByteCodeLocation? = JavaRuntimeModuleLocation(javaHome, module) + + override fun equals(other: Any?): Boolean { + if (other == null || other !is JavaRuntimeModuleLocation) { + return false + } + return other.module == module && other.javaHome == javaHome + } + + override fun hashCode(): Int { + var result = javaHome.hashCode() + result = 31 * result + module.hashCode() + return result + } + + private inline fun useModule(body: (Path, String) -> T): T { + val fs = newFs(javaHome) + ?: error("JRT file system not available") + + return fs.use { + val module = fs.getPath(MODULES, module) + body(module, "$module/") + } + } + + companion object { + private const val MODULES = "/modules" + + fun loadModules(javaHome: File): List = + newFs(javaHome)?.use { fs -> + val modulesDir = fs.getPath(MODULES) + val modules = modulesDir.listDirectoryEntries() + modules.map { JavaRuntimeModuleLocation(javaHome, it.name) } + }.orEmpty() + + private fun newFs(javaHome: File): FileSystem? { + val env = hashMapOf("java.home" to javaHome.absolutePath) + + return runCatching { + FileSystems.newFileSystem(URI.create(JRT), env) + }.getOrNull() + } + + private const val JRT = "jrt:/" + + // note: use null symbol because it can't appear in any path + private const val SEPARATOR = '\u0000' + + private const val PATH_PREFIX = "$JRT$SEPARATOR" + + fun isModuleLocation(path: String): Boolean = path.startsWith(PATH_PREFIX) + + fun fromPath(path: String): JavaRuntimeModuleLocation { + val moduleHomeSeparatorPos = path.indexOf(SEPARATOR, startIndex = PATH_PREFIX.length) + val module = path.substring(PATH_PREFIX.length + 1, moduleHomeSeparatorPos) + val home = path.substring(moduleHomeSeparatorPos + 1) + return JavaRuntimeModuleLocation(File(home), module) + } + + private fun createPath(javaHome: String, module: String): String = + "$PATH_PREFIX$module$SEPARATOR$javaHome" + + private fun Path.isValidClassFile(): Boolean = + name.endsWith(".class") && !name.contains("module-info") + + private fun Path.classFileName(moduleBasePath: String): String = + toString().removePrefix(moduleBasePath) + + private fun Path.className(moduleBasePath: String): String = + classFileName(moduleBasePath).removeSuffix(".class").replace('/', '.') + } +} diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/AbstractJIRDbPersistence.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/AbstractJIRDbPersistence.kt index 806e2d0..e6de8b1 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/AbstractJIRDbPersistence.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/AbstractJIRDbPersistence.kt @@ -8,7 +8,8 @@ import org.seqra.ir.api.jvm.RegisteredLocation import org.seqra.ir.api.storage.ers.getEntityOrNull import org.seqra.ir.impl.caches.xodus.XODUS_CACHE_PROVIDER_ID import org.seqra.ir.impl.fs.JavaRuntime -import org.seqra.ir.impl.fs.asByteCodeLocation +import org.seqra.ir.impl.fs.JavaRuntimeModuleLocation +import org.seqra.ir.impl.fs.dirOrJarAsBytecodeLocation import org.seqra.ir.impl.storage.ers.bytecode import org.seqra.ir.impl.storage.jooq.tables.references.BYTECODELOCATIONS import org.seqra.ir.impl.storage.jooq.tables.references.CLASSES @@ -56,7 +57,14 @@ abstract class AbstractJIRDbPersistence( } ).mapNotNull { try { - File(it.path).asByteCodeLocation(javaRuntime.version, isRuntime = it.runtime) + if (it.runtime && JavaRuntimeModuleLocation.isModuleLocation(it.path)) { + return@mapNotNull listOf(JavaRuntimeModuleLocation.fromPath(it.path)) + } + + val file = File(it.path) + if (!file.exists()) return@mapNotNull null + + file.dirOrJarAsBytecodeLocation(javaRuntime.version, it.runtime) } catch (_: Exception) { null } diff --git a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/PersistentByteCodeLocation.kt b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/PersistentByteCodeLocation.kt index 2c88f1b..5af176f 100644 --- a/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/PersistentByteCodeLocation.kt +++ b/seqra-ir-core/src/main/kotlin/org/seqra/ir/impl/storage/PersistentByteCodeLocation.kt @@ -1,14 +1,15 @@ package org.seqra.ir.impl.storage -import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.JIRByteCodeLocation import org.seqra.ir.api.jvm.JIRDatabase import org.seqra.ir.api.jvm.JIRDatabasePersistence +import org.seqra.ir.api.jvm.JavaVersion import org.seqra.ir.api.jvm.RegisteredLocation import org.seqra.ir.api.storage.ers.Entity import org.seqra.ir.api.storage.ers.getEntityOrNull import org.seqra.ir.impl.fs.BuildFolderLocation import org.seqra.ir.impl.fs.JarLocation +import org.seqra.ir.impl.fs.JavaRuntimeModuleLocation import org.seqra.ir.impl.fs.isJar import org.seqra.ir.impl.storage.jooq.tables.records.BytecodelocationsRecord import org.seqra.ir.impl.storage.jooq.tables.references.BYTECODELOCATIONS @@ -97,25 +98,34 @@ class PersistentByteCodeLocation( private fun PersistentByteCodeLocationData.toJIRLocation(): JIRByteCodeLocation? { return try { - with(File(path)) { - if (!exists()) { - null - } else if (isJar()) { - // NB! This JarLocation inheritor is necessary for hacking PersistentLocationsRegistry - // so that isChanged() would work properly in PersistentLocationsRegistry.refresh() - val fsId = fileSystemId - object : JarLocation(this@with, isRuntime, runtimeVersion) { - override val fileSystemIdHash: BigInteger - get() { - return BigInteger(fsId, Character.MAX_RADIX) - } - } - } else if (isDirectory) { - BuildFolderLocation(this) - } else { - error("$absolutePath is nether a jar file nor a build directory") + if (isRuntime && JavaRuntimeModuleLocation.isModuleLocation(path)) { + val location = JavaRuntimeModuleLocation.fromPath(path) + + val fsId = fileSystemId + return object : JavaRuntimeModuleLocation(location.javaHome, location.module) { + override val fileSystemIdHash: BigInteger + get() = BigInteger(fsId, Character.MAX_RADIX) + } + } + + val file = File(path) + if (!file.exists()) return null + + if (file.isJar()) { + // NB! This JarLocation inheritor is necessary for hacking PersistentLocationsRegistry + // so that isChanged() would work properly in PersistentLocationsRegistry.refresh() + val fsId = fileSystemId + return object : JarLocation(file, isRuntime, runtimeVersion) { + override val fileSystemIdHash: BigInteger + get() = BigInteger(fsId, Character.MAX_RADIX) } } + + if (file.isDirectory) { + return BuildFolderLocation(file) + } + + return null } catch (e: Exception) { null } diff --git a/seqra-ir-core/src/test/kotlin/org/seqra/ir/testing/tree/DummyCodeLocation.kt b/seqra-ir-core/src/test/kotlin/org/seqra/ir/testing/tree/DummyCodeLocation.kt index 7be1f61..9797664 100644 --- a/seqra-ir-core/src/test/kotlin/org/seqra/ir/testing/tree/DummyCodeLocation.kt +++ b/seqra-ir-core/src/test/kotlin/org/seqra/ir/testing/tree/DummyCodeLocation.kt @@ -4,7 +4,6 @@ import org.seqra.ir.api.jvm.JIRByteCodeLocation import org.seqra.ir.api.jvm.LocationType import org.seqra.ir.api.jvm.RegisteredLocation import org.seqra.ir.impl.storage.longHash -import java.io.File import java.math.BigInteger open class DummyCodeLocation(private val name: String) : JIRByteCodeLocation, RegisteredLocation { @@ -30,8 +29,6 @@ open class DummyCodeLocation(private val name: String) : JIRByteCodeLocation, Re override val classes: Map get() = emptyMap() - override val jarOrFolder: File - get() = TODO("Not yet implemented") override val path: String get() = TODO("")