From 17281ebdeadaeacdf0e3ce56dd3c3ab598a57780 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 10 Mar 2023 13:33:23 -0500 Subject: [PATCH 1/2] [mono] Use `unsigned char` when computing UTF8 string hashes The C standard does not specify whether `char` is signed or unsigned, it is implementation defined. Apparently Android aarch64 makes a different choice than other platforms (at least macOS arm64 and Windows x64 give different results). Mono uses `mono_metadata_str_hash` in the AOT compiler and AOT runtime to optimize class name lookup. As a result, classes whose names include UTF-8 continuation bytes (with the high bit = 1) will hash differently in the AOT compiler and on the device. Fixes https://github.com/dotnet/runtime/issues/82187 Fixes https://github.com/dotnet/runtime/issues/78638 --- src/mono/mono/eglib/ghashtable.c | 2 +- src/mono/mono/metadata/metadata.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/mono/eglib/ghashtable.c b/src/mono/mono/eglib/ghashtable.c index 5329fab0cabc5..8799ed46f8e57 100644 --- a/src/mono/mono/eglib/ghashtable.c +++ b/src/mono/mono/eglib/ghashtable.c @@ -673,7 +673,7 @@ guint g_str_hash (gconstpointer v1) { guint hash = 0; - char *p = (char *) v1; + unsigned char *p = (unsigned char *) v1; while (*p++) hash = (hash << 5) - (hash + *p); diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index c16b320e5147c..1e954b1c3ded1 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -5386,7 +5386,8 @@ guint mono_metadata_str_hash (gconstpointer v1) { /* Same as g_str_hash () in glib */ - char *p = (char *) v1; + /* note: signed/unsigned char matters - we feed UTF-8 to this function, so the high bit will give diferent results if we don't match. */ + unsigned char *p = (unsigned char *) v1; guint hash = *p; while (*p++) { From e6dd08798bb8d0e38be3c9a0d855812a0490da66 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 10 Mar 2023 14:35:44 -0500 Subject: [PATCH 2/2] Add regression test --- .../GitHub_82187/GitHub_82187.csproj | 9 ++++++ .../regressions/GitHub_82187/repro.cs | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj create mode 100644 src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs diff --git a/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj b/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj new file mode 100644 index 0000000000000..32355f272f908 --- /dev/null +++ b/src/tests/Loader/classloader/regressions/GitHub_82187/GitHub_82187.csproj @@ -0,0 +1,9 @@ + + + true + Exe + + + + + diff --git a/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs b/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs new file mode 100644 index 0000000000000..1ab3e5476a66b --- /dev/null +++ b/src/tests/Loader/classloader/regressions/GitHub_82187/repro.cs @@ -0,0 +1,31 @@ +using System; + +/* Regression test for https://github.com/dotnet/runtime/issues/78638 + * and https://github.com/dotnet/runtime/issues/82187 ensure AOT + * cross-compiler and AOT runtime use the same name hashing for names + * that include UTF-8 continuation bytes. + */ + +[MySpecial(typeof(MeineTüre))] +public class Program +{ + public static int Main() + { + var attr = (MySpecialAttribute)Attribute.GetCustomAttribute(typeof (Program), typeof(MySpecialAttribute), false); + if (attr == null) + return 101; + if (attr.Type == null) + return 102; + if (attr.Type.FullName != "MeineTüre") + return 103; + return 100; + } +} + +public class MySpecialAttribute : Attribute +{ + public Type Type {get; private set; } + public MySpecialAttribute(Type t) { Type = t; } +} + +public class MeineTüre {}