mirror of https://github.com/icsharpcode/ILSpy.git
31 changed files with 1249 additions and 57 deletions
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.Linq; |
||||
using System.Reflection.Metadata; |
||||
using System.Reflection.Metadata.Ecma335; |
||||
using System.Xml.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.Disassembler; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.BamlDecompiler.Rewrite |
||||
{ |
||||
/// <summary>
|
||||
/// Recovers the StartupUri of an application from the code the markup compiler generated for it.
|
||||
/// <para>
|
||||
/// StartupUri is written in App.xaml, but it does not reach the BAML: the markup compiler turns
|
||||
/// the attribute into an assignment inside InitializeComponent. The project decompiler deletes
|
||||
/// the generated members, so a document decompiled without this would build into an application
|
||||
/// that starts and shows nothing.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal class StartupUriRewritePass : IRewritePass |
||||
{ |
||||
const string StartupUriPropertyName = "StartupUri"; |
||||
|
||||
public void Run(XamlContext ctx, XDocument document) |
||||
{ |
||||
var root = document.Elements().FirstOrDefault()?.Elements().FirstOrDefault(); |
||||
if (root == null || root.Attribute(StartupUriPropertyName) != null) |
||||
return; |
||||
// The type of the document, which the x:Class pass has recorded by the time this runs.
|
||||
if (ctx.XClassNames.FirstOrDefault() is not string className) |
||||
return; |
||||
var typeDefinition = ctx.TypeSystem.MainModule.GetTypeDefinition(new FullTypeName(className).TopLevelTypeName); |
||||
if (typeDefinition == null) |
||||
return; |
||||
|
||||
string startupUri = FindAssignedStartupUri(typeDefinition); |
||||
if (startupUri != null) |
||||
root.Add(new XAttribute(StartupUriPropertyName, startupUri)); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The string assigned to a StartupUri property in InitializeComponent, if there is one.
|
||||
/// The generated code reads
|
||||
/// <c>StartupUri = new Uri("MainWindow.xaml", UriKind.Relative)</c>, so the string wanted is
|
||||
/// the last one loaded before the call to the setter.
|
||||
/// </summary>
|
||||
static string FindAssignedStartupUri(ITypeDefinition typeDefinition) |
||||
{ |
||||
var method = typeDefinition.Methods.FirstOrDefault( |
||||
m => m.Name == "InitializeComponent" && m.Parameters.Count == 0); |
||||
if (method?.MetadataToken.IsNil != false) |
||||
return null; |
||||
var module = typeDefinition.ParentModule?.MetadataFile; |
||||
if (module == null) |
||||
return null; |
||||
|
||||
try |
||||
{ |
||||
var metadata = module.Metadata; |
||||
var methodDefinition = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken); |
||||
if (methodDefinition.RelativeVirtualAddress == 0) |
||||
return null; |
||||
var body = module.GetMethodBody(methodDefinition.RelativeVirtualAddress); |
||||
var reader = body.GetILReader(); |
||||
string lastLoadedString = null; |
||||
while (reader.RemainingBytes > 0) |
||||
{ |
||||
var opCode = reader.DecodeOpCode(); |
||||
switch (opCode) |
||||
{ |
||||
case ILOpCode.Ldstr: |
||||
lastLoadedString = metadata.GetUserString( |
||||
MetadataTokens.UserStringHandle(reader.ReadInt32())); |
||||
break; |
||||
case ILOpCode.Call: |
||||
case ILOpCode.Callvirt: |
||||
var target = MetadataTokens.EntityHandle(reader.ReadInt32()); |
||||
if (lastLoadedString != null && IsStartupUriSetter(metadata, target)) |
||||
return lastLoadedString; |
||||
break; |
||||
default: |
||||
ILParser.SkipOperand(ref reader, opCode); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
catch (BadImageFormatException) |
||||
{ |
||||
// A method body nobody can read says nothing about the StartupUri.
|
||||
} |
||||
return null; |
||||
} |
||||
|
||||
static bool IsStartupUriSetter(MetadataReader metadata, EntityHandle handle) |
||||
{ |
||||
StringHandle name; |
||||
switch (handle.Kind) |
||||
{ |
||||
case HandleKind.MethodDefinition: |
||||
name = metadata.GetMethodDefinition((MethodDefinitionHandle)handle).Name; |
||||
break; |
||||
case HandleKind.MemberReference: |
||||
name = metadata.GetMemberReference((MemberReferenceHandle)handle).Name; |
||||
break; |
||||
default: |
||||
return false; |
||||
} |
||||
return metadata.StringComparer.Equals(name, "set_" + StartupUriPropertyName); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
|
||||
using ICSharpCode.Decompiler.CSharp; |
||||
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.ProjectDecompiler; |
||||
|
||||
[TestFixture] |
||||
public sealed class ProjectFileWriterDefaultTests |
||||
{ |
||||
/// <summary>
|
||||
/// Item metadata as attributes is MSBuild 15 syntax. The non-SDK format exists for the
|
||||
/// toolchains that came before it, and those reject an unknown attribute on an item element,
|
||||
/// so metadata has to be written the way every non-SDK project writes it: as child elements.
|
||||
/// </summary>
|
||||
[Test] |
||||
public void ItemMetadataIsWrittenAsChildElements() |
||||
{ |
||||
ProjectItemInfo[] files = [ |
||||
new ProjectItemInfo("Page", "Themes/Generic.xaml") |
||||
.With("Generator", "MSBuild:Compile") |
||||
.With("SubType", "Designer"), |
||||
]; |
||||
|
||||
string project = WriteProjectFile(files); |
||||
|
||||
using (Assert.EnterMultipleScope()) |
||||
{ |
||||
Assert.That(project, Does.Contain(@"<Page Include=""Themes/Generic.xaml"">"), project); |
||||
Assert.That(project, Does.Contain(@"<Generator>MSBuild:Compile</Generator>"), project); |
||||
Assert.That(project, Does.Contain(@"<SubType>Designer</SubType>"), project); |
||||
Assert.That(project, Does.Not.Contain(@"Generator="""), "metadata does not belong in an attribute"); |
||||
} |
||||
} |
||||
|
||||
[Test] |
||||
public void AnItemWithoutMetadataStaysOnOneLine() |
||||
{ |
||||
ProjectItemInfo[] files = [new ProjectItemInfo("Compile", "Program.cs")]; |
||||
|
||||
string project = WriteProjectFile(files); |
||||
|
||||
Assert.That(project, Does.Contain(@"<Compile Include=""Program.cs"" />"), project); |
||||
} |
||||
|
||||
static string WriteProjectFile(ProjectItemInfo[] files) |
||||
{ |
||||
StringWriter output = new(); |
||||
ProjectFileWriterDefault.Instance.Write(output, new TestProjectInfoProvider(), files, |
||||
new PEFile("ICSharpCode.Decompiler.dll")); |
||||
return output.ToString(); |
||||
} |
||||
|
||||
sealed class TestProjectInfoProvider : IProjectInfoProvider |
||||
{ |
||||
public IAssemblyResolver AssemblyResolver { get; } = new UniversalAssemblyResolver(null, false, null); |
||||
public IAssemblyReferenceClassifier AssemblyReferenceClassifier { get; } = new AssemblyReferenceClassifier(); |
||||
public LanguageVersion LanguageVersion => LanguageVersion.Latest; |
||||
public bool CheckForOverflowUnderflow => false; |
||||
public Guid ProjectGuid { get; } = Guid.NewGuid(); |
||||
public string TargetDirectory { get; } = Environment.CurrentDirectory; |
||||
public string StrongNameKeyFile => null; |
||||
} |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.
|
||||
|
||||
namespace ICSharpCode.ILSpyCmd.Tests.Views |
||||
{ |
||||
/// <summary>
|
||||
/// Plays the role of a WPF code-behind class: the BAML fixture "views/deeppage.baml" names it
|
||||
/// as its root, which is how the BAML decompiler recognises an x:Class type.
|
||||
/// </summary>
|
||||
public class DeepPage |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
using static ICSharpCode.ILSpyCmd.Tests.CliTestRunner; |
||||
|
||||
namespace ICSharpCode.ILSpyCmd.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// This assembly carries "mainwindow.baml" as an embedded resource, so exporting it as a
|
||||
/// project exercises what happens to a BAML stream on the way into the project.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class ProjectExportBamlTests |
||||
{ |
||||
static readonly string testAssemblyPath = typeof(ProjectExportBamlTests).Assembly.Location; |
||||
|
||||
string outputDirectory; |
||||
|
||||
[SetUp] |
||||
public void SetUp() |
||||
{ |
||||
outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); |
||||
Directory.CreateDirectory(outputDirectory); |
||||
} |
||||
|
||||
[TearDown] |
||||
public void TearDown() |
||||
{ |
||||
if (Directory.Exists(outputDirectory)) |
||||
Directory.Delete(outputDirectory, recursive: true); |
||||
} |
||||
|
||||
string ProjectFileContent() |
||||
{ |
||||
string projectFile = Directory.EnumerateFiles(outputDirectory, "*.csproj").Single(); |
||||
return File.ReadAllText(projectFile); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task BamlBecomesXamlWithoutAskingForIt() |
||||
{ |
||||
var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory); |
||||
|
||||
Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); |
||||
string xamlFile = Path.Combine(outputDirectory, "mainwindow.xaml"); |
||||
Assert.That(File.Exists(xamlFile), Is.True, "the BAML resource is exported as XAML"); |
||||
Assert.That(File.ReadAllText(xamlFile), Does.Contain("Hello from BAML")); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task TheProjectNamesTheXamlAsAPage() |
||||
{ |
||||
await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory); |
||||
|
||||
string project = ProjectFileContent(); |
||||
Assert.Multiple(() => { |
||||
Assert.That(project, Does.Contain("<Page Include=\"mainwindow.xaml\"")); |
||||
Assert.That(project, Does.Not.Contain("mainwindow.baml"), "the raw stream is not carried along as well"); |
||||
}); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task TheCodeBehindSitsNextToItsDocument() |
||||
{ |
||||
// WPF tooling pairs MainWindow.xaml with MainWindow.xaml.cs by name and location; a
|
||||
// code-behind anywhere else is an unrelated file as far as the project is concerned.
|
||||
await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory); |
||||
|
||||
string documentDirectory = Path.Combine(outputDirectory, "ICSharpCode.ILSpyCmd.Tests.Views"); |
||||
Assert.Multiple(() => { |
||||
Assert.That(File.Exists(Path.Combine(documentDirectory, "DeepPage.xaml")), Is.True, "the document"); |
||||
Assert.That(File.Exists(Path.Combine(documentDirectory, "DeepPage.xaml.cs")), Is.True, "its code-behind"); |
||||
}); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task DocumentsFollowTheNamespaceDirectoriesToo() |
||||
{ |
||||
await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "--nested-directories", "-o", outputDirectory); |
||||
|
||||
string documentDirectory = Path.Combine(outputDirectory, "ICSharpCode", "ILSpyCmd", "Tests", "Views"); |
||||
Assert.Multiple(() => { |
||||
Assert.That(File.Exists(Path.Combine(documentDirectory, "DeepPage.xaml")), Is.True, "the document"); |
||||
Assert.That(File.Exists(Path.Combine(documentDirectory, "DeepPage.xaml.cs")), Is.True, "its code-behind"); |
||||
}); |
||||
} |
||||
|
||||
[Test] |
||||
public async Task TheOldOptInFlagStillWorks() |
||||
{ |
||||
// It is documented and scripted against; asking for what is now the default has to
|
||||
// keep meaning the same thing.
|
||||
var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory, "--decompile-baml"); |
||||
|
||||
Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); |
||||
Assert.That(File.Exists(Path.Combine(outputDirectory, "mainwindow.xaml")), Is.True); |
||||
} |
||||
} |
||||
} |
||||
Binary file not shown.
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
<Label xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" DataContext="{Binding Blub}" Content="{Binding Path=Blah, StringFormat={}{0} items}"> |
||||
<Label xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" DataContext="{Binding Blub}" Content="{Binding Path=Blah, StringFormat='{}{0} items'}"> |
||||
<FrameworkElement.Style> |
||||
<Style /> |
||||
</FrameworkElement.Style> |
||||
|
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Baml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// .NET ships a WindowsBase facade on every platform, so the assembly resolves everywhere - but
|
||||
/// it carries none of the types BAML means by it, because those live in the WindowsDesktop
|
||||
/// runtime pack. A well-known type that only exists in the real assembly then resolves to
|
||||
/// nothing, and the whole resource is lost: ten of the BAML entries in a DevExpress theme
|
||||
/// assembly are unreadable on a machine without WPF for exactly this reason.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class FacadeAssemblyTests |
||||
{ |
||||
static ushort TypeId(KnownTypes type) => unchecked((ushort)-(short)type); |
||||
|
||||
static ushort MemberId(KnownMembers member) => unchecked((ushort)-(short)member); |
||||
|
||||
static MemoryStream CreateBaml(params BamlRecord[] records) |
||||
{ |
||||
var version = new BamlDocument.BamlVersion { Major = 0, Minor = 0x60 }; |
||||
var document = new BamlDocument { |
||||
Signature = "MSBAML", |
||||
ReaderVersion = version, |
||||
UpdaterVersion = version, |
||||
WriterVersion = version |
||||
}; |
||||
document.Add(new DocumentStartRecord()); |
||||
document.AddRange(records); |
||||
document.Add(new DocumentEndRecord()); |
||||
|
||||
var stream = new MemoryStream(); |
||||
BamlWriter.WriteDocument(document, stream); |
||||
stream.Position = 0; |
||||
return stream; |
||||
} |
||||
|
||||
static string Decompile(Stream baml) |
||||
{ |
||||
var location = typeof(FacadeAssemblyTests).Assembly.Location; |
||||
using var fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, fileStream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
var decompiler = new XamlDecompiler(new BamlDecompilerTypeSystem(file, resolver), |
||||
new BamlDecompilerSettings()); |
||||
return decompiler.Decompile(baml).Xaml.ToString(); |
||||
} |
||||
|
||||
[Test] |
||||
public void ATypeOfTheRealWindowsBaseStillDecompiles() |
||||
{ |
||||
// System.Windows.Size is a well-known BAML type of WindowsBase, and one the facade does
|
||||
// not have.
|
||||
string xaml = Decompile(CreateBaml( |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new PropertyComplexStartRecord { AttributeId = MemberId(KnownMembers.Button_Content) }, |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Size) }, |
||||
new ElementEndRecord(), |
||||
new PropertyComplexEndRecord(), |
||||
new ElementEndRecord())); |
||||
|
||||
Assert.That(xaml, Does.Contain("Size"), xaml); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Baml; |
||||
using ICSharpCode.BamlDecompiler.Xaml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// A markup extension is written back as text that the XAML parser reads again, and its
|
||||
/// grammar gives ',' '=' '{' '}' and the quote characters a meaning. A value carrying one of
|
||||
/// them has to be quoted, or the parser splits it into name/value pairs that do not exist -
|
||||
/// the reported case is DevExpress' {DXBinding Expr='...'}, whose expressions are full of
|
||||
/// commas and equals signs.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class MarkupExtensionQuotingTests |
||||
{ |
||||
static ushort TypeId(KnownTypes type) => unchecked((ushort)-(short)type); |
||||
|
||||
static ushort MemberId(KnownMembers member) => unchecked((ushort)-(short)member); |
||||
|
||||
[TestCase("plain", ExpectedResult = "plain", TestName = "AnOrdinaryValueIsLeftAlone")] |
||||
[TestCase("with space", ExpectedResult = "with space", TestName = "InteriorWhitespaceNeedsNoQuotes")] |
||||
[TestCase("a, b", ExpectedResult = "'a, b'", TestName = "CommaSeparatesArguments")] |
||||
[TestCase("a = b", ExpectedResult = "'a = b'", TestName = "EqualsStartsANamedArgument")] |
||||
[TestCase("{}{0} items", ExpectedResult = "'{}{0} items'", TestName = "AnEscapedLeadingBraceIsQuoted")] |
||||
[TestCase("{x:Static local:Thing.Value}", ExpectedResult = "{x:Static local:Thing.Value}", TestName = "ANestedExtensionStaysOne")] |
||||
[TestCase("Element[{http://ns}Name].Value", ExpectedResult = "Element[{http://ns}Name].Value", TestName = "BracesInsideAValueAreNotGrammar")]
|
||||
[TestCase("Date: {0:dddd, MMMM dd}", ExpectedResult = "'Date: {0:dddd, MMMM dd}'", TestName = "ACommaInsideBracesStillSeparates")] |
||||
[TestCase("a}b", ExpectedResult = "'a}b'", TestName = "AStrayClosingBraceWouldEndTheExtension")] |
||||
[TestCase("a{b", ExpectedResult = "'a{b'", TestName = "AStrayOpeningBraceWouldStartAnExtension")] |
||||
[TestCase(" padded ", ExpectedResult = "' padded '", TestName = "EdgeWhitespaceWouldBeTrimmed")] |
||||
[TestCase("", ExpectedResult = "''", TestName = "AnEmptyValueNeedsToStayEmpty")] |
||||
[TestCase("it's", ExpectedResult = @"'it\'s'", TestName = "TheQuoteCharacterIsEscaped")] |
||||
[TestCase(@"back\slash", ExpectedResult = @"'back\\slash'", TestName = "TheEscapeCharacterIsEscaped")] |
||||
public string QuotingRules(string value) |
||||
{ |
||||
return XamlUtils.QuoteMarkupExtensionValue(value); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Decompiles against this test assembly as the main module: the BAML built here refers
|
||||
/// only to well-known WPF types, which the decompiler resolves without it.
|
||||
/// </summary>
|
||||
static string Decompile(Stream baml) |
||||
{ |
||||
var location = typeof(MarkupExtensionQuotingTests).Assembly.Location; |
||||
using var fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, fileStream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
var decompiler = new XamlDecompiler(new BamlDecompilerTypeSystem(file, resolver), |
||||
new BamlDecompilerSettings()); |
||||
return decompiler.Decompile(baml).Xaml.ToString(); |
||||
} |
||||
|
||||
static MemoryStream CreateBaml(params BamlRecord[] records) |
||||
{ |
||||
var version = new BamlDocument.BamlVersion { Major = 0, Minor = 0x60 }; |
||||
var document = new BamlDocument { |
||||
Signature = "MSBAML", |
||||
ReaderVersion = version, |
||||
UpdaterVersion = version, |
||||
WriterVersion = version |
||||
}; |
||||
document.Add(new DocumentStartRecord()); |
||||
document.AddRange(records); |
||||
document.Add(new DocumentEndRecord()); |
||||
|
||||
var stream = new MemoryStream(); |
||||
BamlWriter.WriteDocument(document, stream); |
||||
stream.Position = 0; |
||||
return stream; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Builds "<Button Content="{StaticResource <value>}" />", whose resource key is
|
||||
/// the single argument of the extension.
|
||||
/// </summary>
|
||||
static string DecompileExtensionArgument(string value) |
||||
{ |
||||
return Decompile(CreateBaml( |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new StringInfoRecord { StringId = 0, Value = value }, |
||||
new PropertyWithExtensionRecord { |
||||
AttributeId = MemberId(KnownMembers.Button_Content), |
||||
Flags = (ushort)KnownTypes.StaticResourceExtension, |
||||
ValueId = 0 |
||||
}, |
||||
new ElementEndRecord())); |
||||
} |
||||
|
||||
[Test] |
||||
public void AnArgumentCarryingACommaIsQuoted() |
||||
{ |
||||
string xaml = DecompileExtensionArgument("ctor, arg = with comma"); |
||||
|
||||
Assert.That(xaml, Does.Contain("{StaticResource 'ctor, arg = with comma'}")); |
||||
} |
||||
|
||||
[Test] |
||||
public void AnOrdinaryArgumentIsNotQuoted() |
||||
{ |
||||
// Quoting everything would change every document ILSpy prints today.
|
||||
string xaml = DecompileExtensionArgument("MyResourceKey"); |
||||
|
||||
Assert.That(xaml, Does.Contain("{StaticResource MyResourceKey}")); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Handlers; |
||||
using ICSharpCode.BamlDecompiler.Xaml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// A type of the assembly being decompiled with a CLR property of its own called "Name".
|
||||
/// </summary>
|
||||
public class HelperWithItsOwnName |
||||
{ |
||||
public string Name { get; set; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// x:Name is the directive that registers a name for an element, and it is recorded as the
|
||||
/// runtime name property of that element - FrameworkElement.Name for everything WPF, a property
|
||||
/// of the framework rather than of the assembly being decompiled. Writing the directive for a
|
||||
/// type's own property of the same name registers a name and leaves the property unset, which
|
||||
/// still compiles and silently means something else (issue #2253).
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class RuntimeNamePropertyTests |
||||
{ |
||||
static ICompilation compilation; |
||||
|
||||
[OneTimeSetUp] |
||||
public void LoadTestAssembly() |
||||
{ |
||||
string location = typeof(RuntimeNamePropertyTests).Assembly.Location; |
||||
using var fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, fileStream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
compilation = new BamlDecompilerTypeSystem(file, resolver); |
||||
} |
||||
|
||||
static XamlType XamlTypeOf(Type type) |
||||
{ |
||||
var definition = compilation.FindType(type).GetDefinition(); |
||||
return new XamlType(definition.ParentModule, definition.ParentModule.FullAssemblyName, |
||||
definition.Namespace, definition.Name) { |
||||
ResolvedType = definition |
||||
}; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// A property of <paramref name="declaringType"/>, the way the decompiler resolves one from
|
||||
/// the type a BAML attribute record names as its owner.
|
||||
/// </summary>
|
||||
static XamlProperty PropertyOf(Type declaringType, string propertyName) |
||||
{ |
||||
var declaring = XamlTypeOf(declaringType); |
||||
return new XamlProperty(declaring, propertyName) { |
||||
ResolvedMember = declaring.ResolvedType.GetDefinition() |
||||
.GetProperties(p => p.Name == propertyName).FirstOrDefault() |
||||
}; |
||||
} |
||||
|
||||
[Test] |
||||
public void ATypesOwnNamePropertyIsNotTheRuntimeName() |
||||
{ |
||||
// <local:Helper Name="theName" />: the property belongs to the assembly being
|
||||
// decompiled, and the value has to reach it.
|
||||
var property = PropertyOf(typeof(HelperWithItsOwnName), "Name"); |
||||
|
||||
Assert.That(PropertyHandler.IsRuntimeNameOfElement(property, XamlTypeOf(typeof(HelperWithItsOwnName))), |
||||
Is.False); |
||||
} |
||||
|
||||
[Test] |
||||
public void ANameInheritedFromTheFrameworkIsTheRuntimeName() |
||||
{ |
||||
// <local:MyControl x:Name="theName" />: the document names the control as the owner of
|
||||
// the attribute, because that is the element it sits on, but the property comes from
|
||||
// the framework type the control derives from.
|
||||
var property = new XamlProperty(XamlTypeOf(typeof(HelperWithItsOwnName)), "Name") { |
||||
ResolvedMember = compilation.FindType(typeof(MemberInfo)).GetDefinition() |
||||
.GetProperties(p => p.Name == "Name").First() |
||||
}; |
||||
|
||||
Assert.That(PropertyHandler.IsRuntimeNameOfElement(property, XamlTypeOf(typeof(HelperWithItsOwnName))), |
||||
Is.True); |
||||
} |
||||
|
||||
[Test] |
||||
public void AnElementFromOutsideTheAssemblyKeepsItsProperty() |
||||
{ |
||||
var property = PropertyOf(typeof(Uri), "Name"); |
||||
|
||||
Assert.That(PropertyHandler.IsRuntimeNameOfElement(property, XamlTypeOf(typeof(Uri))), Is.False); |
||||
} |
||||
|
||||
[Test] |
||||
public void APropertyOfAnotherNameIsNeverTheRuntimeName() |
||||
{ |
||||
var property = PropertyOf(typeof(Uri), "Title"); |
||||
|
||||
Assert.That(PropertyHandler.IsRuntimeNameOfElement(property, XamlTypeOf(typeof(HelperWithItsOwnName))), |
||||
Is.False); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Baml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// StartupUri is written in App.xaml but compiled into App.g.cs, not into the BAML: the markup
|
||||
/// compiler turns the attribute into an assignment inside InitializeComponent. The project
|
||||
/// exporter deletes the generated members, so without recovering the assignment the exported
|
||||
/// application builds and then opens no window (issue #2253).
|
||||
/// </summary>
|
||||
public class TestApplication |
||||
{ |
||||
public Uri StartupUri { get; set; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// What the markup compiler generates for an Application with a StartupUri.
|
||||
/// </summary>
|
||||
public class AppWithStartupUri : TestApplication |
||||
{ |
||||
public void InitializeComponent() |
||||
{ |
||||
StartupUri = new Uri("MainWindow.xaml", UriKind.Relative); |
||||
Uri resourceLocator = new Uri("/Demo;component/app.xaml", UriKind.Relative); |
||||
GC.KeepAlive(resourceLocator); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The same without one, which must stay without one.
|
||||
/// </summary>
|
||||
public class AppWithoutStartupUri : TestApplication |
||||
{ |
||||
public void InitializeComponent() |
||||
{ |
||||
Uri resourceLocator = new Uri("/Demo;component/app.xaml", UriKind.Relative); |
||||
GC.KeepAlive(resourceLocator); |
||||
} |
||||
} |
||||
|
||||
[TestFixture] |
||||
public class StartupUriTests |
||||
{ |
||||
static MemoryStream CreateBaml(params BamlRecord[] records) |
||||
{ |
||||
var version = new BamlDocument.BamlVersion { Major = 0, Minor = 0x60 }; |
||||
var document = new BamlDocument { |
||||
Signature = "MSBAML", |
||||
ReaderVersion = version, |
||||
UpdaterVersion = version, |
||||
WriterVersion = version |
||||
}; |
||||
document.Add(new DocumentStartRecord()); |
||||
document.AddRange(records); |
||||
document.Add(new DocumentEndRecord()); |
||||
|
||||
var stream = new MemoryStream(); |
||||
BamlWriter.WriteDocument(document, stream); |
||||
stream.Position = 0; |
||||
return stream; |
||||
} |
||||
|
||||
static string Decompile(Stream baml) |
||||
{ |
||||
var location = typeof(StartupUriTests).Assembly.Location; |
||||
using var fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, fileStream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
var decompiler = new XamlDecompiler(new BamlDecompilerTypeSystem(file, resolver), |
||||
new BamlDecompilerSettings()); |
||||
return decompiler.Decompile(baml).Xaml.ToString(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// A document whose root is <paramref name="typeName"/> of this assembly, which is what
|
||||
/// makes the BAML decompiler treat it as the code-behind class of the document.
|
||||
/// </summary>
|
||||
static string DecompileDocumentOf(string typeName) |
||||
{ |
||||
return Decompile(CreateBaml( |
||||
new AssemblyInfoRecord { AssemblyId = 0, AssemblyFullName = "ILSpy.BamlDecompiler.Tests" }, |
||||
new TypeInfoRecord { TypeId = 0, AssemblyId = 0, TypeFullName = "ILSpy.BamlDecompiler.Tests." + typeName }, |
||||
new ElementStartRecord { TypeId = 0 }, |
||||
new ElementEndRecord())); |
||||
} |
||||
|
||||
[Test] |
||||
public void TheStartupUriOfTheGeneratedCodeComesBackAsAnAttribute() |
||||
{ |
||||
string xaml = DecompileDocumentOf(nameof(AppWithStartupUri)); |
||||
|
||||
Assert.That(xaml, Does.Contain(@"StartupUri=""MainWindow.xaml"""), xaml); |
||||
} |
||||
|
||||
[Test] |
||||
public void NoStartupUriIsInventedForADocumentThatHasNone() |
||||
{ |
||||
string xaml = DecompileDocumentOf(nameof(AppWithoutStartupUri)); |
||||
|
||||
Assert.That(xaml, Does.Not.Contain("StartupUri=\""), xaml); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// 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.IO; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Baml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// A document that already binds a prefix to a CLR namespace must keep using it. The document
|
||||
/// records the assembly the way it was written - "mscorlib" - while a well-known type carries
|
||||
/// the assembly it actually resolves to, which is the implementation assembly of whatever
|
||||
/// runtime ILSpy runs on. Comparing the two by name never matches, and every use of such a
|
||||
/// type then declared a second prefix for a namespace the root already had (issue #2253).
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class XmlnsDeclarationPlacementTests |
||||
{ |
||||
const string MscorlibFullName = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; |
||||
const string SystemNamespaceXmlns = "clr-namespace:System;assembly=mscorlib"; |
||||
|
||||
static ushort TypeId(KnownTypes type) => unchecked((ushort)-(short)type); |
||||
|
||||
static ushort MemberId(KnownMembers member) => unchecked((ushort)-(short)member); |
||||
|
||||
static MemoryStream CreateBaml(params BamlRecord[] records) |
||||
{ |
||||
var version = new BamlDocument.BamlVersion { Major = 0, Minor = 0x60 }; |
||||
var document = new BamlDocument { |
||||
Signature = "MSBAML", |
||||
ReaderVersion = version, |
||||
UpdaterVersion = version, |
||||
WriterVersion = version |
||||
}; |
||||
document.Add(new DocumentStartRecord()); |
||||
document.AddRange(records); |
||||
document.Add(new DocumentEndRecord()); |
||||
|
||||
var stream = new MemoryStream(); |
||||
BamlWriter.WriteDocument(document, stream); |
||||
stream.Position = 0; |
||||
return stream; |
||||
} |
||||
|
||||
static string Decompile(Stream baml) |
||||
{ |
||||
var location = typeof(XmlnsDeclarationPlacementTests).Assembly.Location; |
||||
using var fileStream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, fileStream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
var decompiler = new XamlDecompiler(new BamlDecompilerTypeSystem(file, resolver), |
||||
new BamlDecompilerSettings()); |
||||
return decompiler.Decompile(baml).Xaml.ToString(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Builds a document whose root binds "sys" to the System namespace of mscorlib and then
|
||||
/// puts a System.String into the tree.
|
||||
/// </summary>
|
||||
static string DecompileDocumentUsingStringUnderAPrefixedRoot() |
||||
{ |
||||
return Decompile(CreateBaml( |
||||
new AssemblyInfoRecord { AssemblyId = 0, AssemblyFullName = MscorlibFullName }, |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new XmlnsPropertyRecord { |
||||
Prefix = "sys", |
||||
XmlNamespace = SystemNamespaceXmlns, |
||||
AssemblyIds = new ushort[] { 0 } |
||||
}, |
||||
new PropertyComplexStartRecord { AttributeId = MemberId(KnownMembers.Button_Content) }, |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.String) }, |
||||
new ElementEndRecord(), |
||||
new PropertyComplexEndRecord(), |
||||
new ElementEndRecord())); |
||||
} |
||||
|
||||
[Test] |
||||
public void ThePrefixTheDocumentDeclaredIsTheOneThatGetsUsed() |
||||
{ |
||||
string xaml = DecompileDocumentUsingStringUnderAPrefixedRoot(); |
||||
|
||||
Assert.That(xaml, Does.Contain("<sys:String"), xaml); |
||||
} |
||||
|
||||
[Test] |
||||
public void NoSecondPrefixIsDeclaredForANamespaceTheRootAlreadyBinds() |
||||
{ |
||||
string xaml = DecompileDocumentUsingStringUnderAPrefixedRoot(); |
||||
|
||||
Assert.Multiple(() => { |
||||
Assert.That(xaml, Does.Not.Contain("xmlns:system="), xaml); |
||||
Assert.That(xaml, Does.Not.Contain("System.Private.CoreLib"), xaml); |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue