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