Skip to content

Commit 60f4026

Browse files
paulirwinclaude
andcommitted
BytesRef: Show "Invalid UTF-8" label and string-before-bytes in DebuggerDisplay, #1024
Address NightOwl888's review on #1171. The prior DebuggerDisplay used Utf8ToStringWithFallback, which silently substitutes U+FFFD for malformed input — indistinguishable from a real U+FFFD in the data. Switch to a private DebuggerDisplay property that uses TryUtf8ToString, shows the literal "Invalid UTF-8" on decode failure, and puts the decoded string before the raw bytes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent af1e1f3 commit 60f4026

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

src/Lucene.Net.Tests/Util/TestBytesRef.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using NUnit.Framework;
44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Reflection;
68
using System.Runtime.CompilerServices;
79
using System.Runtime.InteropServices;
810
using Assert = Lucene.Net.TestFramework.Assert;
@@ -612,5 +614,64 @@ public static void Test_CopyChars_UnpairedSurrogates()
612614
}
613615

614616
#endregion
617+
618+
#region DebuggerDisplay
619+
620+
private static string GetDebuggerDisplay(BytesRef br)
621+
{
622+
var prop = typeof(BytesRef).GetProperty("DebuggerDisplay", BindingFlags.Instance | BindingFlags.NonPublic);
623+
Assert.IsNotNull(prop, "BytesRef.DebuggerDisplay private property not found");
624+
return (string)prop.GetValue(br);
625+
}
626+
627+
[Test]
628+
[LuceneNetSpecific]
629+
public static void Test_DebuggerDisplayAttribute_ReferencesExistingMember()
630+
{
631+
// Guards against typos/renames in the [DebuggerDisplay] expression.
632+
var attr = typeof(BytesRef).GetCustomAttribute<DebuggerDisplayAttribute>();
633+
Assert.IsNotNull(attr);
634+
Assert.AreEqual("{DebuggerDisplay,nq}", attr.Value);
635+
636+
var prop = typeof(BytesRef).GetProperty("DebuggerDisplay", BindingFlags.Instance | BindingFlags.NonPublic);
637+
Assert.IsNotNull(prop);
638+
}
639+
640+
[Test]
641+
[LuceneNetSpecific]
642+
public static void Test_DebuggerDisplay_ValidUtf8_ShowsStringThenBytes()
643+
{
644+
var br = new BytesRef("abc");
645+
Assert.AreEqual("abc [61 62 63]", GetDebuggerDisplay(br));
646+
}
647+
648+
[Test]
649+
[LuceneNetSpecific]
650+
public static void Test_DebuggerDisplay_InvalidUtf8_ShowsInvalidLabel()
651+
{
652+
// 0xC3 starts a 2-byte UTF-8 sequence but the continuation byte is missing.
653+
var br = new BytesRef(new byte[] { 0xC3 });
654+
Assert.AreEqual("Invalid UTF-8 [c3]", GetDebuggerDisplay(br));
655+
}
656+
657+
[Test]
658+
[LuceneNetSpecific]
659+
public static void Test_DebuggerDisplay_EmbeddedFFFD_IsNotMistakenForInvalid()
660+
{
661+
// U+FFFD encoded as legitimate UTF-8 (EF BF BD) must round-trip, not be reported as invalid.
662+
var br = new BytesRef("\uFFFD");
663+
Assert.AreEqual("\uFFFD [ef bf bd]", GetDebuggerDisplay(br));
664+
}
665+
666+
[Test]
667+
[LuceneNetSpecific]
668+
public static void Test_DebuggerDisplay_RespectsOffsetAndLength()
669+
{
670+
var bytes = new[] { (byte)'a', (byte)'b', (byte)'c', (byte)'d' };
671+
var br = new BytesRef(bytes, 1, 2); // "bc"
672+
Assert.AreEqual("bc [62 63]", GetDebuggerDisplay(br));
673+
}
674+
675+
#endregion
615676
}
616677
}

src/Lucene.Net/Util/BytesRef.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ namespace Lucene.Net.Util
4545
[Serializable]
4646
#endif
4747
// LUCENENET specific: Not implementing ICloneable per Microsoft's recommendation
48-
[DebuggerDisplay("{ToString()} {Utf8ToStringWithFallback()}")]
48+
[DebuggerDisplay("{DebuggerDisplay,nq}")]
4949
public sealed class BytesRef : IComparable<BytesRef>, IComparable, IEquatable<BytesRef> // LUCENENET specific - implemented IComparable for FieldComparator, IEquatable<BytesRef>
5050
{
5151
/// <summary>
@@ -317,6 +317,11 @@ public bool TryUtf8ToString([NotNullWhen(true)] out string? result)
317317
}
318318
#nullable restore
319319

320+
// LUCENENET specific: "Invalid UTF-8" disambiguates a decode failure from a legitimate U+FFFD in the data.
321+
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Referenced by DebuggerDisplay attribute")]
322+
private string DebuggerDisplay
323+
=> $"{(TryUtf8ToString(out var s) ? s : "Invalid UTF-8")} {ToString()}";
324+
320325
/// <summary>
321326
/// Returns hex encoded bytes, eg [0x6c 0x75 0x63 0x65 0x6e 0x65] </summary>
322327
public override string ToString()

0 commit comments

Comments
 (0)