Browse Source

Merge pull request #3201 from icsharpcode/replaceargvinterop

Replace native interop CommandLineToArgvW with parsing in Process.Unix.cs from System.Diagnostics.Process
pull/3203/head
Siegfried Pammer 1 year ago committed by GitHub
parent
commit
1971f6961b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 220
      ILSpy/CommandLineTools.cs
  3. 4
      ILSpy/Commands/ScopeSearchToAssembly.cs
  4. 4
      ILSpy/Commands/ScopeSearchToNamespace.cs
  5. 110
      ILSpy/NativeMethods.cs
  6. 2
      ILSpy/Options/MiscSettingsViewModel.cs
  7. 4
      ILSpy/Search/SearchPane.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602</NoWarn>
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>

220
ILSpy/CommandLineTools.cs

@ -0,0 +1,220 @@ @@ -0,0 +1,220 @@
// Copyright (c) 2024 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.Text;
namespace ICSharpCode.ILSpy
{
public class CommandLineTools
{
/// <summary>
/// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules.
/// </summary>
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static string[] CommandLineToArgumentArray(string commandLine)
{
if (string.IsNullOrEmpty(commandLine))
return Array.Empty<string>();
var results = new List<string>();
ParseArgv.ParseArgumentsIntoList(commandLine, results);
return results.ToArray();
}
static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' };
/// <summary>
/// Escapes a set of arguments according to the CommandLineToArgvW rules.
/// </summary>
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static string ArgumentArrayToCommandLine(params string[] arguments)
{
if (arguments == null)
return null;
StringBuilder b = new StringBuilder();
for (int i = 0; i < arguments.Length; i++)
{
if (i > 0)
b.Append(' ');
AppendArgument(b, arguments[i]);
}
return b.ToString();
}
static void AppendArgument(StringBuilder b, string arg)
{
if (arg == null)
{
return;
}
if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0)
{
b.Append(arg);
}
else
{
b.Append('"');
for (int j = 0; ; j++)
{
int backslashCount = 0;
while (j < arg.Length && arg[j] == '\\')
{
backslashCount++;
j++;
}
if (j == arg.Length)
{
b.Append('\\', backslashCount * 2);
break;
}
else if (arg[j] == '"')
{
b.Append('\\', backslashCount * 2 + 1);
b.Append('"');
}
else
{
b.Append('\\', backslashCount);
b.Append(arg[j]);
}
}
b.Append('"');
}
}
}
// Source: https://github.com/dotnet/runtime/blob/bc9fc5a774d96f95abe0ea5c90fac48b38ed2e67/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L574-L606
// Minor adaptations in GetNextArgument (ValueStringBuilder replaced), otherwise kept as identical as possible
public static class ParseArgv
{
/// <summary>Parses a command-line argument string into a list of arguments.</summary>
/// <param name="arguments">The argument string.</param>
/// <param name="results">The list into which the component arguments should be stored.</param>
/// <remarks>
/// This follows the rules outlined in "Parsing C++ Command-Line Arguments" at
/// https://msdn.microsoft.com/en-us/library/17w5ykft.aspx.
/// </remarks>
public static void ParseArgumentsIntoList(string arguments, List<string> results)
{
// Iterate through all of the characters in the argument string.
for (int i = 0; i < arguments.Length; i++)
{
while (i < arguments.Length && (arguments[i] == ' ' || arguments[i] == '\t'))
i++;
if (i == arguments.Length)
break;
results.Add(GetNextArgument(arguments, ref i));
}
}
private static string GetNextArgument(string arguments, ref int i)
{
var currentArgument = new StringBuilder();
bool inQuotes = false;
while (i < arguments.Length)
{
// From the current position, iterate through contiguous backslashes.
int backslashCount = 0;
while (i < arguments.Length && arguments[i] == '\\')
{
i++;
backslashCount++;
}
if (backslashCount > 0)
{
if (i >= arguments.Length || arguments[i] != '"')
{
// Backslashes not followed by a double quote:
// they should all be treated as literal backslashes.
currentArgument.Append('\\', backslashCount);
}
else
{
// Backslashes followed by a double quote:
// - Output a literal slash for each complete pair of slashes
// - If one remains, use it to make the subsequent quote a literal.
currentArgument.Append('\\', backslashCount / 2);
if (backslashCount % 2 != 0)
{
currentArgument.Append('"');
i++;
}
}
continue;
}
char c = arguments[i];
// If this is a double quote, track whether we're inside of quotes or not.
// Anything within quotes will be treated as a single argument, even if
// it contains spaces.
if (c == '"')
{
if (inQuotes && i < arguments.Length - 1 && arguments[i + 1] == '"')
{
// Two consecutive double quotes inside an inQuotes region should result in a literal double quote
// (the parser is left in the inQuotes region).
// This behavior is not part of the spec of code:ParseArgumentsIntoList, but is compatible with CRT
// and .NET Framework.
currentArgument.Append('"');
i++;
}
else
{
inQuotes = !inQuotes;
}
i++;
continue;
}
// If this is a space/tab and we're not in quotes, we're done with the current
// argument, it should be added to the results and then reset for the next one.
if ((c == ' ' || c == '\t') && !inQuotes)
{
break;
}
// Nothing special; add the character to the current argument.
currentArgument.Append(c);
i++;
}
return currentArgument.ToString();
}
}
}

