375 lines
14 KiB
C#
Raw Permalink Normal View History

2021-09-20 18:20:01 +02:00
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using dnlib.DotNet;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Text;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.ILAst;
using ICSharpCode.NRefactory.VB;
using ICSharpCode.NRefactory.VB.Ast;
using CSharp2 = ICSharpCode.NRefactory.CSharp;
namespace dnSpy.Decompiler.ILSpy.Core.VisualBasic {
sealed class VBTextOutputFormatter : IOutputFormatter {
readonly IDecompilerOutput output;
readonly DecompilerContext context;
readonly Stack<AstNode> nodeStack = new Stack<AstNode>();
public VBTextOutputFormatter(IDecompilerOutput output, DecompilerContext context) {
this.output = output ?? throw new ArgumentNullException(nameof(output));
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
MethodDebugInfoBuilder? currentMethodDebugInfoBuilder;
Stack<MethodDebugInfoBuilder?> parentMethodDebugInfoBuilder = new Stack<MethodDebugInfoBuilder?>();
List<Tuple<MethodDebugInfoBuilder, List<ILSpan>>>? multiMappings;
public void StartNode(AstNode node) {
nodeStack.Push(node);
MethodDebugInfoBuilder mapping = node.Annotation<MethodDebugInfoBuilder>();
if (mapping is not null) {
parentMethodDebugInfoBuilder.Push(currentMethodDebugInfoBuilder);
currentMethodDebugInfoBuilder = mapping;
}
// For ctor/cctor field initializers
var mms = node.Annotation<List<Tuple<MethodDebugInfoBuilder, List<ILSpan>>>>();
if (mms is not null) {
Debug2.Assert(multiMappings is null);
multiMappings = mms;
}
}
public void EndNode(AstNode node) {
if (nodeStack.Pop() != node)
throw new InvalidOperationException();
if (node.Annotation<MethodDebugInfoBuilder>() is not null) {
Debug2.Assert(currentMethodDebugInfoBuilder is not null);
if (context.CalculateILSpans) {
foreach (var ns in context.UsingNamespaces)
currentMethodDebugInfoBuilder.Scope.Imports.Add(ImportInfo.CreateNamespace(ns));
}
output.AddDebugInfo(currentMethodDebugInfoBuilder.Create());
currentMethodDebugInfoBuilder = parentMethodDebugInfoBuilder.Pop();
}
var mms = node.Annotation<List<Tuple<MethodDebugInfoBuilder, List<ILSpan>>>>();
if (mms is not null) {
Debug.Assert(mms == multiMappings);
if (mms == multiMappings) {
foreach (var mm in mms)
output.AddDebugInfo(mm.Item1.Create());
multiMappings = null;
}
}
}
public void WriteIdentifier(string identifier, object data, object extraData) {
var definition = GetCurrentDefinition();
if (definition is not null) {
output.Write(IdentifierEscaper.Escape(identifier), definition, DecompilerReferenceFlags.Definition, data);
return;
}
var memberRef = GetCurrentMemberReference() ?? (object?)(extraData as NamespaceReference);
if (memberRef is not null) {
output.Write(IdentifierEscaper.Escape(identifier), memberRef, DecompilerReferenceFlags.None, data);
return;
}
definition = GetCurrentLocalDefinition();
if (definition is not null) {
output.Write(IdentifierEscaper.Escape(identifier), definition, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, data);
return;
}
memberRef = GetCurrentLocalReference();
if (memberRef is not null) {
output.Write(IdentifierEscaper.Escape(identifier), memberRef, DecompilerReferenceFlags.Local, data);
return;
}
output.Write(IdentifierEscaper.Escape(identifier), data);
}
IMemberRef? GetCurrentMemberReference() {
AstNode node = nodeStack.Peek();
if (node.Annotation<ILVariable>() is not null)
return null;
if (node.Role == AstNode.Roles.Type && node.Parent is ObjectCreationExpression)
node = node.Parent;
var memberRef = node.Annotation<IMemberRef>();
if (memberRef is null && node is Identifier) {
node = node.Parent ?? node;
memberRef = node.Annotation<IMemberRef>();
}
if (memberRef is null && node.Role == AstNode.Roles.TargetExpression && (node.Parent is InvocationExpression || node.Parent is ObjectCreationExpression)) {
memberRef = node.Parent.Annotation<IMemberRef>();
}
return memberRef;
}
object? GetCurrentLocalReference() {
AstNode node = nodeStack.Peek();
ILVariable variable = node.Annotation<ILVariable>();
if (variable is null && node.Parent is IdentifierExpression)
variable = node.Parent.Annotation<ILVariable>();
if (variable is not null)
return variable.GetTextReferenceObject();
var lbl = (node.Parent?.Parent as GoToStatement)?.Label ?? (node.Parent?.Parent as LabelDeclarationStatement)?.Label;
if (lbl is not null) {
var method = nodeStack.Select(nd => nd.Annotation<IMethod>()).FirstOrDefault(mr => mr is not null && mr.IsMethod);
if (method is not null)
return method.ToString() + lbl;
}
return null;
}
object? GetCurrentLocalDefinition() {
AstNode node = nodeStack.Peek();
if (node is Identifier && node.Parent is CatchBlock)
node = node.Parent;
var parameterDef = node.Annotation<Parameter>();
if (parameterDef is not null)
return parameterDef;
if (node is ParameterDeclaration) {
node = ((ParameterDeclaration)node).Name;
parameterDef = node.Annotation<Parameter>();
if (parameterDef is not null)
return parameterDef;
}
if (node is VariableIdentifier) {
var variable = ((VariableIdentifier)node).Name.Annotation<ILVariable>();
if (variable is not null)
return variable.GetTextReferenceObject();
node = node.Parent ?? node;
}
if (node is VariableDeclaratorWithTypeAndInitializer || node is VariableInitializer || node is CatchBlock || node is ForEachStatement) {
var variable = node.Annotation<ILVariable>();
if (variable is not null)
return variable.GetTextReferenceObject();
}
if (node is LabelDeclarationStatement label) {
var method = nodeStack.Select(nd => nd.Annotation<IMethod>()).FirstOrDefault(mr => mr is not null && mr.IsMethod);
if (method is not null)
return method.ToString() + label.Label;
}
return null;
}
object? GetCurrentDefinition() {
if (nodeStack is null || nodeStack.Count == 0)
return null;
var node = nodeStack.Peek();
if (node is ParameterDeclaration)
return null;
if (node is VariableIdentifier)
return ((VariableIdentifier)node).Name.Annotation<IMemberDef>();
if (IsDefinition(node))
return node.Annotation<IMemberRef>();
if (node is Identifier) {
node = node.Parent;
if (IsDefinition(node))
return node.Annotation<IMemberRef>();
}
return null;
}
public void WriteKeyword(string keyword) {
var memberRef = GetCurrentMemberReference();
var node = nodeStack.Peek();
if (memberRef is not null && (node is PrimitiveType || node is InstanceExpression))
output.Write(keyword, memberRef, DecompilerReferenceFlags.None, BoxedTextColor.Keyword);
else if (memberRef is not null && (node is ConstructorDeclaration && keyword == "New"))
output.Write(keyword, memberRef, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword);
else if (memberRef is not null && (node is Accessor && (keyword == "Get" || keyword == "Set" || keyword == "AddHandler" || keyword == "RemoveHandler" || keyword == "RaiseEvent"))) {
if (canPrintAccessor)
output.Write(keyword, memberRef, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword);
else
output.Write(keyword, BoxedTextColor.Keyword);
canPrintAccessor = !canPrintAccessor;
}
else if (memberRef is not null && node is OperatorDeclaration && keyword == "Operator")
output.Write(keyword, memberRef, DecompilerReferenceFlags.Definition, BoxedTextColor.Keyword);
else
output.Write(keyword, BoxedTextColor.Keyword);
}
bool canPrintAccessor = true;
public void WriteToken(string token, object data, object? reference) {
var memberRef = GetCurrentMemberReference();
var node = nodeStack.Peek();
bool addRef = memberRef is not null &&
(node is BinaryOperatorExpression ||
node is UnaryOperatorExpression ||
node is AssignmentExpression);
// Add a ref to the method if it's a delegate call
if (!addRef && node is InvocationExpression && memberRef is IMethod) {
var md = Resolve(memberRef as IMethod);
if (md is not null && md.DeclaringType is not null && md.DeclaringType.IsDelegate)
addRef = true;
}
if (addRef)
output.Write(token, memberRef, DecompilerReferenceFlags.None, data);
else if (reference is not null)
output.Write(token, reference, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Hidden | DecompilerReferenceFlags.NoFollow, data);
else
output.Write(token, data);
}
static MethodDef? Resolve(IMethod? method) {
if (method is MethodSpec)
method = ((MethodSpec)method).Method;
if (method is MemberRef)
return ((MemberRef)method).ResolveMethod();
else
return (MethodDef?)method;
}
public void Space() => output.Write(" ", BoxedTextColor.Text);
public void Indent() => output.IncreaseIndent();
public void Unindent() => output.DecreaseIndent();
public void NewLine() => output.WriteLine();
public void WriteComment(bool isDocumentation, string content, CSharp2.CommentReference[] refs) {
if (isDocumentation) {
Debug2.Assert(refs is null);
output.Write("'''", BoxedTextColor.XmlDocCommentDelimiter);
output.WriteXmlDoc(content);
output.WriteLine();
}
else {
output.Write("'", BoxedTextColor.Comment);
Write(content, refs);
output.WriteLine();
}
}
void Write(string content, CSharp2.CommentReference[] refs)
{
if (refs is null) {
output.Write(content, BoxedTextColor.Comment);
return;
}
int offs = 0;
for (int i = 0; i < refs.Length; i++) {
var @ref = refs[i];
var s = content.Substring(offs, @ref.Length);
offs += @ref.Length;
if (@ref.Reference is null)
output.Write(s, BoxedTextColor.Comment);
else
output.Write(s, @ref.Reference, @ref.IsLocal ? DecompilerReferenceFlags.Local : DecompilerReferenceFlags.None, BoxedTextColor.Comment);
}
Debug.Assert(offs == content.Length);
}
static bool IsDefinition(AstNode node) =>
node is FieldDeclaration ||
node is ConstructorDeclaration ||
node is EventDeclaration ||
node is DelegateDeclaration ||
node is OperatorDeclaration ||
node is MemberDeclaration ||
node is TypeDeclaration ||
node is EnumDeclaration ||
node is EnumMemberDeclaration ||
node is TypeParameterDeclaration;
class DebugState {
public List<AstNode> Nodes = new List<AstNode>();
public List<ILSpan> ExtraILSpans = new List<ILSpan>();
public int StartLocation;
}
readonly Stack<DebugState> debugStack = new Stack<DebugState>();
public void DebugStart(AstNode node) => debugStack.Push(new DebugState { StartLocation = output.NextPosition });
public void DebugHidden(object hiddenILSpans) {
if (hiddenILSpans is IList<ILSpan> list) {
if (debugStack.Count > 0)
debugStack.Peek().ExtraILSpans.AddRange(list);
}
}
public void DebugExpression(AstNode node) {
if (debugStack.Count > 0)
debugStack.Peek().Nodes.Add(node);
}
public void DebugEnd(AstNode node) {
var state = debugStack.Pop();
if (currentMethodDebugInfoBuilder is not null) {
foreach (var ilSpan in ILSpan.OrderAndCompact(GetILSpans(state)))
currentMethodDebugInfoBuilder.Add(new SourceStatement(ilSpan, new TextSpan(state.StartLocation, output.NextPosition - state.StartLocation)));
}
else if (multiMappings is not null) {
foreach (var mm in multiMappings) {
foreach (var ilSpan in ILSpan.OrderAndCompact(mm.Item2))
mm.Item1.Add(new SourceStatement(ilSpan, new TextSpan(state.StartLocation, output.NextPosition - state.StartLocation)));
}
}
}
static IEnumerable<ILSpan> GetILSpans(DebugState state) {
foreach (var node in state.Nodes) {
foreach (var ann in node.Annotations) {
var list = ann as IList<ILSpan>;
if (list is null)
continue;
foreach (var ilSpan in list)
yield return ilSpan;
}
}
foreach (var ilSpan in state.ExtraILSpans)
yield return ilSpan;
}
public void AddHighlightedKeywordReference(object reference, int start, int end) {
Debug2.Assert(reference is not null);
if (reference is not null)
output.AddSpanReference(reference, start, end, PredefinedSpanReferenceIds.HighlightRelatedKeywords);
}
public int NextPosition => output.NextPosition;
public void AddBracePair(int leftStart, int leftEnd, int rightStart, int rightEnd, CodeBracesRangeFlags flags) =>
output.AddBracePair(TextSpan.FromBounds(leftStart, leftEnd), TextSpan.FromBounds(rightStart, rightEnd), flags);
public void AddBlock(int start, int end, CodeBracesRangeFlags flags) =>
output.AddBracePair(new TextSpan(start, 0), new TextSpan(end, 0), flags);
public void AddLineSeparator(int position) => output.AddLineSeparator(position);
}
}