File: Linker\MessageOrigin.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// 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;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
 
namespace Mono.Linker
{
	public readonly struct MessageOrigin : IComparable<MessageOrigin>, IEquatable<MessageOrigin>
	{
		public string? FileName { get; }
		public ICustomAttributeProvider? Provider { get; }
 
		public int SourceLine { get; }
		public int SourceColumn { get; }
		internal int ILOffset { get; }
 
		internal const int UnsetILOffset = -1;
 
		const int HiddenLineNumber = 0xfeefee;
 
		public MessageOrigin (IMemberDefinition? memberDefinition, int? ilOffset = null)
			: this (memberDefinition as ICustomAttributeProvider, ilOffset ?? UnsetILOffset)
		{
		}
 
		public MessageOrigin (ICustomAttributeProvider? provider)
			: this (provider, UnsetILOffset)
		{
		}
 
		public MessageOrigin (string fileName, int sourceLine = 0, int sourceColumn = 0)
			: this (fileName, sourceLine, sourceColumn, null)
		{
		}
 
		// The assembly attribute should be specified if available as it allows assigning the diagnostic
		// to a an assembly (we group based on assembly).
		public MessageOrigin (string fileName, int sourceLine, int sourceColumn, AssemblyDefinition? assembly)
		{
			FileName = fileName;
			SourceLine = sourceLine;
			SourceColumn = sourceColumn;
			Provider = assembly;
			ILOffset = UnsetILOffset;
		}
 
		public MessageOrigin (ICustomAttributeProvider? provider, int? ilOffset)
		{
			Debug.Assert (provider == null || provider is IMemberDefinition || provider is AssemblyDefinition);
			FileName = null;
			Provider = provider;
			SourceLine = 0;
			SourceColumn = 0;
			ILOffset = ilOffset ?? UnsetILOffset;
		}
 
		public MessageOrigin (MessageOrigin other)
		{
			FileName = other.FileName;
			Provider = other.Provider;
			SourceLine = other.SourceLine;
			SourceColumn = other.SourceColumn;
			ILOffset = other.ILOffset;
		}
 
		public MessageOrigin (MessageOrigin other, int ilOffset)
		{
			FileName = other.FileName;
			Provider = other.Provider;
			SourceLine = other.SourceLine;
			SourceColumn = other.SourceColumn;
			ILOffset = ilOffset;
		}
 
		public MessageOrigin WithInstructionOffset (int ilOffset) => new MessageOrigin (this, ilOffset);
 
		public override string? ToString ()
		{
			int sourceLine = SourceLine, sourceColumn = SourceColumn;
			string? fileName = FileName;
			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 ();
 
				// 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) {
					correspondingSequencePoint = method.DebugInformation.SequencePoints
						.Where (s => s.StartLine != HiddenLineNumber).FirstOrDefault ();
				}
 
				if (correspondingSequencePoint != null) {
					fileName = correspondingSequencePoint.Document.Url;
					sourceLine = correspondingSequencePoint.StartLine;
					sourceColumn = correspondingSequencePoint.StartColumn;
				}
			}
 
			if (fileName == null)
				return null;
 
			StringBuilder sb = new StringBuilder (fileName);
			if (sourceLine != 0) {
				sb.Append ('(').Append (sourceLine);
				if (sourceColumn != 0)
					sb.Append (',').Append (sourceColumn);
 
				sb.Append (')');
			}
 
			return sb.ToString ();
		}
 
		public bool Equals (MessageOrigin other) =>
			(FileName, Provider, SourceLine, SourceColumn, ILOffset) == (other.FileName, other.Provider, other.SourceLine, other.SourceColumn, other.ILOffset);
 
		public override bool Equals (object? obj) => obj is MessageOrigin messageOrigin && Equals (messageOrigin);
		public override int GetHashCode () => (FileName, Provider, SourceLine, SourceColumn, ILOffset).GetHashCode ();
		public static bool operator == (MessageOrigin lhs, MessageOrigin rhs) => lhs.Equals (rhs);
		public static bool operator != (MessageOrigin lhs, MessageOrigin rhs) => !lhs.Equals (rhs);
 
		public int CompareTo (MessageOrigin other)
		{
			if (Provider != null && other.Provider != null) {
				var thisMember = Provider as IMemberDefinition;
				var otherMember = other.Provider as IMemberDefinition;
				TypeDefinition? thisTypeDef = (Provider as TypeDefinition) ?? (Provider as IMemberDefinition)?.DeclaringType;
				TypeDefinition? otherTypeDef = (other.Provider as TypeDefinition) ?? (other.Provider as IMemberDefinition)?.DeclaringType;
				var thisAssembly = thisTypeDef?.Module.Assembly ?? Provider as AssemblyDefinition;
				var otherAssembly = otherTypeDef?.Module.Assembly ?? other.Provider as AssemblyDefinition;
				int result = (thisAssembly?.Name.Name, thisTypeDef?.Name, thisMember?.Name).CompareTo
					((otherAssembly?.Name.Name, otherTypeDef?.Name, otherMember?.Name));
				if (result != 0)
					return result;
 
				if (ILOffset != UnsetILOffset && other.ILOffset != UnsetILOffset)
					return ILOffset.CompareTo (other.ILOffset);
 
				return ILOffset == UnsetILOffset ? (other.ILOffset == UnsetILOffset ? 0 : 1) : -1;
			} else if (Provider == null && other.Provider == null) {
				if (FileName != null && other.FileName != null) {
					return string.Compare (FileName, other.FileName);
				} else if (FileName == null && other.FileName == null) {
					return (SourceLine, SourceColumn).CompareTo ((other.SourceLine, other.SourceColumn));
				}
 
				return (FileName == null) ? 1 : -1;
			}
 
			return (Provider == null) ? 1 : -1;
		}
	}
}