Skip to content

Commit

Permalink
Add IL syntax highlighting in SourceViewWindow
Browse files Browse the repository at this point in the history
- Add CilInstruction.ToSyntax()
- Add CIL syntax highlighting for Show source feature

Issue: #98
  • Loading branch information
MSDN-WhiteKnight committed Aug 25, 2022
1 parent dc87b2e commit ce164a9
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 12 deletions.
18 changes: 18 additions & 0 deletions CilTools.BytecodeAnalysis/CilInstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,24 @@ public override string ToString()
return sb.ToString();
}

/// <summary>
/// Gets the instruction syntax
/// </summary>
/// <returns>The collection of syntax nodes that represent this instruction syntax</returns>
public IEnumerable<SyntaxNode> ToSyntax()
{
int spaces = 10 - this.Name.Length;

if (spaces < 0) spaces = 0;

yield return new KeywordSyntax(string.Empty, this.Name, "".PadLeft(spaces + 1), KeywordKind.InstructionName);

foreach (SyntaxNode node in this.OperandToSyntax())
{
yield return node;
}
}

#if !NETSTANDARD
/// <summary>
/// Emits CIL code for this instruction into the specified IL generator.
Expand Down
41 changes: 40 additions & 1 deletion CilView.Core/SourceCode/PdbUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Text;
using CilTools.BytecodeAnalysis;
using CilTools.SourceCode;
using CilTools.Syntax;

namespace CilView.SourceCode
{
Expand Down Expand Up @@ -38,7 +39,45 @@ public static string GetCilText(MethodBase mb, uint startOffset, uint endOffset)

return sb.ToString();
}


class LineBreakSyntax : SyntaxNode
{
internal static readonly LineBreakSyntax Value = new LineBreakSyntax();

public override IEnumerable<SyntaxNode> EnumerateChildNodes()
{
return new SyntaxNode[0];
}

public override void ToText(TextWriter target)
{
target.WriteLine();
}
}

public static IEnumerable<SyntaxNode> GetCilSyntax(MethodBase mb, uint startOffset, uint endOffset)
{
foreach (CilInstruction instr in CilReader.GetInstructions(mb))
{
if (instr.ByteOffset >= startOffset &&
instr.ByteOffset + instr.TotalSize <= endOffset)
{
IEnumerable<SyntaxNode> syntax = instr.ToSyntax();

foreach (SyntaxNode node in syntax)
{
yield return node;
}

yield return LineBreakSyntax.Value;
}
else if (instr.ByteOffset > endOffset)
{
break;
}
}
}

public static SourceFragment FindFragment(IEnumerable<SourceFragment> fragments, uint offset)
{
SourceFragment fragment = null;
Expand Down
29 changes: 24 additions & 5 deletions CilView/CilVisualization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,12 @@ static void VisualizeNode(
if (instr != null)
{
//attach context menu to instruction opcode
r.ContextMenu = InstructionMenu.GetInstructionMenu();
r.ContextMenuOpening += InstructionMenu.R_ContextMenuOpening;
r.Tag = par;
if (ctx.ContextMenuEnabled)
{
r.ContextMenu = InstructionMenu.GetInstructionMenu();
r.ContextMenuOpening += InstructionMenu.R_ContextMenuOpening;
r.Tag = par;
}
}
}

Expand Down Expand Up @@ -267,7 +270,7 @@ static void VisualizeNode(
r = new Run();
MethodBase m = id.TargetMember as MethodBase;

if (m != null && !(node.Parent is DirectiveSyntax))
if (m != null && !(node.Parent is DirectiveSyntax) && ctx.navigation != null)
{
//if target is method and we are not in directive (method sig),
//enable navigation functionality
Expand All @@ -280,7 +283,7 @@ static void VisualizeNode(
Hyperlink lnk = new Hyperlink(r);
lnk.TextDecorations = new TextDecorationCollection(); //remove underline
lnk.Tag = m;
if (ctx.navigation != null) lnk.Click += ctx.navigation;
lnk.Click += ctx.navigation;
target.Inlines.Add(lnk);
}
else
Expand Down Expand Up @@ -330,6 +333,7 @@ class VisualizeGraphContext
public RoutedEventHandler navigation;
public int highlight_start=-1;
public int highlight_end = Int32.MaxValue;
public bool ContextMenuEnabled = true;
public bool ScrollCallbackApplied = false;
}

Expand Down Expand Up @@ -361,6 +365,21 @@ public static UIElement VisualizeGraph(
return scroll;
}

public static FlowDocument VisualizeNodes(IEnumerable<SyntaxNode> nodes)
{
FlowDocument fd = new FlowDocument();
fd.TextAlignment = TextAlignment.Left;
fd.FontFamily = new FontFamily("Courier New");
Paragraph par = new Paragraph();
VisualizeGraphContext ctx = new VisualizeGraphContext();
ctx.ContextMenuEnabled = false;

foreach (SyntaxNode node in nodes) VisualizeNode(node, par, ctx);

fd.Blocks.Add(par);
return fd;
}

public static UIElement VisualizeType(Type t, RoutedEventHandler navigation,out string plaintext)
{
FlowDocumentScrollViewer scroll = new FlowDocumentScrollViewer();
Expand Down
8 changes: 5 additions & 3 deletions CilView/UI.Dialogs/SourceViewWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
</Grid.RowDefinitions>
<TextBox x:Name="tbMethod" HorizontalAlignment="Stretch" Height="23" Margin="5,5,5,5" TextWrapping="NoWrap"
Text="" VerticalAlignment="Top" IsReadOnly="True"/>
<TextBox x:Name="tbCIL" HorizontalAlignment="Stretch" Margin="5,35,5,5" Text=""
VerticalAlignment="Stretch" IsReadOnly="True" Style="{StaticResource CodeBlock}"
Grid.Column="0" />
<Border HorizontalAlignment="Stretch" Margin="5,35,5,5"
VerticalAlignment="Stretch" Grid.Column="0" BorderBrush="DarkGray" BorderThickness="1">
<FlowDocumentScrollViewer x:Name="fdCIL" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalScrollBarVisibility="Visible" />
</Border>

<TextBlock x:Name="tbFileName" Text="File" HorizontalAlignment="Stretch" Margin="5,5,0,0"
VerticalAlignment="Top" Foreground="Blue" Cursor="Hand"
Expand Down
13 changes: 11 additions & 2 deletions CilView/UI.Dialogs/SourceViewWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using CilTools.SourceCode;
using CilTools.Syntax;
using CilView.Common;
using CilView.SourceCode;

Expand Down Expand Up @@ -41,13 +43,20 @@ void LoadSourceInfo(SourceFragment f)
try
{
//get CIL for the range of the sequence point
tbCIL.Text = PdbUtils.GetCilText(f.Method, (uint)f.CilStart, (uint)f.CilEnd);
IEnumerable<SyntaxNode> nodes = PdbUtils.GetCilSyntax(f.Method, (uint)f.CilStart, (uint)f.CilEnd);
fdCIL.Document = CilVisualization.VisualizeNodes(nodes);
}
catch (Exception ex)
{
//don't stop the rest of method to work if this errors out
//showing source can still be useful
tbCIL.Text = "[error!]";
FlowDocument errorDocument = new FlowDocument();
errorDocument.TextAlignment = TextAlignment.Left;
Paragraph par = new Paragraph();
par.Inlines.Add(new Run("[error!]"));
errorDocument.Blocks.Add(par);
fdCIL.Document = errorDocument;

ErrorHandler.Current.Error(ex);
}

Expand Down
58 changes: 57 additions & 1 deletion tests/CilTools.BytecodeAnalysis.Tests/CilInstructionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using CilTools.BytecodeAnalysis;
using CilTools.Syntax;
using CilTools.Tests.Common;
using CilTools.Tests.Common.TextUtils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -61,5 +61,61 @@ public void Test_CilInstruction_ToString(MethodBase mi)
}
);
}

