From f0886cc93204c586844eb715a52c65f130e5e888 Mon Sep 17 00:00:00 2001 From: Elie Gambache Date: Tue, 1 Sep 2026 02:49:58 +0300 Subject: [PATCH] fix: invalidate native lib cache by content, not size only The generated KneRuntime only refreshed the extracted-library cache when the file size changed. A rebuilt library of identical size (e.g. after a symbol rename) kept serving stale symbols, causing UnsatisfiedLinkError. Keep the size check as a fast path and fall back to a byte comparison of the cached file against the JAR resource before trusting the cache. Fixes #29 --- .../nna/plugin/codegen/FfmProxyGenerator.kt | 7 ++++- .../codegen/FfmProxyGeneratorRuntimeTest.kt | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 plugin-build/plugin/src/test/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGeneratorRuntimeTest.kt diff --git a/plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGenerator.kt b/plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGenerator.kt index 34ec126..054450e 100644 --- a/plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGenerator.kt +++ b/plugin-build/plugin/src/main/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGenerator.kt @@ -428,7 +428,12 @@ class FfmProxyGenerator { appendLine(" val target = cacheDir.resolve(fileName)") appendLine(" stream.use { input ->") appendLine(" val bytes = input.readAllBytes()") - appendLine(" if (!Files.exists(target) || Files.size(target) != bytes.size.toLong()) {") + appendLine(" // Size match alone is not enough: same-size builds can carry different") + appendLine(" // symbols (e.g. after an API rename), so compare content before trusting the cache.") + appendLine(" val stale = !Files.exists(target) ||") + appendLine(" Files.size(target) != bytes.size.toLong() ||") + appendLine(" !Files.readAllBytes(target).contentEquals(bytes)") + appendLine(" if (stale) {") appendLine(" val tmp = Files.createTempFile(cacheDir, name, \".tmp\")") appendLine(" Files.write(tmp, bytes)") appendLine(" Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)") diff --git a/plugin-build/plugin/src/test/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGeneratorRuntimeTest.kt b/plugin-build/plugin/src/test/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGeneratorRuntimeTest.kt new file mode 100644 index 0000000..579e734 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/dev/nucleusframework/nna/plugin/codegen/FfmProxyGeneratorRuntimeTest.kt @@ -0,0 +1,31 @@ +package dev.nucleusframework.nna.plugin.codegen + +import dev.nucleusframework.nna.plugin.ir.KneModule +import org.junit.Assert.assertTrue +import org.junit.Test + +class FfmProxyGeneratorRuntimeTest { + + @Test + fun `cache staleness check compares content, not only size`() { + val runtime = generateRuntime() + + // A same-size library with different symbols must refresh the cache (issue #29). + assertTrue( + "Generated runtime must compare cached bytes with resource bytes", + runtime.contains("!Files.readAllBytes(target).contentEquals(bytes)"), + ) + } + + private fun generateRuntime(): String { + val module = KneModule( + libName = "demo", + packages = setOf("demo"), + classes = emptyList(), + dataClasses = emptyList(), + enums = emptyList(), + functions = emptyList(), + ) + return FfmProxyGenerator().generate(module, "demo").getValue("KneRuntime.kt") + } +}