From f725afacb713a3715d68fc98e89ceb7ea23f5afd Mon Sep 17 00:00:00 2001 From: Valentyn Sobol Date: Thu, 6 Feb 2025 15:27:03 +0300 Subject: [PATCH] [jacodb-core] In JarFacade, avoid caching JarEntries returned by JarFile Some implementations of JarEntry are inner classes referencing their JarFiles for doing some internal checks,e.g., on getting input stream. Such caching may require the JarFiles to always be open. To address the issue, copies of all JarEntries should be cached. --- .../main/kotlin/org/jacodb/impl/fs/Jars.kt | 9 +++-- .../org/jacodb/impl/util/SequenceUtil.kt | 12 ++++++ .../org/jacodb/testing/JarFacadeTest.kt | 18 ++++++++- .../incrementality/IncrementalDbTest.kt | 19 +--------- .../kotlin/org/jacodb/testing/Jars.kt | 37 +++++++++++++++++++ 5 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/Jars.kt diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/fs/Jars.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/fs/Jars.kt index 258a9782e..ab04546fa 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/fs/Jars.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/fs/Jars.kt @@ -17,6 +17,7 @@ package org.jacodb.impl.fs import jetbrains.exodus.util.LightByteArrayOutputStream +import org.jacodb.impl.util.asSequence import java.io.InputStream import java.io.OutputStream import java.util.jar.Attributes @@ -43,9 +44,9 @@ class JarFacade(private val runtimeVersion: Int, private val getter: () -> JarFi getter().use { jarFile -> isJmod = jarFile?.name?.endsWith(".jmod") ?: false isMultiReleaseEnabledInManifest = jarFile?.manifest?.mainAttributes?.getValue(MULTI_RELEASE).toBoolean() - entries = jarFile?.entries()?.toList()?.filter { + entries = jarFile?.entries()?.asSequence()?.filter { it.name.endsWith(".class") && !it.name.contains("module-info") - }?.associateBy { it.name } + }?.associate { it.name to JarEntry(it) } } } @@ -85,8 +86,8 @@ class JarFacade(private val runtimeVersion: Int, private val getter: () -> JarFi val jarFile = getter() ?: return emptyMap() return jarFile.use { val buffer = ByteArray(DEFAULT_BUFFER_SIZE * 8) - classes.map { it.key to jarFile.getInputStream(it.value).use { it.readBytes(buffer) } } - }.toMap() + classes.entries.associate { it.key to jarFile.getInputStream(it.value).use { it.readBytes(buffer) } } + } } } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/util/SequenceUtil.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/util/SequenceUtil.kt index e148c4f12..6a6fbe015 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/util/SequenceUtil.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/util/SequenceUtil.kt @@ -16,6 +16,18 @@ package org.jacodb.impl.util +import java.util.* + inline fun Sequence(crossinline it: () -> Iterable): Sequence = object : Sequence { override fun iterator(): Iterator = it().iterator() +} + +fun Enumeration?.asSequence(): Sequence { + if (this == null) return emptySequence() + return object : Sequence { + override fun iterator(): Iterator = object : Iterator { + override fun hasNext() = this@asSequence.hasMoreElements() + override fun next(): T = this@asSequence.nextElement() + } + } } \ No newline at end of file diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/JarFacadeTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/JarFacadeTest.kt index 97b1f0f95..708f299da 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/JarFacadeTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/JarFacadeTest.kt @@ -16,9 +16,14 @@ package org.jacodb.testing +import kotlinx.coroutines.runBlocking +import org.jacodb.impl.JcRamErsSettings import org.jacodb.impl.fs.JarFacade import org.jacodb.impl.fs.parseRuntimeVersion -import org.junit.jupiter.api.Assertions.* +import org.jacodb.impl.jacodb +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledOnJre import org.junit.jupiter.api.condition.JRE @@ -93,4 +98,15 @@ class JarFacadeTest { assertTrue(javaBase.classes.contains("java.lang.String")) assertNotNull(javaBase.inputStreamOf("java.lang.String")) } + + @Test + fun `load bouncycastle`(): Unit = runBlocking { + val jar = cookJar("https://repo1.maven.org/maven2/org/bouncycastle/bcpg-jdk18on/1.78.1/bcpg-jdk18on-1.78.1.jar") + val db = jacodb { + persistenceImpl(JcRamErsSettings) + loadByteCode(listOf(jar.toFile())) + }.apply { awaitBackgroundJobs() } + val cp = db.classpath(listOf(jar.toFile())) + assertTrue(cp.locations.flatMap { location -> location.classes.values }.isNotEmpty()) + } } \ No newline at end of file diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/persistence/incrementality/IncrementalDbTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/persistence/incrementality/IncrementalDbTest.kt index fcffc76a5..4299dd01c 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/persistence/incrementality/IncrementalDbTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/persistence/incrementality/IncrementalDbTest.kt @@ -19,15 +19,12 @@ package org.jacodb.testing.persistence.incrementality import kotlinx.coroutines.runBlocking import org.jacodb.api.jvm.ext.findClass import org.jacodb.testing.WithDb +import org.jacodb.testing.cookJar +import org.jacodb.testing.createTempJar import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import java.net.URL import java.nio.file.Files -import java.nio.file.Files.createDirectories -import java.nio.file.Path import java.nio.file.StandardCopyOption -import kotlin.io.path.Path -import kotlin.io.path.createTempDirectory class IncrementalDbTest { @@ -55,16 +52,4 @@ class IncrementalDbTest { db.awaitBackgroundJobs() Assertions.assertTrue(bc1 contentEquals cp.findClass("com.github.penemue.keap.PriorityQueue").bytecode()) } - - private fun cookJar(link: String): Path { - val url = URL(link) - val result = createTempJar(url.file) - Files.copy(url.openStream(), result, StandardCopyOption.REPLACE_EXISTING) - return result - } - - private fun createTempJar(name: String) = - Path(createTempDirectory("jcdb-temp-jar").toString(), name).also { - createDirectories(it.parent) - } } \ No newline at end of file diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/Jars.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/Jars.kt new file mode 100644 index 000000000..69e891b64 --- /dev/null +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/Jars.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing + +import java.net.URL +import java.nio.file.Files +import java.nio.file.Files.createDirectories +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import kotlin.io.path.Path +import kotlin.io.path.createTempDirectory + +fun cookJar(link: String): Path { + val url = URL(link) + val result = createTempJar(url.file) + Files.copy(url.openStream(), result, StandardCopyOption.REPLACE_EXISTING) + return result +} + +fun createTempJar(name: String) = + Path(createTempDirectory("jcdb-temp-jar").toString(), name).also { + createDirectories(it.parent) + } \ No newline at end of file