|
// 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;
}
}
}
|