Skip to content

Commit 85a9dfc

Browse files
authored
[wasm] Correctly escape library names when generating symbols for .c (dotnet#79007)
* [wasm] Correctly escape library names when generating symbols for .c files. Use the existing `FixupSymbolName` method for fixing library names too, when converting to symbols. * [wasm] *TableGenerator task: Cache the symbol name fixups .. as it is called frequently, and for repeated strings. For a consolewasm template build, we get 490 calls but only 140 of them are for unique strings. * Add tests Fixes dotnet#78992 .
1 parent c01ad86 commit 85a9dfc

File tree

4 files changed

+130
-67
lines changed

4 files changed

+130
-67
lines changed

src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using System.Linq;
7+
using System.Text;
78
using Xunit;
89
using Xunit.Abstractions;
910

@@ -551,6 +552,72 @@ public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture,
551552
Assert.Contains("square: 25", output);
552553
}
553554

555+
[Theory]
556+
[BuildAndRun(host: RunHost.Chrome, parameters: new object[] { new object[] {
557+
"with-hyphen",
558+
"with#hash-and-hyphen",
559+
"with.per.iod",
560+
"with🚀unicode#"
561+
} })]
562+
563+
public void CallIntoLibrariesWithNonAlphanumericCharactersInTheirNames(BuildArgs buildArgs, string[] libraryNames, RunHost host, string id)
564+
{
565+
buildArgs = ExpandBuildArgs(buildArgs,
566+
extraItems: @$"<NativeFileReference Include=""*.c"" />",
567+
extraProperties: buildArgs.AOT
568+
? string.Empty
569+
: "<WasmBuildNative>true</WasmBuildNative>");
570+
571+
int baseArg = 10;
572+
(_, string output) = BuildProject(buildArgs,
573+
id: id,
574+
new BuildProjectOptions(
575+
InitProject: () => GenerateSourceFiles(_projectDir!, baseArg),
576+
Publish: buildArgs.AOT,
577+
DotnetWasmFromRuntimePack: false
578+
));
579+
580+
output = RunAndTestWasmApp(buildArgs,
581+
buildDir: _projectDir,
582+
expectedExitCode: 42,
583+
host: host,
584+
id: id);
585+
586+
for (int i = 0; i < libraryNames.Length; i ++)
587+
{
588+
Assert.Contains($"square_{i}: {(i + baseArg) * (i + baseArg)}", output);
589+
}
590+
591+
void GenerateSourceFiles(string outputPath, int baseArg)
592+
{
593+
StringBuilder csBuilder = new($@"
594+
using System;
595+
using System.Runtime.InteropServices;
596+
");
597+
598+
StringBuilder dllImportsBuilder = new();
599+
for (int i = 0; i < libraryNames.Length; i ++)
600+
{
601+
dllImportsBuilder.AppendLine($"[DllImport(\"{libraryNames[i]}\")] static extern int square_{i}(int x);");
602+
csBuilder.AppendLine($@"Console.WriteLine($""square_{i}: {{square_{i}({i + baseArg})}}"");");
603+
604+
string nativeCode = $@"
605+
#include <stdarg.h>
606+
607+
int square_{i}(int x)
608+
{{
609+
return x * x;
610+
}}";
611+
File.WriteAllText(Path.Combine(outputPath, $"{libraryNames[i]}.c"), nativeCode);
612+
}
613+
614+
csBuilder.AppendLine("return 42;");
615+
csBuilder.Append(dllImportsBuilder);
616+
617+
File.WriteAllText(Path.Combine(outputPath, "Program.cs"), csBuilder.ToString());
618+
}
619+
}
620+
554621
private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id, string? verbosity = null, string extraProperties = "")
555622
{
556623
extraProperties += "<AllowUnsafeBlocks>true</AllowUnsafeBlocks><_WasmDevel>true</_WasmDevel>";

src/tasks/WasmAppBuilder/IcallTableGenerator.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Collections.Immutable;
7-
using System.Diagnostics;
8-
using System.Diagnostics.CodeAnalysis;
96
using System.IO;
107
using System.Linq;
118
using System.Text;
@@ -23,8 +20,13 @@ internal sealed class IcallTableGenerator
2320
private Dictionary<string, IcallClass> _runtimeIcalls = new Dictionary<string, IcallClass>();
2421

2522
private TaskLoggingHelper Log { get; set; }
23+
private readonly Func<string, string> _fixupSymbolName;
2624

27-
public IcallTableGenerator(TaskLoggingHelper log) => Log = log;
25+
public IcallTableGenerator(Func<string, string> fixupSymbolName, TaskLoggingHelper log)
26+
{
27+
Log = log;
28+
_fixupSymbolName = fixupSymbolName;
29+
}
2830

2931
//
3032
// Given the runtime generated icall table, and a set of assemblies, generate
@@ -86,7 +88,7 @@ private void EmitTable(StreamWriter w)
8688
if (assembly == "System.Private.CoreLib")
8789
aname = "corlib";
8890
else
89-
aname = assembly.Replace(".", "_");
91+
aname = _fixupSymbolName(assembly);
9092
w.WriteLine($"#define ICALL_TABLE_{aname} 1\n");
9193

9294
w.WriteLine($"static int {aname}_icall_indexes [] = {{");

src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using System.Collections.Generic;
6-
using System.Collections.Immutable;
7-
using System.Diagnostics;
85
using System.Diagnostics.CodeAnalysis;
9-
using System.IO;
106
using System.Linq;
117
using System.Text;
12-
using System.Text.Json;
13-
using System.Reflection;
148
using Microsoft.Build.Framework;
159
using Microsoft.Build.Utilities;
1610

17-
#nullable enable
18-
1911
public class ManagedToNativeGenerator : Task
2012
{
2113
[Required]
@@ -37,6 +29,11 @@ public class ManagedToNativeGenerator : Task
3729
[Output]
3830
public string[]? FileWrites { get; private set; }
3931

32+
private static readonly char[] s_charsToReplace = new[] { '.', '-', '+' };
33+
34+
// Avoid sharing this cache with all the invocations of this task throughout the build
35+
private readonly Dictionary<string, string> _symbolNameFixups = new();
36+
4037
public override bool Execute()
4138
{
4239
if (Assemblies!.Length == 0)
@@ -65,8 +62,8 @@ public override bool Execute()
6562

6663
private void ExecuteInternal()
6764
{
68-
var pinvoke = new PInvokeTableGenerator(Log);
69-
var icall = new IcallTableGenerator(Log);
65+
var pinvoke = new PInvokeTableGenerator(FixupSymbolName, Log);
66+
var icall = new IcallTableGenerator(FixupSymbolName, Log);
7067

7168
IEnumerable<string> cookies = Enumerable.Concat(
7269
pinvoke.Generate(PInvokeModules, Assemblies!, PInvokeOutputPath!),
@@ -80,4 +77,37 @@ private void ExecuteInternal()
8077
? new string[] { PInvokeOutputPath, IcallOutputPath, InterpToNativeOutputPath }
8178
: new string[] { PInvokeOutputPath, InterpToNativeOutputPath };
8279
}
80+
81+
public string FixupSymbolName(string name)
82+
{
83+
if (_symbolNameFixups.TryGetValue(name, out string? fixedName))
84+
return fixedName;
85+
86+
UTF8Encoding utf8 = new();
87+
byte[] bytes = utf8.GetBytes(name);
88+
StringBuilder sb = new();
89+
90+
foreach (byte b in bytes)
91+
{
92+
if ((b >= (byte)'0' && b <= (byte)'9') ||
93+
(b >= (byte)'a' && b <= (byte)'z') ||
94+
(b >= (byte)'A' && b <= (byte)'Z') ||
95+
(b == (byte)'_'))
96+
{
97+
sb.Append((char)b);
98+
}
99+
else if (s_charsToReplace.Contains((char)b))
100+
{
101+
sb.Append('_');
102+
}
103+
else
104+
{
105+
sb.Append($"_{b:X}_");
106+
}
107+
}
108+
109+
fixedName = sb.ToString();
110+
_symbolNameFixups[name] = fixedName;
111+
return fixedName;
112+
}
83113
}

src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313

1414
internal sealed class PInvokeTableGenerator
1515
{
16-
private static readonly char[] s_charsToReplace = new[] { '.', '-', '+' };
1716
private readonly Dictionary<Assembly, bool> _assemblyDisableRuntimeMarshallingAttributeCache = new();
1817

1918
private TaskLoggingHelper Log { get; set; }
19+
private readonly Func<string, string> _fixupSymbolName;
2020

21-
public PInvokeTableGenerator(TaskLoggingHelper log) => Log = log;
21+
public PInvokeTableGenerator(Func<string, string> fixupSymbolName, TaskLoggingHelper log)
22+
{
23+
Log = log;
24+
_fixupSymbolName = fixupSymbolName;
25+
}
2226

2327
public IEnumerable<string> Generate(string[] pinvokeModules, string[] assemblies, string outputPath)
2428
{
@@ -234,14 +238,14 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules
234238

235239
foreach (var module in modules.Keys)
236240
{
237-
string symbol = ModuleNameToId(module) + "_imports";
241+
string symbol = _fixupSymbolName(module) + "_imports";
238242
w.WriteLine("static PinvokeImport " + symbol + " [] = {");
239243

240244
var assemblies_pinvokes = pinvokes.
241245
Where(l => l.Module == module && !l.Skip).
242246
OrderBy(l => l.EntryPoint).
243247
GroupBy(d => d.EntryPoint).
244-
Select(l => "{\"" + FixupSymbolName(l.Key) + "\", " + FixupSymbolName(l.Key) + "}, " +
248+
Select(l => "{\"" + _fixupSymbolName(l.Key) + "\", " + _fixupSymbolName(l.Key) + "}, " +
245249
"// " + string.Join(", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName()!.Name!).Distinct().OrderBy(n => n)));
246250

247251
foreach (var pinvoke in assemblies_pinvokes)
@@ -255,7 +259,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules
255259
w.Write("static void *pinvoke_tables[] = { ");
256260
foreach (var module in modules.Keys)
257261
{
258-
string symbol = ModuleNameToId(module) + "_imports";
262+
string symbol = _fixupSymbolName(module) + "_imports";
259263
w.Write(symbol + ",");
260264
}
261265
w.WriteLine("};");
@@ -266,18 +270,6 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> modules
266270
}
267271
w.WriteLine("};");
268272

269-
static string ModuleNameToId(string name)
270-
{
271-
if (name.IndexOfAny(s_charsToReplace) < 0)
272-
return name;
273-
274-
string fixedName = name;
275-
foreach (char c in s_charsToReplace)
276-
fixedName = fixedName.Replace(c, '_');
277-
278-
return fixedName;
279-
}
280-
281273
static bool ShouldTreatAsVariadic(PInvoke[] candidates)
282274
{
283275
if (candidates.Length < 2)
@@ -295,43 +287,15 @@ static bool ShouldTreatAsVariadic(PInvoke[] candidates)
295287
}
296288
}
297289

298-
private static string FixupSymbolName(string name)
299-
{
300-
UTF8Encoding utf8 = new();
301-
byte[] bytes = utf8.GetBytes(name);
302-
StringBuilder sb = new();
303-
304-
foreach (byte b in bytes)
305-
{
306-
if ((b >= (byte)'0' && b <= (byte)'9') ||
307-
(b >= (byte)'a' && b <= (byte)'z') ||
308-
(b >= (byte)'A' && b <= (byte)'Z') ||
309-
(b == (byte)'_'))
310-
{
311-
sb.Append((char)b);
312-
}
313-
else if (s_charsToReplace.Contains((char)b))
314-
{
315-
sb.Append('_');
316-
}
317-
else
318-
{
319-
sb.Append($"_{b:X}_");
320-
}
321-
}
322-
323-
return sb.ToString();
324-
}
325-
326-
private static string SymbolNameForMethod(MethodInfo method)
290+
private string SymbolNameForMethod(MethodInfo method)
327291
{
328292
StringBuilder sb = new();
329293
Type? type = method.DeclaringType;
330294
sb.Append($"{type!.Module!.Assembly!.GetName()!.Name!}_");
331295
sb.Append($"{(type!.IsNested ? type!.FullName : type!.Name)}_");
332296
sb.Append(method.Name);
333297

334-
return FixupSymbolName(sb.ToString());
298+
return _fixupSymbolName(sb.ToString());
335299
}
336300

337301
private static string MapType(Type t) => t.Name switch
@@ -374,7 +338,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN
374338
{
375339
// FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types
376340
// https://github.com/dotnet/runtime/issues/43791
377-
sb.Append($"int {FixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);");
341+
sb.Append($"int {_fixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);");
378342
return sb.ToString();
379343
}
380344

@@ -390,7 +354,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN
390354
}
391355

392356
sb.Append(MapType(method.ReturnType));
393-
sb.Append($" {FixupSymbolName(pinvoke.EntryPoint)} (");
357+
sb.Append($" {_fixupSymbolName(pinvoke.EntryPoint)} (");
394358
int pindex = 0;
395359
var pars = method.GetParameters();
396360
foreach (var p in pars)
@@ -404,7 +368,7 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN
404368
return sb.ToString();
405369
}
406370

407-
private static void EmitNativeToInterp(StreamWriter w, ref List<PInvokeCallback> callbacks)
371+
private void EmitNativeToInterp(StreamWriter w, ref List<PInvokeCallback> callbacks)
408372
{
409373
// Generate native->interp entry functions
410374
// These are called by native code, so they need to obtain
@@ -450,7 +414,7 @@ private static void EmitNativeToInterp(StreamWriter w, ref List<PInvokeCallback>
450414

451415
bool is_void = method.ReturnType.Name == "Void";
452416

453-
string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_");
417+
string module_symbol = _fixupSymbolName(method.DeclaringType!.Module!.Assembly!.GetName()!.Name!);
454418
uint token = (uint)method.MetadataToken;
455419
string class_name = method.DeclaringType.Name;
456420
string method_name = method.Name;
@@ -517,7 +481,7 @@ private static void EmitNativeToInterp(StreamWriter w, ref List<PInvokeCallback>
517481
foreach (var cb in callbacks)
518482
{
519483
var method = cb.Method;
520-
string module_symbol = method.DeclaringType!.Module!.Assembly!.GetName()!.Name!.Replace(".", "_");
484+
string module_symbol = _fixupSymbolName(method.DeclaringType!.Module!.Assembly!.GetName()!.Name!);
521485
string class_name = method.DeclaringType.Name;
522486
string method_name = method.Name;
523487
w.WriteLine($"\"{module_symbol}_{class_name}_{method_name}\",");

0 commit comments

Comments
 (0)