diff --git a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs index ff92bf39a9..c59f95474d 100644 --- a/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs +++ b/src/Tools/dotnet-counters/Exporters/ConsoleWriter.cs @@ -192,17 +192,33 @@ private bool RenderTagSetsInColumnMode(ref int row, ObservedCounter counter) for (int i = 0; i < tags.Length; i++) { string tag = tags[i]; - string[] keyValue = tag.Split("="); - int posTag = observedTags.FindIndex (tag => tag.header == keyValue[0]); + string[] keyValue = tag.Split(new char[] { '=' }, 2); // Split into at most 2 parts to handle values with '=' + + // Skip malformed tags that don't have both key and value + if (keyValue.Length != 2) + { + continue; + } + + string key = keyValue[0].Trim(); + string value = keyValue[1]; + + // Skip empty keys + if (string.IsNullOrEmpty(key)) + { + continue; + } + + int posTag = observedTags.FindIndex (tag => tag.header == key); if (posTag == -1) { - observedTags.Add((keyValue[0], new string[counter.TagSets.Count])); - columnHeaderLen.Add(keyValue[0].Length); + observedTags.Add((key, new string[counter.TagSets.Count])); + columnHeaderLen.Add(key.Length); maxValueColumnLen.Add(default(int)); posTag = observedTags.Count - 1; } - observedTags[posTag].values[tagsCount] = keyValue[1]; - maxValueColumnLen[posTag] = Math.Max(keyValue[1].Length, maxValueColumnLen[posTag]); + observedTags[posTag].values[tagsCount] = value; + maxValueColumnLen[posTag] = Math.Max(value.Length, maxValueColumnLen[posTag]); } tagsCount++; } diff --git a/src/tests/dotnet-counters/ConsoleExporterTests.cs b/src/tests/dotnet-counters/ConsoleExporterTests.cs index 53e1ca1706..9a26e77302 100644 --- a/src/tests/dotnet-counters/ConsoleExporterTests.cs +++ b/src/tests/dotnet-counters/ConsoleExporterTests.cs @@ -300,6 +300,30 @@ public void CountersAreTruncatedBeyondScreenHeight() " color"); } + [Fact] + public void MalformedTagsDoNotCrash() + { + // Defensive test: verifies that malformed tags are handled gracefully + // WITHOUT the defensive fix, this test throws IndexOutOfRangeException + // WITH the defensive fix, malformed tags are skipped and no crash occurs + MockConsole console = new MockConsole(60, 40); + ConsoleWriter exporter = new ConsoleWriter(console); + exporter.Initialize(); + + // These would crash the old code by trying to access keyValue[1] when keyValue.Length == 1 + // Tag with no '=' - would cause IndexOutOfRangeException in old code + exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter1", "{widget}", "BadTagNoEquals", 10), false); + + // Tag with only whitespace + exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter2", "{widget}", " ", 20), false); + + // Mix of valid and invalid tags - only valid one should be processed + exporter.CounterPayloadReceived(CreateMeterCounterPreNet8("Provider1", "Counter3", "{widget}", "InvalidTag,color=blue", 30), false); + + // If we got here without throwing IndexOutOfRangeException, the defensive code worked! + // The test passes simply by completing successfully + } + [Fact] public void ErrorStatusIsDisplayed() {