mirror of
https://github.com/apple/pkl.git
synced 2026-05-24 23:59:16 +02:00
Fix bug where reusing a pklbinary#Renderer could result in incorrect output (#1525)
This commit is contained in:
@@ -24,37 +24,24 @@ import org.pkl.core.runtime.VmPklBinaryEncoder;
|
|||||||
import org.pkl.core.runtime.VmTyped;
|
import org.pkl.core.runtime.VmTyped;
|
||||||
import org.pkl.core.stdlib.ExternalMethod1Node;
|
import org.pkl.core.stdlib.ExternalMethod1Node;
|
||||||
import org.pkl.core.stdlib.PklConverter;
|
import org.pkl.core.stdlib.PklConverter;
|
||||||
import org.pkl.core.util.Nullable;
|
|
||||||
|
|
||||||
public final class RendererNodes {
|
public final class RendererNodes {
|
||||||
|
|
||||||
private abstract static class RenderMethod extends ExternalMethod1Node {
|
public abstract static class renderDocument extends ExternalMethod1Node {
|
||||||
private @Nullable MessageBufferPacker messagePacker;
|
|
||||||
|
|
||||||
protected MessageBufferPacker getMessagePacker() {
|
|
||||||
if (messagePacker == null) {
|
|
||||||
messagePacker = MessagePack.newDefaultBufferPacker();
|
|
||||||
}
|
|
||||||
messagePacker.clear();
|
|
||||||
return messagePacker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class renderDocument extends RenderMethod {
|
|
||||||
@Specialization
|
@Specialization
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
protected VmBytes eval(VmTyped self, Object value) {
|
protected VmBytes eval(VmTyped self, Object value) {
|
||||||
var packer = getMessagePacker();
|
var packer = MessagePack.newDefaultBufferPacker();
|
||||||
createRenderer(self, packer).renderDocument(value);
|
createRenderer(self, packer).renderDocument(value);
|
||||||
return new VmBytes(packer.toByteArray());
|
return new VmBytes(packer.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class renderValue extends RenderMethod {
|
public abstract static class renderValue extends ExternalMethod1Node {
|
||||||
@Specialization
|
@Specialization
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
protected VmBytes eval(VmTyped self, Object value) {
|
protected VmBytes eval(VmTyped self, Object value) {
|
||||||
var packer = getMessagePacker();
|
var packer = MessagePack.newDefaultBufferPacker();
|
||||||
createRenderer(self, packer).renderValue(value);
|
createRenderer(self, packer).renderValue(value);
|
||||||
return new VmBytes(packer.toByteArray());
|
return new VmBytes(packer.toByteArray());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.nio.file.Path
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
import kotlin.io.path.createParentDirectories
|
import kotlin.io.path.createParentDirectories
|
||||||
import kotlin.io.path.writeText
|
import kotlin.io.path.writeText
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@@ -45,6 +46,8 @@ import org.pkl.core.module.ModuleKeyFactories
|
|||||||
import org.pkl.core.module.ModuleKeyFactory
|
import org.pkl.core.module.ModuleKeyFactory
|
||||||
import org.pkl.core.module.ResolvedModuleKey
|
import org.pkl.core.module.ResolvedModuleKey
|
||||||
import org.pkl.core.project.Project
|
import org.pkl.core.project.Project
|
||||||
|
import org.pkl.core.resource.Resource
|
||||||
|
import org.pkl.core.resource.ResourceReader
|
||||||
import org.pkl.core.util.IoUtils
|
import org.pkl.core.util.IoUtils
|
||||||
|
|
||||||
class EvaluatorTest {
|
class EvaluatorTest {
|
||||||
@@ -241,9 +244,9 @@ class EvaluatorTest {
|
|||||||
evaluator.evaluate(
|
evaluator.evaluate(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
function fib(n) = if (n < 2) 0 else fib(n - 1) + fib(n - 2)
|
function fib(n) = if (n < 2) 0 else fib(n - 1) + fib(n - 2)
|
||||||
x = fib(100)
|
x = fib(100)
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -259,10 +262,10 @@ class EvaluatorTest {
|
|||||||
evaluator.evaluate(
|
evaluator.evaluate(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
a = b
|
a = b
|
||||||
b = c
|
b = c
|
||||||
c = a
|
c = a
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -376,7 +379,7 @@ class EvaluatorTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
val output = evaluator.evaluateOutputFiles(text(program))
|
val output = evaluator.evaluateOutputFiles(text(program))
|
||||||
assertThat(output.keys).isEqualTo(setOf("foo.yml", "bar.yml", "bar/biz.yml", "bar/../bark.yml"))
|
assertThat(output.keys).isEqualTo(setOf("foo.yml", "bar.yml", "bar/biz.yml", "bar/../bark.yml"))
|
||||||
@@ -398,14 +401,14 @@ class EvaluatorTest {
|
|||||||
assertThat(result)
|
assertThat(result)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"""
|
"""
|
||||||
prop1 {
|
prop1 {
|
||||||
name = "Apple"
|
name = "Apple"
|
||||||
}
|
}
|
||||||
prop2 {
|
prop2 {
|
||||||
res = 1
|
res = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -434,8 +437,9 @@ class EvaluatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val evaluator = evaluatorBuilder.setProjectDependencies(project.dependencies).build()
|
val evaluator = evaluatorBuilder.setProjectDependencies(project.dependencies).build()
|
||||||
val output =
|
val output = evaluator.use {
|
||||||
evaluator.use { it.evaluateOutputText(uri("custom:/org/pkl/core/project/project5/main.pkl")) }
|
it.evaluateOutputText(uri("custom:/org/pkl/core/project/project5/main.pkl"))
|
||||||
|
}
|
||||||
assertThat(output)
|
assertThat(output)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
"""
|
"""
|
||||||
@@ -471,7 +475,7 @@ class EvaluatorTest {
|
|||||||
} else
|
} else
|
||||||
"""
|
"""
|
||||||
birds = import("@birds/catalog/Ostrich.pkl")
|
birds = import("@birds/catalog/Ostrich.pkl")
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
|
|
||||||
override fun resolve(securityManager: SecurityManager): ResolvedModuleKey {
|
override fun resolve(securityManager: SecurityManager): ResolvedModuleKey {
|
||||||
@@ -519,11 +523,11 @@ class EvaluatorTest {
|
|||||||
}
|
}
|
||||||
.hasMessageContaining(
|
.hasMessageContaining(
|
||||||
"""
|
"""
|
||||||
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
||||||
|
|
||||||
1 | res = import*("*.pkl")
|
1 | res = import*("*.pkl")
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
assertThatCode {
|
assertThatCode {
|
||||||
@@ -533,12 +537,12 @@ class EvaluatorTest {
|
|||||||
}
|
}
|
||||||
.hasMessageContaining(
|
.hasMessageContaining(
|
||||||
"""
|
"""
|
||||||
–– Pkl Error ––
|
–– Pkl Error ––
|
||||||
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
Cannot resolve import in local dependency because scheme `modulepath` is not globbable.
|
||||||
|
|
||||||
1 | import* "@project7/*.pkl" as proj7Files
|
1 | import* "@project7/*.pkl" as proj7Files
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -579,8 +583,8 @@ class EvaluatorTest {
|
|||||||
evaluator.evaluate(
|
evaluator.evaluate(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
foo: String(chars.first == "a") = "boo"
|
foo: String(chars.first == "a") = "boo"
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -600,8 +604,8 @@ class EvaluatorTest {
|
|||||||
evaluator.evaluate(
|
evaluator.evaluate(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
foo: String(startsWith("a")) | String(startsWith("b")) | String(startsWith("c")) = "cool"
|
foo: String(startsWith("a")) | String(startsWith("b")) | String(startsWith("c")) = "cool"
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -620,8 +624,8 @@ class EvaluatorTest {
|
|||||||
evaluator.evaluate(
|
evaluator.evaluate(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
foo = "bar" is Int(this > 0)
|
foo = "bar" is Int(this > 0)
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -629,6 +633,68 @@ class EvaluatorTest {
|
|||||||
assertThat((evaluator as EvaluatorImpl).isInstrumentationEverUsed()).isFalse
|
assertThat((evaluator as EvaluatorImpl).isInstrumentationEverUsed()).isFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nested pkl-binary rendiring produces correct results`() {
|
||||||
|
val evaluator =
|
||||||
|
with(EvaluatorBuilder.preconfigured()) {
|
||||||
|
allowedResources.add(Pattern.compile("b64:"))
|
||||||
|
addResourceReader(
|
||||||
|
object : ResourceReader {
|
||||||
|
override fun getUriScheme() = "b64"
|
||||||
|
|
||||||
|
override fun hasHierarchicalUris() = false
|
||||||
|
|
||||||
|
override fun isGlobbable() = false
|
||||||
|
|
||||||
|
override fun read(uri: URI): Optional<in Any> {
|
||||||
|
val req = PklBinaryDecoder.decode(Base64.decode(uri.schemeSpecificPart)) as PObject
|
||||||
|
val kind = req.getProperty("kind") as String
|
||||||
|
return Optional.of(
|
||||||
|
Resource(
|
||||||
|
uri,
|
||||||
|
when (kind) {
|
||||||
|
"enc" ->
|
||||||
|
Base64.encode((req.getProperty("input") as String).encodeToByteArray())
|
||||||
|
.encodeToByteArray()
|
||||||
|
"dec" -> Base64.decode(req.getProperty("input") as String)
|
||||||
|
else -> throw RuntimeException("unknown request kind $kind")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluator.use {
|
||||||
|
it.evaluate(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
import "pkl:pklbinary"
|
||||||
|
abstract class Base {
|
||||||
|
fixed kind: String
|
||||||
|
input: String
|
||||||
|
local encodedRequest = new pklbinary.Renderer {}.renderValue(this).base64
|
||||||
|
hidden fixed requestUri: String = "b64:\(encodedRequest)"
|
||||||
|
hidden fixed result: Resource = read(requestUri) as Resource
|
||||||
|
}
|
||||||
|
class Enc extends Base {
|
||||||
|
fixed kind: "enc"
|
||||||
|
}
|
||||||
|
class Dec extends Base {
|
||||||
|
fixed kind: "dec"
|
||||||
|
}
|
||||||
|
local enc = new Enc { input = "hello world" }
|
||||||
|
local dec = new Dec { input = enc.result.text }
|
||||||
|
valid = if (enc.input == dec.result.text) true else throw("pklbinary.Renderer reuse bug")
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkModule(module: PModule) {
|
private fun checkModule(module: PModule) {
|
||||||
assertThat(module.properties.size).isEqualTo(2)
|
assertThat(module.properties.size).isEqualTo(2)
|
||||||
assertThat(module.getProperty("name")).isEqualTo("pigeon")
|
assertThat(module.getProperty("name")).isEqualTo("pigeon")
|
||||||
@@ -647,7 +713,7 @@ class EvaluatorTest {
|
|||||||
|
|
||||||
name = module2.name
|
name = module2.name
|
||||||
age = module2.age
|
age = module2.age
|
||||||
"""
|
"""
|
||||||
.trimIndent()
|
.trimIndent()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user