Skip to content

Releases: DevAM-Tools/ZeroAlloc

0.4.0

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 28 May 19:19
- **HIGH — TempStringBuilder/TempBytesBuilder heap-fallback grow corrupted ThreadStatic
  buffer** (H01): When a second `TempStringBuilder` or `TempBytesBuilder` was created
  while one was already active on the same thread (heap-fallback mode,
  `_IsThreadStatic = false`), any buffer growth in the inner builder incorrectly called
  `ZeroAllocHelper.GrowCharBuffer` / `GrowByteBuffer`. Those helpers replace the
  ThreadStatic buffer shared with the outer builder, silently invalidating its internal
  `_Array`/`_Span` reference and producing corrupt content or crashes in the outer
  builder after the inner builder grew. Fix: `EnsureCapacity`, `TryEnsureCapacity`, and
  the `AppendFormat` growth loops in both `TempStringBuilder` and `TempBytesBuilder` now
  branch on `_IsThreadStatic`. The heap-fallback path allocates a new private array,
  copies existing content, and reassigns `_Array`/`_Span` without touching the
  ThreadStatic field, making nested-builder grow fully isolated.

- **Generator: missing blank lines in generated `TryParse` code** (BinaryParsableGenerator):
  `AppendLine()` calls were absent after the bounds-check block and after each
  `LengthBytes` case arm in all three variable-length string decode paths
  (`Parse`/`TryParse` for UTF-8, UTF-16, and ASCII variants). The missing line breaks
  caused consecutive statements to run together without blank-line separation in the
  emitted source, degrading generated-code readability. Fix: `AppendLine()` inserted
  after every affected block boundary in `BinaryParsableGenerator`.

- **Package metadata update**: `Authors`, `Company`, and `Copyright` in
  `Directory.Build.props` updated from the placeholder "ZeroAlloc Contributors" to the
  canonical "DevAM" identity and copyright year.

- **`ContinuousIntegrationBuild` for Release**: Added
  `<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>` under a
  `'$(Configuration)' == 'Release'` condition in `Directory.Build.props`. This enables
  deterministic builds and source-link embedding for NuGet packages produced in Release
  configuration.

- **Roslyn dependency downgrade — 5.3.0-preview → 4.14.0**: `Microsoft.CodeAnalysis.Analyzers`
  and `Microsoft.CodeAnalysis.CSharp` downgraded from the unstable pre-release
  `5.3.0-2.25625.1` / `5.3.0` to the stable `4.14.0` in `Directory.Packages.props`,
  restoring build reproducibility on standard SDK installations.

- **Test runner configuration**: Added `"test": { "runner": "Microsoft.Testing.Platform" }`
  to `global.json`, aligning the dotnet test host with the TUnit / Microsoft.Testing.Platform
  runner used by the test suite.

- **Regression tests for nested-builder grow** (`BuilderTests.cs`): Two new tests cover
  the H01 fix end-to-end.
  `TempStringBuilder_NestedBuilder_GrowsBeyondDefaultBuffer_InnerContentCorrect` creates
  a nested `TempStringBuilder` in heap-fallback mode, appends 3 MiB of content to force
  a grow past the 2 MiB default, and asserts that both inner content and outer content
  remain correct. The symmetric `TempBytesBuilder_NestedBuilder_GrowsBeyondDefaultBuffer_InnerContentCorrect`
  test applies the same scenario to `TempBytesBuilder`.

- **`.gitignore` casing fix**: Entry for the local commit-message file corrected from
  `CommitMessage.md` to `commit_message.md` to match the actual filename.

0.3.0

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 26 May 17:58
- **BitReader: `Try*` read API**: Added a complete non-throwing counterpart for every
  read and skip operation: `TryReadBits`, `TryReadBit1`–`TryReadBit6`, `TryReadNibble`,
  `TryReadByte`, `TryReadUInt16`–`TryReadUInt128`, `TryReadInt8`–`TryReadInt128`,
  `TryReadBytes`, and `TrySkipBits`. Each returns `false` without advancing the reader
  position when insufficient bits remain, enabling buffer parsing without exception
  overhead in incremental or streaming scenarios.

