From ac6e66f59f19a70ae82e1ef45e0f2e20a565a296 Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 13 May 2026 23:02:15 +0200 Subject: [PATCH] Remove depth param from serializers; use context field Refactored AcBinary, AcJson, and AcToon serializers to eliminate the explicit depth parameter from all serialization/deserialization methods, generated code, and interfaces. Introduced a global RecursionDepth field on the serialization context, incremented/decremented at recursion entry/exit, and enforced against MaxDepth as a safety net (except when ReferenceHandling=All). Updated all usages, including property, array, and dictionary handling, to use the new context-based depth tracking. Ensured consistency across runtime and generated code. --- .claude/settings.local.json | 5 +- .../AcBinarySourceGenerator.cs | 163 +++++++++-------- .../GeneratedWriters/TestOrderWriter.cs | 35 ++-- .../Serializers/AcSerializerContextBase.cs | 1 + .../AcBinaryDeserializer.CrossType.cs | 4 +- .../Binaries/AcBinaryDeserializer.Populate.cs | 32 ++-- .../Binaries/AcBinaryDeserializer.cs | 163 ++++++++--------- ...rySerializer.BinarySerializationContext.cs | 9 + .../Binaries/AcBinarySerializer.ScanPass.cs | 41 +++-- .../Binaries/AcBinarySerializer.cs | 171 ++++++++---------- .../Binaries/IGeneratedBinaryReader.cs | 6 +- .../Binaries/IGeneratedBinaryWriter.cs | 5 +- .../Jsons/AcJsonDeserializer.JsonElement.cs | 74 +++----- .../Serializers/Jsons/AcJsonDeserializer.cs | 20 +- .../Serializers/Jsons/AcJsonSerializer.cs | 4 +- .../Toons/AcToonSerializer.DataSection.cs | 36 ++-- .../Serializers/Toons/AcToonSerializer.cs | 2 +- 17 files changed, 367 insertions(+), 404 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b1cff9b..44ed17f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -76,7 +76,10 @@ "Bash(dotnet-trace convert *)", "Bash(find ~/.nuget/packages/memorypack* -name \"*.cs\" 2>/dev/null | head -5; find /mnt/c/Users/Fullepi/.nuget/packages/memorypack* -name \"MemoryPackSerializer*.cs\" 2>/dev/null | head -5)", "PowerShell($path = \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core.Serializers.Console\\\\Program.cs\"; $c = [IO.File]::ReadAllText\\($path\\); $c = $c -replace 'MeasureAllocationTotal', 'BenchmarkLoop.MeasureAllocationTotal'; $c = $c -replace 'MeasureAllocation\\\\\\(', 'BenchmarkLoop.MeasureAllocation\\('; $c = $c -replace 'ForceGcCollect\\\\\\(', 'BenchmarkLoop.ForceGcCollect\\('; $c = $c -replace 'CalibrateIterations\\\\\\(', 'BenchmarkLoop.CalibrateIterations\\('; $c = $c -replace 'RunTimed\\\\\\(', 'BenchmarkLoop.RunTimed\\('; $c = $c -replace 'DeepEqualsViaJson\\\\\\(', 'BenchmarkLoop.DeepEqualsViaJson\\('; $c = $c -replace 'ValidateMemoryPackSetup\\\\\\(', 'BenchmarkLoop.ValidateMemoryPackSetup\\('; $c = $c -replace 'FilterByLayer\\\\\\(', 'BenchmarkLoop.FilterByLayer\\('; [IO.File]::WriteAllText\\($path, $c\\); Write-Output \"OK new length: $\\($c.Length\\)\")", - "PowerShell($progPath = \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core.Serializers.Console\\\\Program.cs\"; $c = [IO.File]::ReadAllText\\($progPath\\); $usings = \"using AyCode.Core.Serializers.Console.Benchmarks;`r`n\"; if \\(-not $c.Contains\\($usings.Trim\\(\\)\\)\\) { $idx = $c.IndexOf\\(\"namespace AyCode.Core.Serializers.Console;\"\\); $before = $c.Substring\\(0, $idx\\); $after = $c.Substring\\($idx\\); $c2 = $before + $usings + \"`r`n\" + $after; [IO.File]::WriteAllText\\($progPath, $c2\\); Write-Output \"Added 'using AyCode.Core.Serializers.Console.Benchmarks;' to Program.cs\" } else { Write-Output \"Using already present in Program.cs\" })" + "PowerShell($progPath = \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core.Serializers.Console\\\\Program.cs\"; $c = [IO.File]::ReadAllText\\($progPath\\); $usings = \"using AyCode.Core.Serializers.Console.Benchmarks;`r`n\"; if \\(-not $c.Contains\\($usings.Trim\\(\\)\\)\\) { $idx = $c.IndexOf\\(\"namespace AyCode.Core.Serializers.Console;\"\\); $before = $c.Substring\\(0, $idx\\); $after = $c.Substring\\($idx\\); $c2 = $before + $usings + \"`r`n\" + $after; [IO.File]::WriteAllText\\($progPath, $c2\\); Write-Output \"Added 'using AyCode.Core.Serializers.Console.Benchmarks;' to Program.cs\" } else { Write-Output \"Using already present in Program.cs\" })", + "Bash(git -C H:/Applications/Aycode/Source/AyCode.Core log --oneline -10 -- AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs)", + "Bash(git -C H:/Applications/Aycode/Source/AyCode.Core log -p --all -S \"if \\(isComplexElement\\)\" -- AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs)", + "Bash(git -C H:/Applications/Aycode/Source/AyCode.Core show 2aa2eec -- AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs)" ] } } diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index 8badf04..33aba58 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -542,8 +542,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.Append(string.Join(", ", ci.PropertyNameHashes)); sb.AppendLine(" };"); sb.AppendLine(); - sb.AppendLine(" public void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context, int depth) where TOutput : struct, IBinaryOutputBase"); + sb.AppendLine(" public void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase"); sb.AppendLine(" {"); + sb.AppendLine(" // Global recursion depth safety net — only when ReferenceHandling != All"); + sb.AppendLine(" // (HasAllRefHandling=true tracks every type → write plan already prevents cycles via ObjectRef)."); + sb.AppendLine(" var needsDepthCheck = !context.HasAllRefHandling;"); + sb.AppendLine(" if (needsDepthCheck)"); + sb.AppendLine(" {"); + sb.AppendLine(" if (context.RecursionDepth >= context.MaxDepth)"); + sb.AppendLine($" throw new System.InvalidOperationException(\"AcBinary serialize: recursion depth exceeded MaxDepth=\" + context.MaxDepth + \" at {ci.FullTypeName}\");"); + sb.AppendLine(" context.RecursionDepth++;"); + sb.AppendLine(" }"); + sb.AppendLine(); sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);"); foreach (var p in ci.Properties) @@ -552,6 +562,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator EmitProp(sb, p, " ", ci.FullTypeName, ci.EnableMetadata, ci.EnablePropertyFilter); } + sb.AppendLine(); + sb.AppendLine(" if (needsDepthCheck) context.RecursionDepth--;"); sb.AppendLine(" }"); sb.AppendLine(); @@ -565,7 +577,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine(" where TOutput : struct, IBinaryOutputBase"); sb.AppendLine(" {"); sb.AppendLine(" if (!context.HasCaching) return;"); - sb.AppendLine(" ScanObject(value, context, 0);"); + sb.AppendLine(" ScanObject(value, context);"); sb.AppendLine(" context.SortWritePlan();"); sb.AppendLine(" }"); @@ -580,7 +592,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator /// private static void GenScanProperties(StringBuilder sb, SerializableClassInfo ci) { - sb.AppendLine(" public void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context, int depth) where TOutput : struct, IBinaryOutputBase"); + sb.AppendLine(" public void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase"); sb.AppendLine(" {"); // Compile-time proven: no scan work needed for this type @@ -602,8 +614,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine(" if (!context.HasStringInterning) return;"); } - // Null/depth guard — matches runtime ScanValue entry - sb.AppendLine(" if (value == null || depth > context.MaxDepth) return;"); + // Null guard — MaxDepth option removed (was: cycle protection via runtime depth check). + // Cycle safety now comes from IId-tracking; future [AcBinaryCircular] attr will mark non-IId circular refs. + sb.AppendLine(" if (value == null) return;"); sb.AppendLine(); sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);"); @@ -663,6 +676,19 @@ public class AcBinarySourceGenerator : IIncrementalGenerator (p.TypeKind == PropertyTypeKind.Collection && p.ElementKind == PropertyTypeKind.String && p.InterningFlags != 0) || (p.TypeKind == PropertyTypeKind.Dictionary && (p.DictKeyKind == PropertyTypeKind.String || p.DictValueKind == PropertyTypeKind.String) && p.InterningFlags != 0)); + // Global recursion depth safety net — only when ReferenceHandling != All + // (HasAllRefHandling=true tracks every type → 2nd-occurrence early-return already prevents cycles). + // Emitted AFTER all early returns (NeedsScan=false, feature-flag, null guard, IId 2nd-occurrence) + // and BEFORE the property scan loop that recurses into children. + sb.AppendLine(); + sb.AppendLine(" var needsDepthCheck = !context.HasAllRefHandling;"); + sb.AppendLine(" if (needsDepthCheck)"); + sb.AppendLine(" {"); + sb.AppendLine(" if (context.RecursionDepth >= context.MaxDepth)"); + sb.AppendLine($" throw new System.InvalidOperationException(\"AcBinary scan: recursion depth exceeded MaxDepth=\" + context.MaxDepth + \" at {ci.FullTypeName}\");"); + sb.AppendLine(" context.RecursionDepth++;"); + sb.AppendLine(" }"); + if (hasStringScan) { // Use pre-computed InternBit from context (avoids Options.UseStringInterning field chain + shift per object). @@ -688,6 +714,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine(" // No reference properties to scan"); } + sb.AppendLine(); + sb.AppendLine(" if (needsDepthCheck) context.RecursionDepth--;"); sb.AppendLine(" }"); } @@ -774,16 +802,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} context.WriteStringUtf8({a}.GetType().AssemblyQualifiedName!);"); sb.AppendLine($"{i} }}"); } - sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);"); + sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context);"); sb.AppendLine($"{i}}}"); } else if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context);"); } else - sb.AppendLine($"{i}AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + sb.AppendLine($"{i}AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context);"); break; case PropertyTypeKind.Collection: // Direct collection write for List/T[] with Complex element types that have generated writers @@ -792,10 +820,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator else if (p.IsNullable) { sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context);"); } else - sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);"); + sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context);"); break; case PropertyTypeKind.Dictionary: EmitDirectDictionaryWrite(sb, p, a, i); @@ -1024,7 +1052,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} var {childVar} = {a};"); sb.AppendLine($"{i} if ({childVar} != null)"); - sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context, depth + 1);"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);"); sb.AppendLine($"{i}}}"); } else @@ -1032,7 +1060,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // IId in subtree — always call (active in OnlyId + All) sb.AppendLine($"{i}var {childVar} = {a};"); sb.AppendLine($"{i}if ({childVar} != null)"); - sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context, depth + 1);"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);"); } } @@ -1046,9 +1074,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}var {childVar} = {a};"); sb.AppendLine($"{i}if ({childVar} != null)"); if (p.IsObjectDeclaredType) - sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context, depth + 1);"); + sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context);"); else - sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);"); + sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context);"); } /// @@ -1130,7 +1158,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} var scol_{p.Name} = {a};"); sb.AppendLine($"{i} if (scol_{p.Name} != null)"); sb.AppendLine($"{i} {{"); - sb.AppendLine($"{i} var snd_{p.Name} = depth + 2;"); if (p.CollectionKind == "Array") { @@ -1160,7 +1187,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator var e = $"se_{p.Name}"; // Null check only — ScanObject handles depth + ref tracking internally sb.AppendLine($"{i} if ({e} == null) continue;"); - sb.AppendLine($"{i} {writer}.Instance.ScanObject({e}, context, snd_{p.Name});"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject({e}, context);"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); @@ -1172,7 +1199,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i}var scol_{p.Name} = {a};"); sb.AppendLine($"{i}if (scol_{p.Name} != null)"); - sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated(scol_{p.Name}, typeof({p.TypeNameForTypeof}), context, depth);"); + sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated(scol_{p.Name}, typeof({p.TypeNameForTypeof}), context);"); return; } @@ -1253,7 +1280,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} var sd_{s} = {a};"); sb.AppendLine($"{i} if (sd_{s} != null)"); sb.AppendLine($"{i} {{"); - sb.AppendLine($"{i} var snd_{s} = depth + 2;"); sb.AppendLine($"{i} foreach (var sde_{s} in sd_{s})"); sb.AppendLine($"{i} {{"); @@ -1287,11 +1313,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} if ({complexGuard})"); - sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context);"); sb.AppendLine($"{i} }}"); } else - sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});"); + sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context);"); } sb.AppendLine($"{i} }}"); @@ -1313,24 +1339,23 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Reference type properties can always be null at runtime regardless of nullable annotation sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); if (!p.ChildNeedsRefScan && !p.ChildEnableMetadata) { // Compile-time proven: no ref, no metadata → ZERO branches: always Object + WriteProperties - sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({a}, context, depth + 1); }}"); + sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({a}, context); }}"); } else if (p.ChildNeedsRefScan && !p.ChildEnableMetadata) { - sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context, depth + 1);"); + sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context);"); } else if (!p.ChildNeedsRefScan && p.ChildEnableMetadata) { - sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({a}, context, depth + 1); }}"); + sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({a}, context); }}"); } else { - sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context, depth + 1);"); + sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context);"); } } @@ -1361,7 +1386,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Reference type collections can always be null at runtime regardless of nullable annotation sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); @@ -1372,8 +1396,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i} var arr_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)arr_{p.Name}.Length);"); - sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;"); - sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;"); sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < arr_{p.Name}.Length; i_{p.Name}++)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];"); @@ -1382,8 +1404,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i} var col_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);"); - sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;"); - sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;"); sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})"); sb.AppendLine($"{i} {{"); } @@ -1391,8 +1411,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i} var col_{p.Name} = {a};"); sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);"); - sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;"); - sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;"); sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < col_{p.Name}.Count; i_{p.Name}++)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var elem_{p.Name} = col_{p.Name}[i_{p.Name}];"); @@ -1401,8 +1419,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan({a});"); sb.AppendLine($"{i} context.WriteVarUInt((uint)span_{p.Name}.Length);"); - sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;"); - sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;"); sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < span_{p.Name}.Length; i_{p.Name}++)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var elem_{p.Name} = span_{p.Name}[i_{p.Name}];"); @@ -1410,7 +1426,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Per-element write var e = $"elem_{p.Name}"; - sb.AppendLine($"{i} if ({e} == null || depthExceeded_{p.Name}) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}"); + sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}"); var elemRefSuffix = p.ElementIsIId ? "IId" : "All"; @@ -1418,20 +1434,20 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { // Compile-time proven: no ref, no metadata → ZERO branches per element: always Object sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); - sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context);"); } else if (p.ElementNeedsRefScan && !p.ElementEnableMetadata) { - sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); + sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context);"); } else if (!p.ElementNeedsRefScan && p.ElementEnableMetadata) { sb.AppendLine($"{i} context.WriteObjectMetaMarker({e}, {writer}.s_wrapperSlot);"); - sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); + sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context);"); } else { - sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});"); + sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context);"); } sb.AppendLine($"{i} }}"); @@ -1520,13 +1536,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Reference type dictionaries can always be null at runtime regardless of nullable annotation sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);"); - sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);"); sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Dictionary);"); sb.AppendLine($"{i} context.WriteVarUInt((uint){a}.Count);"); - sb.AppendLine($"{i} var nd_{s} = depth + 2;"); sb.AppendLine($"{i} foreach (var kvp_{s} in {a})"); sb.AppendLine($"{i} {{"); @@ -1549,7 +1563,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator } else { - sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context, nd_{s});"); + sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context);"); } // Write value @@ -1577,7 +1591,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator else { // Fallback for non-inlineable value types - sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context, nd_{s});"); + sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context);"); } sb.AppendLine($"{i} }}"); @@ -1593,26 +1607,25 @@ public class AcBinarySourceGenerator : IIncrementalGenerator var writer = p.DictValueWriterClassName!; sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}"); - sb.AppendLine($"{i}else if (nd_{s} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); }}"); var dvRefSuffix = p.DictValueIsIId ? "IId" : "All"; if (!p.DictValueNeedsRefScan && !p.DictValueEnableMetadata) { // No ref, no metadata → always Object - sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}"); + sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context); }}"); } else if (p.DictValueNeedsRefScan && !p.DictValueEnableMetadata) { - sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context);"); } else if (!p.DictValueNeedsRefScan && p.DictValueEnableMetadata) { - sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}"); + sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({v}, context); }}"); } else { - sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context, nd_{s});"); + sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context);"); } } @@ -1700,7 +1713,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});"); break; default: - sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context, depth);"); + sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context);"); break; } } @@ -1746,7 +1759,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine(); // ReadProperties — reads all properties into an existing instance (mirrors WriteProperties) - sb.AppendLine(" public void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context, int depth)"); + // No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter + // already prevents pathological depth in well-formed payloads. + sb.AppendLine(" public void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context)"); sb.AppendLine(" where TInput : struct, IBinaryInputBase"); sb.AppendLine(" {"); sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);"); @@ -1762,13 +1777,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine(); // ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties) - sb.AppendLine(" public object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int depth, int cacheIndex)"); + sb.AppendLine(" public object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int cacheIndex)"); sb.AppendLine(" where TInput : struct, IBinaryInputBase"); sb.AppendLine(" {"); sb.AppendLine($" var obj = new {ci.FullTypeName}();"); sb.AppendLine(" if (cacheIndex >= 0)"); sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);"); - sb.AppendLine(" ReadProperties(obj, context, depth);"); + sb.AppendLine(" ReadProperties(obj, context);"); sb.AppendLine(" return obj;"); sb.AppendLine(" }"); sb.AppendLine("}"); @@ -1856,9 +1871,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback sb.AppendLine($"{i} context._position--;"); if (p.IsNullable) - sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);"); + sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));"); else - sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;"); + sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;"); break; } } @@ -2013,20 +2028,19 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} context._position--;"); - sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);"); + sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));"); sb.AppendLine($"{i}}}"); } else { sb.AppendLine($"{i}context._position--;"); - sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;"); + sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;"); } return; } var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"); var cast = $"({p.TypeNameForTypeof})"; - var nd = "depth + 1"; if (!p.ChildNeedsRefScan) { @@ -2041,7 +2055,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}"); sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);"); sb.AppendLine($"{i} {a} = rc_{p.Name};"); sb.AppendLine($"{i}}}"); } @@ -2051,7 +2065,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}"); sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);"); sb.AppendLine($"{i} {a} = rc_{p.Name};"); sb.AppendLine($"{i}}}"); } @@ -2069,7 +2083,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} case BinaryTypeCode.Object:"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);"); sb.AppendLine($"{i} {a} = rc_{p.Name};"); sb.AppendLine($"{i} break;"); sb.AppendLine($"{i} }}"); @@ -2078,7 +2092,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} var ci_{p.Name} = (int)context.ReadVarUInt();"); sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();"); sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{p.Name}, rc_{p.Name});"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);"); sb.AppendLine($"{i} {a} = rc_{p.Name};"); sb.AppendLine($"{i} break;"); sb.AppendLine($"{i} }}"); @@ -2095,7 +2109,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc});"); sb.AppendLine($"{i} if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1;"); sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);"); sb.AppendLine($"{i} {a} = rc_{p.Name};"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} break;"); @@ -2136,13 +2150,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}else"); sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} context._position--;"); - sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);"); + sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));"); sb.AppendLine($"{i}}}"); } else { sb.AppendLine($"{i}context._position--;"); - sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;"); + sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;"); } } @@ -2176,7 +2190,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();"); sb.AppendLine($"{i} var dict_{s} = new System.Collections.Generic.Dictionary<{keyType}, {valType}>(cnt_{s});"); - sb.AppendLine($"{i} var nd_{s} = depth + 1;"); sb.AppendLine($"{i} for (var di_{s} = 0; di_{s} < cnt_{s}; di_{s}++)"); sb.AppendLine($"{i} {{"); @@ -2184,7 +2197,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator if (canInlineKey) EmitReadDictElement(sb, p.DictKeyKind, keyType, $"dk_{s}", s, i + " ", null, false); else - sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}), nd_{s})!;"); + sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}))!;"); // Read value if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter) @@ -2196,7 +2209,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var rv_{s} = new {valType}();"); - sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});"); + sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);"); sb.AppendLine($"{i} dv_{s} = rv_{s};"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRefFirst)"); @@ -2204,7 +2217,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} var rci_{s} = (int)context.ReadVarUInt();"); sb.AppendLine($"{i} var rv_{s} = new {valType}();"); sb.AppendLine($"{i} context.RegisterInternedValueAt(rci_{s}, rv_{s});"); - sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});"); + sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);"); sb.AppendLine($"{i} dv_{s} = rv_{s};"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)"); @@ -2212,13 +2225,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} context._position--;"); - sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});"); + sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));"); sb.AppendLine($"{i} }}"); } else if (canInlineValue) EmitReadDictElement(sb, p.DictValueKind, valType, $"dv_{s}", s, i + " ", null, true); else - sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});"); + sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));"); // Add to dictionary sb.AppendLine($"{i} if (dk_{s} != null) dict_{s}[dk_{s}] = dv_{s}!;"); @@ -2351,8 +2364,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();"); - if (isComplexElement) - sb.AppendLine($"{i} var nd_{s} = depth + 1;"); // Create collection + loop based on kind if (p.CollectionKind == "Array") @@ -2420,7 +2431,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i}{{"); sb.AppendLine($"{i} if ({etc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({elemTypeName}), {etc}); if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1; }}"); sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);"); sb.AppendLine($"{i} {assignExpr}"); sb.AppendLine($"{i}}}"); } @@ -2434,7 +2445,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} case BinaryTypeCode.Object:"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);"); sb.AppendLine($"{i} {assignExpr}"); sb.AppendLine($"{i} break;"); sb.AppendLine($"{i} }}"); @@ -2443,7 +2454,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} var ci_{propSuffix} = (int)context.ReadVarUInt();"); sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();"); sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{propSuffix}, re_{propSuffix});"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);"); sb.AppendLine($"{i} {assignExpr}"); sb.AppendLine($"{i} break;"); sb.AppendLine($"{i} }}"); @@ -2464,7 +2475,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} context.GetWrapper(typeof({elemTypeName}), {etc});"); sb.AppendLine($"{i} if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1;"); sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();"); - sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});"); + sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);"); sb.AppendLine($"{i} {assignExpr}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} break;"); diff --git a/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs b/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs index 70d4195..b0e78b8 100644 --- a/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs +++ b/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs @@ -21,22 +21,19 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter { internal static readonly TestOrderWriter Instance = new(); - public void WriteProperties( - object value, - AcBinarySerializer.BinarySerializationContext context, - int depth) + public void WriteProperties(object value, + AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var obj = Unsafe.As(value); - var nextDepth = depth; // Properties in alphabetical order (matching runtime serializer): // AuditMetadata: MetadataInfo_All_True? (complex, nullable) - WriteComplexOrNull(obj.AuditMetadata, context, nextDepth); + WriteComplexOrNull(obj.AuditMetadata, context); // Category: SharedCategory_All_True? (complex, nullable) - WriteComplexOrNull(obj.Category, context, nextDepth); + WriteComplexOrNull(obj.Category, context); // CreatedAt: DateTime (markerless) context.WriteDateTimeBits(obj.CreatedAt); @@ -45,22 +42,22 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter context.WriteVarInt(obj.Id); // Items: List (collection) - WriteComplexOrNull(obj.Items, context, nextDepth); + WriteComplexOrNull(obj.Items, context); // MetadataList: List (collection) - WriteComplexOrNull(obj.MetadataList, context, nextDepth); + WriteComplexOrNull(obj.MetadataList, context); // NoMergeItems: List (collection) - WriteComplexOrNull(obj.NoMergeItems, context, nextDepth); + WriteComplexOrNull(obj.NoMergeItems, context); // OrderMetadata: MetadataInfo_All_True? (complex, nullable) - WriteComplexOrNull(obj.OrderMetadata, context, nextDepth); + WriteComplexOrNull(obj.OrderMetadata, context); // OrderNumber: string AcBinarySerializer.WriteStringGenerated(obj.OrderNumber, context); // Owner: SharedUser? (complex, nullable) - WriteComplexOrNull(obj.Owner, context, nextDepth); + WriteComplexOrNull(obj.Owner, context); // PaidDateUtc: DateTime? (nullable) var paidDate = obj.PaidDateUtc; @@ -75,22 +72,22 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter } // PrimaryTag: SharedTag_All_True? (complex, nullable) - WriteComplexOrNull(obj.PrimaryTag, context, nextDepth); + WriteComplexOrNull(obj.PrimaryTag, context); // SecondaryTag: SharedTag_All_True? (complex, nullable) - WriteComplexOrNull(obj.SecondaryTag, context, nextDepth); + WriteComplexOrNull(obj.SecondaryTag, context); // Status: TestStatus (enum, markerless) context.WriteVarInt((int)obj.Status); // Tags: List (collection) - WriteComplexOrNull(obj.Tags, context, nextDepth); + WriteComplexOrNull(obj.Tags, context); // TotalAmount: decimal (markerless) context.WriteDecimalBits(obj.TotalAmount); } - public void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context, int depth) where TOutput : struct, IBinaryOutputBase + public void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { throw new NotImplementedException(); } @@ -98,12 +95,12 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter public void ScanForDuplicates(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { if (!context.HasCaching) return; - ScanObject(value, context, 0); + ScanObject(value, context); context.SortWritePlan(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteComplexOrNull(object? value, AcBinarySerializer.BinarySerializationContext context, int depth) + private static void WriteComplexOrNull(object? value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { if (value == null) @@ -112,6 +109,6 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter return; } - AcBinarySerializer.WriteValueGenerated(value, value.GetType(), context, depth); + AcBinarySerializer.WriteValueGenerated(value, value.GetType(), context); } } diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs index e91902b..2f5d7c4 100644 --- a/AyCode.Core/Serializers/AcSerializerContextBase.cs +++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs @@ -24,6 +24,7 @@ public abstract class AcSerializerContextBase public TOptions Options { get; private set; } = null!; public byte MaxDepth => Options.MaxDepth; + public ReferenceHandlingMode ReferenceHandling => Options.ReferenceHandling; /// diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs index 6a3179b..3e92597 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs @@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer BinaryTypeCode.Null => null, BinaryTypeCode.Object => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: false), BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: true), - _ => ReadValue(context, destType, depth) // Primitives, arrays, etc. use normal path + _ => ReadValue(context, destType) // Primitives, arrays, etc. use normal path }; } @@ -329,7 +329,7 @@ public static partial class AcBinaryDeserializer if (TryReadAndSetTypedValue(context, target, propInfo, peekCode)) return; - var value = ReadValue(context, propInfo.PropertyType, nextDepth); + var value = ReadValue(context, propInfo.PropertyType); propInfo.SetValue(target, value); } catch (InvalidCastException ex) diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs index 32c3de6..d6c3f0d 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs @@ -100,14 +100,12 @@ public static partial class AcBinaryDeserializer /// UseMetadata=true: cacheMap[i] gives the setter (null → skip). /// UseMetadata=false: properties[i] gives the setter directly. /// - private static void PopulateObjectPropertiesIndexed( - BinaryDeserializationContext context, - object target, - TypeMetadataWrapper wrapper, - int depth, - bool skipDefaultWrite) + private static void PopulateObjectPropertiesIndexed(BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, bool skipDefaultWrite) where TInput : struct, IBinaryInputBase { + // No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter + // already prevents pathological depth in well-formed payloads. Malicious wire is out of scope. + var metadata = wrapper.Metadata; var properties = metadata.PropertiesArray; var cacheMap = wrapper.CacheMap; @@ -246,7 +244,7 @@ public static partial class AcBinaryDeserializer { // Marker already consumed → rewind so ReadValue can read it context._position--; - var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth); + var nestedValue = ReadValue(context, propInfo.PropertyType); if (nestedValue != null) { var complexIdx = propInfo.ComplexPropertyIndex; @@ -276,14 +274,14 @@ public static partial class AcBinaryDeserializer { // Marker already consumed — go straight to ReadObjectCoreWithWrapper var propWrapper = ResolvePropertyWrapper(state.ParentWrapper, complexIdx, propInfo.PropertyType, context); - var value = ReadObjectCoreWithWrapper(context, propWrapper, nextDepth, cacheIndex: -1); + var value = ReadObjectCoreWithWrapper(context, propWrapper, cacheIndex: -1); propInfo.SetValue(target, value); } else { // Marker already consumed → rewind so ReadValue can read it context._position--; - var value = ReadValue(context, propInfo.PropertyType, nextDepth); + var value = ReadValue(context, propInfo.PropertyType); propInfo.SetValue(target, value); } } @@ -365,10 +363,12 @@ public static partial class AcBinaryDeserializer /// Called from ReadObject/ReadObjectWithMetadata for new instances. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, int depth, bool skipDefaultWrite) + private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper wrapper, bool skipDefaultWrite) where TInput : struct, IBinaryInputBase { - PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite); + // Bridge: PopulateObjectPropertiesIndexed still has int depth in signature (other callers use it). + // Pass 0 as placeholder; depth-cleanup of PopulateObjectPropertiesIndexed + its other callers is pending. + PopulateObjectPropertiesIndexed(context, target, wrapper, 0, skipDefaultWrite); } #endregion @@ -393,7 +393,7 @@ public static partial class AcBinaryDeserializer for (int i = 0; i < count; i++) { // ReadValue handles ChainMode internally (ReadObject returns cached instance) - var value = ReadValue(context, elementType, nextDepth); + var value = ReadValue(context, elementType); targetList.Add(value); } } @@ -461,13 +461,13 @@ public static partial class AcBinaryDeserializer object? value; if (typeCode == BinaryTypeCode.Object && elementMetadata != null) { - value = ReadObjectCoreWithWrapper(context, wrapper, nextDepth, cacheIndex: cacheIndex); + value = ReadObjectCoreWithWrapper(context, wrapper, cacheIndex: cacheIndex); } else { // Marker already consumed → rewind so ReadValue can read it context._position--; - value = ReadValue(context, elementType, nextDepth); + value = ReadValue(context, elementType); } if (i < existingCount) @@ -558,7 +558,7 @@ public static partial class AcBinaryDeserializer { // Marker already consumed → rewind so ReadValue can read it context._position--; - newItem = ReadValue(context, elementType, nextDepth); + newItem = ReadValue(context, elementType); if (newItem == null) continue; } @@ -667,7 +667,7 @@ public static partial class AcBinaryDeserializer { // Marker already consumed → rewind so ReadValue can read it context._position--; - newItem = ReadValue(context, elementType, nextDepth); + newItem = ReadValue(context, elementType); if (newItem == null) continue; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 2c2d28b..5a31493 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -72,7 +72,7 @@ public static partial class AcBinaryDeserializer private static Dictionary? t_typeConversionLocalCache; // Type dispatch table for fast ReadValue — generic per TInput, JIT specializes each - private delegate object? TypeReader(BinaryDeserializationContext context, Type targetType, int depth) + private delegate object? TypeReader(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase; private static class TypeReaderTable where TInput : struct, IBinaryInputBase @@ -82,37 +82,37 @@ public static partial class AcBinaryDeserializer private static TypeReader?[] InitReaders() { var readers = new TypeReader?[byte.MaxValue + 1]; - readers[BinaryTypeCode.Null] = static (_, _, _) => null; - readers[BinaryTypeCode.True] = static (_, _, _) => true; - readers[BinaryTypeCode.False] = static (_, _, _) => false; - readers[BinaryTypeCode.Int8] = static (ctx, _, _) => (sbyte)ctx.ReadByte(); - readers[BinaryTypeCode.UInt8] = static (ctx, _, _) => ctx.ReadByte(); - readers[BinaryTypeCode.Int16] = static (ctx, _, _) => ctx.ReadInt16Unsafe(); - readers[BinaryTypeCode.UInt16] = static (ctx, _, _) => ctx.ReadUInt16Unsafe(); - readers[BinaryTypeCode.Int32] = static (ctx, type, _) => ReadInt32Value(ctx, type); - readers[BinaryTypeCode.UInt32] = static (ctx, _, _) => ctx.ReadVarUInt(); - readers[BinaryTypeCode.Int64] = static (ctx, _, _) => ctx.ReadVarLong(); - readers[BinaryTypeCode.UInt64] = static (ctx, _, _) => ctx.ReadVarULong(); - readers[BinaryTypeCode.Float32] = static (ctx, _, _) => ctx.ReadSingleUnsafe(); - readers[BinaryTypeCode.Float64] = static (ctx, _, _) => ctx.ReadDoubleUnsafe(); - readers[BinaryTypeCode.Decimal] = static (ctx, _, _) => ctx.ReadDecimalUnsafe(); - readers[BinaryTypeCode.Char] = static (ctx, _, _) => ctx.ReadCharUnsafe(); + readers[BinaryTypeCode.Null] = static (_, _) => null; + readers[BinaryTypeCode.True] = static (_, _) => true; + readers[BinaryTypeCode.False] = static (_, _) => false; + readers[BinaryTypeCode.Int8] = static (ctx, _) => (sbyte)ctx.ReadByte(); + readers[BinaryTypeCode.UInt8] = static (ctx, _) => ctx.ReadByte(); + readers[BinaryTypeCode.Int16] = static (ctx, _) => ctx.ReadInt16Unsafe(); + readers[BinaryTypeCode.UInt16] = static (ctx, _) => ctx.ReadUInt16Unsafe(); + readers[BinaryTypeCode.Int32] = static (ctx, type) => ReadInt32Value(ctx, type); + readers[BinaryTypeCode.UInt32] = static (ctx, _) => ctx.ReadVarUInt(); + readers[BinaryTypeCode.Int64] = static (ctx, _) => ctx.ReadVarLong(); + readers[BinaryTypeCode.UInt64] = static (ctx, _) => ctx.ReadVarULong(); + readers[BinaryTypeCode.Float32] = static (ctx, _) => ctx.ReadSingleUnsafe(); + readers[BinaryTypeCode.Float64] = static (ctx, _) => ctx.ReadDoubleUnsafe(); + readers[BinaryTypeCode.Decimal] = static (ctx, _) => ctx.ReadDecimalUnsafe(); + readers[BinaryTypeCode.Char] = static (ctx, _) => ctx.ReadCharUnsafe(); // H2Q6 non-ASCII tier readers (Compact mode): fixed-width header [charLen][utf8Len] + 1-pass decode. // FastWire mode dispatches the StringSmall (=91) marker through the same handler — see ReadStringSmall. - readers[BinaryTypeCode.StringSmall] = static (ctx, _, _) => ReadStringSmall(ctx); - readers[BinaryTypeCode.StringMedium] = static (ctx, _, _) => ReadStringMedium(ctx); - readers[BinaryTypeCode.StringBig] = static (ctx, _, _) => ReadStringBig(ctx); - readers[BinaryTypeCode.StringInterned] = static (ctx, _, _) => ctx.GetInternedString((int)ctx.ReadVarUInt()); - readers[BinaryTypeCode.StringEmpty] = static (_, _, _) => string.Empty; + readers[BinaryTypeCode.StringSmall] = static (ctx, _) => ReadStringSmall(ctx); + readers[BinaryTypeCode.StringMedium] = static (ctx, _) => ReadStringMedium(ctx); + readers[BinaryTypeCode.StringBig] = static (ctx, _) => ReadStringBig(ctx); + readers[BinaryTypeCode.StringInterned] = static (ctx, _) => ctx.GetInternedString((int)ctx.ReadVarUInt()); + readers[BinaryTypeCode.StringEmpty] = static (_, _) => string.Empty; // H2Q6 interning tier readers (Compact mode only — Big tier never engages on interning path) - readers[BinaryTypeCode.StringInternFirstSmall] = static (ctx, _, _) => ReadAndRegisterInternedStringSmall(ctx); - readers[BinaryTypeCode.StringInternFirstMedium] = static (ctx, _, _) => ReadAndRegisterInternedStringMedium(ctx); - readers[BinaryTypeCode.StringAscii] = static (ctx, _, _) => ReadPlainStringAscii(ctx); - readers[BinaryTypeCode.DateTime] = static (ctx, _, _) => ctx.ReadDateTimeUnsafe(); - readers[BinaryTypeCode.DateTimeOffset] = static (ctx, _, _) => ctx.ReadDateTimeOffsetUnsafe(); - readers[BinaryTypeCode.TimeSpan] = static (ctx, _, _) => ctx.ReadTimeSpanUnsafe(); - readers[BinaryTypeCode.Guid] = static (ctx, _, _) => ctx.ReadGuidUnsafe(); - readers[BinaryTypeCode.Enum] = static (ctx, type, _) => ReadEnumValue(ctx, type); + readers[BinaryTypeCode.StringInternFirstSmall] = static (ctx, _) => ReadAndRegisterInternedStringSmall(ctx); + readers[BinaryTypeCode.StringInternFirstMedium] = static (ctx, _) => ReadAndRegisterInternedStringMedium(ctx); + readers[BinaryTypeCode.StringAscii] = static (ctx, _) => ReadPlainStringAscii(ctx); + readers[BinaryTypeCode.DateTime] = static (ctx, _) => ctx.ReadDateTimeUnsafe(); + readers[BinaryTypeCode.DateTimeOffset] = static (ctx, _) => ctx.ReadDateTimeOffsetUnsafe(); + readers[BinaryTypeCode.TimeSpan] = static (ctx, _) => ctx.ReadTimeSpanUnsafe(); + readers[BinaryTypeCode.Guid] = static (ctx, _) => ctx.ReadGuidUnsafe(); + readers[BinaryTypeCode.Enum] = static (ctx, type) => ReadEnumValue(ctx, type); readers[BinaryTypeCode.Object] = ReadObject; readers[BinaryTypeCode.ObjectRefFirst] = ReadObjectRefFirst; readers[BinaryTypeCode.ObjectWithMetadata] = ReadObjectWithMetadata; @@ -124,7 +124,7 @@ public static partial class AcBinaryDeserializer readers[BinaryTypeCode.ObjectWithTypeIndexRefFirst] = ReadObjectWithTypeIndexRefFirst; readers[BinaryTypeCode.Array] = ReadArray; readers[BinaryTypeCode.Dictionary] = ReadDictionary; - readers[BinaryTypeCode.ByteArray] = static (ctx, _, _) => ReadByteArray(ctx); + readers[BinaryTypeCode.ByteArray] = static (ctx, _) => ReadByteArray(ctx); // V4N5 cleanup (2026-05-06): FixStr (UTF-8 short non-ASCII, 103..134) range REMOVED. // Non-ASCII short strings now use StringSmall tier marker (registered above). @@ -155,9 +155,9 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TypeReader CreateFixStrAsciiReader(int length) where TInput : struct, IBinaryInputBase { - if (length == 0) return static (_, _, _) => string.Empty; + if (length == 0) return static (_, _) => string.Empty; - return (ctx, _, _) => ctx.ReadAsciiBytesAsString(length); + return (ctx, _) => ctx.ReadAsciiBytesAsString(length); } /// @@ -166,7 +166,7 @@ public static partial class AcBinaryDeserializer [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TypeReader CreateFixObjReader(int slot) where TInput : struct, IBinaryInputBase { - return (ctx, targetType, depth) => ReadObjectFromSlot(ctx, slot, targetType, depth); + return (ctx, targetType) => ReadObjectFromSlot(ctx, slot, targetType); } //private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); @@ -433,7 +433,7 @@ public static partial class AcBinaryDeserializer try { context.ReadHeader(); - return ReadValue(context, targetType, 0); + return ReadValue(context, targetType); } catch (AcBinaryDeserializationException) { throw; } catch (Exception ex) @@ -1084,16 +1084,16 @@ public static partial class AcBinaryDeserializer /// Reads typeCode + dispatches via TypeReaderTable — same as runtime ReadValue. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static object? ReadValueGenerated(BinaryDeserializationContext context, Type targetType, int depth) + internal static object? ReadValueGenerated(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { - return ReadValue(context, targetType, depth); + return ReadValue(context, targetType); } /// /// Optimized value reader using FrozenDictionary dispatch table. /// - private static object? ReadValue(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadValue(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { if (context.IsAtEnd) return null; @@ -1131,7 +1131,7 @@ public static partial class AcBinaryDeserializer var reader = TypeReaderTable.Readers[typeCode]; if (reader != null) { - return reader(context, targetType, depth); + return reader(context, targetType); } throw new AcBinaryDeserializationException( @@ -1439,7 +1439,7 @@ public static partial class AcBinaryDeserializer /// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectRef(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectRef(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var cacheIndex = (int)context.ReadVarUInt(); @@ -1452,12 +1452,7 @@ public static partial class AcBinaryDeserializer /// Subsequent: direct array access (~1-2ns). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObjectFromSlot( - BinaryDeserializationContext context, - int slot, - Type targetType, - int depth) - where TInput : struct, IBinaryInputBase + private static object? ReadObjectFromSlot(BinaryDeserializationContext context, int slot, Type targetType) where TInput : struct, IBinaryInputBase { var wrapper = context.GetWrapper(targetType, slot); @@ -1470,20 +1465,20 @@ public static partial class AcBinaryDeserializer { var generatedReader = wrapper.GeneratedReader; if (generatedReader != null) - return generatedReader.ReadObject(context, depth, cacheIndex: -1); + return generatedReader.ReadObject(context, cacheIndex: -1); } - return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex: -1); + return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex: -1); } /// /// Object olvasása (nem tracked, vagy UseMetadata nélkül). /// Wire format: [Object][props...] /// - private static object? ReadObject(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObject(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { - return ReadObjectCore(context, targetType, depth, cacheIndex: -1); + return ReadObjectCore(context, targetType, cacheIndex: -1); } /// @@ -1491,11 +1486,11 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...] /// Az objektumot regisztráljuk a cache-be a megadott index-re. /// - private static object? ReadObjectRefFirst(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectRefFirst(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex); + return ReadObjectCore(context, targetType, cacheIndex: cacheIndex); } /// @@ -1504,7 +1499,7 @@ public static partial class AcBinaryDeserializer /// Reads the runtime type name, resolves it, registers wrapper in poly slot cache, /// then reads the inner marker via ReadValue. /// - private static object? ReadObjectWithTypeName(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithTypeName(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var typeName = ReadPlainString(context); @@ -1515,7 +1510,7 @@ public static partial class AcBinaryDeserializer var wrapper = context.GetWrapper(resolvedType); context.RegisterPolymorphicWrapper(wrapper); // Next byte is the actual inner marker (Object/Array/Dict/etc.) — read it via ReadValue - return ReadValue(context, resolvedType, depth); + return ReadValue(context, resolvedType); } /// @@ -1523,7 +1518,7 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectWithTypeNameRefFirst (69)] [TypeName string] [VarUInt refCacheIndex] [properties...] /// Object body follows directly — no inner Object/ObjectRefFirst marker. /// - private static object? ReadObjectWithTypeNameRefFirst(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithTypeNameRefFirst(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var typeName = ReadPlainString(context); @@ -1534,7 +1529,7 @@ public static partial class AcBinaryDeserializer var wrapper = context.GetWrapper(resolvedType); context.RegisterPolymorphicWrapper(wrapper); var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex); + return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex); } /// @@ -1543,12 +1538,12 @@ public static partial class AcBinaryDeserializer /// Looks up the previously registered wrapper by index (~1-2ns array access), /// then reads the inner marker via ReadValue. /// - private static object? ReadObjectWithTypeIndex(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithTypeIndex(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var typeIndex = (int)context.ReadVarUInt(); var wrapper = context.GetPolymorphicWrapper(typeIndex); - return ReadValue(context, wrapper.Metadata.SourceType, depth); + return ReadValue(context, wrapper.Metadata.SourceType); } /// @@ -1556,26 +1551,28 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectWithTypeIndexRefFirst (71)] [VarUInt typeIndex] [VarUInt refCacheIndex] [properties...] /// Object body follows directly — no inner Object/ObjectRefFirst marker. 0 dictionary lookup. /// - private static object? ReadObjectWithTypeIndexRefFirst(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithTypeIndexRefFirst(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var typeIndex = (int)context.ReadVarUInt(); var wrapper = context.GetPolymorphicWrapper(typeIndex); var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex); + return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex); } /// /// Object olvasás core implementáció. /// + /// + /// /// -1 = not cached, 0+ = register at this cache index - private static object? ReadObjectCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) + private static object? ReadObjectCore(BinaryDeserializationContext context, Type targetType, int cacheIndex) where TInput : struct, IBinaryInputBase { // Handle dictionary types if (IsDictionaryType(targetType, out var keyType, out var valueType)) { - return ReadDictionaryAsObject(context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!); } var wrapper = context.GetWrapper(targetType); @@ -1588,16 +1585,16 @@ public static partial class AcBinaryDeserializer { var generatedReader = wrapper.GeneratedReader; if (generatedReader != null) - return generatedReader.ReadObject(context, depth, cacheIndex); + return generatedReader.ReadObject(context, cacheIndex); } - return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex); + return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex); } /// /// Object olvasás with pre-resolved wrapper (eliminates GetWrapper dictionary lookup). /// - private static object? ReadObjectCoreWithWrapper(BinaryDeserializationContext context, TypeMetadataWrapper wrapper, int depth, int cacheIndex) + private static object? ReadObjectCoreWithWrapper(BinaryDeserializationContext context, TypeMetadataWrapper wrapper, int cacheIndex) where TInput : struct, IBinaryInputBase { var metadata = wrapper.Metadata; @@ -1611,7 +1608,7 @@ public static partial class AcBinaryDeserializer context.RegisterInternedValueAt(cacheIndex, instance); } - PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true); + PopulateObject(context, instance, wrapper, skipDefaultWrite: true); // ChainMode kezelés if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) @@ -1637,10 +1634,10 @@ public static partial class AcBinaryDeserializer /// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...] /// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...] /// - private static object? ReadObjectWithMetadata(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithMetadata(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { - return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1); + return ReadObjectWithMetadataCore(context, targetType, cacheIndex: -1); } /// @@ -1648,18 +1645,20 @@ public static partial class AcBinaryDeserializer /// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...] /// Az objektumot regisztráljuk a cache-be a megadott index-re. /// - private static object? ReadObjectWithMetadataRefFirst(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadObjectWithMetadataRefFirst(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var cacheIndex = (int)context.ReadVarUInt(); - return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex); + return ReadObjectWithMetadataCore(context, targetType, cacheIndex: cacheIndex); } /// /// ObjectWithMetadata olvasás core implementáció. /// + /// + /// /// -1 = not cached, 0+ = register at this cache index - private static object? ReadObjectWithMetadataCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex) + private static object? ReadObjectWithMetadataCore(BinaryDeserializationContext context, Type targetType, int cacheIndex) where TInput : struct, IBinaryInputBase { // Inline metadata: propNameHash mindig jön @@ -1681,7 +1680,7 @@ public static partial class AcBinaryDeserializer // Handle dictionary types if (IsDictionaryType(targetType, out var keyType, out var valueType)) { - return ReadDictionaryAsObject(context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!); } var wrapper = context.GetWrapper(targetType); @@ -1699,7 +1698,7 @@ public static partial class AcBinaryDeserializer if (wrapper.CacheMap == null) BuildCacheMap(wrapper, sourceHashes); - PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true); + PopulateObject(context, instance, wrapper, skipDefaultWrite: true); // ChainMode kezelés if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) @@ -1784,15 +1783,14 @@ public static partial class AcBinaryDeserializer #region Array Reading - private static object? ReadArray(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadArray(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { var elementType = GetCollectionElementType(targetType); if (elementType == null) elementType = typeof(object); var count = (int)context.ReadVarUInt(); - var nextDepth = depth + 1; - + // Optimized path for primitive arrays if (targetType.IsArray && count > 0) { @@ -1805,7 +1803,7 @@ public static partial class AcBinaryDeserializer var array = Array.CreateInstance(elementType, count); for (var i = 0; i < count; i++) { - var value = ReadValue(context, elementType, nextDepth); + var value = ReadValue(context, elementType); array.SetValue(value, i); } @@ -1832,7 +1830,7 @@ public static partial class AcBinaryDeserializer { for (var i = 0; i < count; i++) { - var value = ReadValue(context, elementType, nextDepth); + var value = ReadValue(context, elementType); list.Add(value); } } @@ -2093,7 +2091,7 @@ public static partial class AcBinaryDeserializer #region Dictionary Reading - private static object? ReadDictionary(BinaryDeserializationContext context, Type targetType, int depth) + private static object? ReadDictionary(BinaryDeserializationContext context, Type targetType) where TInput : struct, IBinaryInputBase { if (!IsDictionaryType(targetType, out var keyType, out var valueType)) @@ -2102,21 +2100,20 @@ public static partial class AcBinaryDeserializer valueType = typeof(object); } - return ReadDictionaryAsObject(context, keyType!, valueType!, depth); + return ReadDictionaryAsObject(context, keyType!, valueType!); } - private static object ReadDictionaryAsObject(BinaryDeserializationContext context, Type keyType, Type valueType, int depth) + private static object ReadDictionaryAsObject(BinaryDeserializationContext context, Type keyType, Type valueType) where TInput : struct, IBinaryInputBase { var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType); var count = (int)context.ReadVarUInt(); var dict = (IDictionary)Activator.CreateInstance(dictType, count)!; - var nextDepth = depth + 1; for (var i = 0; i < count; i++) { - var key = ReadValue(context, keyType, nextDepth); - var value = ReadValue(context, valueType, nextDepth); + var key = ReadValue(context, keyType); + var value = ReadValue(context, valueType); if (key != null) dict.Add(key, value); } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 5b7e5c5..4aef045 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -86,6 +86,14 @@ public static partial class AcBinarySerializer private int _nextCacheIndex; public int NextFirstIndex; // Next first occurrence index for scan pass. Direct access for performance. + /// + /// Global recursion depth counter — final safety net against pathological graphs and non-IId cycles. + /// Incremented/decremented at object-recursion entry/exit points (WriteObject runtime + generated WriteProperties/ScanObject). + /// Checked against (byte, default 255). + /// Replaces the per-call int depth parameter passing — one byte field on context, ~5-7 cycles per object instead of per call. + /// + internal byte RecursionDepth; + #region WriteDuplicateEntry — scan pass output for write pass cursor private WriteDuplicateEntry[]? _writePlan; @@ -306,6 +314,7 @@ public static partial class AcBinarySerializer _nextCacheIndex = 0; NextFirstIndex = 0; ScanVisitIndex = 0; + RecursionDepth = 0; WritePlanCursor = 0; WriteVisitIndex = 0; _nextWritePlanVisitIndex = int.MaxValue; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs index 355042c..164c4c2 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs @@ -31,19 +31,19 @@ public static partial class AcBinarySerializer var genWriter = wrapper.GeneratedWriter; if (genWriter != null && context.Options.UseGeneratedCode) { - genWriter.ScanObject(value, context, 0); + genWriter.ScanObject(value, context); context.SortWritePlan(); return; } - ScanValue(value, wrapper, context, 0); + ScanValue(value, wrapper, context); context.SortWritePlan(); } - private static void ScanValue(object? value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void ScanValue(object? value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { - if (value == null || depth > context.MaxDepth) + if (value == null) return; var metadata = wrapper.Metadata; @@ -64,8 +64,6 @@ public static partial class AcBinarySerializer if (metadata.ElementNeedsScan && !((isStringCollectionElementType = ReferenceEquals(metadata.CollectionElementType, StringType)) && !context.UseStringInterning)) { - var nextDepth = depth + 1; - if (isStringCollectionElementType) { ScanStringCollection(value, context); @@ -81,7 +79,7 @@ public static partial class AcBinarySerializer { var item = list[i]; if (item != null) - ScanItem(item, elementWrapper, context, nextDepth); + ScanItem(item, elementWrapper, context); } } else if (value is IEnumerable enumerable) @@ -89,7 +87,7 @@ public static partial class AcBinarySerializer foreach (var item in enumerable) { if (item != null) - ScanItem(item, elementWrapper, context, nextDepth); + ScanItem(item, elementWrapper, context); } } } @@ -104,7 +102,7 @@ public static partial class AcBinarySerializer var genWriter = wrapper.GeneratedWriter; if (genWriter != null && context.Options.UseGeneratedCode) { - genWriter.ScanObject(value, context, depth); + genWriter.ScanObject(value, context); return; } @@ -158,9 +156,17 @@ public static partial class AcBinarySerializer } // Fallback: runtime property loop (no SGen writer for this type) + // Global recursion depth safety net — only when ReferenceHandling != All + // (HasAllRefHandling=true tracks every type → 2nd-occurrence early-return above already prevents cycles). + var needsDepthCheck = !context.HasAllRefHandling; + if (needsDepthCheck) + { + if (context.RecursionDepth >= context.MaxDepth) throw new InvalidOperationException($"AcBinary scan: recursion depth exceeded MaxDepth={context.MaxDepth}"); + context.RecursionDepth++; + } + var refProperties = metadata.ReferenceProperties; var hasPropertyFilter = context.HasPropertyFilter; - var nextDepth2 = depth + 1; for (var i = 0; i < refProperties.Length; i++) { @@ -199,10 +205,12 @@ public static partial class AcBinarySerializer propWrapper = context.GetWrapper(runtimeType); wrapper.SetPropertyTypeWrapper(prop.ComplexPropertyIndex, propWrapper); } - ScanValue(propValue, propWrapper, context, nextDepth2); + ScanValue(propValue, propWrapper, context); } } } + + if (needsDepthCheck) context.RecursionDepth--; } /// @@ -218,12 +226,13 @@ public static partial class AcBinarySerializer internal static void ScanValueGenerated( object value, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type, - BinarySerializationContext context, - int depth) + BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { + // Bridge: ScanValue still has int depth in its signature (other internal callers use it). + // Pass 0 placeholder; full depth-cleanup of ScanValue chain is pending. var wrapper = context.GetWrapper(type); - ScanValue(value, wrapper, context, depth); + ScanValue(value, wrapper, context); } /// @@ -255,12 +264,12 @@ public static partial class AcBinarySerializer /// falls back to GetWrapper for polymorphic items. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ScanItem(object item, TypeMetadataWrapper? elementWrapper, BinarySerializationContext context, int depth) + private static void ScanItem(object item, TypeMetadataWrapper? elementWrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var itemType = item.GetType(); var itemWrapper = itemType == elementWrapper!.Metadata.SourceType ? elementWrapper : context.GetWrapper(itemType); - ScanValue(item, itemWrapper, context, depth); + ScanValue(item, itemWrapper, context); } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 534b0ab..206c5c0 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -101,7 +101,7 @@ public static partial class AcBinarySerializer // Run serialization to trigger callbacks context.WriteHeader(); - WriteValue(value, runtimeType, context, 0); + WriteValue(value, runtimeType, context); return result; } @@ -328,7 +328,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteObject(value, wrapper, context, 0); + WriteObject(value, wrapper, context); if (options.UseCompression != Lz4CompressionMode.None) return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression); @@ -340,7 +340,7 @@ public static partial class AcBinarySerializer var actualValue = ConvertExpressionValue(value, ref runtimeType); ScanForDuplicates(actualValue, runtimeType, context); context.WriteHeader(); - WriteValue(actualValue, runtimeType, context, 0); + WriteValue(actualValue, runtimeType, context); if (options.UseCompression != Lz4CompressionMode.None) return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression); @@ -374,7 +374,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteObject(value, wrapper, context, 0); + WriteObject(value, wrapper, context); if (options.UseCompression != Lz4CompressionMode.None) return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression); @@ -385,7 +385,7 @@ public static partial class AcBinarySerializer var actualValue = ConvertExpressionValue(value, ref runtimeType); ScanForDuplicates(actualValue, runtimeType, context); context.WriteHeader(); - WriteValue(actualValue, runtimeType, context, 0); + WriteValue(actualValue, runtimeType, context); if (options.UseCompression != Lz4CompressionMode.None) return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression); @@ -447,7 +447,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteObject(value, wrapper, context, 0); + WriteObject(value, wrapper, context); if (options.UseCompression != Lz4CompressionMode.None) ThrowCompressionNotSupportedWithBufferWriter(context); @@ -462,7 +462,7 @@ public static partial class AcBinarySerializer var actualValue = ConvertExpressionValue(value, ref runtimeType); ScanForDuplicates(actualValue, runtimeType, context); context.WriteHeader(); - WriteValue(actualValue, runtimeType, context, 0); + WriteValue(actualValue, runtimeType, context); if (options.UseCompression != Lz4CompressionMode.None) ThrowCompressionNotSupportedWithBufferWriter(context); @@ -507,7 +507,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteObject(value, wrapper, context, 0); + WriteObject(value, wrapper, context); if (options.UseCompression != Lz4CompressionMode.None) ThrowCompressionNotSupportedWithBufferWriter(context); @@ -521,7 +521,7 @@ public static partial class AcBinarySerializer var actualValue = ConvertExpressionValue(value, ref runtimeType); ScanForDuplicates(actualValue, runtimeType, context); context.WriteHeader(); - WriteValue(actualValue, runtimeType, context, 0); + WriteValue(actualValue, runtimeType, context); if (options.UseCompression != Lz4CompressionMode.None) ThrowCompressionNotSupportedWithBufferWriter(context); @@ -721,7 +721,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteObject(value, wrapper, context, 0); + WriteObject(value, wrapper, context); if (options.UseCompression != Compression.Lz4CompressionMode.None) ThrowCompressionNotSupportedWithPipeWriter(context); @@ -735,7 +735,7 @@ public static partial class AcBinarySerializer var actualValue = ConvertExpressionValue(value, ref runtimeType); ScanForDuplicates(actualValue, runtimeType, context); context.WriteHeader(); - WriteValue(actualValue, runtimeType, context, 0); + WriteValue(actualValue, runtimeType, context); if (options.UseCompression != Compression.Lz4CompressionMode.None) ThrowCompressionNotSupportedWithPipeWriter(context); @@ -766,7 +766,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteValue(value, runtimeType, context, 0); + WriteValue(value, runtimeType, context); return context.Position; } finally @@ -791,7 +791,7 @@ public static partial class AcBinarySerializer { ScanForDuplicates(value, runtimeType, context); context.WriteHeader(); - WriteValue(value, runtimeType, context, 0); + WriteValue(value, runtimeType, context); // If compression enabled, compress directly from buffer span (1 allocation) if (options.UseCompression != Lz4CompressionMode.None) @@ -891,10 +891,10 @@ public static partial class AcBinarySerializer /// Equivalent to the runtime's WriteValueNonPrimitive path. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void WriteValueGenerated(object value, Type type, BinarySerializationContext context, int depth) + internal static void WriteValueGenerated(object value, Type type, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { - WriteValueNonPrimitive(value, type, context, depth); + WriteValueNonPrimitive(value, type, context); } /// @@ -930,17 +930,11 @@ public static partial class AcBinarySerializer /// Uses pre-resolved wrapper type to avoid GetWrapper dictionary lookup. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void WriteObjectGenerated(object value, Type type, BinarySerializationContext context, int depth) + internal static void WriteObjectGenerated(object value, Type type, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { - if (depth > context.MaxDepth) - { - context.WriteByte(BinaryTypeCode.Null); - return; - } - var wrapper = context.GetWrapper(type); - WriteObject(value, wrapper, context, depth); + WriteObject(value, wrapper, context); } /// @@ -949,24 +943,18 @@ public static partial class AcBinarySerializer /// First call per slot per context: populates slot from GetWrapper. Subsequent calls: direct array index. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void WriteObjectGenerated(object value, Type type, int wrapperSlot, BinarySerializationContext context, int depth) + internal static void WriteObjectGenerated(object value, Type type, int wrapperSlot, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { - if (depth > context.MaxDepth) - { - context.WriteByte(BinaryTypeCode.Null); - return; - } - var wrapper = context.GetWrapper(type, wrapperSlot); - WriteObject(value, wrapper, context, depth); + WriteObject(value, wrapper, context); } #endregion #region Value Writing [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteValue(object? value, Type type, BinarySerializationContext context, int depth) + private static void WriteValue(object? value, Type type, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { if (value == null) @@ -979,7 +967,7 @@ public static partial class AcBinarySerializer if (TryWritePrimitive(value, type, context)) return; - WriteValueNonPrimitive(value, type, context, depth); + WriteValueNonPrimitive(value, type, context); } /// @@ -987,7 +975,7 @@ public static partial class AcBinarySerializer /// Skips null check and TryWritePrimitive — caller guarantees value is non-null and not a primitive type. /// Called from WritePropertyOrSkip default case (PropertyAccessorType.Object) and WriteValue fallback. /// - private static void WriteValueNonPrimitive(object value, Type type, BinarySerializationContext context, int depth) + private static void WriteValueNonPrimitive(object value, Type type, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { // Nullable where T is a value type: boxed value may be a primitive. @@ -998,12 +986,6 @@ public static partial class AcBinarySerializer return; } - if (depth > context.MaxDepth) - { - context.WriteByte(BinaryTypeCode.Null); - return; - } - // Handle byte arrays specially (value-like, no reference tracking) if (value is byte[] byteArray) { @@ -1014,7 +996,7 @@ public static partial class AcBinarySerializer // Handle dictionaries if (value is IDictionary dictionary) { - WriteDictionary(dictionary, context, depth); + WriteDictionary(dictionary, context); return; } @@ -1024,19 +1006,19 @@ public static partial class AcBinarySerializer // Handle collections/arrays if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { - WriteArray(enumerable, wrapper, context, depth); + WriteArray(enumerable, wrapper, context); return; } // Handle complex objects with single-pass reference tracking - WriteObject(value, wrapper, context, depth); + WriteObject(value, wrapper, context); } /// /// Writes a non-primitive value with a pre-resolved wrapper (from PropertyTypeWrappers cache). /// Avoids GetWrapper dictionary lookup. Handles byte[], dictionary, collection, and complex objects. /// - private static void WriteValueNonPrimitiveWithWrapper(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteValueNonPrimitiveWithWrapper(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var type = wrapper.Metadata.SourceType; @@ -1048,12 +1030,6 @@ public static partial class AcBinarySerializer return; } - if (depth > context.MaxDepth) - { - context.WriteByte(BinaryTypeCode.Null); - return; - } - // Handle byte arrays specially (value-like, no reference tracking) if (value is byte[] byteArray) { @@ -1064,19 +1040,19 @@ public static partial class AcBinarySerializer // Handle dictionaries if (value is IDictionary dictionary) { - WriteDictionary(dictionary, context, depth); + WriteDictionary(dictionary, context); return; } // Handle collections/arrays if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { - WriteArray(enumerable, wrapper, context, depth); + WriteArray(enumerable, wrapper, context); return; } // Handle complex objects - WriteObject(value, wrapper, context, depth); + WriteObject(value, wrapper, context); } /// @@ -1085,7 +1061,7 @@ public static partial class AcBinarySerializer /// delegates to WriteObjectPolymorphic for combined poly+ref marker handling. /// [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteValueNonPrimitiveWithWrapperPoly(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth, Type polyRuntimeType) + private static void WriteValueNonPrimitiveWithWrapperPoly(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, Type polyRuntimeType) where TOutput : struct, IBinaryOutputBase { var type = wrapper.Metadata.SourceType; @@ -1096,13 +1072,6 @@ public static partial class AcBinarySerializer return; } - if (depth > context.MaxDepth) - { - context.WritePolymorphicPrefix(polyRuntimeType); - context.WriteByte(BinaryTypeCode.Null); - return; - } - if (value is byte[] byteArray) { context.WritePolymorphicPrefix(polyRuntimeType); @@ -1113,19 +1082,19 @@ public static partial class AcBinarySerializer if (value is IDictionary dictionary) { context.WritePolymorphicPrefix(polyRuntimeType); - WriteDictionary(dictionary, context, depth); + WriteDictionary(dictionary, context); return; } if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { context.WritePolymorphicPrefix(polyRuntimeType); - WriteArray(enumerable, wrapper, context, depth); + WriteArray(enumerable, wrapper, context); return; } // Complex object — handles combined poly+ref markers - WriteObjectPolymorphic(value, wrapper, context, depth, polyRuntimeType); + WriteObjectPolymorphic(value, wrapper, context, polyRuntimeType); } /// @@ -1501,7 +1470,7 @@ public static partial class AcBinarySerializer #region Complex Type Writers [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteObject(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteObject(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var metadata = wrapper.Metadata; @@ -1511,9 +1480,9 @@ public static partial class AcBinarySerializer if (context.UseTypeReferenceHandling(metadata)) { if (useMetaForType) - WriteObjectWithRefHandlingMeta(value, wrapper, context, depth); + WriteObjectWithRefHandlingMeta(value, wrapper, context); else - WriteObjectWithRefHandling(value, wrapper, context, depth); + WriteObjectWithRefHandling(value, wrapper, context); return; } @@ -1540,7 +1509,7 @@ public static partial class AcBinarySerializer context.WriteByte(BinaryTypeCode.Object); } - WriteObjectProperties(value, wrapper, context, depth, useMetaForType); + WriteObjectProperties(value, wrapper, context, useMetaForType); } /// @@ -1548,7 +1517,7 @@ public static partial class AcBinarySerializer /// Cold path: only IId types with ref tracking enabled. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteObjectWithRefHandling(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteObjectWithRefHandling(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { // Reference handling: consume pre-computed write plan entry from scan pass cursor @@ -1586,7 +1555,7 @@ public static partial class AcBinarySerializer context.WriteByte(BinaryTypeCode.Object); } - WriteObjectProperties(value, wrapper, context, depth, useMetaForType: false); + WriteObjectProperties(value, wrapper, context, useMetaForType: false); } /// @@ -1594,7 +1563,7 @@ public static partial class AcBinarySerializer /// Cold path: IId types with ref tracking + UseMetadata enabled. /// //[MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteObjectWithRefHandlingMeta(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteObjectWithRefHandlingMeta(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var isFirstMetadataOccurrence = BinarySerializationContext.RegisterMetadataType(wrapper); @@ -1627,39 +1596,49 @@ public static partial class AcBinarySerializer } context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence); - WriteObjectProperties(value, wrapper, context, depth, useMetaForType: true); + WriteObjectProperties(value, wrapper, context, useMetaForType: true); } /// /// Shared property writing loop — used by WriteObject, WriteObjectWithRefHandling, WriteObjectPolymorphic. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteObjectProperties(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth, bool useMetaForType) + private static void WriteObjectProperties(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, bool useMetaForType) where TOutput : struct, IBinaryOutputBase { - var nextDepth = depth + 1; - if (context.UseGeneratedCode) { var generatedWriter = wrapper.GeneratedWriter; if (generatedWriter != null) { - generatedWriter.WriteProperties(value, context, nextDepth); + // SGen path handles its own RecursionDepth inc/dec via generated emit (gated on !HasAllRefHandling) + generatedWriter.WriteProperties(value, context); return; } } + // Runtime path: global recursion depth safety net — only when ReferenceHandling != All + // (HasAllRefHandling=true tracks every type → write plan already prevents cycles via ObjectRef). + var needsDepthCheck = !context.HasAllRefHandling; + if (needsDepthCheck) + { + if (context.RecursionDepth >= context.MaxDepth) throw new InvalidOperationException($"AcBinary serialize: recursion depth exceeded MaxDepth={context.MaxDepth}"); + context.RecursionDepth++; + } + if (!useMetaForType) { - WritePropertiesMarkerless(value, wrapper, context, nextDepth); + WritePropertiesMarkerless(value, wrapper, context); } else { - WritePropertiesWithMeta(value, wrapper, context, nextDepth); + WritePropertiesWithMeta(value, wrapper, context); } + + if (needsDepthCheck) context.RecursionDepth--; } - private static void WritePropertiesWithMeta(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int nextDepth) where TOutput : struct, IBinaryOutputBase + private static void WritePropertiesWithMeta(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var properties = wrapper.Metadata.Properties; var propCount = properties.Length; @@ -1679,12 +1658,12 @@ public static partial class AcBinarySerializer continue; } - WritePropertyOrSkip(value, prop, wrapper, context, nextDepth); + WritePropertyOrSkip(value, prop, wrapper, context); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WritePropertiesMarkerless(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int nextDepth) where TOutput : struct, IBinaryOutputBase + private static void WritePropertiesMarkerless(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { var properties = wrapper.Metadata.Properties; var propCount = properties.Length; @@ -1701,7 +1680,7 @@ public static partial class AcBinarySerializer } else { - WritePropertyOrSkip(value, prop, wrapper, context, nextDepth); + WritePropertyOrSkip(value, prop, wrapper, context); } } } @@ -1742,7 +1721,7 @@ public static partial class AcBinarySerializer /// Poly always implies UseMetadata=false (checked in WritePropertyOrSkip). /// [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteObjectPolymorphic(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth, Type polyRuntimeType) + private static void WriteObjectPolymorphic(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, Type polyRuntimeType) where TOutput : struct, IBinaryOutputBase { var metadata = wrapper.Metadata; @@ -1769,7 +1748,7 @@ public static partial class AcBinarySerializer // Poly marker (handles combined poly+ref) WritePolymorphicMarker(context, polyRuntimeType, cachedObjectCacheIndex); - WriteObjectProperties(value, wrapper, context, depth, false); + WriteObjectProperties(value, wrapper, context, false); } /// @@ -1916,7 +1895,7 @@ public static partial class AcBinarySerializer [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Polymorphism via obj.GetType() is a documented trimmer blind spot. Consumers must root " + "polymorphic concrete types via [AcBinarySerializable] (SGen) or TrimmerRootAssembly.")] - private static void WritePropertyOrSkip(object obj, BinaryPropertyAccessor prop, TypeMetadataWrapper parentWrapper, BinarySerializationContext context, int depth) + private static void WritePropertyOrSkip(object obj, BinaryPropertyAccessor prop, TypeMetadataWrapper parentWrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { switch (prop.AccessorType) @@ -2013,16 +1992,16 @@ public static partial class AcBinarySerializer parentWrapper.SetPropertyTypeWrapper(complexIdx, propWrapper); } if (!context.UseMetadata && prop.IsPolymorphicCandidate && runtimeType != prop.PropertyType) - WriteValueNonPrimitiveWithWrapperPoly(value, propWrapper, context, depth, runtimeType); + WriteValueNonPrimitiveWithWrapperPoly(value, propWrapper, context, runtimeType); else - WriteValueNonPrimitiveWithWrapper(value, propWrapper, context, depth); + WriteValueNonPrimitiveWithWrapper(value, propWrapper, context); } else { // Non-complex in default case (nullable value type, etc.) if (!context.UseMetadata && prop.IsPolymorphicCandidate && runtimeType != prop.PropertyType) context.WritePolymorphicPrefix(runtimeType); - WriteValueNonPrimitive(value, runtimeType, context, depth); + WriteValueNonPrimitive(value, runtimeType, context); } } return; @@ -2037,11 +2016,10 @@ public static partial class AcBinarySerializer /// /// Optimized array writer with specialized paths for primitive collections. /// - private static void WriteArray(IEnumerable enumerable, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteArray(IEnumerable enumerable, TypeMetadataWrapper wrapper, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { context.WriteByte(BinaryTypeCode.Array); - var nextDepth = depth + 1; // Use pre-computed metadata — no GetWrapper or GetCollectionElementType needed var metadata = wrapper.Metadata; @@ -2063,7 +2041,7 @@ public static partial class AcBinarySerializer { var item = list[i]; var itemType = item?.GetType() ?? typeof(object); - WriteValue(item, itemType, context, nextDepth); + WriteValue(item, itemType, context); } return; } @@ -2075,7 +2053,7 @@ public static partial class AcBinarySerializer foreach (var item in enumerable) { var itemType = item?.GetType() ?? typeof(object); - WriteValue(item, itemType, context, nextDepth); + WriteValue(item, itemType, context); } return; } @@ -2091,7 +2069,7 @@ public static partial class AcBinarySerializer foreach (var item in items) { var itemType = item?.GetType() ?? typeof(object); - WriteValue(item, itemType, context, nextDepth); + WriteValue(item, itemType, context); } } @@ -2376,22 +2354,21 @@ public static partial class AcBinarySerializer return false; } - private static void WriteDictionary(IDictionary dictionary, BinarySerializationContext context, int depth) + private static void WriteDictionary(IDictionary dictionary, BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase { context.WriteByte(BinaryTypeCode.Dictionary); context.WriteVarUInt((uint)dictionary.Count); - var nextDepth = depth + 1; foreach (DictionaryEntry entry in dictionary) { // Write key var keyType = entry.Key?.GetType() ?? typeof(object); - WriteValue(entry.Key, keyType, context, nextDepth); + WriteValue(entry.Key, keyType, context); // Write value var valueType = entry.Value?.GetType() ?? typeof(object); - WriteValue(entry.Value, valueType, context, nextDepth); + WriteValue(entry.Value, valueType, context); } } diff --git a/AyCode.Core/Serializers/Binaries/IGeneratedBinaryReader.cs b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryReader.cs index 675e1a9..a0e11ef 100644 --- a/AyCode.Core/Serializers/Binaries/IGeneratedBinaryReader.cs +++ b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryReader.cs @@ -20,11 +20,10 @@ internal interface IGeneratedBinaryReader /// UseMetadata=true falls back to runtime path (cross-type CacheMap not known at compile time). /// /// The deserialization context (owns buffer, position, options). - /// Current depth in the object graph. /// -1 = not cached, 0+ = register at this cache index for ref tracking. /// Input strategy (ArrayBinaryInput or SequenceBinaryInput). /// The deserialized object, or null if creation failed. - object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int depth, int cacheIndex) + object? ReadObject(AcBinaryDeserializer.BinaryDeserializationContext context, int cacheIndex) where TInput : struct, IBinaryInputBase; /// @@ -34,8 +33,7 @@ internal interface IGeneratedBinaryReader /// /// The pre-created instance to populate. Implementation casts to the concrete type. /// The deserialization context (owns buffer, position, options). - /// Current depth in the object graph. /// Input strategy (ArrayBinaryInput or SequenceBinaryInput). - void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context, int depth) + void ReadProperties(object value, AcBinaryDeserializer.BinaryDeserializationContext context) where TInput : struct, IBinaryInputBase; } diff --git a/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs index fc39b0d..32fb06c 100644 --- a/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs +++ b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs @@ -22,9 +22,8 @@ internal interface IGeneratedBinaryWriter /// /// The object whose properties to write. Implementation casts to the concrete type. /// The serialization context (owns buffer, position, options). - /// Current depth in the object graph (for nested object serialization). /// Output strategy (ArrayBinaryOutput or BufferWriterBinaryOutput). - void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context, int depth) + void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase; /// @@ -33,7 +32,7 @@ internal interface IGeneratedBinaryWriter /// Replaces the entire runtime ScanValue for SGen types — no GetWrapper, no delegate invoke. /// Called from ScanForDuplicates or from parent SGen ScanObject (child). /// - void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context, int depth) + void ScanObject(object value, AcBinarySerializer.BinarySerializationContext context) where TOutput : struct, IBinaryOutputBase; /// diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs index 64087b3..8164046 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs @@ -12,17 +12,17 @@ public static partial class AcJsonDeserializer #region With Reference Handling (JsonElement Path) [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadValue(in JsonElement element, in Type targetType, DeserializationContext context, int depth) + private static object? ReadValue(in JsonElement element, in Type targetType, DeserializationContext context) { var kind = element.ValueKind; - if (kind == JsonValueKind.Object) return ReadObject(element, targetType, context, depth); - if (kind == JsonValueKind.Array) return ReadArray(element, targetType, context, depth); + if (kind == JsonValueKind.Object) return ReadObject(element, targetType, context); + if (kind == JsonValueKind.Array) return ReadArray(element, targetType, context); if (kind == JsonValueKind.Null || kind == JsonValueKind.Undefined) return null; return ReadPrimitive(element, targetType, kind); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadObject(in JsonElement element, in Type targetType, DeserializationContext context, int depth) + private static object? ReadObject(in JsonElement element, in Type targetType, DeserializationContext context) { // Check for $ref first - support both string (Newtonsoft) and int formats if (element.TryGetProperty(RefPropertyUtf8, out var refElement)) @@ -31,10 +31,8 @@ public static partial class AcJsonDeserializer return context.TryGetReferencedObject(refId, out var refObj) ? refObj : new DeferredReference(refId, targetType); } - if (depth > context.MaxDepth) return null; - if (IsDictionaryType(targetType, out var keyType, out var valueType)) - return ReadDictionary(element, keyType!, valueType!, context, depth); + return ReadDictionary(element, keyType!, valueType!, context); var metadata = GetTypeMetadata(targetType); @@ -56,7 +54,7 @@ public static partial class AcJsonDeserializer if (element.TryGetProperty(IdPropertyUtf8, out var idElement)) context.RegisterObject(ParseRefId(idElement), instance); - PopulateObjectInternal(element, instance, metadata, context, depth); + PopulateObjectInternal(element, instance, metadata, context); // ChainMode: Use cached IId info from metadata (no runtime reflection!) if (context.IsChainMode && metadata.IsIId && metadata.IdGetter != null && metadata.IdType != null) @@ -122,10 +120,9 @@ public static partial class AcJsonDeserializer } } - private static void PopulateObjectInternal(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth) + private static void PopulateObjectInternal(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context) { var propsDict = metadata.PropertySettersFrozen; - var nextDepth = depth + 1; foreach (var jsonProp in element.EnumerateObject()) { @@ -136,7 +133,7 @@ public static partial class AcJsonDeserializer if (!propsDict.TryGetValue(propName, out var propInfo)) continue; - var value = ReadValue(jsonProp.Value, propInfo.PropertyType, context, nextDepth); + var value = ReadValue(jsonProp.Value, propInfo.PropertyType, context); if (value is DeferredReference deferred) context.AddPropertyToResolve(target, propInfo, deferred.RefId); @@ -145,11 +142,9 @@ public static partial class AcJsonDeserializer } } - private static void PopulateObjectInternalMerge(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth) + private static void PopulateObjectInternalMerge(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context) { var propsDict = metadata.PropertySettersFrozen; - var nextDepth = depth + 1; - var maxDepthReached = nextDepth > context.MaxDepth; foreach (var jsonProp in element.EnumerateObject()) { @@ -163,23 +158,13 @@ public static partial class AcJsonDeserializer var propValue = jsonProp.Value; var propValueKind = propValue.ValueKind; - if (maxDepthReached) - { - if (propValueKind != JsonValueKind.Object && propValueKind != JsonValueKind.Array) - { - var primitiveValue = ReadPrimitive(propValue, propInfo.PropertyType, propValueKind); - propInfo.SetValue(target, primitiveValue); - } - continue; - } - // Handle IId collection merge if (propInfo.IsIIdCollection && propValueKind == JsonValueKind.Array) { var existingCollection = propInfo.GetValue(target); if (existingCollection != null) { - MergeIIdCollection(propValue, existingCollection, propInfo, context, depth); + MergeIIdCollection(propValue, existingCollection, propInfo, context); continue; } } @@ -190,7 +175,7 @@ public static partial class AcJsonDeserializer // Check for $ref if (propValue.TryGetProperty(RefPropertyUtf8, out _)) { - var value = ReadValue(propValue, propInfo.PropertyType, context, nextDepth); + var value = ReadValue(propValue, propInfo.PropertyType, context); if (value is DeferredReference deferred) context.AddPropertyToResolve(target, propInfo, deferred.RefId); else @@ -205,13 +190,13 @@ public static partial class AcJsonDeserializer if (existingObj != null) { var nestedMetadata = GetTypeMetadata(propInfo.PropertyType); - PopulateObjectInternalMerge(propValue, existingObj, nestedMetadata, context, nextDepth); + PopulateObjectInternalMerge(propValue, existingObj, nestedMetadata, context); continue; } } } - var value2 = ReadValue(propValue, propInfo.PropertyType, context, nextDepth); + var value2 = ReadValue(propValue, propInfo.PropertyType, context); if (value2 is DeferredReference deferred2) context.AddPropertyToResolve(target, propInfo, deferred2.RefId); @@ -220,10 +205,8 @@ public static partial class AcJsonDeserializer } } - private static void PopulateList(in JsonElement arrayElement, IList targetList, in Type listType, DeserializationContext context, int depth) + private static void PopulateList(in JsonElement arrayElement, IList targetList, in Type listType, DeserializationContext context) { - if (depth > context.MaxDepth) return; - var elementType = GetCollectionElementType(listType); if (elementType == null) return; @@ -233,8 +216,7 @@ public static partial class AcJsonDeserializer try { targetList.Clear(); - var nextDepth = depth + 1; - + // ChainMode: Use cached IId info from element type metadata (no runtime reflection!) JsonDeserializeTypeMetadata? elementMetadata = null; if (context.IsChainMode && !IsPrimitiveOrStringFast(elementType)) @@ -244,7 +226,7 @@ public static partial class AcJsonDeserializer foreach (var item in arrayElement.EnumerateArray()) { - var value = ReadValue(item, elementType, context, nextDepth); + var value = ReadValue(item, elementType, context); // ChainMode: Check if we already have this IId object using cached metadata if (context.IsChainMode && value != null && elementMetadata != null && @@ -270,20 +252,16 @@ public static partial class AcJsonDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object? ReadArray(in JsonElement element, in Type targetType, DeserializationContext context, int depth) + private static object? ReadArray(in JsonElement element, in Type targetType, DeserializationContext context) { - if (depth > context.MaxDepth) return null; - var elementType = GetCollectionElementType(targetType); if (elementType == null) return null; - var nextDepth = depth + 1; - if (targetType.IsArray) { var list = GetOrCreateListFactory(elementType)(0); foreach (var item in element.EnumerateArray()) - list.Add(ReadValue(item, elementType, context, nextDepth)); + list.Add(ReadValue(item, elementType, context)); var array = Array.CreateInstance(elementType, list.Count); list.CopyTo(array, 0); @@ -306,14 +284,14 @@ public static partial class AcJsonDeserializer try { foreach (var item in element.EnumerateArray()) - targetList.Add(ReadValue(item, elementType, context, nextDepth)); + targetList.Add(ReadValue(item, elementType, context)); } finally { acObservable?.EndUpdate(); } return targetList; } - private static void MergeIIdCollection(in JsonElement arrayElement, object existingCollection, PropertySetterInfo propInfo, DeserializationContext context, int depth) + private static void MergeIIdCollection(in JsonElement arrayElement, object existingCollection, PropertySetterInfo propInfo, DeserializationContext context) { var elementType = propInfo.ElementType!; var idGetter = propInfo.ElementIdGetter!; @@ -343,7 +321,6 @@ public static partial class AcJsonDeserializer } } - var nextDepth = depth + 1; foreach (var jsonItem in arrayElement.EnumerateArray()) { if (jsonItem.ValueKind != JsonValueKind.Object) continue; @@ -357,24 +334,23 @@ public static partial class AcJsonDeserializer if (existingById.TryGetValue(itemId, out var existingItem)) { var itemMetadata = GetTypeMetadata(elementType); - PopulateObjectInternalMerge(jsonItem, existingItem, itemMetadata, context, nextDepth); + PopulateObjectInternalMerge(jsonItem, existingItem, itemMetadata, context); continue; } } - var newItem = ReadValue(jsonItem, elementType, context, nextDepth); + var newItem = ReadValue(jsonItem, elementType, context); if (newItem != null) existingList.Add(newItem); } } finally { acObservable?.EndUpdate(); } } - private static object ReadDictionary(in JsonElement element, in Type keyType, in Type valueType, DeserializationContext context, int depth) + private static object ReadDictionary(in JsonElement element, in Type keyType, in Type valueType, DeserializationContext context) { var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType); var dict = (IDictionary)Activator.CreateInstance(dictType)!; - var nextDepth = depth + 1; - + foreach (var prop in element.EnumerateObject()) { var name = prop.Name; @@ -388,7 +364,7 @@ public static partial class AcJsonDeserializer else if (keyType.IsEnum) key = Enum.Parse(keyType, name); else key = Convert.ChangeType(name, keyType, CultureInfo.InvariantCulture); - dict.Add(key, ReadValue(prop.Value, valueType, context, nextDepth)); + dict.Add(key, ReadValue(prop.Value, valueType, context)); } return dict; diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs index cdc16b8..e2f5f8d 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs @@ -71,7 +71,7 @@ public static partial class AcJsonDeserializer var context = JsonDeserializationContextPool.Get(options); try { - var result = ReadValue(doc.RootElement, targetType, context, 0); + var result = ReadValue(doc.RootElement, targetType, context); context.ResolveReferences(); return (T?)result; } @@ -130,7 +130,7 @@ public static partial class AcJsonDeserializer var context = JsonDeserializationContextPool.Get(options); try { - var result = ReadValue(doc.RootElement, targetType, context, 0); + var result = ReadValue(doc.RootElement, targetType, context); context.ResolveReferences(); return (T?)result; } @@ -197,7 +197,7 @@ public static partial class AcJsonDeserializer var context = JsonDeserializationContextPool.Get(options); try { - var result = ReadValue(doc.RootElement, targetType, context, 0); + var result = ReadValue(doc.RootElement, targetType, context); context.ResolveReferences(); return result; } @@ -282,7 +282,7 @@ public static partial class AcJsonDeserializer var context = JsonDeserializationContextPool.Get(options); try { - var result = ReadValue(doc.RootElement, targetType, context, 0); + var result = ReadValue(doc.RootElement, targetType, context); context.ResolveReferences(); return result; } @@ -398,7 +398,7 @@ public static partial class AcJsonDeserializer if (rootElement.ValueKind == JsonValueKind.Array) { if (target is IList targetList) - PopulateList(rootElement, targetList, targetType, context, 0); + PopulateList(rootElement, targetList, targetType, context); else throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", json, targetType); @@ -409,7 +409,7 @@ public static partial class AcJsonDeserializer if (rootElement.ValueKind == JsonValueKind.Object) { var metadata = GetTypeMetadata(targetType); - PopulateObjectInternalMerge(rootElement, target, metadata, context, 0); + PopulateObjectInternalMerge(rootElement, target, metadata, context); } else throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", json, targetType); @@ -502,7 +502,7 @@ public static partial class AcJsonDeserializer try { - var result = ReadValue(doc.RootElement, targetType, context, 0); + var result = ReadValue(doc.RootElement, targetType, context); context.ResolveReferences(); return new JsonDeserializeChain(doc, context, chainTracker, (T?)result); } @@ -523,14 +523,14 @@ public static partial class AcJsonDeserializer if (rootElement.ValueKind == JsonValueKind.Array) { if (target is IList targetList) - PopulateList(rootElement, targetList, targetType, context, 0); + PopulateList(rootElement, targetList, targetType, context); else throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", null, targetType); } else if (rootElement.ValueKind == JsonValueKind.Object) { var metadata = GetTypeMetadata(targetType); - PopulateObjectInternalMerge(rootElement, target, metadata, context, 0); + PopulateObjectInternalMerge(rootElement, target, metadata, context); } else throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", null, targetType); @@ -571,7 +571,7 @@ public static partial class AcJsonDeserializer try { - var result = ReadValue(_document.RootElement, targetType, _context, 0); + var result = ReadValue(_document.RootElement, targetType, _context); _context.ResolveReferences(); return (TResult?)result; } diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs index 346d002..466a11c 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs @@ -127,7 +127,7 @@ public static partial class AcJsonSerializer private static void ScanReferences(object? value, JsonSerializationContext context, int depth) { - if (value == null || depth > context.MaxDepth) return; + if (value == null) return; var type = value.GetType(); if (IsPrimitiveOrStringFast(type)) return; @@ -167,8 +167,6 @@ public static partial class AcJsonSerializer if (TryWritePrimitive(value, type, context.Writer)) return; - if (depth > context.MaxDepth) { context.Writer.WriteNullValue(); return; } - if (value is IDictionary dictionary) { WriteDictionary(dictionary, context, depth); return; } if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { WriteArray(enumerable, context, depth); return; } diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs index 8c61916..a4884bc 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs @@ -25,7 +25,7 @@ public static partial class AcToonSerializer context.WriteLine("@data {"); context.CurrentIndentLevel++; - WriteValue(value, type, context, 0); + WriteValue(value, type, context); context.WriteLine(); context.CurrentIndentLevel--; @@ -35,7 +35,7 @@ public static partial class AcToonSerializer /// /// Write a value (dispatcher for different types). /// - private static void WriteValue(object? value, Type type, ToonSerializationContext context, int depth) + private static void WriteValue(object? value, Type type, ToonSerializationContext context) { if (value == null) { @@ -47,12 +47,6 @@ public static partial class AcToonSerializer if (TryWritePrimitive(value, type, context)) return; - if (depth > context.MaxDepth) - { - context.Write("null"); - return; - } - // Check for reference if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryGetExistingRef(value, out var refId)) { @@ -63,19 +57,19 @@ public static partial class AcToonSerializer // Handle dictionaries if (value is IDictionary dictionary) { - WriteDictionary(dictionary, context, depth); + WriteDictionary(dictionary, context); return; } // Handle collections/arrays if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { - WriteArray(enumerable, type, context, depth); + WriteArray(enumerable, type, context); return; } // Handle complex objects - WriteObject(value, type, context, depth); + WriteObject(value, type, context); } /// @@ -223,7 +217,7 @@ public static partial class AcToonSerializer /// /// Write complex object. /// - private static void WriteObject(object value, Type type, ToonSerializationContext context, int depth) + private static void WriteObject(object value, Type type, ToonSerializationContext context) { var metadata = GetTypeMetadata(type); @@ -243,8 +237,6 @@ public static partial class AcToonSerializer context.WriteLine(" {"); context.CurrentIndentLevel++; - var nextDepth = depth + 1; - // Write properties foreach (var prop in metadata.Properties) { @@ -273,7 +265,7 @@ public static partial class AcToonSerializer } else { - WriteValue(propValue, prop.PropertyType, context, nextDepth); + WriteValue(propValue, prop.PropertyType, context); context.WriteLine(); } } @@ -286,7 +278,7 @@ public static partial class AcToonSerializer /// /// Write array/collection. /// - private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context, int depth) + private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context) { var elementType = GetCollectionElementType(type) ?? typeof(object); @@ -312,12 +304,10 @@ public static partial class AcToonSerializer context.WriteLine("["); context.CurrentIndentLevel++; - var nextDepth = depth + 1; - foreach (var item in enumerable) { context.WriteIndent(); - WriteValue(item, item?.GetType() ?? elementType, context, nextDepth); + WriteValue(item, item?.GetType() ?? elementType, context); context.WriteLine(); } @@ -377,7 +367,7 @@ public static partial class AcToonSerializer /// /// Write dictionary. /// - private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context, int depth) + private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context) { // Write dictionary header with count and type information if (context.Options.ShowCollectionCount) @@ -390,21 +380,19 @@ public static partial class AcToonSerializer context.WriteLine("{"); context.CurrentIndentLevel++; - var nextDepth = depth + 1; - foreach (DictionaryEntry entry in dictionary) { context.WriteIndent(); // Write key var keyType = entry.Key?.GetType() ?? typeof(object); - WriteValue(entry.Key, keyType, context, nextDepth); + WriteValue(entry.Key, keyType, context); context.Write(" => "); // Write value var valueType = entry.Value?.GetType() ?? typeof(object); - WriteValue(entry.Value, valueType, context, nextDepth); + WriteValue(entry.Value, valueType, context); context.WriteLine(); } diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs index 62ca22a..8b94bdd 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs @@ -285,7 +285,7 @@ public static partial class AcToonSerializer private static void ScanReferences(object? value, ToonSerializationContext context, int depth) { - if (value == null || depth > context.MaxDepth) return; + if (value == null) return; var type = value.GetType(); if (IsPrimitiveOrStringFast(type)) return;