Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .agents/languages/cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ Load this file when changing `cpp/`, Cython build plumbing, or C++ xlang behavio
- When invoking a method that returns `Result`, use `FORY_TRY` unless you are in control-flow logic that cannot use it cleanly.
- Wrap error checks with `FORY_PREDICT_FALSE` for branch prediction.
- Continue on trivial errors; return early only for critical errors such as buffer overflow.
- `ReadContext` and related codec owners intentionally accumulate non-critical
errors and inspect them at established serializer or root safepoints. Bounds-safe
work may continue after an error is recorded. Do not add eager per-field checks,
cursor rollback, or tests that pin the first detection point unless deferral can
cause out-of-bounds access, undefined behavior, resource amplification, state
pollution, or success past the required safepoint.
- Put private methods last in class definitions, immediately before private fields.
- Do not redesign alias-based or low-level public type shapes to add convenience methods unless the user explicitly asks for that API change.
- For cross-language feature ports, match protocol behavior but use idiomatic C++ ownership and layering instead of mirroring Java structure literally.
Expand Down
6 changes: 0 additions & 6 deletions .agents/languages/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ Load this file when changing `go/fory/` or Go xlang behavior.
- Run Go commands from within `go/fory/`.
- Changes under `go/` must pass formatting and tests.
- The Go implementation focuses on fast serializers.
- Go codec paths may intentionally record an error and continue bounds-safe work
until an established serializer or root safepoint checks it. Do not add eager
per-field or per-element error branches, cursor rollback, or tests that pin the
first detection point unless deferral can cause a panic, out-of-bounds access,
disproportionate work or allocation, persistent state pollution, or success
past the required safepoint.
- Root deserialization graph memory budget state belongs to `ReadContext`.
`WithMaxGraphMemoryBytes` uses a fixed `128 MiB` default; positive explicit
values override it, and explicit non-positive values are invalid at config creation.
Expand Down
16 changes: 0 additions & 16 deletions .agents/languages/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,22 +159,6 @@ Load this file when changing anything under `java/` or when Java drives a cross-
- In `MemoryBuffer` and `MemoryOps` hot paths, duplicate small straight-line copy/read/write logic
when that keeps control flow direct. Do not add private helper indirection to hot paths just to
reduce local code duplication; keep helpers for slow, cold, or error paths.
- Add a Java `MemoryBuffer` check only when its absence can cause a JVM or native crash, OOM, or
attacker-controlled memory amplification. Delayed, masked, less precise, or differently typed
failures do not justify a check, and neither does an incorrect decoded result without one of
those crash or memory consequences. Do not duplicate an array, `ByteBuffer`, VarHandle, stream,
or other existing bounds owner merely to move or normalize an error.
- The JDK 25 `MemoryBuffer` overlay is intentionally unchecked at its logical buffer boundary.
Indexed array accesses, absolute `ByteBuffer` accesses, and VarHandle accesses own bounds
enforcement and already provide a controlled failure; its exact exception type, message, and
detection point are not contracts. Do not copy the JDK 8-24 Unsafe-path range checks into this
overlay. Keep an explicit check only before allocation, capacity growth, or another side effect
when it is required to prevent a crash, OOM, or attacker-controlled memory amplification that the
access owner cannot contain.
- `MemoryAllocator.grow` owns the postcondition that a successful return has supplied at least the
requested capacity. `MemoryBuffer` must not recheck that postcondition after calling `grow` or
`ensure`; an allocator that returns without satisfying it violates the allocator contract and
must be fixed in the allocator implementation rather than burdening every buffer hot path.
- In JDK 25 Fory JSON C2-sensitive code, preserve measured, naturally large hot-method boundaries.
A method that exceeds HotSpot's 325-byte hot-inline limit through real representation, scalar,
array, collection, or generated-schema work is an independent subtree owner. Generated group
Expand Down
40 changes: 7 additions & 33 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
- Preserve architecture. Do not introduce new layers, parallel flows, or public APIs unless explicitly requested; prefer local repair in the existing owner over shared-infra expansion, and stop if a fix conflicts with an ADR, spec, or invariant.
- Do not change an existing `RefReader`/`RefWriter` architecture or API to support compatible skip. Compatible skip must not add alternate reference slots or tables, alternate reference lookup or publication methods, or forwarding APIs in read/write contexts, builders, serializers, or generated-code plumbing. Keep ordinary reference publication and lookup unchanged and resolve the case in the existing compatible generated owner. For an authorized removed-field read of an unregistered Struct, the empty object created by the skip reader is that path's final owner: publish that same object for `RefValue`, consume the Struct fields, and let later `RefFlag` values resolve to it. This preserves reference numbering and identity without registering the Struct; an independent dynamic root still requires normal registration. Do not add parallel reference state, a sentinel, a rejection, or a common-path branch for this case.
- Respect ownership. Keep logic, state, and helpers in their natural owner, and do not move serializer-local, context-local, runtime-type-local, or protocol-local problems into global utilities.
- Low-level APIs explicitly named `unsafe` or `unchecked` may be public and may omit local bounds or
capacity checks. Their callers own the proof required by the operation. Do not hide these APIs
behind private access, checked forwarding wrappers, friend adapters, or duplicate implementations
solely to prevent misuse; add validation only at the caller or owner that lacks the required proof.
- Keep hardening changes causal. A shared wire defect may require aligned runtime fixes, but it does
not justify unrelated buffer checks, performance rewrites, or cleanup without their own concrete
consequence and evidence.
- Check the spec before implementation. For wire behavior and xlang mapping, use the specs as the source of truth and never copy one runtime's bug into another runtime just to make tests pass.
- `foryc` is a build-time compiler for trusted schema inputs and is never invoked by runtime serialization or deserialization. Schema provenance, package/namespace options, output-path options, and generated-source review belong to the application or build owner. Do not classify hostile-schema source injection or path traversal as a Fory runtime security vulnerability; `foryc` does not promise to sandbox untrusted schemas.
- Row format accepts only trusted input and is outside Fory's untrusted binary-deserialization security boundary. Rust `check_string_read(false)` is likewise an explicit trusted-input mode that disables UTF-8 validation; its caller owns the validity guarantee. Classify issues in those paths as correctness, soundness, or hardening bugs when applicable, not as attacker-controlled deserialization vulnerabilities under the default security model.
Expand All @@ -45,20 +52,6 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
hot-path branches, helper APIs, allocations, or generated-code expansion
solely to make an error earlier, more specific, or more uniform, and do not
write tests that force such error normalization.
- Non-strict, non-precise, delayed, masked, differently typed, or differently layered controlled
errors are not security findings. Security remediation must not add checks solely to normalize,
sharpen, advance, or otherwise make such errors deterministic. Security tests must not require a
particular error type, message, layer, offset, or detection point unless an explicit public
contract makes that precision part of the policy boundary.
- Runtimes with an established lazy-error accumulator, including C++ and Go,
may keep executing bounds-safe codec work after recording an error and inspect
it at an existing serializer or root-operation safepoint. Deferred inspection
is an intentional hot-path design, not by itself a correctness or security
defect. Do not add per-field or per-element error branches, cursor rollback,
or tests that require immediate propagation solely because an earlier check is
possible. Treat the behavior as a defect only when the deferred path has a
concrete consequence listed below or an established safepoint can return
success instead of a controlled root error.
- Never add a reader-side check solely to produce a more precise malformed-input
error. Retain or add a check only when the unchecked path has a concrete
consequence such as a crash, panic, undefined behavior, out-of-bounds access,
Expand All @@ -69,15 +62,6 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
helper when the language supports it. A bounds-safe downstream operation that
already raises a controlled root error is sufficient; do not duplicate it for
error precision.
- Buffer and memory-buffer checks require a stricter consequence test. Security remediation may add
a check only when its absence can cause a process crash or panic, undefined memory access, OOM, or
attacker-controlled memory amplification. Delayed, masked, less precise, or differently typed
errors are not sufficient reasons. Neither is an incorrect decoded result when the unchecked
path cannot cause one of those crash or memory consequences. A downstream bounds owner that
already fails in a controlled way is sufficient; do not duplicate its check in a hotter wrapper.
Apply this admission rule prospectively: do not remove an already completed and validated check
solely by reclassifying it when it has no demonstrated performance cost. Preserve that check as
a correctness fix unless an explicit task requires the behavioral rollback.
- Before reporting or fixing a robustness finding, prove that the current path
causes at least one concrete consequence: crash, panic, undefined behavior,
or out-of-bounds access; disproportionate allocation, CPU work, or stream
Expand All @@ -88,16 +72,6 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
malformed or noncanonical flag, enum value, marker, length form, or reserved
value is accepted, rejected late, decoded differently, or produces a less
precise error.
- Keep security remediation scope frozen. Do not fix a standalone non-security
correctness, interoperability, API, or lifecycle issue merely because it is
discovered during security work. Include an adjacent non-security change only
when omitting it would leave the security fix incomplete or force the same
owner into an ugly, duplicated, or knowingly unsound design; otherwise record
the issue separately and leave production code unchanged. This rule prevents
new scope; it does not authorize rolling back a non-security fix that is
already implemented and validated. Preserve such fixes. If one may affect
performance, inspect and measure the affected path first, then optimize it on
evidence instead of removing it by classification.
- Arbitrary-precision binary Decimal codecs accept only scales in
`[-10_000, 10_000]` and an absolute unscaled magnitude of at most `10_000`
binary bytes. The Java standalone `BigInteger` serializer uses the same
Expand Down
18 changes: 9 additions & 9 deletions cpp/fory/serialization/array_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ struct Serializer<
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
// write array length in bytes
writer_index += buffer.put_var_uint32(writer_index,
static_cast<uint32_t>(N * sizeof(T)));
writer_index += buffer.put_var_uint32_unchecked(
writer_index, static_cast<uint32_t>(N * sizeof(T)));