- **BitWriter: `Try*` write API**: Symmetric non-throwing counterparts for every write
  and skip operation: `TryWriteBits`, `TryWriteByte`, `TryWriteUInt16`–`TryWriteUInt64`,
  `TryWriteInt16`–`TryWriteInt64`, `TrySkipBits`, and `TryWriteBytes`. Each returns
  `false` without modifying the buffer when capacity is insufficient.

- **BitReader/BitWriter: 128-bit integer support**: Added `ReadUInt128`, `ReadInt128`,
  `WriteUInt128`, and `WriteInt128`. All four methods use big-endian bit order (upper
  64 bits first), composing two 64-bit `ReadBits`/`WriteBits` calls.

- **HIGH — StringWrappers null-path partial-write** (H01): All 15 fixed-length encoding
  wrapper types (`Utf8FixBE`, `Utf16LEFixLE`, `AsciiFixBE`, `Latin1FixLE`, and their
  variants) wrote a zero-length prefix into the destination span before verifying that
  the span was large enough to hold it. On an undersized destination this produced a
  partial, corrupt write before returning `false` — violating the `TryFormat` contract
  that nothing is written on failure. Fix: three `EncodingHelper` helpers
  (`TryWriteZeroLengthPrefix32BE`, `TryWriteZeroLengthPrefix32LE`,
  `TryWriteZeroLengthPrefix16LE`) perform the capacity check first; every affected
  null branch now delegates to one of them so the operation is truly atomic.

- **HIGH — SpanBytesBuilder.Advance() silent position corruption** (H02):
  `Advance(int count)` accepted negative values and counts beyond `Remaining`, silently
  corrupting internal position state without any diagnostic. Fix: unsigned-comparison
  pattern `(uint)count > (uint)Remaining` catches both negative and over-limit values
  in a single branch and throws `ArgumentOutOfRangeException` with a descriptive
  message identifying the requested advance and the actual remaining capacity.

- **HIGH — SpanBytesBuilder throwing Append* relied on late BCL exceptions** (H03):
  All throwing `Append*` methods detected overflow only via downstream BCL exceptions
  (`ArgumentException` from `Span.CopyTo`, `IndexOutOfRangeException` from index
  access, `ArgumentOutOfRangeException` from `BinaryPrimitives`), with some paths
  producing partial writes before failing. Fix: all variants route through their
  `TryAppend*` counterparts; on `false` a shared `_ThrowBufferTooSmall` helper (marked
  `[MethodImpl(NoInlining)]` to keep the hot path lean) throws
  `InvalidOperationException` with the operation name. Covers `Append(byte)`,
  `Append(ReadOnlySpan<byte>)`, `Append(byte[]?)`, all 16 integer BE/LE variants,
  all 6 float BE/LE variants, `AppendVarInt`, `AppendHex2/4/8/16`, and
  `AppendBinary8/16/32/64`.

- **HIGH — SpanBytesBuilder compound UTF-8 partial state** (H04):
  `AppendUtf8WithVarIntPrefix`, `AppendUtf8WithLengthPrefixBE`, and
  `AppendUtf8WithLengthPrefixLE` wrote a length prefix (or partial content) before
  detecting insufficient space, leaving the builder position advanced past those bytes
  with no clean recovery. Fix: all compound UTF-8 operations calculate the total
  required byte count up front; if `Remaining < requiredBytes` the method fails
  atomically without advancing the position. `AppendUtf8` and `AppendUtf8NullTerminated`
  (throwing variants) likewise route through their `Try*` counterparts.

- **HIGH — BitReader missing bounds checks** (H05): `ReadBit1()`, `ReadByte()`,
  `SkipBits()`, and `ReadBytes()` did not validate remaining capacity before accessing
  the buffer, producing context-free `IndexOutOfRangeException` on overflow. Fix: all
  four methods now throw `InvalidOperationException` on overflow, or
  `ArgumentOutOfRangeException` for negative arguments, with messages stating exactly
  how many bits/bytes were requested vs. available.

