Skip to content

Commit f66dd8c

Browse files
authored
[jacodb-core] In JarFacade, avoid caching JarEntries returned by JarFile (#303)
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.
1 parent ad9d047 commit f66dd8c

File tree

5 files changed

+73
-22
lines changed

5 files changed

+73
-22
lines changed

jacodb-core/src/main/kotlin/org/jacodb/impl/fs/Jars.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.jacodb.impl.fs
1818

1919
import jetbrains.exodus.util.LightByteArrayOutputStream
20+
import org.jacodb.impl.util.asSequence
2021
import java.io.InputStream
2122
import java.io.OutputStream
2223
import java.util.jar.Attributes
@@ -43,9 +44,9 @@ class JarFacade(private val runtimeVersion: Int, private val getter: () -> JarFi
4344
getter().use { jarFile ->
4445
isJmod = jarFile?.name?.endsWith(".jmod") ?: false
4546
isMultiReleaseEnabledInManifest = jarFile?.manifest?.mainAttributes?.getValue(MULTI_RELEASE).toBoolean()
46-
entries = jarFile?.entries()?.toList()?.filter {
47+
entries = jarFile?.entries()?.asSequence()?.filter {
4748
it.name.endsWith(".class") && !it.name.contains("module-info")
48-
}?.associateBy { it.name }
49+
}?.associate { it.name to JarEntry(it) }
4950
}
5051
}
5152

@@ -85,8 +86,8 @@ class JarFacade(private val runtimeVersion: Int, private val getter: () -> JarFi
8586
val jarFile = getter() ?: return emptyMap()
8687
return jarFile.use {
8788
val buffer = ByteArray(DEFAULT_BUFFER_SIZE * 8)
88-
classes.map { it.key to jarFile.getInputStream(it.value).use { it.readBytes(buffer) } }
89-
}.toMap()
89+
classes.entries.associate { it.key to jarFile.getInputStream(it.value).use { it.readBytes(buffer) } }
90+
}
9091
}
9192

9293
}

jacodb-core/src/main/kotlin/org/jacodb/impl/util/SequenceUtil.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616

1717
package org.jacodb.impl.util
1818

19+
import java.util.*
20+
1921
inline fun <T> Sequence(crossinline it: () -> Iterable<T>): Sequence<T> = object : Sequence<T> {
2022
override fun iterator(): Iterator<T> = it().iterator()
23+
}
24+
25+
fun <T> Enumeration<T>?.asSequence(): Sequence<T> {
26+
if (this == null) return emptySequence()
27+
return object : Sequence<T> {
28+
override fun iterator(): Iterator<T> = object : Iterator<T> {
29+
override fun hasNext() = this@asSequence.hasMoreElements()
30+
override fun next(): T = this@asSequence.nextElement()
31+
}
32+
}
2133
}

jacodb-core/src/test/kotlin/org/jacodb/testing/JarFacadeTest.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616

1717
package org.jacodb.testing
1818

19+
import kotlinx.coroutines.runBlocking
20+
import org.jacodb.impl.JcRamErsSettings
1921
import org.jacodb.impl.fs.JarFacade
2022
import org.jacodb.impl.fs.parseRuntimeVersion
21-
import org.junit.jupiter.api.Assertions.*
23+
import org.jacodb.impl.jacodb
24+
import org.junit.jupiter.api.Assertions.assertEquals
25+
import org.junit.jupiter.api.Assertions.assertNotNull
26+
import org.junit.jupiter.api.Assertions.assertTrue
2227
import org.junit.jupiter.api.Test
2328
import org.junit.jupiter.api.condition.EnabledOnJre
2429
import org.junit.jupiter.api.condition.JRE
@@ -93,4 +98,15 @@ class JarFacadeTest {
9398
assertTrue(javaBase.classes.contains("java.lang.String"))
9499
assertNotNull(javaBase.inputStreamOf("java.lang.String"))
95100
}
101+
102+
@Test
103+
fun `load bouncycastle`(): Unit = runBlocking {
104+
val jar = cookJar("https://repo1.maven.org/maven2/org/bouncycastle/bcpg-jdk18on/1.78.1/bcpg-jdk18on-1.78.1.jar")
105+
val db = jacodb {
106+
persistenceImpl(JcRamErsSettings)
107+
loadByteCode(listOf(jar.toFile()))
108+
}.apply { awaitBackgroundJobs() }
109+
val cp = db.classpath(listOf(jar.toFile()))
110+
assertTrue(cp.locations.flatMap { location -> location.classes.values }.isNotEmpty())
111+
}
96112
}

jacodb-core/src/test/kotlin/org/jacodb/testing/persistence/incrementality/IncrementalDbTest.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@ package org.jacodb.testing.persistence.incrementality
1919
import kotlinx.coroutines.runBlocking
2020
import org.jacodb.api.jvm.ext.findClass
2121
import org.jacodb.testing.WithDb
22+
import org.jacodb.testing.cookJar
23+
import org.jacodb.testing.createTempJar
2224
import org.junit.jupiter.api.Assertions
2325
import org.junit.jupiter.api.Test
24-
import java.net.URL
2526
import java.nio.file.Files
26-
import java.nio.file.Files.createDirectories
27-
import java.nio.file.Path
2827
import java.nio.file.StandardCopyOption
29-
import kotlin.io.path.Path
30-
import kotlin.io.path.createTempDirectory
3128

3229
class IncrementalDbTest {
3330

@@ -55,16 +52,4 @@ class IncrementalDbTest {
5552
db.awaitBackgroundJobs()
5653
Assertions.assertTrue(bc1 contentEquals cp.findClass("com.github.penemue.keap.PriorityQueue").bytecode())
5754
}
58-
59-
private fun cookJar(link: String): Path {
60-
val url = URL(link)
61-
val result = createTempJar(url.file)
62-
Files.copy(url.openStream(), result, StandardCopyOption.REPLACE_EXISTING)
63-
return result
64-
}
65-
66-
private fun createTempJar(name: String) =
67-
Path(createTempDirectory("jcdb-temp-jar").toString(), name).also {
68-
createDirectories(it.parent)
69-
}
7055
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.testing
18+
19+
import java.net.URL
20+
import java.nio.file.Files
21+
import java.nio.file.Files.createDirectories
22+
import java.nio.file.Path
23+
import java.nio.file.StandardCopyOption
24+
import kotlin.io.path.Path
25+
import kotlin.io.path.createTempDirectory
26+
27+
fun cookJar(link: String): Path {
28+
val url = URL(link)
29+
val result = createTempJar(url.file)
30+
Files.copy(url.openStream(), result, StandardCopyOption.REPLACE_EXISTING)
31+
return result
32+
}
33+
34+
fun createTempJar(name: String) =
35+
Path(createTempDirectory("jcdb-temp-jar").toString(), name).also {
36+
createDirectories(it.parent)
37+
}

0 commit comments

Comments
 (0)