// write data
if constexpr (N > 0) {
Expand All @@ -110,7 +110,7 @@ struct Serializer<
}
}
}
buffer.writer_index(writer_index + N * sizeof(T));
buffer.unsafe_set_writer_index(writer_index + N * sizeof(T));
}

static inline void write_data_generic(const std::array<T, N> &arr,
Expand Down Expand Up @@ -221,14 +221,14 @@ template <size_t N> struct Serializer<std::array<bool, N>> {
uint32_t writer_index = buffer.writer_index();
// write array length
writer_index +=
buffer.put_var_uint32(writer_index, static_cast<uint32_t>(N));
buffer.put_var_uint32_unchecked(writer_index, static_cast<uint32_t>(N));

// write each boolean as a byte
for (size_t i = 0; i < N; ++i) {
buffer.unsafe_put_byte(writer_index + i,
static_cast<uint8_t>(arr[i] ? 1 : 0));
}
buffer.writer_index(writer_index + N);
buffer.unsafe_set_writer_index(writer_index + N);
}

static inline void write_data_generic(const std::array<bool, N> &arr,
Expand Down Expand Up @@ -323,7 +323,7 @@ template <size_t N> struct Serializer<std::array<float16_t, N>> {
constexpr size_t max_size = 8 + N * sizeof(float16_t);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index += buffer.put_var_uint32(
writer_index += buffer.put_var_uint32_unchecked(
writer_index, static_cast<uint32_t>(N * sizeof(float16_t)));
if constexpr (N > 0) {
if constexpr (FORY_LITTLE_ENDIAN) {
Expand All @@ -336,7 +336,7 @@ template <size_t N> struct Serializer<std::array<float16_t, N>> {
}
}
}
buffer.writer_index(writer_index + N * sizeof(float16_t));
buffer.unsafe_set_writer_index(writer_index + N * sizeof(float16_t));
}

static inline void write_data_generic(const std::array<float16_t, N> &arr,
Expand Down Expand Up @@ -436,7 +436,7 @@ template <size_t N> struct Serializer<std::array<bfloat16_t, N>> {
constexpr size_t max_size = 8 + N * sizeof(bfloat16_t);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index += buffer.put_var_uint32(
writer_index += buffer.put_var_uint32_unchecked(
writer_index, static_cast<uint32_t>(N * sizeof(bfloat16_t)));
if constexpr (N > 0) {
if constexpr (FORY_LITTLE_ENDIAN) {
Expand All @@ -449,7 +449,7 @@ template <size_t N> struct Serializer<std::array<bfloat16_t, N>> {
}
}
}
buffer.writer_index(writer_index + N * sizeof(bfloat16_t));
buffer.unsafe_set_writer_index(writer_index + N * sizeof(bfloat16_t));
}

static inline void write_data_generic(const std::array<bfloat16_t, N> &arr,
Expand Down
Loading
Loading