- **HIGH — BitWriter missing bounds checks** (H06): Same structural gap as BitReader.
  `WriteBits()`, `WriteByte()`, `SkipBits()`, and `WriteBytes()` lacked capacity
  validation. Fix: identical treatment — `InvalidOperationException` on overflow,
  `ArgumentOutOfRangeException` for negative counts.

- **MEDIUM — SpanStringBuilder throwing Append* relied on late BCL exceptions** (M01):
  Identical structural problem to SpanBytesBuilder. Fix: `Append(string?)`,
  `Append(ReadOnlySpan<char>)`, `Append(char)`, `Append(bool)`, all `AppendHex*`, and
  all `AppendBinary*` methods route through their `TryAppend*` counterparts and throw
  `InvalidOperationException` on overflow.

- **MEDIUM — SpanStringBuilder.AppendLine partial advance** (M02):
  `AppendLine(string?)` and `AppendLine(ReadOnlySpan<char>)` could write the value
  content and then fail on the trailing newline, leaving the builder advanced by the
  value length while producing unterminated output. Fix: both overloads pre-check
  `Remaining < value.Length + NewLine.Length` before touching the buffer and fail
  atomically.

- **Exception contract clarification**: All overflow and capacity failures in both
  builder types now surface consistently as `InvalidOperationException`. The previous
  mix of `ArgumentException` (from `Span.CopyTo`), `IndexOutOfRangeException` (from
  span index access), and incidental `ArgumentOutOfRangeException` (from
  `BinaryPrimitives`) is gone. `ArgumentOutOfRangeException` is reserved exclusively
  for `Advance(count)`, where `count` itself is the out-of-range argument.

- **SpanBytesBuilder: `_GetVarIntByteCount` extraction**: The 7-branch variable-integer
  byte-count calculation was duplicated between `AppendVarInt` and `TryAppendVarInt`.
  Extracted to a shared private `_GetVarIntByteCount(ulong)` method.

- **BitReader/BitWriter: `ReadBitsCore`/`WriteBitsCore` extraction**: Hot-path
  bit-manipulation logic factored into private `[AggressiveInlining]` methods shared
  between throwing and non-throwing call sites, eliminating duplication while
  maintaining inlining for the fast path.

- **Generator: `ParsableMemberKind.Float`/`Double` removed**: The two dedicated enum
  members were redundant; float and double members are handled uniformly via
  `PrimitiveInteger`. Removing them eliminates a dead code path and reduces the risk
  of divergence between generator branches.

- **Generator: `Parse()` exception type corrected**: Documentation (and generated code
  comments) previously stated `Parse()` throws `FormatException` on failure; the
  implementation throws `InvalidOperationException`. Doc updated to match.

- **Generator: code generation quality improvements**: File header and namespace
  declarations in `BinaryParsableGenerator` and `BinaryWritableGenerator` now use raw
  string literals for cleaner, indent-preserving output. Copyright header in generated
  files updated to match the repository standard.

- **NuGet packaging fix — generator DLL inclusion**: The `Condition`-based
  `<None Include=...>` approach silently omitted the generator DLL when building in a
  configuration where the output path did not yet exist. Replaced with an MSBuild
  `Target` (`_IncludeGeneratorInPackage`) that resolves the generator's actual output
  path at pack time via `GetTargetPath`, guaranteeing correct inclusion regardless of
  build order.

- **Test framework migration — xunit.v3 → TUnit 1.45.8**: All test classes and methods
  migrated from xunit.v3 (`[Fact]`, `Assert.Equal`) to TUnit (`[Test]`,
  `await Assert.That(...).IsEqualTo(...)`). All test classes changed to `sealed`.
  `Microsoft.NET.Test.Sdk`, `xunit.v3`, and `xunit.runner.visualstudio` removed from
  `Directory.Packages.props`; replaced with `TUnit 1.45.8`. Ref-struct workarounds
  (snapshotting `Length` before `await`) applied where CS4007 applies.

- **Copyright header standardization**: Full 26-line MIT license block replaced with
  the single-line short form from the new `COPYRIGHT` file across all 60+ source files.
  `COPYRIGHT` file added to the repository root.

- **SDK version pinning**: `global.json` added, pinning the build to .NET SDK 10.0.100
  with `rollForward: latestMinor`.