4
ILSpy/Commands/ScopeSearchToAssembly.cs

@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy @@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy
// asmName cannot be null here, because Execute is only called if IsEnabled/IsVisible return true.
string asmName = GetAssembly(context)!;
string searchTerm = MainWindow.Instance.SearchPane.SearchTerm;
string[] args = NativeMethods.CommandLineToArgumentArray(searchTerm);
string[] args = CommandLineTools.CommandLineToArgumentArray(searchTerm);
bool replaced = false;
for (int i = 0; i < args.Length; i++)
{
@ -50,7 +50,7 @@ namespace ICSharpCode.ILSpy @@ -50,7 +50,7 @@ namespace ICSharpCode.ILSpy
}
else
{
searchTerm = NativeMethods.ArgumentArrayToCommandLine(args);
searchTerm = CommandLineTools.ArgumentArrayToCommandLine(args);
}
MainWindow.Instance.SearchPane.SearchTerm = searchTerm;
}

4
ILSpy/Commands/ScopeSearchToNamespace.cs

@ -30,7 +30,7 @@ namespace ICSharpCode.ILSpy @@ -30,7 +30,7 @@ namespace ICSharpCode.ILSpy
{
string ns = GetNamespace(context);
string searchTerm = MainWindow.Instance.SearchPane.SearchTerm;
string[] args = NativeMethods.CommandLineToArgumentArray(searchTerm);
string[] args = CommandLineTools.CommandLineToArgumentArray(searchTerm);
bool replaced = false;
for (int i = 0; i < args.Length; i++)
{
@ -47,7 +47,7 @@ namespace ICSharpCode.ILSpy @@ -47,7 +47,7 @@ namespace ICSharpCode.ILSpy
}
else
{
searchTerm = NativeMethods.ArgumentArrayToCommandLine(args);
searchTerm = CommandLineTools.ArgumentArrayToCommandLine(args);
}
MainWindow.Instance.SearchPane.SearchTerm = searchTerm;
}

110
ILSpy/NativeMethods.cs