[TestMethod]
public void Test_CilInstruction_ToSyntax()
{
CilInstruction instr = CilInstruction.Create(OpCodes.Ldc_I4, 1, sizeof(int));
SyntaxNode[] syntax = instr.ToSyntax().ToArray();

Assert.AreEqual(2, syntax.Length);

Assert.IsTrue(syntax[0] is KeywordSyntax);
Assert.AreEqual("ldc.i4",((KeywordSyntax)syntax[0]).Content);
Assert.AreEqual(KeywordKind.InstructionName, ((KeywordSyntax)syntax[0]).Kind);
Assert.AreEqual(string.Empty, syntax[0].LeadingWhitespace);
Assert.AreEqual(" ", syntax[0].TrailingWhitespace);

Assert.IsTrue(syntax[1] is LiteralSyntax);
Assert.AreEqual(1, (int)((LiteralSyntax)syntax[1]).Value);
Assert.AreEqual(string.Empty, syntax[1].LeadingWhitespace);
Assert.AreEqual(string.Empty, syntax[1].TrailingWhitespace);
}

[TestMethod]
public void Test_CilInstruction_ToSyntax_Simple()
{
CilInstruction instr = CilInstruction.Create(OpCodes.Nop);
SyntaxNode[] syntax = instr.ToSyntax().ToArray();

Assert.AreEqual(1, syntax.Length);

Assert.IsTrue(syntax[0] is KeywordSyntax);
Assert.AreEqual("nop", ((KeywordSyntax)syntax[0]).Content);
Assert.AreEqual(KeywordKind.InstructionName, ((KeywordSyntax)syntax[0]).Kind);
}

[TestMethod]
[MethodTestData(typeof(SampleMethods), "PrintHelloWorld", BytecodeProviders.All)]
public void Test_CilInstruction_ToSyntax_Call(MethodBase mi)
{
CilInstruction[] instructions = CilReader.GetInstructions(mi).ToArray();
CilInstruction instr = instructions.Where(x => x.OpCode == OpCodes.Call).Single();
SyntaxNode[] syntax = instr.ToSyntax().ToArray();

Assert.AreEqual(2, syntax.Length);

Assert.IsTrue(syntax[0] is KeywordSyntax);
Assert.AreEqual("call", ((KeywordSyntax)syntax[0]).Content);
Assert.AreEqual(KeywordKind.InstructionName, ((KeywordSyntax)syntax[0]).Kind);
Assert.AreEqual(string.Empty, syntax[0].LeadingWhitespace);
Assert.AreEqual(" ", syntax[0].TrailingWhitespace);

Assert.IsTrue(syntax[1] is MemberRefSyntax);
Assert.AreEqual("WriteLine", ((MemberRefSyntax)syntax[1]).Member.Name);
Assert.IsTrue(((MemberRefSyntax)syntax[1]).Member is MethodBase);
Assert.AreEqual(string.Empty, syntax[1].LeadingWhitespace);
Assert.AreEqual(string.Empty, syntax[1].TrailingWhitespace);
}
}
}

0 comments on commit ce164a9

Please sign in to comment.