- **ZeroAllocBaseTests** (new test file, 433 lines): Covers `ZeroAllocBase`
  fallback-method contracts (`String`, `TryString`, `Utf8`, `TryUtf8`, `Bytes`,
  `TryBytes`) and all attribute types defined in `Attributes.cs`.

- **BitReader/BitWriter tests expanded**: Full TUnit coverage for all new `Try*`
  methods, 128-bit read/write, bounds-check error paths, and `SkipBits` validation
  in both `BitReaderTests.cs` and `BitWriterTests.cs`.

- **SpanBuilder/FormattedWrapper regression tests**: Coverage for all buffer-safety
  fixes. `SpanBuilderTests.cs`: four tests renamed to reflect the new exception
  contract; three new regression tests added (Advance past remaining, compound UTF-8
  partial state, AppendLine partial advance). `FormattedWrapperTests.cs`: two new tests
  for the fixed-length wrapper null-path bug, plus a parameterised theory covering all
  15 wrapper types. `NegativeAndEdgeCaseTests.cs`: two assertions updated from
  `IndexOutOfRangeException`/`ArgumentException` to `InvalidOperationException`.

- **Total tests**: 893 (up from 647 in 0.2.0)

0.2.0

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 26 May 17:58
- **LazyString**: Deferred string evaluation that captures format state and only
  materializes when needed. Use `ZA.Lazy()` for simple deferred values and
  `ZA.LazyInterpolated()` for interpolated strings. Implements `ISpanFormattable`
  and `IUtf8SpanFormattable` for zero-allocation formatting when consumed by
  TempString/TempBytes builders. Includes `IDeferredFormat<TState>` interface and
  `DeferredFormat<TState>` base class for custom deferred formatting logic.
  Generator support for `ZA.Lazy()` and `ZA.LazyInterpolated()` added to
  `ZeroAllocGenerator`.

- **BinaryWritable Generator** (`[BinaryWritable]`): New source generator that
  produces `IBinarySerializable` implementations for structs, enabling zero-
  allocation binary serialization. This is the write counterpart to the existing
  `[BinaryParsable]` generator. Supports all the same member types: endian
  wrappers, bit fields, VarInt, nested serializable structs, fixed/dynamic byte
  arrays, and string encodings. Diagnostics ZA3001–ZA3013 for write-side
  validation.

- **ZeroAllocBase**: New base class for ZeroAlloc helper types, providing a
  foundation for `ZA` to inherit from while enabling generator detection of
  types that use ZeroAlloc APIs internally.

- **Central Package Management**: Introduced `Directory.Build.props` and
  `Directory.Packages.props` for centralized build configuration and NuGet
  package version management across all projects.

- **CRITICAL — BitReader.RemainingBytes operator precedence** (C01): Fixed
  `_Buffer.Length << 3 - _BitOffset` to `(_Buffer.Length << 3) - _BitOffset`.
  The subtraction operator has lower precedence than the left-shift operator,
  causing the shift amount to be computed as `3 - _BitOffset` instead of shifting
  first and then subtracting. This produced incorrect remaining-byte counts and
  could cause downstream index errors.

- **HIGH — BitReader.ReadBits missing bounds check** (H01): Added explicit
  validation that `_BitOffset + bitCount <= totalBits` before buffer access.
  Previously, reading past the end of the buffer produced a generic
  `IndexOutOfRangeException`; now throws a descriptive `InvalidOperationException`
  stating how many bits remain.

- **HIGH — Utf8Formatted\<T\>.ToString() heap allocation** (H02): Replaced the
  disproportionate 256B → 4KB → 65KB fallback chain with 256B stackalloc →
  4KB `ArrayPool<byte>.Shared.Rent()` with try/finally → `_Value.ToString()`
  fallback. Similarly updated `Formatted<T>.ToString()` to use
  `ArrayPool<char>` for the 4KB path. This eliminates unnecessary large heap
  allocations while maintaining correctness for values that exceed the stack
  buffer.

- **HIGH — Infinite loop risk in AppendFormattable** (H03): Added growth-loop
  safety guards in `TempStringBuilder` and `TempBytesBuilder`. All
  `AppendFormattable`, `TryAppendFormattable`, `Append<IBinarySerializable>`,
  and `AppendUtf8Formattable` loops now detect when the buffer fails to grow
  (capacity unchanged after `GrowBuffer`), preventing infinite loops from
  buggy `ISpanFormattable` implementations.

- **MEDIUM — UTF32Encoding instance duplication** (M01): Eliminated 4 identical
  `new UTF32Encoding(bigEndian: true, byteOrderMark: false)` instances in
  `StringWrappers.cs`. Introduced a shared `Utf32BEEncoding` helper class with
  a single static instance referenced by `Utf32BE`, `Utf32BEVar`, `Utf32BEFixBE`,
  and `Utf32BEFixLE`.

- **MEDIUM — Integer overflow in CalculateGrowth** (M02): Fixed potential integer
  overflow in `ZeroAllocHelper.CalculateGrowth` when buffer sizes exceed ~1.72 GiB.
  Growth calculation now uses `long` arithmetic before clamping to `int.MaxValue`.

- **MEDIUM — Missing generator validation** (M03): Added diagnostics ZA2014/ZA2015
  (BinaryParsableGenerator) and ZA3012/ZA3013 (BinaryWritableGenerator) for:
  - `[BinaryFixedLength]` or string encoding with length ≤ 0
  - Multiple conflicting length/encoding attributes on the same member

- **Generator code deduplication**: Extracted 7 shared helper methods
  (`IsEndianWrapper`, `IsPrimitiveInteger`, `GetEndianWrapperSize`,
  `GetPrimitiveSize`, `CalculateFixedSize`, `CalculateFixedBits`,
  `GetDefaultBitCount`) into `BinaryGeneratorHelpers.cs`, reducing maintenance
  risk when new wrapper types are added.

- **BitWriter relocation**: Moved `BitWriter.cs` from `Parsing/` to `src/` root,
  since BitWriter performs serialization (writing), not parsing.

- **Duplicated inheritdoc cleanup**: Removed ~50 occurrences of duplicated
  `/// <inheritdoc/>` tags in `StringWrappers.cs` (~3800 characters of noise).

- **Global usings consolidation**: Moved all common `using` directives into
  `GlobalUsings.cs` files for each project, reducing clutter in individual files.

- **Code style enforcement**: Added `.editorconfig` and applied `dotnet format`
  across the codebase for consistent formatting.

- **README documentation**: Updated with LazyString usage examples, `ToString()`
  preference guidance, and implicit conversion documentation.

- **FormattedWrappers tests** (14 new tests): Covers `Formatted<T>` and
  `Utf8Formatted<T>` including format string pass-through, culture handling,
  TryFormat buffer-too-small, Create factory methods, and ZA API integration.

- **VarInt boundary tests** (extended): Added parametrized test cases for 3-byte
  (2,097,151/2,097,152), 4-byte (268,435,455/268,435,456), and 5-byte
  (34,359,738,367) boundaries. Extended `EncodedSize` assertions through 5-byte
  and `ulong.MaxValue` (10-byte). Added roundtrip test covering all boundary
  values up to `ulong.MaxValue`.

- **VarIntZigZag boundary tests** (extended): Added symmetrical boundary tests
  for 2-byte (8,191/-8,192), 3-byte (1,048,575/-1,048,576), 4-byte boundaries,
  and `long.MaxValue`/`long.MinValue`. Extended roundtrip test with all boundary
  values.

- **BitWriter roundtrip tests**: Added Write→Read roundtrip tests using BitReader
  for mixed bit fields, aligned integers, and signed integers.

- **Total tests**: 647 (up from 626 in v0.1.3)

0.1.3

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 08 Jan 21:16

0.1.3

Add odd-length HexWrappers & improve recursive detection

Features

  • Hex Wrappers: Add Hex1, Hex3, Hex5, Hex6, Hex7 for odd bit-length values
    • Hex1: 4-bit nibble (0-F)
    • Hex3: 12-bit values (000-FFF)
    • Hex5: 20-bit values (00000-FFFFF)
    • Hex6: 24-bit values like RGB colors (000000-FFFFFF)
    • Hex7: 28-bit values (0000000-FFFFFFF)

