From b79404eb94364b1406ac5e6cd99de994b8dcd789 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 25 Jun 2026 13:41:27 -0700 Subject: [PATCH 1/2] Handle ILLink origins before first sequence point Avoid throwing while formatting diagnostics whose IL offset precedes the first available PDB sequence point. Fall back to the first sequence point and cover the case with a focused MessageContainer test. Assisted-by: Copilot:gpt-5.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../illink/src/linker/Linker/MessageOrigin.cs | 11 +++- .../Tests/MessageContainerTests.cs | 60 +++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/MessageOrigin.cs b/src/tools/illink/src/linker/Linker/MessageOrigin.cs index df7f28d288ecef..394d0af01cae4c 100644 --- a/src/tools/illink/src/linker/Linker/MessageOrigin.cs +++ b/src/tools/illink/src/linker/Linker/MessageOrigin.cs @@ -86,9 +86,14 @@ public MessageOrigin(MessageOrigin other, int ilOffset) if (Provider is MethodDefinition method && method.DebugInformation.HasSequencePoints) { - var offset = ILOffset == UnsetILOffset ? method.DebugInformation.SequencePoints[0].Offset : ILOffset; - SequencePoint? correspondingSequencePoint = method.DebugInformation.SequencePoints - .Where(s => s.Offset <= offset)?.Last(); + var sequencePoints = method.DebugInformation.SequencePoints; + var offset = ILOffset == UnsetILOffset ? sequencePoints[0].Offset : ILOffset; + SequencePoint? correspondingSequencePoint = sequencePoints + .Where(s => s.Offset <= offset).LastOrDefault(); + + // If the warning comes from IL before the first sequence point + // (for example, rewritten IL), report the first available sequence point as a best effort. + correspondingSequencePoint ??= sequencePoints.FirstOrDefault(); // If the warning comes from hidden line (compiler generated code typically) // search for any sequence point with non-hidden line number and report that as a best effort. diff --git a/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs b/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs index 208ffe38369255..06d1b39c4f8a04 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs @@ -1,7 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.IO; +using System.Linq; using Xunit; +using Mono.Cecil; +using Mono.Cecil.Cil; namespace Mono.Linker.Tests { @@ -21,5 +25,61 @@ public void MSBuildFormat() msg = MessageContainer.CreateInfoMessage("log test"); Assert.Equal("ILLink: log test", msg.ToMSBuildString()); } + + [Fact] + public void OriginBeforeFirstSequencePointUsesFirstAvailableSequencePoint() + { + using ModuleDefinition module = ModuleDefinition.CreateModule("test", ModuleKind.Dll); + var type = new TypeDefinition("Test", "Type", TypeAttributes.Public, module.TypeSystem.Object); + module.Types.Add(type); + + var method = new MethodDefinition("Method", MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void); + type.Methods.Add(method); + + var body = method.Body; + var firstInstruction = Instruction.Create(OpCodes.Nop); + var secondInstruction = Instruction.Create(OpCodes.Ret); + body.Instructions.Add(firstInstruction); + body.Instructions.Add(secondInstruction); + + method.DebugInformation.SequencePoints.Add(new SequencePoint(secondInstruction, new Document("test.cs")) + { + StartLine = 10, + StartColumn = 5, + EndLine = 10, + EndColumn = 6 + }); + + using var assemblyStream = new MemoryStream(); + using var symbolStream = new MemoryStream(); + + module.Write(assemblyStream, new WriterParameters + { + WriteSymbols = true, + SymbolStream = symbolStream, + SymbolWriterProvider = new PortablePdbWriterProvider() + }); + + assemblyStream.Position = 0; + symbolStream.Position = 0; + + using var assembly = AssemblyDefinition.ReadAssembly(assemblyStream, new ReaderParameters + { + ReadSymbols = true, + SymbolStream = symbolStream, + SymbolReaderProvider = new PortablePdbReaderProvider() + }); + + method = assembly.MainModule.GetType("Test.Type").Methods.Single(m => m.Name == "Method"); + firstInstruction = method.Body.Instructions[0]; + Assert.True(firstInstruction.Offset < method.DebugInformation.SequencePoints[0].Offset); + + var msg = MessageContainer.CreateCustomErrorMessage( + "message", + 6001, + origin: new MessageOrigin(method, firstInstruction.Offset)); + + Assert.Equal("test.cs(10,5): error IL6001: Test.Type.Method(): message", msg.ToMSBuildString()); + } } } From e2f384f5df13c7ce3eee8af043d356b8a62beed4 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 25 Jun 2026 13:48:48 -0700 Subject: [PATCH 2/2] Align ILLink origin fallback with ILC Keep the existing LINQ-based sequence point lookup but use LastOrDefault so diagnostics whose IL offset precedes the first sequence point fall back to the tool name instead of throwing, matching ILC behavior. Assisted-by: Copilot:gpt-5.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/tools/illink/src/linker/Linker/MessageOrigin.cs | 9 ++------- .../Mono.Linker.Tests/Tests/MessageContainerTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/MessageOrigin.cs b/src/tools/illink/src/linker/Linker/MessageOrigin.cs index 394d0af01cae4c..7d9b3d51eaa9b1 100644 --- a/src/tools/illink/src/linker/Linker/MessageOrigin.cs +++ b/src/tools/illink/src/linker/Linker/MessageOrigin.cs @@ -86,15 +86,10 @@ public MessageOrigin(MessageOrigin other, int ilOffset) if (Provider is MethodDefinition method && method.DebugInformation.HasSequencePoints) { - var sequencePoints = method.DebugInformation.SequencePoints; - var offset = ILOffset == UnsetILOffset ? sequencePoints[0].Offset : ILOffset; - SequencePoint? correspondingSequencePoint = sequencePoints + var offset = ILOffset == UnsetILOffset ? method.DebugInformation.SequencePoints[0].Offset : ILOffset; + SequencePoint? correspondingSequencePoint = method.DebugInformation.SequencePoints .Where(s => s.Offset <= offset).LastOrDefault(); - // If the warning comes from IL before the first sequence point - // (for example, rewritten IL), report the first available sequence point as a best effort. - correspondingSequencePoint ??= sequencePoints.FirstOrDefault(); - // If the warning comes from hidden line (compiler generated code typically) // search for any sequence point with non-hidden line number and report that as a best effort. if (correspondingSequencePoint?.StartLine == HiddenLineNumber) diff --git a/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs b/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs index 06d1b39c4f8a04..99eca61a6bf513 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/Tests/MessageContainerTests.cs @@ -27,7 +27,7 @@ public void MSBuildFormat() } [Fact] - public void OriginBeforeFirstSequencePointUsesFirstAvailableSequencePoint() + public void OriginBeforeFirstSequencePointFallsBackToApplicationName() { using ModuleDefinition module = ModuleDefinition.CreateModule("test", ModuleKind.Dll); var type = new TypeDefinition("Test", "Type", TypeAttributes.Public, module.TypeSystem.Object); @@ -79,7 +79,7 @@ public void OriginBeforeFirstSequencePointUsesFirstAvailableSequencePoint() 6001, origin: new MessageOrigin(method, firstInstruction.Offset)); - Assert.Equal("test.cs(10,5): error IL6001: Test.Type.Method(): message", msg.ToMSBuildString()); + Assert.Equal("ILLink: error IL6001: Test.Type.Method(): message", msg.ToMSBuildString()); } } }