From 79f852fb446bf52ba1ecd7d9c4d200e694a3f739 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 06:28:05 +0000 Subject: [PATCH] Reject Long defaults outside the 64-bit range [patch] Validation range-checked whole-number defaults for Int but not for Long, so a default such as 1e19, or long.MaxValue written exactly (which rounds to 2^63 as a double), passed validation. The C# generator then clamped it and the C++ generator emitted an undefined double-to-int64 conversion. Add FitsInt64 with an exclusive upper bound next to FitsInt32. Fixes ktsu-dev/Schema#219 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011PMSp3sjbza8Td6fxS2Kjr --- Schema.Test/MemberMetadataTests.cs | 30 ++++++++++++++++++++++++++++++ Schema/Models/Schema.Validation.cs | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/Schema.Test/MemberMetadataTests.cs b/Schema.Test/MemberMetadataTests.cs index b680862..cb21737 100644 --- a/Schema.Test/MemberMetadataTests.cs +++ b/Schema.Test/MemberMetadataTests.cs @@ -248,6 +248,36 @@ public void ADefaultOutsideInt32RangeOnAnIntMemberIsAnError() AssertMentions(Errors(schema), "outside the 32-bit range"); } + /// + /// 9223372036854775807 is long.MaxValue as written, but as a double it rounds to 2^63, which + /// no Long can hold. + /// + [TestMethod] + [DataRow(1e19)] + [DataRow(-1e19)] + [DataRow(9223372036854775807.0)] + public void ADefaultOutsideInt64RangeOnALongMemberIsAnError(double value) + { + SchemaMember member = MemberWith(new SchemaTypes.Long(), out Schema schema); + member.DefaultValue = new NumberDefault { Value = value }; + + AssertMentions(Errors(schema), "outside the 64-bit range"); + } + + /// + /// The largest double below 2^63, and the smallest Long, are both representable. + /// + [TestMethod] + [DataRow(9223372036854774784.0)] + [DataRow(-9223372036854775808.0)] + public void ADefaultInsideInt64RangeOnALongMemberIsAccepted(double value) + { + SchemaMember member = MemberWith(new SchemaTypes.Long(), out Schema schema); + member.DefaultValue = new NumberDefault { Value = value }; + + Assert.AreEqual(0, Errors(schema).Count); + } + [TestMethod] public void ADefaultOfTheWrongKindIsAnError() { diff --git a/Schema/Models/Schema.Validation.cs b/Schema/Models/Schema.Validation.cs index a304b29..c1d2357 100644 --- a/Schema/Models/Schema.Validation.cs +++ b/Schema/Models/Schema.Validation.cs @@ -369,6 +369,11 @@ private static void ValidateMemberDefault(Collection issu Report(issues, path, element, $"The default {Number(number.Value)} is outside the 32-bit range of Int ({int.MinValue}..{int.MaxValue})."); } + if (type is Long && isWholeNumber && !FitsInt64(number.Value)) + { + Report(issues, path, element, $"The default {Number(number.Value)} is outside the 64-bit range of Long ({long.MinValue}..{long.MaxValue})."); + } + // A wrapping range is a period rather than a bound, so a value outside it is // un-normalised rather than wrong -- the same reading a validator must take of // live data, applied here to the default. @@ -489,6 +494,12 @@ private static void ValidateMemberNetwork(Collection issu private static bool FitsInt32(double value) => value is >= int.MinValue and <= int.MaxValue; + /// + /// The upper bound is exclusive because (double)long.MaxValue rounds up to 2^63, which is + /// already one past the range: an inclusive check would pass the one value it most needs to stop. + /// + private static bool FitsInt64(double value) => value is >= -9223372036854775808.0 and < 9223372036854775808.0; + /// /// Formats a number for a message, culture-invariantly: a validation message that says /// "0,5" on one machine and "0.5" on another is a support problem.