Skip to content

API misuse checks compile out of release builds: the C# check model matches the C++ reference

Choose a tag to compare

@rowan-claude rowan-claude released this 19 Aug 07:23

No wire change. Wire bytes are identical to 1.3.1: the golden pins and the C++ interop gate (byte identity plus cross reads) pass unchanged.

Read this before upgrading if you catch exceptions. The library now throws no exceptions of its own. In a Release build, API misuse that previously raised ArgumentException or ArgumentOutOfRangeException completes and produces garbage instead. That is the C++ library's model, and it is why this is a MINOR rather than a patch.

A Release build now performs the same amount of API-misuse checking the C++ library does: none. Six invented release-check classes become Debug.Assert / Conditional("DEBUG") on every stream, read and write alike (#17), per the family standard: "the caller is responsible for well formed writes... We want MINIMAL runtime checking in release."

The six classes, now debug-only

  1. Bits-range parameter throws ([1,32] / [1,64]) on all five serialize surfaces: WriteStream, ReadStream, MeasureStream, WriteBatch, ReadBatch.
  2. min/max ordering parameter throws on the same five surfaces.
  3. Buffer-size and declaration parameter throws: string bufferSize >= 2, compressed float min < max and resolution > 0, and fixed point Q format validation. ValidateBufferSize and ValidateFixedPointFormat now compile out whole via Conditional("DEBUG"), the C# analog of static_assert.
  4. Bitpacker construction throws: BitWriter buffer %8, BitReader bytes bound.
  5. The checked bitpacker API: WriteBits, WriteBytes, ReadBits, ReadBytes width, alignment and overflow throws on the raw BitWriter / BitReader.
  6. The write-side sticky branch: the per-field _error early-out on the write and measure spines, which the C++ write path does not have. The latch itself survives where it is the model, so SerializeObject still latches user aborts and refuses to descend after one, and the read side keeps its sticky no-op model in full.

Kept, in every build

All read-side packet data validation: buffer-end Overflow, Align, range, and the string and wstring content refusals. The red team harness re-proves the read path holds. The runtime's own array bounds checks stay too, the memory-safety floor named in the source header.

If you compute a range from previously decoded packet data, validate it before passing it in. In Release a violated parameter contract yields garbage-in-garbage-out bytes, never memory unsafety, and a checked reader rejects the malformed stream.

Proof

Both legs prove the model: test_api_misuse_asserts fires on every moved class in Debug, and test_api_misuse_checks_absent_in_release shows the same calls completing in Release. CI's Debug leg is where the assert-execution tests run, at 54 tests in Debug against 53 in Release.

src/Serialize.csproj carries 1.4.0, matching this tag.

Full changelog: v1.3.1...v1.4.0