Improvements

  • Generator: Improve MayUseZeroAllocInternally detection
    • Analyze TryFormat method body for actual ZeroAlloc API calls
    • Check for ZeroAllocBase inheritance instead of broad assembly reference check
    • Reduces false positive ZA1002 warnings for types that don't use ZeroAlloc

Internal

  • Cleanup: Use Encoding.UTF8 instead of System.Text.Encoding.UTF8 in FormattedWrappers
  • Version bump to 0.1.3 for both ZeroAlloc and ZeroAlloc.Generator

0.1.2

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 22 Dec 19:46

0.1.2

Fix nuget package issue and make ZeroAlloc containing the generator

0.1.1

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 22 Dec 19:28

Fix Generator nuget configuration

0.1.0

Choose a tag to compare

@DevAM-Tools DevAM-Tools released this 22 Dec 19:22

Summary

This is the first public release of ZeroAlloc, a zero-allocation serialization library for .NET 10+ that uses Roslyn source generators to create optimized, type-specific formatting and parsing code at compile time.


✨ Features

Core Library (ZeroAlloc)

  • Zero-allocation string formatting via ZA.String() with compile-time generated code
  • Zero-allocation UTF-8 generation via ZA.Utf8()
  • Culture-sensitive variants via ZA.LocalizedString() and ZA.LocalizedUtf8()
  • Zero-allocation binary serialization via ZA.Bytes()
  • Generated binary parsing via ZA.ParseBytes<T>() and [BinaryParsable] attribute
  • Manual builders for advanced scenarios:
    • TempStringBuilder / SpanStringBuilder for string building
    • TempBytesBuilder / SpanBytesBuilder for byte building
  • Endian-aware type wrappers: U16BE, U32LE, I64BE, F32LE, etc.
  • VarInt encoding: VarInt7, VarInt14, VarInt21, VarInt28
  • String encodings: Utf8, Ascii, length-prefixed variants (Utf8L8, Utf8L16BE, etc.)
  • Hex formatting wrappers: HexU8, HexU16, HexU32, HexU64, HexBytes
  • Bit-level parsing via BitReader for sub-byte field extraction
  • Configurable buffer sizes (default 2 MiB per thread, compile-time or runtime adjustable)

Source Generator (ZeroAlloc.Generator)

  • Roslyn incremental source generator targeting netstandard2.0
  • Generates optimized ZA.String(), ZA.Utf8(), ZA.Bytes() overloads at compile time
  • Generates TryParse() / Parse() methods for [BinaryParsable] structs
  • 17 analyzer diagnostics (ZA0001–ZA2013) for compile-time validation:
    • Class structure checks (partial, internal)
    • Argument type validation
    • Binary parsing attribute consistency
    • Length field ordering and alignment checks

📦 Project Structure

Project Description
ZeroAlloc Core library (net10.0)
ZeroAlloc.Generator Roslyn source generator (netstandard2.0)
ZeroAlloc.Tests xUnit test suite with >50 test classes
ZeroAlloc.Benchmarks BenchmarkDotNet performance tests
ZeroAlloc.Demo Demo/example application

🔧 Build & Packaging

  • Solution file: ZeroAlloc.slnx
  • VS Code tasks for build, test, pack, clean
  • NuGet packages auto-generated on Release build:
    • ZeroAlloc.0.1.0.nupkg + .snupkg
    • ZeroAlloc.Generator.0.1.0.nupkg + .snupkg
  • Deterministic builds with source-link support
  • MIT License

📝 Files Changed

  • 59 files changed, ~29,800 insertions
  • Complete library implementation
  • Comprehensive test coverage
  • Full documentation in README.md
  • VS Code workspace configuration
  • NuGet packaging configuration

🎯 Target Scenarios

  • Network protocol serialization (CAN, FlexRay, custom binary protocols)
  • High-performance logging without allocations
  • Embedded systems with memory constraints
  • Game development with GC-sensitive code paths
  • Any scenario requiring predictable, allocation-free serialization