@ -56,116 +56,6 @@ namespace ICSharpCode.ILSpy @@ -56,116 +56,6 @@ namespace ICSharpCode.ILSpy
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern unsafe char** CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")]
[DllImport("kernel32.dll")]
static extern IntPtr LocalFree(IntPtr hMem);
#region CommandLine <-> Argument Array
/// <summary>
/// Decodes a command line into an array of arguments according to the CommandLineToArgvW rules.
/// </summary>
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static unsafe string[] CommandLineToArgumentArray(string commandLine)
{
if (string.IsNullOrEmpty(commandLine))
return new string[0];
int numberOfArgs;
char** arr = CommandLineToArgvW(commandLine, out numberOfArgs);
if (arr == null)
throw new Win32Exception();
try
{
string[] result = new string[numberOfArgs];
for (int i = 0; i < numberOfArgs; i++)
{
result[i] = new string(arr[i]);
}
return result;
}
finally
{
// Free memory obtained by CommandLineToArgW.
LocalFree(new IntPtr(arr));
}
}
static readonly char[] charsNeedingQuoting = { ' ', '\t', '\n', '\v', '"' };
/// <summary>
/// Escapes a set of arguments according to the CommandLineToArgvW rules.
/// </summary>
/// <remarks>
/// Command line parsing rules:
/// - 2n backslashes followed by a quotation mark produce n backslashes, and the quotation mark is considered to be the end of the argument.
/// - (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark.
/// - n backslashes not followed by a quotation mark simply produce n backslashes.
/// </remarks>
public static string ArgumentArrayToCommandLine(params string[] arguments)
{
if (arguments == null)
return null;
StringBuilder b = new StringBuilder();
for (int i = 0; i < arguments.Length; i++)
{
if (i > 0)
b.Append(' ');
AppendArgument(b, arguments[i]);
}
return b.ToString();
}
static void AppendArgument(StringBuilder b, string arg)
{
if (arg == null)
{
return;
}
if (arg.Length > 0 && arg.IndexOfAny(charsNeedingQuoting) < 0)
{
b.Append(arg);
}
else
{
b.Append('"');
for (int j = 0; ; j++)
{
int backslashCount = 0;
while (j < arg.Length && arg[j] == '\\')
{
backslashCount++;
j++;
}
if (j == arg.Length)
{
b.Append('\\', backslashCount * 2);
break;
}
else if (arg[j] == '"')
{
b.Append('\\', backslashCount * 2 + 1);
b.Append('"');
}
else
{
b.Append('\\', backslashCount);
b.Append(arg[j]);
}
}
b.Append('"');
}
}
#endregion
public unsafe static string GetProcessNameFromWindow(IntPtr hWnd)
{
int processId;

2
ILSpy/Options/MiscSettingsViewModel.cs

@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.Options @@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.Options
private void AddRemoveShellIntegration(object obj)
{
string commandLine = NativeMethods.ArgumentArrayToCommandLine(Path.ChangeExtension(Assembly.GetEntryAssembly().Location, ".exe")) + " \"%L\"";
string commandLine = CommandLineTools.ArgumentArrayToCommandLine(Path.ChangeExtension(Assembly.GetEntryAssembly().Location, ".exe")) + " \"%L\"";
if (RegistryEntriesExist())
{
if (MessageBox.Show(string.Format(Properties.Resources.RemoveShellIntegrationMessage, commandLine), "ILSpy", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)

4
ILSpy/Search/SearchPane.cs

@ -293,7 +293,7 @@ namespace ICSharpCode.ILSpy.Search @@ -293,7 +293,7 @@ namespace ICSharpCode.ILSpy.Search
SearchRequest Parse(string input)
{
string[] parts = NativeMethods.CommandLineToArgumentArray(input);
string[] parts = CommandLineTools.CommandLineToArgumentArray(input);
SearchRequest request = new SearchRequest();
List<string> keywords = new List<string>();
@ -338,7 +338,7 @@ namespace ICSharpCode.ILSpy.Search @@ -338,7 +338,7 @@ namespace ICSharpCode.ILSpy.Search
string searchTerm = part.Substring(prefixLength + delimiterLength).Trim();
if (searchTerm.Length > 0)
{
searchTerm = NativeMethods.CommandLineToArgumentArray(searchTerm)[0];
searchTerm = CommandLineTools.CommandLineToArgumentArray(searchTerm)[0];
}
else
{

Loading…
Cancel
Save