mirror of https://github.com/icsharpcode/ILSpy.git
16 changed files with 527 additions and 7 deletions
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
// 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.Linq; |
||||
using System.Xml.Linq; |
||||
|
||||
using ICSharpCode.BamlDecompiler.Xaml; |
||||
|
||||
namespace ICSharpCode.BamlDecompiler.Rewrite |
||||
{ |
||||
/// <summary>
|
||||
/// Escapes attribute values, text and comments that carry characters XML cannot represent -
|
||||
/// a string record from an obfuscated assembly may hold any byte sequence. Without this the
|
||||
/// document builds fine and only throws when it is written, taking the resource with it.
|
||||
/// Names and namespace URIs are escaped where they are built, so this pass only has to cover
|
||||
/// the content.
|
||||
/// </summary>
|
||||
internal class EscapeInvalidXmlCharactersRewritePass : IRewritePass |
||||
{ |
||||
public void Run(XamlContext ctx, XDocument document) |
||||
{ |
||||
foreach (var element in document.Descendants()) |
||||
{ |
||||
foreach (var attribute in element.Attributes()) |
||||
{ |
||||
// Namespace declarations carry a URI that was escaped when the XNamespace was
|
||||
// created; rewriting it here would desync it from the names using it.
|
||||
if (!attribute.IsNamespaceDeclaration) |
||||
attribute.Value = XamlUtils.EscapeInvalidXmlCharacters(attribute.Value); |
||||
} |
||||
} |
||||
|
||||
foreach (var node in document.DescendantNodes().ToList()) |
||||
{ |
||||
switch (node) |
||||
{ |
||||
case XText text: |
||||
text.Value = XamlUtils.EscapeInvalidXmlCharacters(text.Value); |
||||
break; |
||||
case XComment comment: |
||||
comment.Value = XamlUtils.EscapeInvalidXmlCharacters(comment.Value); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Binary file not shown.
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<ContextMenu x:Class="ILSpy.BamlDecompiler.Tests.Cases.Issue1688" xmlns="http://schemas.microsoft.com/netfx/2007/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cases="clr-namespace:ILSpy.BamlDecompiler.Tests.Cases"> |
||||
<MenuItem Header="Assign Place" Click="Click_AssignPlace" /> |
||||
<MenuItem Header="Assign Move" Click="Click_AssignMove" /> |
||||
</ContextMenu> |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
// 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.Windows; |
||||
using System.Windows.Controls; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests.Cases |
||||
{ |
||||
/// <summary>
|
||||
/// Interaction logic for Issue1688.xaml
|
||||
/// </summary>
|
||||
public partial class Issue1688 : ContextMenu |
||||
{ |
||||
public Issue1688() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
void Click_AssignPlace(object sender, RoutedEventArgs e) |
||||
{ |
||||
} |
||||
|
||||
void Click_AssignMove(object sender, RoutedEventArgs e) |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
@ -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.IO; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.BamlDecompiler.Baml; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// Obfuscators put characters into BAML strings that XML cannot carry at all - not even as a
|
||||
/// numeric character reference. They have to be escaped before they reach the XDocument;
|
||||
/// otherwise writing the decompiled XAML throws and the whole resource is lost.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class InvalidXmlCharacterTests |
||||
{ |
||||
static ushort TypeId(KnownTypes type) => unchecked((ushort)-(short)type); |
||||
|
||||
static ushort MemberId(KnownMembers member) => unchecked((ushort)-(short)member); |
||||
|
||||
/// <summary>
|
||||
/// Builds a BAML stream out of <paramref name="records"/>, wrapped in the document
|
||||
/// start/end records and the header the reader insists on.
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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(InvalidXmlCharacterTests).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 ControlCharacterInPropertyValue_IsEscaped() |
||||
{ |
||||
string xaml = Decompile(CreateBaml( |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new PropertyRecord { |
||||
AttributeId = MemberId(KnownMembers.Button_Content), |
||||
Value = "a\u0018b" |
||||
}, |
||||
new ElementEndRecord())); |
||||
|
||||
Assert.That(xaml, Does.Contain(@"Content=""a\u0018b""")); |
||||
} |
||||
|
||||
[Test] |
||||
public void ControlCharacterInNamespaceUri_IsEscaped() |
||||
{ |
||||
// The URI ends up both in the xmlns declaration and in the namespace of every element
|
||||
// name, so it cannot be repaired after the document has been built.
|
||||
string xaml = Decompile(CreateBaml( |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new XmlnsPropertyRecord { |
||||
Prefix = "obf", |
||||
XmlNamespace = "clr-namespace:Obfuscated\u0018Namespace", |
||||
AssemblyIds = new ushort[0] |
||||
}, |
||||
new ElementEndRecord())); |
||||
|
||||
Assert.That(xaml, Does.Contain(@"xmlns:obf=""clr-namespace:Obfuscated\u0018Namespace""")); |
||||
} |
||||
|
||||
[Test] |
||||
public void CharactersXmlCanCarry_AreLeftAlone() |
||||
{ |
||||
// Tab, newline and astral characters are valid XML content; escaping them would
|
||||
// change the output of every ordinary document.
|
||||
string xaml = Decompile(CreateBaml( |
||||
new ElementStartRecord { TypeId = TypeId(KnownTypes.Button) }, |
||||
new PropertyRecord { |
||||
AttributeId = MemberId(KnownMembers.Button_Content), |
||||
Value = "tab\tastral\U0001F600" |
||||
}, |
||||
new ElementEndRecord())); |
||||
|
||||
Assert.Multiple(() => { |
||||
Assert.That(xaml, Does.Contain("\U0001F600"), "the surrogate pair stays intact"); |
||||
Assert.That(xaml, Does.Not.Contain("\\u"), "nothing valid gets escaped"); |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
// 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 System.Xml.Linq; |
||||
|
||||
using ICSharpCode.BamlDecompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
using ILSpy.BamlDecompiler.Tests; |
||||
|
||||
// This assembly plays the role of an assembly that maps one CLR namespace to two XML namespaces,
|
||||
// the way PresentationFramework maps its namespaces to both presentation namespaces.
|
||||
[assembly: System.Windows.Markup.XmlnsDefinition(XmlNamespaceResolutionTests.Winfx2006Presentation, XmlNamespaceResolutionTests.TestClrNamespace)] |
||||
[assembly: System.Windows.Markup.XmlnsDefinition(XmlNamespaceResolutionTests.Netfx2007Presentation, XmlNamespaceResolutionTests.TestClrNamespace)] |
||||
|
||||
namespace System.Windows.Markup |
||||
{ |
||||
/// <summary>
|
||||
/// Stand-in for the WPF attribute of the same name, which is unavailable on platforms without
|
||||
/// WPF. The BAML decompiler matches it by full name in metadata, so the declaring assembly does
|
||||
/// not matter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] |
||||
internal sealed class XmlnsDefinitionAttribute : Attribute |
||||
{ |
||||
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) |
||||
{ |
||||
XmlNamespace = xmlNamespace; |
||||
ClrNamespace = clrNamespace; |
||||
} |
||||
|
||||
public string XmlNamespace { get; } |
||||
public string ClrNamespace { get; } |
||||
} |
||||
} |
||||
|
||||
namespace ILSpy.BamlDecompiler.Tests |
||||
{ |
||||
/// <summary>
|
||||
/// Tests for the fallback used when neither the BAML xmlns records nor the PI mappings name an
|
||||
/// XML namespace for a type: which of the assembly's XmlnsDefinition mappings is picked.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class XmlNamespaceResolutionTests |
||||
{ |
||||
public const string Winfx2006Presentation = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; |
||||
public const string Netfx2007Presentation = "http://schemas.microsoft.com/netfx/2007/xaml/presentation"; |
||||
public const string TestClrNamespace = "ILSpy.BamlDecompiler.Tests"; |
||||
|
||||
static IModule GetTestAssemblyModule() |
||||
{ |
||||
var location = typeof(XmlNamespaceResolutionTests).Assembly.Location; |
||||
using var stream = new FileStream(location, FileMode.Open, FileAccess.Read); |
||||
var file = new PEFile(location, stream, streamOptions: PEStreamOptions.PrefetchEntireImage); |
||||
var resolver = new UniversalAssemblyResolver(location, throwOnError: false, |
||||
file.DetectTargetFrameworkId(), file.DetectRuntimePack()); |
||||
return new BamlDecompilerTypeSystem(file, resolver).MainModule; |
||||
} |
||||
|
||||
[Test] |
||||
public void PrefersPresentationNamespace_WhenDocumentDeclaresNothing() |
||||
{ |
||||
var xmlNs = XamlContext.TryGetXmlNamespace(GetTestAssemblyModule(), TestClrNamespace); |
||||
|
||||
Assert.That(xmlNs, Is.EqualTo(Winfx2006Presentation)); |
||||
} |
||||
|
||||
[Test] |
||||
public void PrefersNamespaceDeclaredByDocument_OverPresentationNamespace() |
||||
{ |
||||
// Issue #1688: the document binds the default prefix to the netfx/2007 presentation
|
||||
// namespace. Resolving its elements to the winfx/2006 one made the root start tag both
|
||||
// declare and redefine the default prefix, which XmlWriter rejects.
|
||||
var root = new XElement(XName.Get("Root", Netfx2007Presentation), |
||||
new XAttribute("xmlns", Netfx2007Presentation)); |
||||
|
||||
var xmlNs = XamlContext.TryGetXmlNamespace(GetTestAssemblyModule(), TestClrNamespace, root); |
||||
|
||||
Assert.That(xmlNs, Is.EqualTo(Netfx2007Presentation)); |
||||
} |
||||
|
||||
[Test] |
||||
public void PrefersNamespaceDeclaredByAncestor() |
||||
{ |
||||
var child = new XElement(XName.Get("Child", Netfx2007Presentation)); |
||||
new XElement(XName.Get("Root", Netfx2007Presentation), |
||||
new XAttribute("xmlns", Netfx2007Presentation), child); |
||||
|
||||
var xmlNs = XamlContext.TryGetXmlNamespace(GetTestAssemblyModule(), TestClrNamespace, child); |
||||
|
||||
Assert.That(xmlNs, Is.EqualTo(Netfx2007Presentation)); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue