You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: resolve templates of any size and throw RezResolutionException on runaway resolution - #34
Before: the Executor rented fixed 4096-character buffers, so templates or resolved values longer than that threw ArgumentException. The recursion guard counted total substitutions, so a flat template with more than about 4096 independent placeholders threw even though nothing was recursive. When the guard tripped, it threw a bare System.Exception. The XML docs said a variable that resolves to itself hits the limit, but that case actually stops after one pass.
After: buffers are sized to what each pass needs and are always returned to the pool. The limit now applies to nesting depth: Executor.MaxResolutionDepth is 4096. A template with 10,000 flat placeholders resolves, while changing cycles (a = {b}, b = {a}) and ever-growing expansions (a = x{a}) still stop. They throw the new Invex.Rez.Abstraction.RezResolutionException, which derives from Exception, so existing catch blocks keep working. The docs describe the depth limit and the exception accurately.
How: each character carries the depth of the substitution that produced it, and a placeholder resolves one level deeper than the deepest character it covers. The renderer is split into TryFindSubstitution and Substitute. No public API was removed. New tests cover long templates, many flat placeholders, a chain at the maximum depth and one past it, growing expansions, and nested placeholders. All 107 net10.0 tests pass locally.
The executor rented fixed 4096-element buffers, so templates or rendered text
of 4095+ characters threw ArgumentException ("Destination is too short").
Buffers are now rented at the size each pass needs and always returned, even
when resolution throws.
The resolution depth limit now throws the new public RezResolutionException
(derives from Exception, so existing catch blocks still work). XML docs now
say a direct self-reference (a = {a}) stabilises and only cycles whose output
keeps changing hit the limit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy23TCq1bZkdPV2GRWfspU
The 4096 guard counted every substitution, so a flat template with more than
4097 placeholders threw RezResolutionException with no recursion at all.
Each character now carries the depth of the substitution that produced it
(0 for template text). A placeholder resolves at one more than the deepest
character it spans, and the guard throws only when that exceeds 4096. Changing
cycles (a = {b}, b = {a}) and ever-growing expansions (a = x{a}) still throw;
any number of independent placeholders does not.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy23TCq1bZkdPV2GRWfspU
If ArrayPool.Rent threw while growing a buffer, the old array had already been
returned and the finally block would return it a second time.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy23TCq1bZkdPV2GRWfspU
Tokenizer recursed once per open brace, so deeply nested or long runs of
unmatched braces overflowed the stack (100k '{' crashed the process). On main
the fixed 4096-char buffer kept nesting shallow enough; lifting it exposed the
overflow. Open braces are now tracked on a pooled explicit stack, producing
the same tokens as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy23TCq1bZkdPV2GRWfspU
Initial pool rentals leak arrays when a later Rent fails
src/Invex.Rez/Compiler/Executor.cs:57
All six initial pool rentals happen before the try. If any later Rent fails (most plausibly for a large template under memory pressure), every array already acquired is abandoned instead of being returned, contrary to the new ownership guarantee and making recovery from the allocation failure harder. Move acquisition under exception-safe ownership—for example, rent through a helper that returns previously acquired arrays on failure, or use nested try/finally scopes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Dec · project thread
Before: the Executor rented fixed 4096-character buffers, so templates or resolved values longer than that threw
ArgumentException. The recursion guard counted total substitutions, so a flat template with more than about 4096 independent placeholders threw even though nothing was recursive. When the guard tripped, it threw a bareSystem.Exception. The XML docs said a variable that resolves to itself hits the limit, but that case actually stops after one pass.After: buffers are sized to what each pass needs and are always returned to the pool. The limit now applies to nesting depth:
Executor.MaxResolutionDepthis 4096. A template with 10,000 flat placeholders resolves, while changing cycles (a = {b},b = {a}) and ever-growing expansions (a = x{a}) still stop. They throw the newInvex.Rez.Abstraction.RezResolutionException, which derives fromException, so existingcatchblocks keep working. The docs describe the depth limit and the exception accurately.How: each character carries the depth of the substitution that produced it, and a placeholder resolves one level deeper than the deepest character it covers. The renderer is split into
TryFindSubstitutionandSubstitute. No public API was removed. New tests cover long templates, many flat placeholders, a chain at the maximum depth and one past it, growing expansions, and nested placeholders. All 107 net10.0 tests pass locally.🤖 Generated with Claude Code
https://claude.ai/code/session_01Jy23TCq1bZkdPV2GRWfspU
Generated by Claude Code