Skip to content

Fix encoding/decoding of primitive string objects - #10

Merged
h4l merged 5 commits into
mainfrom
issue-8-fix-jsprimitivetype-string
Sep 26, 2025
Merged

Fix encoding/decoding of primitive string objects#10
h4l merged 5 commits into
mainfrom
issue-8-fix-jsprimitivetype-string

Conversation

@h4l

@h4l h4l commented Sep 26, 2025

Copy link
Copy Markdown
Owner

This fixes #8.

This MR fixes the encoding/decoding of wrapped/boxed string values (JavaScript new String("x") / Python
JSPrimitiveObject("x")). They were mistakenly using an older representation of the V8 serialization format, which stored them as raw length-prefixed UTF-8 values without the normal SerializationTag.kUtf8String. This was used before version 12 of the format, which we don't support. This resulted in wrapped strings failing to decode when received from a real V8 implementation, and equally ours would fail to be decoded by V8.

The automated integration tests which round-trip through several external implementations of the format failed to detect the error, because the property test scenarios generating examples to verify were mistakenly not generating wrapped primitive objects.

We now use the correct, current encoding and include wrapped primitive objects in the round-trip integration tests.

The API also gained a new feature, to allow wrapped primitive objects to be retained in decoded values. This is disabled by default, but necessary to round-trip values containing wrapped primitive objects.

h4l added 4 commits September 26, 2025 08:16
TagReader now supports a `unwrap_js_primitive_objects` option
(defaulting to True) which allows primitive values in wrapper objects
to be kept as wrapped after decoding. (e.g. `new String("example")` in
JavaScript or `v8serialize.jstypes.JSPrimitiveObject("example")` in
Python).

Previously wrapped primitives were always unwrapped when decoding, so
they worked transparently like non-wrapped values. However, when doing
round-trip encoding/decoding, wrapped primitives need to be kept as-is
to retain identity. (Our JSPrimitiveObject is not `==` to an unwrapped
version, just as in Javascript `"foo" === new String("foo")` is false.)
The hypothesis strategies that generated values for the V8 round trip
tests were mistakenly not including JSPrimitiveObject values, so we
weren't testing that wrapped primitive values were being round-tripped
from us to V8 and back.
This explicitly reproduces and can remain as a regression test for issue
 #8.
We were encoding/decoding primitive-object-wrapped string values
according to an old (< 12) version of the V8 serialization format,
instead of the >= 13, <=15 version range we support.

The older format always encoded wrapped strings as a UTF-8 string,
omitting the `SerializationTag.kUtf8String` tag.
The current format instead encodes the wrapped
string as any of the primitive string
representations, preceded by the appropriate type tag.

BREAKING CHANGE:
  1. The ReadableTagStream.read_js_primitive_object() function now
    requires a ctx: DecodeContext argument.
  2. primitive-object-wrapped strings (represented by
    `v8serialize.jstypes.JSPrimitiveObject`) that were encoded by
    previous versions of v8serialize will not be decodable by this
    version.

    This should only be observable if a program was explicitly encoding
    JSPrimitiveObject objects and persisting them to be loaded by this
    version, because:
    - v8serialize could not have decoded wrapped strings from a real V8
      implementation, because of the incorrect decoding behaviour.
    - v8serialize unwraps JSPrimitiveObject to the primitive value when
      decoding, so even if it had decoded a wrapped string, it'd be
      re-encoded as an unwrapped string if round-tripped.
@h4l
h4l force-pushed the issue-8-fix-jsprimitivetype-string branch from e1c1b72 to da10d47 Compare September 26, 2025 08:47
@h4l
h4l force-pushed the issue-8-fix-jsprimitivetype-string branch from da10d47 to d471f9d Compare September 26, 2025 09:05
@h4l
h4l merged commit d471f9d into main Sep 26, 2025
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to deserialize nested objects

1 participant